1#!/usr/bin/perl
2
3#
4# bfha -- binkleyforce history analyzer
5#
6# Copyright (C) 2000 Serge N. Pokhodyaev
7#
8# E-mail: snp@ru.ru
9# Fido:   2:5020/1838
10#
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 2 of the License, or
15# (at your option) any later version.
16#
17#
18# $Id: bfha.pl,v 1.1.1.1 2004/09/09 09:52:36 kstepanenkov Exp $
19#
20
21#
22# ������� ���������� �� ���������� ����
23#
24
25#
26# TODO:
27#
28# 1. ������ ������, ����� ��� ��ף���� logrotate'��
29#
30# Known bugs:
31#
32# 1. H���������� ������� cps (��������) ���� ������ � ��� �������
33#
34
35use strict;
36use Time::Local;
37use POSIX qw(strftime);
38
39my $PROGNAME = 'bfha';
40my $VERSION = '$Revision: 1.1.1.1 $ ';
41
42######## Configurable part ###################################################
43
44my $inews = "/usr/bin/inews -h -O -S";
45
46my $log = "/var/spool/fido/history";
47
48my $rep_newsgroups = "ftn.1838.stat";
49my $rep_from = "\"Statistics generator\" <snp\@gloom.intra.eu.org>";
50my $rep1_subj = "Sessions history";
51my $rep2_subj = "Sessions history";
52my $rep3_subj = "Links statistics";
53
54my $count_failed = 1;
55
56#my %line_names = (
57#		  "ttyS1" => "Modem",
58#		  "tcpip" => "IP"
59#);
60
61##############################################################################
62
63my $devel = 0;
64
65my (%st, %lines, %nodes, @nodes_sorted);
66my @tm;
67my $time;
68
69die "Usage: $PROGNAME [-d]\n" if (!((0 == $#ARGV) && ($ARGV[0] eq '-d')) && !(-1 == $#ARGV));
70$devel = 1 if ((0 == $#ARGV) && ($ARGV[0] eq '-d'));
71
72$log = "./history" if ($devel);
73
74# Make version string
75#
76$VERSION =~ s/(\$Rev)ision:\s([\d.]+).*/$PROGNAME\/$2/;
77
78# Get time of 0:00:00 of yesterday
79#
80@tm = localtime(time - 86400);
81$tm[0] = 0;	# sec
82$tm[1] = 0;	# min
83$tm[2] = 0;	# hours
84$time = timelocal(@tm);
85
86# At first read logs
87#
88undef %st;
89undef %lines;
90undef %nodes;
91die "Log reading error\n" if (0 != log_read($time));
92
93@nodes_sorted = sort(node_cmp keys(%nodes));
94
95# Now generate statistics
96#
97out_close();
98#rep1();
99rep2();
100rep3();
101
102
103sub log_read
104  {
105    my $i;
106    my $t;
107    my @l;
108
109    die if (0 != $#_);
110
111    $t = $_[0];
112
113    open(LOG, "< $log") || die("Can't open $log: $!\n");
114    while (<LOG>)
115      {
116	chomp;
117	@l = split(/,/);
118	return 1 if ($#l != 11);
119	return 1 if (! ($l[4] =~ /[IO]/));
120
121	# Check date
122	#
123	next if ($l[2] < $t);
124	last if ($l[2] >= ($t + 86400));
125
126	# Entries without address
127	#
128	if ($l[1] eq '')
129	  {
130	    next if (0 == $count_failed);
131	    $l[1] = 'failed' if (0 != $count_failed);
132	  }
133
134	# Remove domain
135	#
136	$l[1] =~ s/@.*$//;
137
138	# Add statistics
139	#
140	$i = $#{$st{beg}} + 1;
141
142	$lines{$l[0]}[$#{$lines{$l[0]}} + 1] = $i;
143	$nodes{$l[1]}[$#{$nodes{$l[1]}} + 1] = $i;
144
145	$st{beg}[$i] = $l[2];
146	$st{len}[$i] = $l[3];
147	$st{addr}[$i] = $l[1];
148	$st{line}[$i] = $l[0];
149	$st{rc}[$i] = $l[5];
150	$st{snt_nm}[$i] = $l[6];
151	$st{snt_am}[$i] = $l[7];
152	$st{snt_f}[$i] = $l[8];
153	$st{rcv_nm}[$i] = $l[9];
154	$st{rcv_am}[$i] = $l[10];
155	$st{rcv_f}[$i] = $l[11];
156	$st{islst}[$i] = 0;
157	$st{islst}[$i] = 1 if ($l[4] =~ /L/);
158	$st{isprot}[$i] = 0;
159	$st{isprot}[$i] = 1 if ($l[4] =~ /P/);
160	$st{type}[$i] = 'I' if ($l[4] =~ /I/);
161	$st{type}[$i] = 'O' if ($l[4] =~ /O/);
162      }
163  }
164
165######## rep1 ################################################################
166
167sub rep1
168  {
169    my ($i, $j);
170    my (%se, %se_t);
171    my $node;
172
173    out_open($rep1_subj);
174    printf "Date: %s\n\n", strftime("%a, %e %b %Y", localtime($time));
175
176    $~ = "rep1_header";
177    write;
178
179    $~ = "rep1_body";
180    $se_t{num_in} = 0;
181    $se_t{num_out} = 0;
182    $se_t{snt} = 0;
183    $se_t{rcv} = 0;
184    $se_t{len} = 0;
185    foreach $node (@nodes_sorted)
186      {
187	$se{num_in} = 0;
188	$se{num_out} = 0;
189	$se{snt} = 0;
190	$se{rcv} = 0;
191	$se{len} = 0;
192	for ($i = 0; $i <= $#{$nodes{$node}}; ++$i)
193	  {
194	    $j = $nodes{$node}[$i];
195	    ++$se{num_in} if ($st{type}[$j] eq 'I');
196	    ++$se{num_out} if ($st{type}[$j] eq 'O');
197	    $se{snt} += $st{snt_am}[$j] + $st{snt_nm}[$j] + $st{snt_f}[$j];
198	    $se{rcv} += $st{rcv_am}[$j] + $st{rcv_nm}[$j] + $st{rcv_f}[$j];
199	    $se{len} += $st{len}[$j];
200	  }
201	$se{time} = time_int2str($se{len});
202	# FIXME (cps)
203	$se{cps} = div_int(($se{snt} + $se{rcv}), $se{len});
204	write;
205
206	$se_t{num_in} += $se{num_in};
207	$se_t{num_out} += $se{num_out};
208	$se_t{snt} += $se{snt};
209	$se_t{rcv} += $se{rcv};
210	$se_t{len} += $se{len};
211      }
212
213    $~ = "rep1_footer";
214    $se_t{time} = time_int2str($se_t{len});
215    # FIXME (cps)
216    $se_t{cps} = div_int(($se_t{snt} + $se_t{rcv}), $se_t{len});
217    write;
218
219    print "\n";
220    out_close();
221
222
223format rep1_header =
224���������������������������������������������������������������������������
225�     System      � Sessions �   Sent    �  Received  �   Time    �  CPS  �
226�     address     � in   out �   bytes   �   bytes    �  online   �       �
227���������������������������������������������������������������������������
228.
229
230format rep1_body =
231� @<<<<<<<<<<<<<< � @>>  @>> � @>>>>>>>> � @>>>>>>>>> � @>>>>>>>> � @>>>> �
232$node, $se{num_in}, $se{num_out}, $se{snt}, $se{rcv}, $se{time}, $se{cps}
233.
234
235format rep1_footer =
236���������������������������������������������������������������������������
237�      TOTAL      � @>>  @>> � @>>>>>>>> � @>>>>>>>>> � @>>>>>>>> � @>>>> �
238$se_t{num_in}, $se_t{num_out}, $se_t{snt}, $se_t{rcv}, $se_t{time}, $se_t{cps}
239���������������������������������������������������������������������������
240.
241  }
242
243######## rep2 ################################################################
244
245sub rep2
246  {
247    my ($i, $j);
248    my @t;
249    my ($t1, $t2, $str, $lv);
250    my $node;
251    my %se;
252
253    out_open($rep2_subj);
254    printf "Date: %s\n\n", strftime("%a, %e %b %Y", localtime($time));
255
256    $~ = "rep2_header";
257    write;
258
259    $~ = "rep2_body";
260    foreach $node (@nodes_sorted)
261      {
262	$se{snt} = 0;
263	$se{rcv} = 0;
264	$se{len} = 0;
265	for ($i = 0; $i <= 95; ++$i)
266	  {
267	    $t[$i] = 0;
268	  }
269	for ($i = 0; $i <= $#{$nodes{$node}}; ++$i)
270	  {
271	    $j = $nodes{$node}[$i];
272
273	    # Fill array
274	    #
275	    $t1 = $st{beg}[$j] - $time;
276	    $t2 = $t1 + $st{len}[$j];
277	    $t2 = 86399 if ($t2 > 86399);
278	    $t1 = div_int($t1, 900);
279	    $t2 = div_int($t2, 900);
280	    while ($t1 <= $t2)
281	      {
282		$t[$t1++] = 1;
283	      }
284
285	    $se{snt} += $st{snt_am}[$j] + $st{snt_nm}[$j] + $st{snt_f}[$j];
286	    $se{rcv} += $st{rcv_am}[$j] + $st{rcv_nm}[$j] + $st{rcv_f}[$j];
287	    $se{len} += $st{len}[$j];
288	  }
289	$se{time} = time_int2str($se{len});
290	# FIXME (cps)
291	$se{cps} = div_int(($se{snt} + $se{rcv}), $se{len});
292
293	# Visualize
294	#
295	$i = 0;
296	$str = "";
297	if ($t[$i++])
298	  {
299	    $str = $str . "�";
300	  }
301	else
302	  {
303	    $str = $str . "�";
304	  }
305	while ($i < $#t)
306	  {
307	    $lv = 0;
308	    $lv += 1 if ($t[$i++]);
309	    $lv += 2 if ($t[$i++]);
310
311	    if (0 == $lv)
312	      {
313		if (div_rest(($i - 1), 8) == 0)
314		  {
315		    $str = $str . "�";
316		  }
317		else
318		  {
319		    $str = $str . " ";
320		  }
321	      }
322	    elsif (1 == $lv)
323	      {
324		$str = $str . "�";
325	      }
326	    elsif (2 == $lv)
327	      {
328		$str = $str . "�";
329	      }
330	    elsif (3 == $lv)
331	      {
332		$str = $str . "�";
333	      }
334	  }
335	if ($t[$i])
336	  {
337	    $str = $str . "�";
338	  }
339	else
340	  {
341	    $str = $str . "�";
342	  }
343
344	write;
345      }
346
347    $~ = "rep2_footer";
348    write;
349
350    out_close();
351
352
353format rep2_header =
354               0   2   4   6   8  10  12  14  16  18  20  22  24
355               �������������������������������������������������
356.
357
358format rep2_body =
359@<<<<<<<<<<<<<<@||||||||||||||||||||||||||||||||||||||||||||||||
360$node, $str
361.
362
363format rep2_footer =
364               �������������������������������������������������
365               0   2   4   6   8  10  12  14  16  18  20  22  24
366.
367  }
368
369######## rep3 ################################################################
370
371sub rep3
372  {
373    my $node;
374    my (%se, %se_t);
375    my ($i, $j);
376
377    out_open($rep3_subj);
378    printf "Date: %s\n\n", strftime("%a, %e %b %Y", localtime($time));
379
380    $~ = "rep3_header";
381    write;
382
383    $~ = "rep3_body";
384    $se_t{num_in} = 0;
385    $se_t{num_out} = 0;
386    $se_t{time_in} = 0;
387    $se_t{time_out} = 0;
388    $se_t{snt} = 0;
389    $se_t{rcv} = 0;
390    $se_t{time} = 0;
391    foreach $node (@nodes_sorted)
392      {
393	$se{num_in} = 0;
394	$se{num_out} = 0;
395	$se{time_in} = 0;
396	$se{time_out} = 0;
397	$se{snt} = 0;
398	$se{rcv} = 0;
399	$se{time} = 0;
400	for ($i = 0; $i <= $#{$nodes{$node}}; ++$i)
401	  {
402	    $j = $nodes{$node}[$i];
403	    ++$se{num_in} if ($st{type}[$j] eq 'I');
404	    ++$se{num_out} if ($st{type}[$j] eq 'O');
405	    $se{time_in} += $st{len}[$j] if ($st{type}[$j] eq 'I');
406	    $se{time_out} += $st{len}[$j] if ($st{type}[$j] eq 'O');
407	    $se{snt} += $st{snt_am}[$j] + $st{snt_nm}[$j] + $st{snt_f}[$j];
408	    $se{rcv} += $st{rcv_am}[$j] + $st{rcv_nm}[$j] + $st{rcv_f}[$j];
409	    $se{time} += $st{len}[$j];
410	  }
411
412	# Total counters
413	#
414	$se_t{num_in} += $se{num_in};
415	$se_t{num_out} += $se{num_out};
416	$se_t{snt} += $se{snt};
417	$se_t{rcv} += $se{rcv};
418	$se_t{time} += $se{time};
419	$se_t{time_out} += $se{time_out};
420	$se_t{time_in} += $se{time_in};
421
422	# Output string
423	#
424	# FIXME (cps)
425	$se{cps} = dash_if_zero(div_int(($se{snt} + $se{rcv}), $se{time}));
426	$se{time} = time_int2str($se{time});
427	$se{time_in} = time_int2str($se{time_in});
428	$se{time_out} = time_int2str($se{time_out});
429	$se{num_in} = dash_if_zero($se{num_in});
430	$se{num_out} = dash_if_zero($se{num_out});
431	$se{rcv} = shrink_size(dash_if_zero($se{rcv}));
432	$se{snt} = shrink_size(dash_if_zero($se{snt}));
433	write;
434      }
435
436    $~ = "rep3_footer";
437    # FIXME (cps)
438    $se_t{cps} = dash_if_zero(div_int(($se_t{snt} + $se_t{rcv}), $se_t{time}));
439    $se_t{time} = time_int2str($se_t{time});
440    $se_t{time_in} = time_int2str($se_t{time_in});
441    $se_t{time_out} = time_int2str($se_t{time_out});
442    $se_t{num_in} = dash_if_zero($se_t{num_in});
443    $se_t{num_out} = dash_if_zero($se_t{num_out});
444    $se_t{rcv} = shrink_size(dash_if_zero($se_t{rcv}));
445    $se_t{snt} = shrink_size(dash_if_zero($se_t{snt}));
446    $~ = "rep3_footer";
447    write;
448
449    print "\n";
450    out_close();
451
452
453format rep3_header =
454�������������������������������������������������������������������������������
455�                �      Sessions/Online      �   Time   �   Traffic   �       �
456�     Address    �����������������������������  online  ���������������  CPS  �
457�                �   Incoming  �   Outgoing  �          � Rcvd � Sent �       �
458�������������������������������������������������������������������������������
459.
460
461format rep3_body =
462� @<<<<<<<<<<<<<<�@>>�@>>>>>>> �@>>�@>>>>>>> �@>>>>>>>> �@>>>>>�@>>>>>�@>>>>> �
463$node, $se{num_in}, $se{time_in}, $se{num_out}, $se{time_out}, $se{time}, $se{rcv}, $se{snt}, $se{cps}
464.
465
466format rep3_footer =
467�������������������������������������������������������������������������������
468� TOTAL          �@>>�@>>>>>>> �@>>�@>>>>>>> �@>>>>>>>> �@>>>>>�@>>>>>�@>>>>> �
469$se_t{num_in}, $se_t{time_in}, $se_t{num_out}, $se_t{time_out}, $se_t{time}, $se_t{rcv}, $se_t{snt}, $se_t{cps}
470�������������������������������������������������������������������������������
471.
472  }
473
474##############################################################################
475
476sub shrink_size
477  {
478    die if (0 != $#_);
479
480    return $_[0] if ($_[0] < 1024);
481    return sprintf("%.1fk", $_[0] / 1024) if ($_[0] < 1048576);
482    return sprintf("%.1fM", $_[0] / 1048576);
483  }
484
485sub dash_if_zero
486  {
487    die if (0 != $#_);
488
489    return "-" if (0 == $_[0]);
490    return $_[0];
491  }
492
493sub time_int2str
494  {
495    my $time;
496    my $h;
497    my $m;
498    my $s;
499
500    die if (0 != $#_);
501    die if (86399 < $time);
502
503    return "-:--:--" if (0 == $_[0]);
504
505    $time = $_[0];
506    $h = div_int($time, 3600);
507    $time = div_rest($time, 3600);
508    $m = div_int($time, 60);
509    $s = div_rest($time, 60);
510
511    return sprintf("%d:%2.2d:%2.2d", $h, $m, $s);
512  }
513
514# ������������� �������
515sub div_int
516  {
517    use integer;
518
519    die if (1 != $#_);
520
521    return 0 if ($_[1] == 0);
522    return $_[0] / $_[1];
523  }
524
525
526# ������� �� �������������� �������
527sub div_rest
528  {
529    die if (1 != $#_);
530
531    return 0 if ($_[1] == 0);
532    return $_[0] - (div_int($_[0], $_[1]) * $_[1]);
533  }
534
535sub out_open
536  {
537    die if (0 != $#_);
538
539    open(STDOUT, "| $inews") || die("Can't pipe to inews: $!\n") if (! $devel);
540    printf "Newsgroups: %s\n", $rep_newsgroups;
541    printf "From: %s\n", $rep_from;
542    printf "Subject: %s\n", $_[0];
543    printf "X-FTN-Tearline: %s\n\n", $VERSION;
544  }
545
546sub out_close
547  {
548    close(STDOUT) if (! $devel);
549  }
550
551sub node_cmp
552  {
553    my (@na, @nb);
554
555    @na = split('[:/.]', $::a);
556    @nb = split('[:/.]', $::b);
557
558    # zone
559    return -1 if ($na[0] < $nb[0]);
560    return 1  if ($na[0] > $nb[0]);
561
562    # net
563    return -1 if ($na[1] < $nb[1]);
564    return 1  if ($na[1] > $nb[1]);
565
566    # node
567    return -1 if ($na[2] < $nb[2]);
568    return 1  if ($na[2] > $nb[2]);
569
570    #point
571    return -1 if ($na[3] < $nb[3]);
572    return 1  if ($na[3] > $nb[3]);
573
574    return 0;
575  }
576