3 # syslog2db - Extract Clarent VoIP CDRs from billing_record files and
4 # insert them into a Postgresql database.
6 # Author: Peter Nixon <codemonkey@peternixon.net>
8 # Summary: Clarent, VoIP, CDR, database, postgresql
9 # Copyright: 2002, Peter Nixon <codemonkey@peternixon.net>
10 # Copy Policy: Free to copy and distribute provided all headers are left
11 # intact and no charge is made for this program. I would
12 # appreciate copies of any modifications to the script.
13 # URL: http://www.peternixon.net/code/
18 # Modules we use to make things easier
25 #use strict; # Errrm. That looks like effort :-)
28 # Program and File locations
29 # gzcat - 'cat for .gz / gzip files'
30 # If you don't have gzcat and do have gzip then use: ln gzip gzcat
31 my $GZCAT = "/usr/bin/zcat";
32 # zcat - 'cat for .Z / compressed files'
33 my $ZCAT = "/usr/bin/zcat";
34 # bzcat - 'cat for .bz2 files'
35 my $BZCAT = "/usr/bin/bzcat";
37 #### You should not have to modify anything below here
39 $| = 1; #Unbuffered output
40 my $progname = "clarent2db.pl";
41 my $progname_long = "Clarent Billing Record to DB Importer";
44 # Set up some basic variables
45 my $double_match_no = 0; my $verbose = 0; my $recordno = 0; my $fileno = 0; my $lineno = 0;
46 my $starttime = time();
49 # Database Information
50 my $database = "clarent";
51 my $defaulthostname = "ist-db1";
53 my $user = "postgres";
57 my $defaulttimezone = "UTC";
58 my $defaultyear = 2002;
62 my $str2time_last_time;
63 my $str2time_last_day;
64 my $str2time_last_month;
65 my $enable_year_decrement = 1; # year-increment algorithm: if in january, if december is seen, decrement
67 my %working_record = ();
70 'Jan' => 0, 'Feb' => 1, 'Mar' => 2,
71 'Apr' => 3, 'May' => 4, 'Jun' => 5,
72 'Jul' => 6, 'Aug' => 7, 'Sep' => 8,
73 'Oct' => 9, 'Nov' =>10, 'Dec' =>11,
74 'jan' => 0, 'feb' => 1, 'mar' => 2,
75 'apr' => 3, 'may' => 4, 'jun' => 5,
76 'jul' => 6, 'aug' => 7, 'sep' => 8,
77 'oct' => 9, 'nov' =>10, 'dec' =>11,
82 if ($verbose > 1) { print "DEBUG: Connecting to Database Server: $hostname\n" }
83 if ($hostname eq 'localhost') {
84 if ($verbose > 1) { print "DEBUG: localhost connection so using UNIX socket instead of network socket.\n" }
85 $dbh = DBI->connect("DBI:Pg:dbname=$database", "$user", "$password")
86 or die "Couldn't connect to database: " . DBI->errstr;
89 $dbh = DBI->connect("DBI:Pg:dbname=$database;host=$hostname", "$user", "$password")
90 or die "Couldn't connect to database: " . DBI->errstr;
95 ### Now, disconnect from the database
96 if ($verbose > 1) { print "DEBUG: Disconnecting from Database Server\n" }
98 or warn "Disconnection failed: $DBI::errstr\n";
103 if ($verbose > 0) { print "Record: $passno) Conf ID: $working_record{h323confid} Start Time: $working_record{start_time} IP: $working_record{ip_addr_egress} Call Length: $working_record{duration}\n"; }
104 my $sth = $dbh->prepare("SELECT Id FROM billing_record
106 AND ip_addr_ingress = ?
108 or die "Couldn't prepare statement: " . $dbh->errstr;
111 $sth->execute($working_record{start_time}, $working_record{ip_addr_egress}, $working_record{h323confid}) # Execute the query
112 or die "Couldn't execute statement: " . $sth->errstr;
113 my $returned_rows = $sth->rows;
115 if ($sth->rows == 0) {
117 } elsif ($sth->rows == 1) {
118 if ($verbose > 0) { print "Exists in DB.\n"; }
121 # FIXME: Log this somewhere!
122 print "********* More than One Match! We have a problem!\n";
130 $sth2 = $dbh->prepare("INSERT into billing_record (local_SetupTime, start_time, duration, service_code, phone_number,
131 ip_addr_ingress, ip_addr_egress, bill_type, disconnect_reason, extended_reason_code, dialed_number, codec, h323ConfID)
132 values(?,?,?,?,?,?,?,?,?,?,?,?,?)");
134 $sth2->execute($working_record{local_setuptime}, $working_record{start_time}, $working_record{duration},
135 $working_record{service_code}, $working_record{phone_number}, $working_record{ip_addr_egress}, $working_record{ip_addr_egress},
136 $working_record{bill_type}, $working_record{disconnect_reason}, $working_record{extended_reason_code}, $working_record{dialed_number},
137 $working_record{codec}, $working_record{h323confid}); # Execute the query
138 #my $returned_rows = $sth2->rows;
139 if ($verbose > 0) { print "$sth2->rows rows added to DB\n"; }
145 my $filename = shift;
146 if ($verbose > 1) { print "DEBUG: Reading detail file: $filename\n" }
147 if ( $filename =~ /.gz$/ ) {
148 open (FILE, "$GZCAT $filename |") || warn "read_detailfile(\"$filename\"): $!\n";
149 } elsif ( $filename =~ /.Z$/ ) {
150 open (FILE, "$ZCAT $filename |") || warn "read_detailfile(\"$filename\"): $!\n";
151 } elsif ( $filename =~ /.bz2$/ ) {
152 open (FILE, "$BZCAT $filename |") || warn "read_detailfile(\"$filename\"): $!\n";
154 open (FILE, "<$filename") || warn "read_detailfile(\"$filename\"): $!\n";
156 $valid_input = (eof(FILE) ? 0 : 1);
157 if ($verbose > 1) { print "DEBUG: Starting to read records from $filename\n"; }
158 while($valid_input) {
159 $valid_input = 0 if (eof(FILE));
160 if ($verbose > 1) { print "DEBUG: Reading Record\n"; }
163 if ($verbose > 1) { print "DEBUG RECORD: $_"; }
169 # 0: sec, 1: min, 2: h, 3: day, 4: month, 5: year
170 sub str2timez($$$$$$$) {
172 my $day_secs = $_[2]*3600+$_[1]*60+$_[0];
173 if(defined $str2time_last_time) {
174 if( $_[3] == $str2time_last_day and
175 $_[4] == $str2time_last_month )
177 return $str2time_last_time + $day_secs;
186 $time = timelocal(@_);
189 $str2time_last_time = $time - $day_secs;
190 $str2time_last_day = $_[3];
191 $str2time_last_month = $_[4];
200 my $seconds = $time = str2time($time1) - str2time($time2);
201 if ($verbose > 0) { print "******** Seconds: $seconds\n"; }
205 sub record_match($) {
207 #if ($verbose > 1) { print "DEBUG Record: $_\n"; }
208 # Check to see if it is a Clarent Billing record
210 (\S{3})\/(\d+)\/(\d{4}) # Month Day Year
212 (\d+):(\d+):(\d+) # Hour Min Sec
216 \w*?:? # RESEND: (Discarded)
218 \S{1}, # U (Discarded)
224 (\w+-\w+-\w+), # phone_number
225 (\d+\.\d+\.\d+\.\d+), # ip_addr_ingress
226 (\d+\.\d+\.\d+\.\d+), # ip_addr_egress
233 (\w{2}), # disconnect_reason
234 (\w{2}), # extended_reason_code
235 (.*), # text we don't care about
236 (\d+), # dialed_number??
243 $recordno++; %working_record = ();
244 if ($verbose > 0) { print "DEBUG: Cleaned Record: $3-$1-$2 $4:$5:$6 $7 $8 : $9 $10 $11 $12 $13 $14 $15 $16 $17 $18 $19 $20 $21 $22 $23 $24 :: $25 $26 $27 $28 $29\n"; }
246 my $month = $months_map{$1}; defined $month or croak "ERROR: Unknown month \"$1\"\n";
252 $working_record{local_setuptime} = "$years-$month-$days $hours:$minutes:$seconds";
253 $working_record{start_time} = $10;
254 $working_record{duration} = $11;
255 $working_record{service_code} = $12;
256 $working_record{phone_number} = $13;
257 $working_record{ip_addr_ingress} = $14;
258 $working_record{ip_addr_egress} = $15;
259 $working_record{h323confid} = $16;
261 # $cust_city_call_totals{$custname}{$nascity} += $mins;
262 # convert to unix time 0: sec, 1: min, 2: h, 3: day, 4: month, 5: year
263 my $unixtime = str2timez($seconds,$minutes,$hours,$days,$month,$years,$defaulttimezone);
264 if ($verbose > 0) { print "DEBUG: Time is $unixtime\n"; }
267 (\S{3})\/(\d+)\/(\d{4}) # Month Day Year
269 (\d+):(\d+):(\d+) # Hour Min Sec
273 FAILED\sTO\sSEND # FAILED TO SEND (Discarded)
275 # Broken FAILED TO SEND record with date time and nothing else
277 (\S{3})\/(\d+)\/(\d{4}) # Month Day Year
279 (\d+):(\d+):(\d+) # Hour Min Sec
284 \srcd\s\=\s\d+ # FAILED TO WRITE (Discarded)
286 # Broken FAILED TO WRITE record with date time and nothing else
288 (\S{3})\/(\d+)\/(\d{4}) # Month Day Year
290 (\d+):(\d+):(\d+) # Hour Min Sec
294 # Broken record with date time and nothing else
296 if ($verbose > 0) { print "ERROR: Record is not in Clarent format: $str\n"; }
300 sub print_usage_info {
302 my $leader = "$progname_long Ver: $version Usage Information";
303 my $underbar = $leader;
304 $underbar =~ s/./-/g;
305 print "$leader\n$underbar\n";
307 print " Syntax: $progname [ options ] file\n";
309 print " -h --help Show this usage information\n";
310 print " -v --verbose Turn on verbose\n";
311 print " -x --debug Turn on debugging\n";
312 #print " -p --procedure Use Postgresql stored procedure (faster!)\n";
313 print " -V --version Show version and copyright\n";
314 print " -H --host Database host to connect to (Default: localhost)\n";
318 sub procedure_get() {
319 return $stored_procedure;
322 sub procedure_set($) {
323 $stored_procedure = $_[0];
328 # Parse the command line for options
329 if (!scalar(@ARGV)) {
335 # See the Getopt::Long man page for details on the syntax of this line
336 @valid_opts = ("h|help", "V|version", "f|file=s", "x|debug", "v|verbose+" => \$verbose, "q|quiet+" => \$quiet, "D|date=s", "H|host=s", "p|procedure");
337 Getopt::Long::Configure("no_getopt_compat", "bundling", "no_ignore_case");
338 Getopt::Long::GetOptions(@valid_opts);
340 # Post-parse the options stuff
341 select STDOUT; $| = 1;
343 # Do not edit this variable. It is updated automatically by CVS when you commit
344 my $rcs_info = 'CVS Revision $Revision$ created on $Date$ by $Author$ ';
346 $rcs_info =~ s/\$\s*Revision: (\S+) \$/$1/;
347 $rcs_info =~ s/\$\s*Date: (\S+) (\S+) \$/$1 at $2/;
348 $rcs_info =~ s/\$\s*Author: (\S+) \$ /$1/;
351 print "$progname Version $version by Peter Nixon <codemonkey\@peternixon.net>\n";
352 print "Copyright (c) 2003 Peter Nixon\n";
353 print " ($rcs_info)\n";
362 print "DEBUG: Debug mode is enabled.\n";
364 } elsif ($quiet) { $verbose -= $quiet; }
365 &procedure_set($opt_p);
368 if ($opt_H) { &db_connect($opt_H);
369 } else { &db_connect($defaulthostname); }
371 # Loop through the defined files
372 foreach my $file (@ARGV) {
376 my $runtime = (time() - $starttime);
378 } else { $runtime = 1; }
379 my $speed = ($recordno / $runtime);
382 print "\n $recordno records from $lineno lines in $fileno files were processed in ~$runtime seconds (~$speed records/sec) \n";
384 print "\n $recordno records from $lineno lines in $filename were processed in ~$runtime seconds (~$speed records/sec) \n";
390 print "ERROR: Please specify one or more detail file(s) to import.\n";