Ticket #3: munin-graph

File munin-graph, 40.7 kB (added by blueyed, 3 years ago)

For reference: my munin-graph (derived from the patch)

Line 
1 #!/usr/bin/perl -w
2 #
3 # Copyright (C) 2002-2004 Jimmy Olsen, Audun Ytterdal
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2 dated June,
8 # 1991.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 #
19 #
20 # $Id: munin-graph.in 1278 2007-08-07 14:59:04Z loic $
21
22 $|=1;
23
24 use strict;
25 use IO::Socket;
26 use RRDs;
27 use Munin;
28 use POSIX qw(strftime);
29 use Digest::MD5;
30 use Getopt::Long;
31 use Time::HiRes;
32
33 my $graph_time= Time::HiRes::time;
34 my $DEBUG = 0;
35 my $VERSION = "1.2.5";
36
37 # Limit graphing to certain hosts and/or services
38 my @limit_hosts = ();
39 my @limit_services = ();
40
41 # RRDtool 1.2 requires \\: in comments
42 my $RRDkludge = $RRDs::VERSION < 1.2 ? '' : '\\';
43
44 # Force drawing of "graph no".
45 my $force_graphing = 0;
46 my $force_lazy = 1;
47 my $force_root = 0;
48 my $do_usage = 0;
49 my $do_version = 0;
50 my $cron = 0;
51 my $list_images = 0;
52 my $skip_locking = 0;
53 my $skip_stats = 0;
54 my $stdout = 0;
55 my $conffile = "/etc/munin/munin.conf";
56 my %draw = ("day" => 1, "week" => 1, "month" => 1, "year" => 1, "sumyear" => 1, "sumweek" => 1);
57
58 my $log = new IO::Handle;
59
60 # Get options
61 $do_usage=1  unless
62 GetOptions ( "force!"       => \$force_graphing,
63              "lazy!"        => \$force_lazy,
64              "force-root!"  => \$force_root,
65              "host=s"       => \@limit_hosts,
66              "service=s"    => \@limit_services,
67              "config=s"     => \$conffile,
68              "stdout!"      => \$stdout,
69              "day!"         => \$draw{'day'},
70              "week!"        => \$draw{'week'},
71              "month!"       => \$draw{'month'},
72              "year!"        => \$draw{'year'},
73              "sumweek!"     => \$draw{'sumweek'},
74              "sumyear!"     => \$draw{'sumyear'},
75              "list-images!" => \$list_images,
76              "skip-locking!"=> \$skip_locking,
77              "skip-stats!"  => \$skip_stats,
78              "debug!"       => \$DEBUG,
79              "version!"     => \$do_version,
80              "cron!"        => \$cron,
81              "help"         => \$do_usage );
82
83 if ($do_usage)
84 {
85     print "Usage: $0 [options]
86
87 Options:
88     --[no]force         Force drawing of graphs that are not usually
89                         drawn due to options in the config file. [--noforce]
90     --[no]force-root    Force running, even as root. [--noforce-root]
91     --[no]lazy          Only redraw graphs when needed. [--lazy]
92     --help              View this message.
93     --version           View version information.
94     --debug             View debug messages.
95     --[no]cron          Behave as expected when run from cron. (Used internally
96                         in Munin.)
97     --service <service> Limit graphed services to <service>. Multiple --service
98                         options may be supplied.
99     --host <host>       Limit graphed hosts to <host>. Multiple --host options
100                         may be supplied.
101     --config <file>     Use <file> as configuration file. [/etc/munin/munin.conf]
102     --[no]list-images   List the filenames of the images created.
103                         [--nolist-images]
104     --[no]day           Create day-graphs.   [--day]
105     --[no]week          Create week-graphs.  [--week]
106     --[no]month         Create month-graphs. [--month]
107     --[no]year          Create year-graphs.  [--year]
108     --[no]sumweek       Create summarised week-graphs.  [--summweek]
109     --[no]sumyear       Create summarised year-graphs.  [--sumyear]
110
111 ";
112     exit 0;
113 }
114
115 if ($do_version)
116 {
117     print "munin-graph version $VERSION.\n";
118     print "Written by Audun Ytterdal, Jimmy Olsen, Tore Anderson / Linpro AS\n";
119     print "\n";
120     print "Copyright (C) 2002-2004\n";
121     print "This is free software released under the GNU Public License. There is NO\n";
122     print "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
123     exit 0;
124 }
125
126 if ($> == 0 and !$force_root)
127 {
128     print "You are running this program as root, which is neither smart nor necessary.
129 If you really want to run it as root, use the --force-root option. Else, run
130 it as the user \"munin\". Aborting.\n\n";
131     exit (1);
132 }
133
134 my $config= &munin_config ($conffile);
135
136 if (&munin_get ($config, "graph_strategy", "cron") ne "cron" and $cron)
137 { # We're run from cron, but munin.conf says we use dynamic graph generation
138     exit 0;
139 }
140
141 munin_runlock("$config->{rundir}/munin-graph.lock") unless $skip_locking;
142
143 unless ($skip_stats)
144 {
145         open (STATS,">$config->{dbdir}/munin-graph.stats.tmp") or logger("Unable to open $config->{dbdir}/munin-graph.stats.tmp");
146 }
147
148 logger("Starting munin-graph");
149
150 my @COLOUR = ("#22ff22", "#0022ff", "#ff0000", "#00aaaa", "#ff00ff",
151               "#ffa500", "#cc0000", "#0000cc", "#0080C0", "#8080C0", "#FF0080",
152               "#800080", "#688e23", "#408080", "#808000", "#000000", "#00FF00",
153               "#0080FF", "#FF8000", "#800000", "#FB31FB");
154 my $range_colour = "#22ff22";
155 my $single_colour = "#00aa00";
156
157 my %times = (
158              "day"   => "-30h",
159              "week"  => "-8d",
160              "month" => "-33d",
161              "year"  => "-400d");
162
163 my %resolutions = (
164              "day"   => "300",
165              "week"  => "1500",
166              "month" => "7200",
167              "year"  => "86400");
168
169 my %sumtimes = ( # time => [ label, seconds-in-period ]
170             "week"   => ["hour", 12],
171             "year"   => ["day", 288]
172         );
173
174 for my $key ( keys %{$config->{domain}}) {
175     my $domain_time= Time::HiRes::time;
176     mkdir "$config->{htmldir}/$key",0777;
177     logger("Processing domain: $key");
178     &process_domain($key);
179     $domain_time = sprintf ("%.2f",(Time::HiRes::time - $domain_time));
180     logger("Processed domain: $key ($domain_time sec)");
181     print STATS "GD|$key|$domain_time\n" unless $skip_stats;
182 }
183
184
185 $graph_time = sprintf ("%.2f",(Time::HiRes::time - $graph_time));
186 logger("Munin-graph finished ($graph_time sec)");
187 print STATS "GT|total|$graph_time\n" unless $skip_stats;
188 rename ("$config->{dbdir}/munin-graph.stats.tmp", "$config->{dbdir}/munin-graph.stats");
189 close STATS unless $skip_stats;
190 close $log;
191
192 sub process_domain {
193     my ($domain) = @_;
194     for my $key ( keys %{$config->{domain}->{$domain}->{node}}) {
195         my $node_time= Time::HiRes::time;
196
197         process_node($domain,$key ,$config->{domain}->{$domain}->{node}->{$key} );     
198         $node_time = sprintf ("%.2f",(Time::HiRes::time - $node_time));
199         logger ("Processed node: $key ($node_time sec)");
200         print STATS "GN|$domain|$key|$node_time\n" unless $skip_stats;
201         
202     }
203 }
204
205 sub get_title {
206     my $node    = shift;
207     my $service = shift;
208     my $scale   = shift;
209
210     return ($node->{client}->{$service}->{'graph_title'}?
211               $node->{client}->{$service}->{'graph_title'}:$service) .
212               " - by $scale";
213 }
214
215 sub get_custom_graph_args
216 {
217     my $node    = shift;
218     my $service = shift;
219     my $result  = [];
220
221     if ($node->{client}->{$service}->{graph_args}) {
222         push @$result, split /\s/,$node->{client}->{$service}->{graph_args};
223         return $result;
224     }
225     else
226     {
227         return undef;
228     }
229 }
230
231 sub get_vlabel
232 {
233     my $node    = shift;
234     my $service = shift;
235     my $scale   = shift;
236
237     if ($node->{client}->{$service}->{graph_vlabel}) {
238         (my $res = $node->{client}->{$service}->{graph_vlabel}) =~ s/\$\{graph_period\}/$scale/g;
239         return $res;
240     }
241     elsif ($node->{client}->{$service}->{graph_vtitle})
242     {
243         return $node->{client}->{$service}->{graph_vtitle};
244     }
245     return undef;
246 }
247
248 sub should_scale
249 {
250     my $node    = shift;
251     my $service = shift;
252
253     if (defined $node->{client}->{$service}->{graph_scale})
254     {
255         return &munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1);
256     }
257     elsif (defined $node->{client}->{$service}->{graph_noscale})
258     {
259         return ! &munin_get_bool_val ($node->{client}->{$service}->{graph_noscale}, 0);
260     }
261
262     return 1;
263 }
264
265 sub get_header {
266     my $node    = shift;
267     my $config  = shift;
268     my $domain  = shift;
269     my $host    = shift;
270     my $service = shift;
271     my $scale   = shift;
272     my $sum     = shift;
273     my $result  = [];
274     my $tmp_field;
275
276     # Picture filename
277     push @$result, &munin_get_picture_filename ($config, $domain, $host, $service, $scale, $sum||undef);
278
279     # Title
280     push @$result, ("--title", &get_title ($node, $service, $scale));
281
282     # When to start the graph
283     push @$result, "--start",$times{$scale};
284
285     # Custom graph args, vlabel and graph title
286     if (defined ($tmp_field = &get_custom_graph_args ($node, $service))) {
287         push (@$result, @{$tmp_field});
288     }
289     if (defined ($tmp_field = &get_vlabel ($node, $service, munin_get ($config, "graph_period", "second", $domain, $host, $service)))) {
290         push @$result, ("--vertical-label", $tmp_field);
291     }
292
293     push @$result,"--height", &munin_get ($config, "graph_height", "175", $domain, $host, $service);
294     push @$result,"--width", &munin_get ($config, "graph_width", "400", $domain, $host, $service);
295     push @$result,"--imgformat", "PNG";
296     push @$result,"--lazy" if ($force_lazy);
297
298     push (@$result, "--units-exponent", "0")
299         if (! &should_scale ($node, $service));
300
301     return $result;
302 }
303
304 sub get_sum_command
305 {
306     my $node    = shift;
307     my $service = shift;
308     my $field   = shift;
309
310     if (defined $node->{client}->{$service}->{$field.".special_sum"})
311     {
312         return $node->{client}->{$service}->{$field.".special_sum"};
313     }
314     elsif (defined $node->{client}->{$service}->{$field.".sum"})
315     {
316         return $node->{client}->{$service}->{$field.".sum"};
317     }
318
319     return undef;
320 }
321
322 sub get_stack_command
323 {
324     my $node    = shift;
325     my $service = shift;
326     my $field   = shift;
327
328     if (defined $node->{client}->{$service}->{$field.".special_stack"})
329     {
330         return $node->{client}->{$service}->{$field.".special_stack"};
331     }
332     elsif (defined $node->{client}->{$service}->{$field.".stack"})
333     {
334         return $node->{client}->{$service}->{$field.".stack"};
335     }
336
337     return undef;
338 }
339
340 sub expand_specials
341 {
342     my $node    = shift;
343     my $config  = shift;
344     my $domain  = shift;
345     my $host    = shift;
346     my $service = shift;
347     my $preproc = shift;
348     my $order   = shift;
349     my $single  = shift;
350     my $result  = [];
351
352     my $fieldnum = 0;
353     for my $field (@$order) { # Search for 'specials'...
354
355         if ($field =~ /^-(.+)$/)
356         {
357             $field = $1;
358             unless (defined $node->{client}->{$service}->{$field.".graph"} or
359                     defined $node->{client}->{$service}->{$field.".skipdraw"})
360             {
361                 $node->{client}->{$service}->{$field.".graph"} = "no";
362             }
363         }
364
365         $fieldnum++;
366         my $tmp_field;
367         if (defined ($tmp_field = &get_stack_command ($node, $service, $field)))
368         {
369             print "DEBUG: Doing special_stack...\n" if $DEBUG;
370             my @spc_stack = ();
371             foreach my $pre (split (/\s+/, $tmp_field))
372             {
373                 (my $name = $pre) =~ s/=.+//;
374                 if (!@spc_stack)
375                 {
376                     $node->{client}->{$service}->{$name.".draw"} = $node->{client}->{$service}->{$field.".draw"};
377                     $node->{client}->{$service}->{$field.".process"} = "no";
378                 }
379                 else
380                 {
381                     $node->{client}->{$service}->{$name.".draw"} = "STACK";
382                 }
383                 push (@spc_stack, $name);
384                 push (@$preproc, $pre);
385                 push @$result, "$name.label";
386                 push @$result, "$name.draw";
387                 push @$result, "$name.cdef";
388
389                 $node->{client}->{$service}->{$name.".label"} = $name;
390                 $node->{client}->{$service}->{$name.".cdef"} = "$name,UN,0,$name,IF";
391                 if (exists $node->{client}->{$service}->{$field.".cdef"} and !exists $node->{client}->{$service}->{$name.".onlynullcdef"})
392                 {
393                     print "NotOnlynullcdef ($field)...\n" if $DEBUG;
394                     $node->{client}->{$service}->{$name.".cdef"} .= "," .
395                         $node->{client}->{$service}->{$field.".cdef"};
396                     $node->{client}->{$service}->{$name.".cdef"} =~ s/\b$field\b/$name/g;
397                 }
398                 else
399                 {
400                     print "Onlynullcdef ($field)...\n" if $DEBUG;
401                     $node->{client}->{$service}->{$name.".onlynullcdef"} = 1;
402                     push @$result, "$name.onlynullcdef";
403                 }
404             }
405         }
406         elsif (defined ($tmp_field = &get_sum_command ($node, $service, $field)))
407         {
408             my @spc_stack = ();
409             my $last_name = "";
410             print "DEBUG: Doing special_sum...\n" if $DEBUG;
411
412                 if (@$order == 1 or
413                         @$order == 2 && $node->{client}->{$service}->{$field.".negative"})
414                 {
415                         $single = 1;
416                 }
417                 
418             foreach my $pre (split (/\s+/, $tmp_field))
419             {
420                 (my $path = $pre) =~ s/.+=//;
421                 my $name = "z".$fieldnum."_".scalar (@spc_stack);
422                 $last_name = $name;
423
424                 $node->{client}->{$service}->{$name.".cdef"}  = "$name,UN,0,$name,IF";
425                 $node->{client}->{$service}->{$name.".graph"} = "no";
426                 $node->{client}->{$service}->{$name.".label"} = $name;
427                 push @$result, "$name.cdef";
428                 push @$result, "$name.graph";
429                 push @$result, "$name.label";
430
431                 push (@spc_stack, $name);
432                 push (@$preproc, "$name=$pre");
433             }
434             $node->{client}->{$service}->{$last_name.".cdef"} .=
435                 "," . join (',+,', @spc_stack[0 .. @spc_stack-2]) . ',+';
436             if (exists $node->{client}->{$service}->{$field.".cdef"} and
437                     length $node->{client}->{$service}->{$field.".cdef"})
438             { # Oh bugger...
439                 my $tc = $node->{client}->{$service}->{$field.".cdef"};
440                 print "Oh bugger...($field)...\n" if $DEBUG;
441                 $tc =~ s/\b$field\b/$node->{client}->{$service}->{$last_name.".cdef"}/;
442                 $node->{client}->{$service}->{$last_name.".cdef"} = $tc;
443             }
444             $node->{client}->{$service}->{$field.".process"} = "no";
445             $node->{client}->{$service}->{$last_name.".draw"} = $node->{client}->{$service}->{$field.".draw"};
446             $node->{client}->{$service}->{$last_name.".label"} = $node->{client}->{$service}->{$field.".label"};
447             if (defined $node->{client}->{$service}->{$field.".graph"})
448             {
449                 $node->{client}->{$service}->{$last_name.".graph"} = $node->{client}->{$service}->{$field.".graph"};
450             }
451             else
452             {
453                 $node->{client}->{$service}->{$last_name.".graph"} = "yes";
454             }
455             if (defined $node->{client}->{$service}->{$field.".negative"})
456             {
457                 $node->{client}->{$service}->{$last_name.".negative"} = $node->{client}->{$service}->{$field.".negative"};;
458             }
459             $node->{client}->{$service}->{$field.".realname"} = $last_name;
460             print "Setting node->{client}->{$service}->{$field} -> realname = $last_name...\n" if $DEBUG;
461         }
462         elsif (defined $node->{client}->{$service}->{$field.".negative"})
463         {
464             my $nf = $node->{client}->{$service}->{$field.".negative"};
465             unless (defined $node->{client}->{$service}->{$nf.".graph"} or
466                     defined $node->{client}->{$service}->{$nf.".skipdraw"})
467             {
468                 $node->{client}->{$service}->{$nf.".graph"} = "no";
469             }
470         }
471     }
472     return $result;
473 }
474
475 sub single_value
476 {
477     my $node    = shift;
478     my $config  = shift;
479     my $domain  = shift;
480     my $host    = shift;
481     my $service = shift;
482     my $field   = shift;
483     my $order   = shift;
484
485     return 1 if @$order == 1;
486     return 1 if (@$order == 2 and $node->{client}->{$service}->{$field.".negative"});
487
488     my $graphable = 0;
489     if (!defined $node->{client}->{$service}->{"graphable"})
490     {
491 #       foreach my $field (keys %{$node->{client}->{$service}})
492         foreach my $field (&munin_get_field_order ($node, $config, $domain, $host, $service))
493         {
494             print "DEBUG: single_value: Checking field \"$field\".\n" if $DEBUG;
495             if ($field =~ /^([^\.]+)\.label/ or $field =~ /=/)
496             {
497                 $graphable++ if &munin_draw_field ($node, $service, $1);
498             }
499         }
500         $node->{client}->{$service}->{"graphable"} = $graphable;
501     }
502     return 1 if ($node->{client}->{$service}->{"graphable"} == 1);
503     
504     return 0;
505 }
506
507 sub get_field_name
508 {
509     my $name = shift;
510     
511     $name = substr (Digest::MD5::md5_hex ($name), -15)
512         if (length $name > 15);
513
514     return $name;
515 }
516
517 sub process_field {
518     my $node    = shift;
519     my $service = shift;
520     my $field   = shift;
521     return (&munin_get_bool_val ($node->{client}->{$service}->{$field.".process"}, 1));
522 }
523
524 sub process_node {
525     my ($domain,$name,$node) = @_;
526
527     # See if we should skip it because of command-line arguments
528     return if (@limit_hosts and not grep (/^$name$/, @limit_hosts));
529
530     # Make my graphs
531     logger ("Processing $name") if $DEBUG;
532     for my $service (keys %{$node->{client}}) {
533         my $service_time= Time::HiRes::time;
534         my $lastupdate = 0;
535         my $now  = time;
536         my $fnum = 0;
537         my @rrd;
538         my @added = ();
539
540         # See if we should skip the service
541         next if (&skip_service ($node, $service));
542
543         my $field_count = 0;
544         my $max_field_len = 0;
545         my @field_order = ();
546         my $rrdname;
547         my $force_single_value;
548
549         @field_order =  @{&munin_get_field_order ($node, $config, $domain, $name, $service, \$force_single_value)};
550
551         # Array to keep 'preprocess'ed fields.
552         my @rrd_preprocess = ();
553         print "DEBUG: Expanding specials \"", join "\",\"", @field_order, "\".\n" if $DEBUG;
554         @added = @{&expand_specials ($node, $config, $domain, $name, $service, \@rrd_preprocess, \@field_order)};
555
556         @field_order = (@rrd_preprocess, @field_order);
557         print "DEBUG: Checking field lengths \"", join "\",\"", (@rrd_preprocess, @field_order), "\".\n" if $DEBUG;
558
559         # Get max label length
560         $max_field_len = &munin_get_max_label_length ($node, $config, $domain, $name, $service, \@field_order);
561         my $global_headers = ($max_field_len > 16);
562
563         # Array to keep negative data until we're finished with positive.
564         my @rrd_negatives = ();
565         my $filename = "unknown";
566         my %total_pos;
567         my %total_neg;
568         print "DEBUG: Treating fields \"", join "\",\"", @field_order, "\".\n" if $DEBUG;
569         for my $field (@field_order) {
570             my $path  = undef;
571             if ($field =~ s/=(.+)//)
572             {
573                 $path = $1;
574             }
575
576             next unless &process_field ($node, $service, $field);
577             print "DEBUG: Processing field \"$field\".\n" if $DEBUG;
578
579             if ($field_count == 0 and munin_get ($config, "draw", "LINE2", $domain, $name, $service, $field) eq "STACK")
580             { # Illegal -- first field is a STACK
581                 logger ("ERROR: First field (\"$field\") of graph \"$domain\" :: \"$name\" :: \"$service\" is STACK. STACK can only be drawn after a LINEx or AREA.");
582             }
583                 
584             # Getting name of rrd file
585             $filename = &munin_get_rrd_filename ($node, $config, $domain, $name, $service, $field, $path);
586
587             my $update = RRDs::last ($filename);
588             $update = 0 if ! defined $update;
589             if ($update > $lastupdate)
590             {
591                 $lastupdate = $update;
592             }
593
594             my $rrdfield = ($node->{client}->{$service}->{$field.".rrdfield"} || "42");
595             my $single_value = $force_single_value || &single_value ($node, $config, $domain, $name, $service, $field, \@field_order);
596             my $has_negative = exists $node->{client}->{$service}->{$field.".negative"};
597
598             # Trim the fieldname to make room for other field names.
599             $rrdname = &get_field_name ($field);
600             if ($rrdname ne $field) # A change was made
601             {
602                 set_cdef_name ($node->{client}->{$service}, $field, $rrdname);
603             }
604
605             push (@rrd, "DEF:g$rrdname=" .
606                   $filename . ":" . $rrdfield . ":AVERAGE");
607             push (@rrd, "DEF:i$rrdname=" .
608                   $filename . ":" . $rrdfield . ":MIN");
609             push (@rrd, "DEF:a$rrdname=" .
610                   $filename . ":" . $rrdfield . ":MAX");
611             if (exists $node->{client}->{$service}->{$field.".onlynullcdef"} and $node->{client}->{$service}->{$field.".onlynullcdef"})
612             {
613                 push (@rrd, "CDEF:c$rrdname=g$rrdname" . (($now-$update)>900 ? ",POP,UNKN" : ""));
614             }
615             if (($node->{client}->{$service}->{$field.".type"}||"GAUGE") ne "GAUGE" and graph_by_minute ($config, $domain, $name, $service))
616             {
617                 push (@rrd, &expand_cdef($node->{client}->{$service}, \$rrdname, "$field,60,*"));
618             }
619             if ($node->{client}->{$service}->{$field.".cdef"})
620             {
621                 push (@rrd, &expand_cdef($node->{client}->{$service}, \$rrdname, $node->{client}->{$service}->{$field.".cdef"}));
622                 push (@rrd, "CDEF:c$rrdname=g$rrdname");
623                 print "DEBUG: Field name after cdef set to $rrdname\n" if $DEBUG;
624             }
625             elsif (!(exists $node->{client}->{$service}->{$field.".onlynullcdef"} and $node->{client}->{$service}->{$field.".onlynullcdef"}))
626             {
627                 push (@rrd, "CDEF:c$rrdname=g$rrdname" . (($now-$update)>900 ? ",POP,UNKN" : ""));
628             }
629
630             next unless &munin_draw_field ($node, $service, $field);
631             print "DEBUG: Drawing field \"$field\".\n" if $DEBUG;
632
633             if ($single_value) # Only one field. Do min/max range.     
634             {
635                 push (@rrd, "CDEF:min_max_diff=a$rrdname,i$rrdname,-");
636                 push (@rrd, "CDEF:re_zero=min_max_diff,min_max_diff,-")
637                     unless ($node->{client}->{$service}->{$field.".negative"});
638                 push (@rrd, "AREA:i$rrdname#ffffff");
639                 push (@rrd, "STACK:min_max_diff$range_colour");
640                 push (@rrd, "LINE2:re_zero#000000")
641                     unless ($node->{client}->{$service}->{$field.".negative"});
642             }
643
644             if ($has_negative and !@rrd_negatives) # Push "global" headers...
645             {
646                 push (@rrd, "COMMENT:" . (" " x $max_field_len));
647                 push (@rrd, "COMMENT:Cur (-/+)");
648                 push (@rrd, "COMMENT:Min (-/+)");
649                 push (@rrd, "COMMENT:Avg (-/+)");
650                 push (@rrd, "COMMENT:Max (-/+) \\j");
651             }
652             elsif ($global_headers == 1)
653             {
654                 push (@rrd, "COMMENT:" . (" " x $max_field_len));
655                 push (@rrd, "COMMENT: Cur$RRDkludge:");
656                 push (@rrd, "COMMENT:Min$RRDkludge:");
657                 push (@rrd, "COMMENT:Avg$RRDkludge:");
658                 push (@rrd, "COMMENT:Max$RRDkludge:  \\j");
659                 $global_headers++;
660             }
661
662             my $custom_colour = $node->{client}->{$service}->{$field.".colour"};
663             $custom_colour = "#" . $custom_colour if $custom_colour;
664
665             push (@rrd, ($node->{client}->{$service}->{$field.".draw"} || "LINE2") .
666                   ":g$rrdname" .
667                   ($custom_colour || ($single_value ? $single_colour : $COLOUR[$field_count++%@COLOUR])) . ":" .
668                   (escape ($node->{client}->{$service}->{"$field.label"}) || escape ($field))
669                   . (" " x ($max_field_len + 1 -
670                             length ($node->{client}->{$service}->{"$field.label"} || $field))));
671
672             # Check for negative fields (typically network traffic)
673             if ($has_negative)
674             {
675                 my $negfield = &orig_to_cdef ($node->{client}->{$service}, $node->{client}->{$service}->{$field.".negative"});
676                 print "DEBUG: negfield = $negfield\n" if $DEBUG;
677                 if (exists $node->{client}->{$service}->{$negfield.".realname"})
678                 {
679                     $negfield = $node->{client}->{$service}->{$negfield.".realname"};
680                 }
681                 
682                 if (!@rrd_negatives) # zero-line, to redraw zero afterwards.
683                 {
684                     push (@rrd_negatives, "CDEF:re_zero=g$negfield,UN,0,0,IF");
685                 }
686
687                 push (@rrd_negatives, "CDEF:ng$negfield=g$negfield,-1,*");
688
689                 if ($single_value) # Only one field. Do min/max range. 
690                 {
691                         push (@rrd, "CDEF:neg_min_max_diff=i$negfield,a$negfield,-");
692                         push (@rrd, "CDEF:ni$negfield=i$negfield,-1,*");
693                         push (@rrd, "AREA:ni$negfield#ffffff");
694                         push (@rrd, "STACK:neg_min_max_diff$range_colour");
695                 }
696
697                 push (@rrd_negatives, ($node->{client}->{$service}->{$negfield.".draw"} || "LINE2") .
698                     ":ng$negfield" .
699                     ((defined $single_value and $single_value) ? $single_colour : $COLOUR[($field_count-1)%@COLOUR]));
700
701                 # Draw HRULEs
702                 my $linedef = munin_get ($config, "line", undef, $domain, $name, $service, $node->{client}->{$service}->{$field.".negative"});
703                 if ($linedef)
704                 {
705                     my ($number, $colour, $label) = split (/:/, $linedef, 3);
706                     push (@rrd_negatives, "HRULE:".$number.
707                         ($colour ? "#$colour" :
708                           ((defined $single_value and $single_value) ? "#ff0000" : $COLOUR[($field_count-1)%@COLOUR]))
709                         );
710                 }
711                 elsif ($node->{client}->{$service}->{"$negfield.warn"})
712                 {
713                     push (@rrd_negatives, "HRULE:".$node->{client}->{$service}->{$node->{client}->{$service}->{$field.".negative"}.".warn"}.
714                         ((defined $single_value and $single_value) ? "#ff0000" : $COLOUR[($field_count-1)%@COLOUR]));
715                 }
716
717                 push (@rrd, "GPRINT:c$negfield:LAST:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "/\\g");
718                 push (@rrd, "GPRINT:c$rrdname:LAST:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
719                 push (@rrd, "GPRINT:i$negfield:MIN:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "/\\g");
720                 push (@rrd, "GPRINT:i$rrdname:MIN:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
721                 push (@rrd, "GPRINT:g$negfield:AVERAGE:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "/\\g");
722                 push (@rrd, "GPRINT:g$rrdname:AVERAGE:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
723                 push (@rrd, "GPRINT:a$negfield:MAX:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "/\\g");
724                 push (@rrd, "GPRINT:a$rrdname:MAX:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "\\j");
725                 push (@{$total_pos{'min'}}, "i$rrdname");
726                 push (@{$total_pos{'avg'}}, "g$rrdname");
727                 push (@{$total_pos{'max'}}, "a$rrdname");
728                 push (@{$total_neg{'min'}}, "i$negfield");
729                 push (@{$total_neg{'avg'}}, "g$negfield");
730                 push (@{$total_neg{'max'}}, "a$negfield");
731             }
732             else
733             {
734                 push (@rrd, "COMMENT: Cur$RRDkludge:") unless $global_headers;
735                 push (@rrd, "GPRINT:c$rrdname:LAST:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, "yes")?"%s":"") . "");
736                 push (@rrd, "COMMENT: Min$RRDkludge:") unless $global_headers;
737                 push (@rrd, "GPRINT:i$rrdname:MIN:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
738                 push (@rrd, "COMMENT: Avg$RRDkludge:") unless $global_headers;
739                 push (@rrd, "GPRINT:g$rrdname:AVERAGE:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
740                 push (@rrd, "COMMENT: Max$RRDkludge:") unless $global_headers;
741                 push (@rrd, "GPRINT:a$rrdname:MAX:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "\\j");
742                 push (@{$total_pos{'min'}}, "i$rrdname");
743                 push (@{$total_pos{'avg'}}, "g$rrdname");
744                 push (@{$total_pos{'max'}}, "a$rrdname");
745             }
746
747
748             # Draw HRULEs
749             my $linedef = munin_get ($config, "line", undef, $domain, $name, $service, $field);
750             if ($linedef)
751             {
752                 my ($number, $colour, $label) = split (/:/, $linedef, 3);
753                 $label =~ s/:/\\:/g if defined $label;
754                 push (@rrd, "HRULE:".$number.
755                     ($colour ? "#$colour" :
756                       ((defined $single_value and $single_value) ? "#ff0000" : $COLOUR[($field_count-1)%@COLOUR])) .
757                       ((defined $label and length ($label)) ? ":$label" : ""),
758                       "COMMENT: \\j"
759                     );
760             }
761             elsif ($node->{client}->{$service}->{"$field.warn"})
762             {
763                 push (@rrd, "HRULE:".$node->{client}->{$service}->{"$field.warn"}.($single_value ? "#ff0000" : $COLOUR[($field_count-1)%@COLOUR]));
764             }
765         }
766
767         if (@rrd_negatives)
768         {
769             push (@rrd, @rrd_negatives);
770             push (@rrd, "LINE2:re_zero#000000"); # Redraw zero.
771             if (exists $node->{client}->{$service}->{graph_total} and
772                     exists $total_pos{'min'} and exists $total_neg{'min'} and
773                     @{$total_pos{'min'}} and @{$total_neg{'min'}})
774             {
775                 push (@rrd, "CDEF:ipostotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_pos{'min'}}).(",+" x (@{$total_pos{'min'}}-1)));
776                 push (@rrd, "CDEF:gpostotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_pos{'avg'}}).(",+" x (@{$total_pos{'avg'}}-1)));
777                 push (@rrd, "CDEF:apostotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_pos{'max'}}).(",+" x (@{$total_pos{'max'}}-1)));
778                 push (@rrd, "CDEF:inegtotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_neg{'min'}}).(",+" x (@{$total_neg{'min'}}-1)));
779                 push (@rrd, "CDEF:gnegtotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_neg{'avg'}}).(",+" x (@{$total_neg{'avg'}}-1)));
780                 push (@rrd, "CDEF:anegtotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_neg{'max'}}).(",+" x (@{$total_neg{'max'}}-1)));
781                 push (@rrd, "CDEF:dpostotal=ipostotal,UN,ipostotal,UNKN,IF");
782                 push (@rrd, "LINE1:dpostotal#000000:" . $node->{client}->{$service}->{graph_total} . (" " x ($max_field_len - length ($node->{client}->{$service}->{graph_total}) + 1)));
783                 push (@rrd, "GPRINT:gnegtotal:LAST:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "/\\g");
784                 push (@rrd, "GPRINT:gpostotal:LAST:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
785                 push (@rrd, "GPRINT:inegtotal:MIN:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "/\\g");
786                 push (@rrd, "GPRINT:ipostotal:MIN:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
787                 push (@rrd, "GPRINT:gnegtotal:AVERAGE:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "/\\g");
788                 push (@rrd, "GPRINT:gpostotal:AVERAGE:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
789                 push (@rrd, "GPRINT:anegtotal:MAX:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "/\\g");
790                 push (@rrd, "GPRINT:apostotal:MAX:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "\\j");
791             }
792         }
793         elsif (exists $node->{client}->{$service}->{graph_total} and exists $total_pos{'min'} and @{$total_pos{'min'}})
794         {
795             push (@rrd, "CDEF:ipostotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_pos{'min'}}).(",+" x (@{$total_pos{'min'}}-1)));
796             push (@rrd, "CDEF:gpostotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_pos{'avg'}}).(",+" x (@{$total_pos{'avg'}}-1)));
797             push (@rrd, "CDEF:apostotal=".join (",", map { "$_,UN,0,$_,IF" } @{$total_pos{'max'}}).(",+" x (@{$total_pos{'max'}}-1)));
798             
799             push (@rrd, "CDEF:dpostotal=ipostotal,UN,ipostotal,UNKN,IF");
800             push (@rrd, "LINE1:dpostotal#000000:" . $node->{client}->{$service}->{graph_total} . (" " x ($max_field_len - length ($node->{client}->{$service}->{graph_total}) + 1)));
801             push (@rrd, "COMMENT: Cur$RRDkludge:") unless $global_headers;
802             push (@rrd, "GPRINT:gpostotal:LAST:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
803             push (@rrd, "COMMENT: Min$RRDkludge:") unless $global_headers;
804             push (@rrd, "GPRINT:ipostotal:MIN:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
805             push (@rrd, "COMMENT: Avg$RRDkludge:") unless $global_headers;
806             push (@rrd, "GPRINT:gpostotal:AVERAGE:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "");
807             push (@rrd, "COMMENT: Max$RRDkludge:") unless $global_headers;
808             push (@rrd, "GPRINT:apostotal:MAX:%6.2lf" . (munin_get_bool_val ($node->{client}->{$service}->{graph_scale}, 1)?"%s":"") . "\\j");
809         }
810
811         for my $time (keys %times) {
812             next unless ($draw{$time});
813             my @complete = ();
814             if ($RRDkludge) {
815                 push (@complete,
816                       '--font' ,'LEGEND:7:/usr/share/munin/VeraMono.ttf',
817                       '--font' ,'UNIT:7:/usr/share/munin/VeraMono.ttf',
818
819                       '--font' ,'AXIS:7:/usr/share/munin/VeraMono.ttf');
820             }
821
822             logger ("Processing $name -> $time") if $DEBUG;
823
824             # Do the header (title, vtitle, size, etc...)
825             push @complete, @{&get_header ($node, $config, $domain, $name, $service, $time)};
826             push @complete, @rrd;
827
828             push (@complete, "COMMENT:Last update$RRDkludge: " . RRDescape(scalar localtime($lastupdate)) .  "\\r");
829             
830             if (time - 300 < $lastupdate)
831             {
832                     push @complete, "--end",(int($lastupdate/$resolutions{$time}))*$resolutions{$time};
833             }
834             print "\n\nrrdtool \"graph\" \"", join ("\"\n\t\"",@complete), "\"\n" if $DEBUG;
835             print RRDs::graph (@complete);
836             if (my $ERROR = RRDs::error) {
837                 logger ("Unable to graph $filename: $ERROR");
838             }
839             else
840             {
841                 # "touch" the file, so that munin-cgi-graph can see the "last update"/modified status:
842                 my $picture_filename = &munin_get_picture_filename ($config, $domain, $name, $service, $time);
843                 utime $lastupdate, $lastupdate, $picture_filename;
844
845                 if ($list_images) # Command-line option to list images created
846                 {
847                     print &munin_get_picture_filename ($config, $domain, $name, $service, $time), "\n";
848                 }
849             }
850         }
851
852         if (&munin_get_bool_val ($node->{client}->{$service}->{"graph_sums"}, 0))
853         {
854             foreach my $time (keys %sumtimes)
855             {
856                 next unless ($draw{"sum".$time});
857                 my @rrd_sum;
858                 push @rrd_sum, @{&get_header ($node, $config, $domain, $name, $service, $time, 1)};
859
860                 if (time - 300 < $lastupdate)
861                 {
862                         push @rrd_sum, "--end",(int($lastupdate/$resolutions{$time}))*$resolutions{$time};
863                 }
864                 push @rrd_sum, @rrd;
865                 push (@rrd_sum, "COMMENT:Last update$RRDkludge: " . RRDescape(scalar localtime($lastupdate)) .  "\\r");
866
867                 my $labelled = 0;
868                 my @defined = ();
869                 for (my $index = 0; $index <= $#rrd_sum; $index++)
870                 {
871                     if ($rrd_sum[$index] =~ /^(--vertical-label|-v)$/)
872                     {
873                         (my $label = $node->{client}->{$service}->{graph_vlabel}) =~ s/\$\{graph_period\}/$sumtimes{$time}[0]/g;
874                         splice (@rrd_sum, $index, 2, ("--vertical-label", $label));
875                         $index++;
876                         $labelled++;
877                     }
878                     elsif ($rrd_sum[$index] =~ /^(LINE[123]|STACK|AREA|GPRINT):([^#:]+)([#:].+)$/)
879                     {
880                         my ($pre, $fname, $post) = ($1, $2, $3);
881                         next if $fname eq "re_zero";
882                         if ($post =~ /^:AVERAGE/)
883                         {
884                             splice (@rrd_sum, $index, 1, $pre . ":x$fname" . $post);
885                             $index++;
886                             next;
887                         }
888                         next if grep /^x$fname$/, @defined;
889                         push @defined, "x$fname";
890                         my @replace;
891
892                         if (!defined ($node->{client}->{$service}->{$fname.".type"}) or $node->{client}->{$service}->{$fname.".type"} ne "GAUGE")
893                         {
894                             if ($time eq "week")
895                             { # Every plot is half an hour. Add two plots and multiply, to get per hour
896                                 if (graph_by_minute ($config, $domain, $name, $service))
897                                 { # Already multiplied by 60
898                                     push @replace, "CDEF:x$fname=PREV($fname),UN,0,PREV($fname),IF,$fname,+,5,*,6,*";
899                                 }
900                                 else
901                                 {
902                                     push @replace, "CDEF:x$fname=PREV($fname),UN,0,PREV($fname),IF,$fname,+,300,*,6,*";
903                                 }
904                             }
905                             else
906                             { # Every plot is one day exactly. Just multiply.
907                                 if (graph_by_minute ($config, $domain, $name, $service))
908                                 { # Already multiplied by 60
909                                     push @replace, "CDEF:x$fname=$fname,5,*,288,*";
910                                 }
911                                 else
912                                 {
913                                     push @replace, "CDEF:x$fname=$fname,300,*,288,*";
914                                 }
915                             }
916                         }
917                         push @replace, $pre . ":x$fname" . $post;
918                         splice (@rrd_sum, $index, 1, @replace);
919                         $index++;
920                     }
921                     elsif ($rrd_sum[$index] =~ /^(--lower-limit|--upper-limit|-l|-u)$/)
922                     {
923                         $index++;
924                         $rrd_sum[$index] = $rrd_sum[$index] * 300 * $sumtimes{$time}->[1];
925                     }
926                 }
927                 unless ($labelled)
928                 {
929                     my $label = $node->{client}->{$service}->{"graph_vlabel_sum_$time"} || $sumtimes{$time}->[0];
930                     unshift @rrd_sum, "--vertical-label", $label;
931                 }
932
933                 print "\n\nrrdtool \"graph\" \"", join ("\"\n\t\"",@rrd_sum), "\"\n" if $DEBUG;
934                 RRDs::graph (@rrd_sum);
935
936                 if (my $ERROR = RRDs::error) {
937                     logger ("Unable to graph $filename: $ERROR");
938                 }
939                 else
940                 {
941                     # "touch" the file, so that munin-cgi-graph can see the "last update"/modified status:
942                     my $picture_filename = &munin_get_picture_filename ($config, $domain, $name, $service, $time);
943                     utime $lastupdate, $lastupdate, $picture_filename;
944
945                     if ($list_images) # Command-line option to list images created
946                     {
947                         print &munin_get_picture_filename ($config, $domain, $name, $service, $time, 1), "\n";
948                     }
949                 }
950             }
951         }
952
953         $service_time = sprintf ("%.2f",(Time::HiRes::time - $service_time));
954         logger ("Graphed service : $service ($service_time sec * 4)");
955         print STATS "GS|$domain|$name|$service|$service_time\n" unless $skip_stats;
956
957         foreach (@added)
958         {
959             delete $node->{client}->{$service}->{$_} if exists $node->{client}->{$service}->{$_};
960         }
961         @added = ();
962     }
963     
964 }
965
966 sub graph_by_minute
967 {
968     my $config  = shift;
969     my $domain  = shift;
970     my $name    = shift;
971     my $service = shift;
972
973     return (munin_get ($config, "graph_period", "second", $domain, $name, $service) eq "minute");
974 }
975
976 sub orig_to_cdef
977 {
978     my $service = shift;
979     my $field   = shift;
980
981     if (defined $service->{$field.".cdef_name"})
982     {
983         return &orig_to_cdef ($service, $service->{$field.".cdef_name"});
984     }
985     return $field;
986 }
987
988 sub set_cdef_name
989 {
990     my $service = shift;
991     my $field   = shift;
992     my $new     = shift;
993
994     $service->{$field.".cdef_name"} = $new;
995     print "DEBUG: set_cdef_name from $field to $new.\n" if $DEBUG;
996 }
997
998 sub skip_service
999 {
1000     my $node = shift;
1001     my $service = shift;
1002
1003     # Check to make sure that service exists
1004     return 1 unless (ref $node->{client}->{$service});
1005
1006     # See if we should skip it because of conf-options
1007     return 1 if ($node->{client}->{$service}->{'graph'} and
1008         ($node->{client}->{$service}->{'graph'} eq "no" ||
1009         ($node->{client}->{$service}->{'graph'} eq "on-demand") && !$force_graphing));
1010
1011     # See if we should skip it because of command-line arguments
1012     return 1 if (@limit_services and not grep (/^$service$/, @limit_services));
1013
1014     # Don't skip
1015     return 0;
1016 }
1017
1018 sub expand_cdef
1019 {
1020     my $service     = shift;
1021     my $cfield_ref  = shift;
1022     my $cdef        = shift;
1023
1024     my $new_field = &get_field_name ("cdef$$cfield_ref");
1025
1026     my ($max, $min, $avg) = ("CDEF:a$new_field=$cdef", "CDEF:i$new_field=$cdef", "CDEF:g$new_field=$cdef");
1027
1028     foreach my $field (keys %$service)
1029     {
1030         next unless ($field =~ /^(.+)\.label$/);
1031         my $fieldname = $1;             
1032         my $rrdname = &orig_to_cdef ($service, $fieldname);
1033         if ($cdef =~ /\b$fieldname\b/)
1034         {
1035                 $max =~ s/([,=])$fieldname([,=]|$)/$1a$rrdname$2/g;
1036                 $min =~ s/([,=])$fieldname([,=]|$)/$1i$rrdname$2/g;
1037                 $avg =~ s/([,=])$fieldname([,=]|$)/$1g$rrdname$2/g;
1038         }
1039     }
1040
1041     &set_cdef_name ($service, $$cfield_ref, $new_field);
1042     $$cfield_ref = $new_field;
1043
1044     return ($max, $min, $avg);
1045 }
1046
1047 sub logger_open {
1048     my $dirname = shift;
1049
1050     if (!$log->opened)
1051     {
1052           unless (open ($log, ">>$dirname/munin-graph.log"))
1053           {
1054                   print STDERR "Warning: Could not open log file \"$dirname/munin-graph.log\" for writing: $!";
1055           }
1056     }
1057 }
1058
1059 sub logger {
1060   my ($comment) = @_;
1061   my $now = strftime "%b %d %H:%M:%S", localtime;
1062
1063   print "$now - $comment\n" if $stdout;
1064   if ($log->opened)
1065   {
1066           print $log "$now - $comment\n";
1067   }
1068   else
1069   {
1070           if (defined $config->{logdir})
1071           {
1072                   if (open ($log, ">>$config->{logdir}/munin-graph.log"))
1073                   {
1074                           print $log "$now - $comment\n";
1075                   }
1076                   else
1077                   {
1078                           print STDERR "Warning: Could not open log file \"$config->{logdir}/munin-graph.log\" for writing: $!";
1079                           print STDERR "$now - $comment\n";
1080                   }
1081           }
1082           else
1083           {
1084                   print STDERR "$now - $comment\n";
1085           }
1086     }
1087 }
1088
1089 sub parse_path
1090 {
1091     my ($path, $domain, $node, $service, $field) = @_;
1092     my $filename = "unknown";
1093
1094     if ($path =~ /^\s*([^:]*):([^:]*):([^:]*):([^:]*)\s*$/)
1095     {
1096         $filename = munin_get_filename ($config, $1, $2, $3, $4);
1097     }
1098     elsif ($path =~ /^\s*([^:]*):([^:]*):([^:]*)\s*$/)
1099     {
1100         $filename = munin_get_filename ($config, $domain, $1, $2, $3);
1101     }
1102     elsif ($path =~ /^\s*([^:]*):([^:]*)\s*$/)
1103     {
1104         $filename = munin_get_filename ($config, $domain, $node, $1, $2);
1105     }
1106     elsif ($path =~ /^\s*([^:]*)\s*$/)
1107     {
1108         $filename = munin_get_filename ($config, $domain, $node, $service, $1);
1109     }
1110     return $filename;
1111 }
1112
1113 sub escape
1114 {
1115     my $text = shift;
1116     return undef if not defined $text;
1117     $text =~ s/\\/\\\\/g;
1118     $text =~ s/:/\\:/g;
1119     return $text;
1120 }
1121
1122 sub RRDescape
1123 {
1124     my $text = shift;
1125     return $RRDs::VERSION < 1.2 ? $text : escape($text);
1126 }
1127
1128 1;
1129
1130 =head1 NAME
1131
1132 munin-graph - A program to create graphs from data contained in rrd-files.
1133
1134 =head1 SYNOPSIS
1135
1136 munin-graph [--options]
1137
1138 =head1 OPTIONS
1139
1140 =over 5
1141
1142 =item B<< --[no]force >>
1143
1144 If set, force drawing of graphs that are not usually drawn due to options in the config file. [--noforce]
1145
1146 =item B<< --[no]lazy >>
1147
1148 If set, only redraw graphs when it would look different from the existing one. [--lazy]
1149
1150 =item B<< --help >>
1151
1152 View help.
1153
1154 =item B<< --[no]force-root >>
1155
1156 Force running as root (stupid and unnecessary). [--noforce-root]
1157
1158 =item B<< --[no]debug >>
1159
1160 If set, view debug messages. [--nodebug]
1161
1162 =item B<< --service <service> >>
1163
1164 Limit graphed services to E<lt>serviceE<gt>. Multiple --service options may be supplied. [unset]
1165
1166 =item B<< --host <host> >>
1167
1168 Limit graphed hosts to E<lt>hostE<gt>. Multiple --host options may be supplied. [unset]
1169
1170 =item B<< --config <file> >>
1171
1172 Use E<lt>fileE<gt> as configuration file. [/etc/munin/munin.conf]
1173
1174 =item B<< --[no]list-images >>
1175
1176 If set, list the filenames of the images created. [--nolist-images]
1177
1178 =item B<< --[no]day >>
1179
1180 If set, create day-based graphs. [--day]
1181
1182 =item B<< --[no]week >>
1183
1184 If set, create week-based graphs. [--week]
1185
1186 =item B<< --[no]month >>
1187
1188 If set, create month-based graphs. [--month]
1189
1190 =item B<< --[no]year >>
1191
1192 If set, create year-based graphs. [--year]
1193
1194 =back
1195
1196 =head1 DESCRIPTION
1197
1198 Munin-graph is a part of the package Munin, which is used in combination
1199 with Munin's node.  Munin is a group of programs to gather data from
1200 Munin's nodes, graph them, create html-pages, and optionally warn Nagios
1201 about any off-limit values.
1202
1203 munin-graph does the graphing. It is usually only used from within munin-cron.
1204
1205 It checks the rrd-files for updated values, and redraws the graphs if
1206 needed. To force redrawing of graphs (after setup-changes et alia), use
1207 '--nolazy'.
1208
1209 =head1 FILES
1210
1211         /etc/munin/munin.conf
1212         /var/lib/munin/*
1213         /var/log/munin/munin-graph
1214         /var/run/munin/*
1215
1216 =head1 VERSION
1217
1218 This is munin-graph version 0.9.2-3
1219
1220 =head1 AUTHORS
1221
1222 Audun Ytterdal and Jimmy Olsen.
1223
1224 =head1 BUGS
1225
1226 munin-graph does, as of now, not check the syntax of the configuration file.
1227
1228 Please report other bugs in the bug tracker at L<http://munin.sf.net/>.
1229
1230 =head1 COPYRIGHT
1231
1232 Copyright © 2002-2004 Audun Ytterdal, Jimmy Olsen, and Tore Anderson / Linpro AS.
1233
1234 This is free software; see the source for copying conditions. There is
1235 NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
1236 PURPOSE.
1237
1238 This program is released under the GNU General Public License
1239
1240 =cut
1241
1242 # vim: syntax=perl ts=8