1 | 1 | simandl | # |
2 | | | # $Id: HotSaNICmod.pm,v 1.42 2004/08/30 11:21:22 bernisys Exp $ |
3 | | | # |
4 | | | |
5 | | | package HotSaNICmod; |
6 | | | |
7 | | | use HotSaNICparser; |
8 | | | use HotSaNIClog; |
9 | | | use RRDs; |
10 | | | |
11 | | | my $MODNAME; |
12 | | | my %MODARGS; |
13 | | | my %CONFglob; |
14 | | | my $SAMPLING=0; |
15 | | | my $SAMPLE_LAST=0; |
16 | | | my $SAMPLE_DURATION=0; |
17 | | | |
18 | | | ($VERSION = '$Revision: 1.42 $') =~ s/.*(\d+\.\d+).*/$1/; |
19 | | | |
20 | | | ###################################################################### |
21 | | | # |
22 | | | # control function to make sure that a module is running only once |
23 | | | # |
24 | | | # Usage: |
25 | | | # dupe_control($what,@messages); |
26 | | | # |
27 | | | # $what: |
28 | | | # start -> call this method at the head of a module to touch the |
29 | | | # dupe-detection checkfile. |
30 | | | # stop -> remove PIDfile and exit normally. |
31 | | | # die -> stop process with error. |
32 | | | # warn -> print $message as warning and continue execution. |
33 | | | # FOR COMPATIBILITY REASONS ONLY! |
34 | | | # please use HotSaNIClog::warn |
35 | | | # info -> print $message and continue execution. |
36 | | | # FOR COMPATIBILITY REASONS ONLY! |
37 | | | # please use HotSaNIClog::info |
38 | | | # |
39 | | | # each message will be preceded with a timestamp and the module name |
40 | | | # |
41 | | | sub dupe_control { |
42 | | | my $what=shift; |
43 | | | my $pid=0; |
44 | | | my $delta=0; |
45 | | | my $line; |
46 | | | |
47 | | | if ( ($what eq "check") || ($what eq "start") ) { $pid=HotSaNICparser::get_pid(); } |
48 | | | |
49 | | | if ($what eq "info") { |
50 | | | HotSaNIClog::error("HotSaNICmod::dupe_control(\"info\",...); is deprecated, use HotSaNIClog::info instead!"); |
51 | | | HotSaNIClog::info(@_); |
52 | | | } |
53 | | | elsif ($what eq "warn") { |
54 | | | HotSaNIClog::error("HotSaNICmod::dupe_control(\"warn\",...); is deprecated, use HotSaNIClog::warn instead!"); |
55 | | | HotSaNIClog::warn(@_); |
56 | | | } |
57 | | | elsif ($what eq "start") { |
58 | | | if ($pid > 0) { |
59 | | | HotSaNIClog::error("process already running on PID $pid for $delta seconds."); |
60 | | | exit 1; |
61 | | | } |
62 | | | open FILE,">running.pid"; |
63 | | | print FILE $$; |
64 | | | close FILE; |
65 | | | HotSaNIClog::info("process forked to background."); |
66 | | | } |
67 | | | elsif ($what eq "stop") { |
68 | | | HotSaNIClog::info("exiting normally."); |
69 | | | if (-e "running.pid") { unlink "running.pid"; } |
70 | | | exit 0; |
71 | | | } |
72 | | | elsif ($what eq "die") { |
73 | | | HotSaNIClog::error(@_); |
74 | | | exit 1; |
75 | | | } |
76 | | | elsif ($what eq "check") { |
77 | | | if (!defined $pid) { $pid=0; } |
78 | | | if (($pid > 0) && (-e "/proc/$pid/stat")) { |
79 | | | open FILE,"/proc/$pid/stat"; |
80 | | | @fileds=split / /,<FILE>; |
81 | | | close FILE; |
82 | | | $starttime=$fileds[21]/100; |
83 | | | open FILE,"/proc/uptime"; |
84 | | | $line=<FILE>; |
85 | | | close FILE; |
86 | | | ($uptime)=split / /,$line; |
87 | | | $delta=int(($uptime-$starttime)*100)/100; |
88 | | | } |
89 | | | if (($pid > 0) && ($^O =~ /bsd/)) { |
90 | | | open FILE,"/proc/$pid/status"; |
91 | | | @fileds=split / /,<FILE>; |
92 | | | close FILE; |
93 | | | ($starttime, $msec)= split /,/, $fileds[7]; |
94 | | | $delta=int((time-$starttime)*100)/100; |
95 | | | } |
96 | | | if (!defined $delta) { $delta=0; } |
97 | | | return ($pid,$delta); |
98 | | | } |
99 | | | else { HotSaNIClog::error("HotSaNICmod::dupe_control method \"$what\" not supported."); } |
100 | | | } |
101 | | | |
102 | | | |
103 | | | ###################################################################### |
104 | | | # |
105 | | | # check cmdline arguments |
106 | | | # |
107 | | | sub init { |
108 | | | $args=shift || ""; |
109 | | | $MODNAME=HotSaNICparser::get_module_name(); |
110 | | | %CONFglob=HotSaNICparser::get_config("../..",$MODNAME); |
111 | | | if (scalar(keys(%CONFglob)) == 0) { HotSaNICmod::dupe_control("die","missing global settings file"); } |
112 | | | HotSaNIClog::set_timestamping($CONFglob{TIMESTAMPING}); |
113 | | | |
114 | | | my $found=0; |
115 | | | my @possible_args=("nodaemon","start","stop","status","configure","sample","update","version","help","showargs"); |
116 | | | foreach (@possible_args) { if ($args eq $_) { $found++; } } |
117 | | | if ($found == 0) { $args=""; } |
118 | | | |
119 | | | if ( $args eq "" ) { |
120 | | | print "usage: $0 [".join("/",@possible_args)."]\n"; |
121 | | | print "\n"; |
122 | | | exit 1; |
123 | | | } |
124 | | | |
125 | | | if ( $args =~ /help/) { |
126 | | | print "usage: $0 [".join("/",@possible_args)."]\n"; |
127 | | | print "\n"; |
128 | | | print "alternative usage: send a signal to <module PID>\n"; |
129 | | | print "\n"; |
130 | | | print "argument signal function\n"; |
131 | | | print "--------- ------- --------------------------------------------------\n"; |
132 | | | print "nodaemon start module in foreground\n"; |
133 | | | print "start start module daemon\n"; |
134 | | | print "stop SIGTERM terminate module daemon\n"; |
135 | | | print "status show status of module daemon\n"; |
136 | | | print "configure SIGHUP daemon re-reads its config file\n"; |
137 | | | print "sample SIGUSR1 sample now\n"; |
138 | | | print "update SIGUSR2 (not implemented yet)\n"; |
139 | | | print "version show version of OS-dependant module used\n"; |
140 | | | print "showargs show settings hash\n"; |
141 | | | print "\n"; |
142 | | | exit 0; |
143 | | | } |
144 | | | |
145 | | | # import common functions |
146 | | | # |
147 | | | my $COM_LIB="./platform/common.pm"; |
148 | | | if ( -e $COM_LIB ) { |
149 | | | eval { require $COM_LIB; }; |
150 | | | if ($@) { HotSaNICmod::dupe_control("die","can't import common library: $COM_LIB",$!,$@); } |
151 | | | } |
152 | | | else { HotSaNICmod::dupe_control("die","can't import common library: $COM_LIB","file not found."); } |
153 | | | |
154 | | | # import OS-specific functions |
155 | | | # |
156 | | | my $fallback=0; |
157 | | | my $OS_LIB="./platform/$^O.pm"; |
158 | | | if ( -e $OS_LIB ) { |
159 | | | eval { require $OS_LIB; }; |
160 | | | if ($@) { |
161 | | | HotSaNIClog::error("can't import library: $OS_LIB",$!,$@); |
162 | | | $fallback=1; |
163 | | | } |
164 | | | } |
165 | | | else { HotSaNIClog::info("no special library available for \"$^O\""); $fallback=1; } |
166 | | | |
167 | | | # OS-lib not found -> try to load default lib |
168 | | | # |
169 | | | if ($fallback == 1) { |
170 | | | HotSaNIClog::info("falling back to library \"default\""); |
171 | | | eval { require "./platform/default.pm"; }; |
172 | | | if ($@) { |
173 | | | HotSaNICmod::dupe_control("die","can't import library ./platform/default.pm",$!,$@); |
174 | | | } |
175 | | | } |
176 | | | |
177 | | | # read configuration, but don't initalize module (parameter "0") |
178 | | | configure(0); |
179 | | | |
180 | | | if ( ($args =~ /version/) or (HotSaNIClog::check_debuglevel("MODULE_VERBOSE"))) { |
181 | | | undef my @VERSIONS; |
182 | | | push @VERSIONS,HotSaNICmod::common::version() if defined &HotSaNICmod::common::version; |
183 | | | push @VERSIONS,HotSaNICmod::OSdep::version() if defined &HotSaNICmod::OSdep::version; |
184 | | | push @VERSIONS,HotSaNICmod::syssnmp::version() if defined &HotSaNICmod::syssnmp::version; |
185 | | | HotSaNIClog::info("using libs: ".join(" / ",@VERSIONS)); |
186 | | | } |
187 | | | |
188 | | | if ( ($args =~ /start/) or ($args =~ /nodaemon/) ) { |
189 | | | my ($pid,$uptime)=HotSaNICmod::dupe_control("check",""); |
190 | | | if ($pid > 0) { HotSaNIClog::info("module already running on PID $pid"); } |
191 | | | else { |
192 | | | |
193 | | | # initialize module |
194 | | | # |
195 | | | unlink "*.dat","*.old"; |
196 | | | if ( -e "init" ) { |
197 | | | HotSaNIClog::info("-- begin init --"); |
198 | | | system "./init"; |
199 | | | HotSaNIClog::info("-- end init --"); |
200 | | | } |
201 | | | elsif (defined &HotSaNICmod::OSdep::init) { |
202 | | | HotSaNIClog::info("-- begin init --"); |
203 | | | HotSaNICmod::OSdep::init(%MODARGS); |
204 | | | HotSaNIClog::info("-- end init --"); |
205 | | | } |
206 | | | |
207 | | | # set signal handlers and create background childprocess |
208 | | | # |
209 | | | $SIG{TERM} = \&terminate; |
210 | | | $SIG{HUP} = \&configure; |
211 | | | $SIG{USR1} = \&sample; |
212 | | | $SIG{USR2} = \&update; |
213 | | | if ($args =~ /start/) { fork && exit; } |
214 | | | HotSaNICmod::dupe_control("start",""); |
215 | | | while (1 == 1) { sleep; } |
216 | | | } |
217 | | | } |
218 | | | |
219 | | | if ( $args =~ /status/) { |
220 | | | my ($pid,$uptime)=HotSaNICmod::dupe_control("check",""); |
221 | | | if ($pid > 0) { |
222 | | | print "module \"$MODNAME\" running on PID $pid for $uptime sec.\n"; |
223 | | | } |
224 | | | else { print "$MODNAME: no process running.\n"; } |
225 | | | my @DBs=HotSaNICmod::get_DBs(); |
226 | | | my ($min,$max)=HotSaNICmod::get_last_DB_changes(); |
227 | | | if ($min>=0) { |
228 | | | print "last DB update: $min"; |
229 | | | if ($max != $min) { print " (max: $max)"; } |
230 | | | print " seconds ago\n"; |
231 | | | print "DBs found: ",join("\n ",@DBs),"\n"; |
232 | | | } |
233 | | | } |
234 | | | |
235 | | | if ( $args =~ /sample/) { |
236 | | | my ($pid,$uptime)=HotSaNICmod::dupe_control("check",""); |
237 | | | if ($pid > 0) { kill "SIGUSR1",$pid; } |
238 | | | else { print "$MODNAME: no process running.\n"; } |
239 | | | } |
240 | | | |
241 | | | if ( $args =~ /stop/) { |
242 | | | my ($pid,$uptime)=HotSaNICmod::dupe_control("check",""); |
243 | | | if ($pid > 0) { kill "SIGTERM",$pid; } |
244 | | | else { print "$MODNAME: no process running.\n"; } |
245 | | | } |
246 | | | |
247 | | | if ( $args =~ /configure/) { |
248 | | | my ($pid,$uptime)=HotSaNICmod::dupe_control("check",""); |
249 | | | if ($pid > 0) { kill "SIGHUP",$pid; } |
250 | | | else { print "$MODNAME: no process running.\n"; } |
251 | | | } |
252 | | | |
253 | | | if ( $args =~ /update/) { |
254 | | | my ($pid,$uptime)=HotSaNICmod::dupe_control("check",""); |
255 | | | if ($pid > 0) { kill "SIGUSR2",$pid; } |
256 | | | else { print "$MODNAME: no process running.\n"; } |
257 | | | } |
258 | | | |
259 | | | if ( $args =~ /showargs/) { |
260 | | | print "---------- CONFIGURATION ----------\n"; |
261 | | | # |
262 | | | # alternative code - higher memory usage! |
263 | | | # |
264 | | | # use Data::Dumper; |
265 | | | # $Data::Dumper::Varname="MODARGS"; |
266 | | | # $Data::Dumper::Sortkeys=1; |
267 | | | # print Dumper(\%MODARGS); |
268 | | | # |
269 | | | for (sort keys %MODARGS) { |
270 | | | print " $_ = ".$MODARGS{$_}."\n"; |
271 | | | if (ref $MODARGS{$_} eq "ARRAY") { |
272 | | | if (! @{$MODARGS{$_}}) { print " " x ((length $_)+5),"(empty)\n"; } |
273 | | | foreach $line (@{$MODARGS{$_}}) { print " " x ((length $_)+5),"-> $line\n"; } |
274 | | | } |
275 | | | } |
276 | | | print "-----------------------------------\n"; |
277 | | | } |
278 | | | } |
279 | | | |
280 | | | |
281 | | | ###################################################################### |
282 | | | # |
283 | | | # terminate module daemon |
284 | | | # |
285 | | | sub terminate { |
286 | | | HotSaNICmod::dupe_control("stop",""); |
287 | | | } |
288 | | | |
289 | | | ###################################################################### |
290 | | | # |
291 | | | # interface to OS-dependent functions |
292 | | | # |
293 | | | sub sample { |
294 | | | my $DIFF=time-$SAMPLE_LAST; |
295 | | | if (HotSaNIClog::check_debuglevel("MODULE_SAMPLING")) { |
296 | | | HotSaNIClog::info("last sampling $DIFF seconds ago took $SAMPLE_DURATION seconds."); |
297 | | | } |
298 | | | if ( $DIFF >= 9) { |
299 | | | if ($SAMPLING == 0) { |
300 | | | # auto throttling |
301 | | | # skip sample if last sampling process took > 5 seconds and divide last duration by 2 |
302 | | | if ($SAMPLE_DURATION > 5) { |
303 | | | $SAMPLE_DURATION/=2; |
304 | | | if (HotSaNIClog::check_debuglevel("MODULE_SAMPLING")) { |
305 | | | HotSaNIClog::warn("can't sample now, last sampling took > 5s."); |
306 | | | } |
307 | | | } |
308 | | | else { |
309 | | | $SAMPLING=1; |
310 | | | $SAMPLE_LAST=time; |
311 | | | HotSaNICmod::OSdep::sample(%MODARGS); |
312 | | | if ( (defined $MODARGS{USE_SNMP}) and (defined &HotSaNICmod::syssnmp::sample) ) { HotSaNICmod::syssnmp::sample(%MODARGS); } |
313 | | | $SAMPLING=0; |
314 | | | $SAMPLE_DURATION=(time-$SAMPLE_LAST); |
315 | | | } |
316 | | | } |
317 | | | elsif (HotSaNIClog::check_debuglevel("MODULE_SAMPLING")) { |
318 | | | HotSaNIClog::warn("can't sample now, old sampling process running."); |
319 | | | } |
320 | | | } |
321 | | | elsif (HotSaNIClog::check_debuglevel("MODULE_SAMPLING")) { |
322 | | | HotSaNIClog::warn("can't sample now, last sample taken less than 10s ago."); |
323 | | | } |
324 | | | } |
325 | | | |
326 | | | sub update { |
327 | | | HotSaNICmod::OSdep::update; |
328 | | | } |
329 | | | |
330 | | | ###################################################################### |
331 | | | # |
332 | | | # configure |
333 | | | # - parse main settings |
334 | | | # - parse module settings |
335 | | | # - set module-specific configuration |
336 | | | sub configure { |
337 | | | $initmode=shift || 0; |
338 | | | |
339 | | | my @CONFmod=HotSaNICparser::read_settings(".",$MODNAME); |
340 | | | if (scalar(@CONFmod) == 0) { HotSaNICmod::dupe_control("die","missing module settings file"); } |
341 | | | |
342 | | | my $modvar=$CONFglob{VARDIR}."/modules/".lc $MODNAME; |
343 | | | $modvar=~ s/\/+/\//g; |
344 | | | if (! -d $CONFglob{VARDIR}."/modules") { mkdir $CONFglob{VARDIR}."/modules",0755; } |
345 | | | if (! -d $modvar) { mkdir $modvar,0755; } |
346 | | | |
347 | | | # configure module-specific settings |
348 | | | # |
349 | | | %MODARGS=HotSaNICmod::common::configure(@CONFmod); |
350 | | | |
351 | | | # import module's SNMP library if necessary |
352 | | | # |
353 | | | if (defined $MODARGS{USE_SNMP}) { |
354 | | | my $SNMP_LIB="./platform/syssnmp.pm"; |
355 | | | $MODARGS{SNMPWALK}=$CONFglob{SNMPWALK}; |
356 | | | $MODARGS{SNMPGET}=$CONFglob{SNMPGET}; |
357 | | | if ( -e $SNMP_LIB ) { |
358 | | | eval { require $SNMP_LIB; }; |
359 | | | if ($@) { HotSaNIClog::error("can't import library: $SNMP_LIB",$!,$@); } |
360 | | | } |
361 | | | else { HotSaNIClog::info("no SNMP library available"); } |
362 | | | } |
363 | | | |
364 | | | # configure common module settings |
365 | | | # |
366 | | | foreach (@CONFmod) { |
367 | | | ($var,$value)=HotSaNICparser::parse_line($_); |
368 | | | if ($var eq "DEBUGLEVEL") { $MODARGS{DEBUGLEVEL}=HotSaNIClog::get_debuglevel($value); } |
369 | | | } |
370 | | | |
371 | | | # add global settings |
372 | | | # |
373 | | | $MODARGS{MODNAME}=$MODNAME; |
374 | | | $MODARGS{VARDIR}=$modvar; |
375 | | | $MODARGS{DEBUGLEVEL} |= HotSaNIClog::get_debuglevel($CONFglob{DEBUGLEVEL}); |
376 | | | HotSaNIClog::set_debuglevel($MODARGS{DEBUGLEVEL}); |
377 | | | if ($initmode eq "HUP") { HotSaNICmod::OSdep::init(%MODARGS) if defined &HotSaNICmod::OSdep::init; } |
378 | | | } |
379 | | | |
380 | | | ###################################################################### |
381 | | | # |
382 | | | # returns an array containing all "*.rrd" files in the current module's "rrd" directory |
383 | | | # |
384 | | | sub get_DBs { |
385 | | | opendir(DIR,"rrd") || HotSaNICmod::dupe_control("die",$MODARGS{"MODNAME"},"error opening database dir - $!"); |
386 | | | my @DBs=grep(/\.rrd/,readdir(DIR)); |
387 | | | closedir(DIR); |
388 | | | return @DBs; |
389 | | | } |
390 | | | |
391 | | | |
392 | | | ###################################################################### |
393 | | | # |
394 | | | # returns an array containing the min and max seconds since last DB changes |
395 | | | # |
396 | | | # if no DBs are found, (-1,-1) will be returned. |
397 | | | # |
398 | | | sub get_last_DB_changes { |
399 | | | |
400 | | | my @DBs=get_DBs(); |
401 | | | |
402 | | | if (!@DBs) { return (-1,-1); } |
403 | | | |
404 | | | my ($min,$max)=(999999999999,0); |
405 | | | foreach $test (@DBs) { |
406 | | | (undef,undef,undef,undef,undef,undef,undef,undef,undef,$mtime,undef,undef,undef)=stat("rrd/".$test); |
407 | | | $mtime=time-$mtime; |
408 | | | if ($mtime<$min) { $min=$mtime; } |
409 | | | if ($mtime>$max) { $max=$mtime; } |
410 | | | } |
411 | | | |
412 | | | return ($min,$max); |
413 | | | } |
414 | | | |
415 | | | |
416 | | | ###################################################################### |
417 | | | # |
418 | | | # updates a database, creates a new one if necessary |
419 | | | # |
420 | | | # USAGE: |
421 | | | # |
422 | | | # do_rrd($database,$maxvalue,$sampletime,@values); |
423 | | | # |
424 | | | # $database name of the database (without ".rrd" suffix) |
425 | | | # |
426 | | | # $maxvalue the maximum value to be expected on this datasource |
427 | | | # (needed for DB creation) |
428 | | | # |
429 | | | # $sampletime timestamp of the current sample |
430 | | | # |
431 | | | # @values array of all values for this timestamp |
432 | | | # |
433 | | | sub do_rrd { |
434 | | | my $dbname = shift; |
435 | | | my $dbmax = shift; |
436 | | | my $sampletime = shift; |
437 | | | |
438 | | | if ( $#_ < 0 ) { |
439 | | | HotSaNIClog::warn("no values passed for $dbname"); |
440 | | | } |
441 | | | |
442 | | | my $values=join(":",@_); |
443 | | | |
444 | | | if (HotSaNIClog::check_debuglevel("MODULE_DB_UPDATE")) { |
445 | | | HotSaNIClog::info("updating $dbname with $values"); |
446 | | | } |
447 | | | |
448 | | | # build new database if needed |
449 | | | if ( ! -e "rrd/$dbname.rrd" ) { system("./makerrd","$dbname","$dbmax") } |
450 | | | |
451 | | | # update database |
452 | | | RRDs::update "rrd/$dbname.rrd",$sampletime.":$values"; |
453 | | | if ($ERROR = RRDs::error) { HotSaNIClog::error("unable to update '$dbname.rrd': $ERROR"); } |
454 | | | } |
455 | | | |
456 | | | 1; |
457 | | | |