File: log-server.pl

package info (click to toggle)
remstats 1.00a4-8woody1
  • links: PTS
  • area: main
  • in suites: woody
  • size: 4,576 kB
  • ctags: 1,020
  • sloc: perl: 11,706; ansic: 2,776; makefile: 944; sh: 869
file content (299 lines) | stat: -rwxr-xr-x 8,541 bytes parent folder | download
1
2
3
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#!@@PERL@@ @@PERLCGIOPTS@@

# Copyright 1999, 2000, 2001 (c) Thomas Erskine <@@AUTHOR@@>
# See the COPYRIGHT file with the distribution.

# log-server - allow the log-collector to get information 
#	about a remote logfile, without transferring it
# $Id: log-server.pl,v 1.8 2001/08/28 15:22:24 remstats Exp $

# - - -   Configuration   - - -

use strict;

# What is this program called, for error-messages and file-names
$main::prog = 'log-server';
# Where to store context (file position per log-file)
$main::context_dir = '/var/tmp/remstats';
$main::context_prefix = 'log-server-';

# - - -   Version History   - - -

(undef, $main::version) = split(' ', '$Revision: 1.8 $');

# - - -   Setup   - - -

# Make sure there is no buffering of output
$| = 1;

# Parse the command-line
use Getopt::Std;
# STRICT use vars qw($opt_d $opt_h $opt_t $opt_p);

getopts('d:hp:t');

if (defined $main::opt_h) { &usage; } # no return
if (defined $main::opt_d) { $main::debug = $main::opt_d; } else { $main::debug = 0; }
if (defined $main::opt_p) { $main::context_prefix = $main::opt_p; }
if (defined $main::opt_t) { $main::testmode = 1; } else { $main::testmode = 0; }

# Make sure some of the specified log-files actually exist
unless ($#ARGV >= 0) { &usage; } # no return
my @logs = ();
foreach my $file (@ARGV) {
	if (-f $file) { push @logs, $file; }
	else { &error("log-file '$file' doesn't exist; ignored"); }
}
unless (@logs > 0) { &abort("no specified log-files exist"); }

# Read the request, variables associated with patterns
my $complete = 0;
my @variables = ();
my ($logfile, $variable, $type, $pattern, %pattern, %type);
while ($_ = &prompt) {
	tr/\015\012//d;
	next if (/^#/ or /^\s*$/);
	if (/^LOGFILE\s+(\S+)/) {
		$logfile = $1;
		unless( grep( $logfile, @logs)) {
			&abort("$logfile isn't one of the log-files I serve.");
		}
	}
	elsif (/^GO$/) {
		$complete = 1;
		last;
	}
	elsif (/^QUIT$/) { exit 0; }
	elsif (/^VERSION$/) { print "$main::prog version $main::version\n"; }
	elsif (/^DEBUG$/) {
		$main::debug = 1;
		&debug("debugging on; version $main::version");
	}
	elsif (/^TEST$/) { $main::testmode = 1; }
	elsif (/^HELP$/) { &do_help; }
	elsif (/^(\S+)\s+(sum|count|first|last|max|min|average)\s+(.+)/) {
		$variable = $1;
		$type = $2;
		$pattern = qr{$3};

# May have some order-dependent stuff later
		push @variables, $variable;
		$pattern{$variable} = $pattern;
		$type{$variable} = lc $type;
	}
	else { &error("unknown line in request: $_"); }
}

# Make sure we've got something comprehensible to do
unless ($complete) { &abort("incomplete request"); }
unless (defined $logfile) { &abort("no logfile requested"); }
unless (%pattern) { &abort("no variables requested"); }
unless (grep $logfile, @logs) { &abort("unknown logfile ($logfile)"); }
&debug("logfiles available: ".join(', ',@logs)) if ($main::debug);
&debug(($#variables+1)." variables read") if ($main::debug);

# - - -   Mainline   - - -

# Make sure the context directory exists
unless (-d $main::context_dir) {
	mkdir ($main::context_dir, 0700) or 
		&abort("can't mkdir ${main::context_dir}: $!\n");
}

# Make sure we have some context
my ($contextfile, $position, %value, %count);
($contextfile = $logfile) =~ tr#/#_#;
$contextfile = $main::context_dir . '/' . $main::context_prefix . $contextfile;
if ( -f $contextfile) {

# Get the current log-file position
	open (CONTEXT, "<$contextfile") or 
		&abort("can't open $contextfile: $!");
	$position = <CONTEXT>;
	close(CONTEXT);
	chomp $position;
	&debug("got $position from context $contextfile") if ($main::debug);

# Has the log-file been rolled-over since last time?
	if ($position > -s $logfile) {
		&debug("logfile rolled over; starting from beginning") 
			if ($main::debug);
		$position = 0;
	}
}

# No context-file; either this is a new log (never collected before) or
# we've lost the context.  In either case, remember where we are now and
# don't give misleading info from the beginning.  Logfile may be large.
else {
	&debug("no context; skipping data this time") if ($main::debug);
	&putcontext( $contextfile, -s $logfile) unless ($main::testmode);
	exit 0;
}

# Now deal with the log-file
open (LOG, "<$logfile") or &abort("can't open $logfile: $!");
seek (LOG, $position, 0) or &abort("can't seek $logfile: $!");
my $records = 0;
while (<LOG>) {
	chomp;
	++$records;
	foreach $variable (@variables) {
		if (/$pattern{$variable}/i) {
			&debug("pattern '$pattern{$variable}' matched rec '$_'") if ($main::debug>1);
			$type = $type{$variable};
			if ($type eq 'count') {
				if (defined $value{$variable}) {
					$value{$variable}++;
				}
				else { $value{$variable} = 1; }
			}
			elsif ($type eq 'sum') {
				if (defined $1) {
					if (defined $value{$variable}) {
						$value{$variable} += $1;
					}
					else { $value{$variable} = $1; }
				}
				else { &error("pattern for $variable doesn't define \$1; line=\n$_"); }
			}
			elsif ($type eq 'first') {
				if (defined $1) {
					unless (defined $value{$variable}) {
						$value{$variable} = $1;
					}
				}
				else { &error("pattern for $variable doesn't define \$1; line=\n$_"); }
			}
			elsif ($type eq 'last') {
				if (defined $1) {
					$value{$variable} = $1;
				}
				else { &error("pattern for $variable doesn't define \$1; line=\n$_"); }
			}
			elsif ($type eq 'min') {
				if (defined $1) {
					if (defined $value{$variable}) {
						if ($1 < $value{$variable}) {
							$value{$variable} = $1;
						}
					}
					else { $value{$variable} = $1; }
				}
				else { &error("pattern for $variable doesn't define \$1; line=\n$_"); }
			}
			elsif ($type eq 'max') {
				if (defined $1) {
					if (defined $value{$variable}) {
						if ($1 > $value{$variable}) {
							$value{$variable} = $1;
						}
					}
					else { $value{$variable} = $1; }
				}
				else { &error("pattern for $variable doesn't define \$1; line=\n$_"); }
			}
			elsif ($type eq 'average') {
				if (defined $1) {
					if (defined $value{$variable}) {
						$value{$variable} += $1;
					}
					else { $value{$variable} = $1; }
					if (defined $count{$variable}) { $count{$variable}++; }
					else { $count{$variable} = 1; }
				}
				else { &error("pattern for $variable doesn't define \$1; line=\n$_"); }
			}
			else { &abort("unknown variable type $type for $variable"); }
		}
	}
}

# Remember where we left off
my $eof = tell(LOG);
&debug("$records log records read") if ($main::debug);
&debug("eof at $eof") if ($main::debug);
close (LOG);
&putcontext($contextfile, $eof) unless ($main::testmode);

# Now report what we found
my $now = time;
foreach $variable (@variables) {
	if ($type{$variable} eq 'average') {
		if (defined $value{$variable}) {
			$value{$variable} = $value{$variable}/$count{$variable};
		}
	}
	unless (defined $value{$variable}) { $value{$variable} = 0; }
	print "$now $variable $value{$variable}\n";
}

exit 0;

#------------------------------------------------------------ prompt ---
sub prompt {
	if (-t STDIN) { print $main::prog .'> '; }
	scalar(<STDIN>);
}

#------------------------------------------------------------ do_help ---
sub do_help {
	print <<"EOD_HELP";
$main::prog version $main::version
Valid commands are:
	LOGFILE GO QUIT VERSION DEBUG TEST HELP
or a variable specification:
	variable function pattern

The LOGFILE command requires the name of the log-file.
EOD_HELP
}

#----------------------------------------------------------------- usage ---
sub usage {
	print STDERR <<"EOD_USAGE";
$main::prog version $main::version
usage: $0 [options] logfile ...
where options are:
	-d nnn	enable debugging output at level 'nnn'
	-p ppp	set the prefix for context-files to 'ppp' [$main::context_prefix]
	-h	show this help
EOD_USAGE
	exit 0;
}

#----------------------------------------------------------------- debug ---
sub debug {
	my ($msg) = @_;

	if ($main::debug) { print "DEBUG: $msg\n"; }
0;
}

#--------------------------------------------------------------- abort ---
sub abort {
	my ($msg) = @_;
	print "ABORT: $msg\n";
	exit 1;
}

#--------------------------------------------------------------- error ---
sub error {
	my ($msg) = @_;
	print "ERROR: $msg\n";
}

#---------------------------------------------------------- putcontext ---
sub putcontext {
	my ($file, $string) = @_;

	open (PUTCONTEXT, ">$file") or &abort("can't open $file: $!");
	print PUTCONTEXT $string;
	close (PUTCONTEXT);
	&debug("saved context $string in $file") if ($main::debug);
}

#---------------------------------------------- keep_strict_happy ---
sub keep_strict_happy {
	$main::opt_h = $main::opt_t = 0;
}