"Fossies" - the Fresh Open Source Software Archive 
Member "munin-2.0.67/master/lib/Munin/Master/GraphOld.pm" (22 Feb 2021, 63367 Bytes) of package /linux/misc/munin-2.0.67.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "GraphOld.pm" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
2.0.66_vs_2.0.67.
1 package Munin::Master::GraphOld;
2
3 # -*- cperl -*-
4
5 =encoding utf-8
6
7 =begin comment
8
9 This is Munin::Master::GraphOld, a package shell to make munin-graph
10 modular (so it can loaded persistently in munin-cgi-graph for example)
11 without making it object oriented yet. The non "old" module will
12 feature propper object orientation like munin-update and will have to
13 wait until later.
14
15 Copyright (C) 2002-2010 Jimmy Olsen, Audun Ytterdal, Kjell Magne
16 Øierud, Nicolai Langfeldt, Linpro AS, Redpill Linpro AS and others.
17
18 This program is free software; you can redistribute it and/or
19 modify it under the terms of the GNU General Public License
20 as published by the Free Software Foundation; version 2 dated June,
21 1991.
22
23 This program is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
27
28 You should have received a copy of the GNU General Public License
29 along with this program. If not, see <http://www.gnu.org/licenses/>.
30
31 =end comment
32
33 =cut
34
35 use warnings;
36 use strict;
37
38 use Exporter;
39
40 our (@ISA, @EXPORT);
41 @ISA = qw(Exporter);
42 @EXPORT = qw(graph_startup graph_check_cron graph_main graph_config);
43
44 use IO::Socket;
45 use IO::Handle;
46 use RRDs;
47 use POSIX qw(strftime);
48 use Digest::MD5;
49 use Getopt::Long;
50 use Time::HiRes;
51 use Text::ParseWords;
52
53 # For UTF-8 handling (plugins are assumed to use Latin 1)
54 if ($RRDs::VERSION >= 1.3) {
55 use Encode;
56 use Encode::Guess;
57 Encode->import;
58 Encode::Guess->import;
59 }
60
61 use Munin::Master::Logger;
62 use Munin::Master::Utils;
63 use Munin::Common::Defaults;
64
65 use Log::Log4perl qw( :easy );
66
67 # RRDtool 1.2 requires \\: in comments
68 my $RRDkludge = $RRDs::VERSION < 1.2 ? '' : '\\';
69
70 # And RRDtool 1.2.* draws lines with crayons so we hack
71 # the LINE* options a bit.
72 my $LINEkluge = 0;
73 if ($RRDs::VERSION >= 1.2 and $RRDs::VERSION < 1.3) {
74
75 # Only kluge the line widths in RRD 1.2*
76 $LINEkluge = 1;
77 }
78
79 # RRD 1.3 has a "ADDNAN" operator which evaluates n + NaN = n instead of = NaN.
80 my $AddNAN = '+';
81 if ($RRDs::VERSION >= 1.3) {
82 $AddNAN = 'ADDNAN';
83 }
84
85 # the ":dashes" syntax for LINEs is supported since rrdtool 1.5.3
86 my $RRDLineThresholdAttribute = ($RRDs::VERSION < 1.50003) ? '' : ':dashes';
87
88 # Force drawing of "graph no".
89 my $force_graphing = 0;
90 my $force_lazy = 1;
91 my $do_usage = 0;
92 my $do_version = 0;
93 my $cron = 0;
94 my $list_images = 0;
95 my $output_file = undef;
96 my $log_file = undef;
97 my $skip_locking = 0;
98 my $skip_stats = 0;
99 my $stdout = 0;
100 my $force_run_as_root = 0;
101 my $conffile = $Munin::Common::Defaults::MUNIN_CONFDIR . "/munin.conf";
102 my $libdir = $Munin::Common::Defaults::MUNIN_LIBDIR;
103 # Note: Nothing by default is more convenient and elliminates code while
104 # for cgi graphing - but it breaks how munin-graph expected stuff to work.
105 # I think.
106 my %draw = (
107 'day' => 0,
108 'week' => 0,
109 'month' => 0,
110 'year' => 0,
111 'sumyear' => 0,
112 'sumweek' => 0,
113 'pinpoint' => 0,
114 );
115 my %init_draw = %draw;
116 my $pinpoint = {};
117
118 my ($size_x, $size_y, $full_size_mode, $only_graph);
119 my ($lower_limit, $upper_limit);
120
121 my %PALETTE; # Hash of available palettes
122 my @COLOUR; # Array of actuall colours to use
123
124 {
125 no warnings;
126 $PALETTE{'old'} = [ # This is the old munin palette. It lacks contrast.
127 qw(22ff22 0022ff ff0000 00aaaa ff00ff
128 ffa500 cc0000 0000cc 0080C0 8080C0 FF0080
129 800080 688e23 408080 808000 000000 00FF00
130 0080FF FF8000 800000 FB31FB
131 )];
132
133 $PALETTE{'default'} = [ # New default palette.Better contrast,more colours
134 #Greens Blues Oranges Dk yel Dk blu Purple lime Reds Gray
135 qw(00CC00 0066B3 FF8000 FFCC00 330099 990099 CCFF00 FF0000 808080
136 008F00 00487D B35A00 B38F00 6B006B 8FB300 B30000 BEBEBE
137 80FF80 80C9FF FFC080 FFE680 AA80FF EE00CC FF8080
138 666600 FFBFFF 00FFCC CC6699 999900
139 )]; # Line variations: Pure, earthy, dark pastel, misc colours
140 }
141
142 my $range_colour = "22ff22";
143 my $single_colour = "00aa00";
144
145 # Use 400 x RRA step, in order to have 1px per RRA sample.
146 my %times = (
147 "day" => "-2000m", # (i.e. -33h20m)
148 "week" => "-12000m", # (i.e. -8d13h20m)
149 "month" => "-48000m", # (i.e. -33d8h)
150 "year" => "-400d",
151 "pinpoint" => "dummy",
152 );
153
154 my %resolutions = (
155 "day" => "300",
156 "week" => "1500",
157 "month" => "7200",
158 "year" => "86400"
159 );
160
161 my %sumtimes = ( # time => [ label, seconds-in-period ]
162 "week" => ["hour", 12],
163 "year" => ["day", 288]);
164
165 # Limit graphing to certain hosts and/or services
166 my @limit_hosts = ();
167 my @limit_services = ();
168 my $only_fqn = '';
169
170 my $watermark = "Munin " . $Munin::Common::Defaults::MUNIN_VERSION;
171
172 # RRD param for RRDCACHED_ADDRESS
173 my @rrdcached_params;
174
175 my $running = 0;
176 my $max_running = 6;
177 my $do_fork = 1;
178
179 # "global" Configuration hash
180 my $config = undef;
181
182 # stats file handle
183 my $STATS;
184
185 my @init_limit_hosts = @limit_hosts;
186 my @init_limit_services = @limit_services;
187
188 sub process_pinpoint {
189 my ($pinpoint, $arg_name, $arg_value) = @_;
190 # XXX - Special hack^h^h^h^h treatment for --pinpoint
191 if ($arg_value && $arg_value =~ m/^(\d+),(\d+)$/ ) {
192 # "pinpoint" replaces all the other timing options
193 $draw{'day'}=0;
194 $draw{'week'}=0;
195 $draw{'month'}=0;
196 $draw{'year'}=0;
197 $draw{'sumweek'}=0;
198 $draw{'sumyear'}=0;
199 $draw{'pinpoint'}=1;
200 $$pinpoint->{'start'} = $1; # preparsed values
201 $$pinpoint->{'end'} = $2;
202 }
203 }
204
205
206 sub process_fqn {
207 my ($fqn, $arg) = @_;
208
209 # Reset what to draw whenever we specify a new fqn
210
211 $draw{'day'} = $draw{'week'} = $draw{'month'} = $draw{'year'} =
212 $draw{'sumweek'} = $draw{'sumyear'} = $draw{'pinpoint'} = 0;
213
214 return $arg;
215 }
216
217
218 sub graph_startup {
219
220 # Parse options and set up. Stuff that is usually only needed once.
221 #
222 # Do once pr. run, pr possebly once pr. graph in the case of
223 # munin-cgi-graph
224
225 # Localise the stuff, overwise it will be stacked up with CGI
226 %draw = %init_draw;
227 @limit_hosts = @init_limit_hosts;
228 @limit_services = @init_limit_services;
229
230 $pinpoint = undef;
231 my $pinpointopt = undef;
232
233 $force_graphing = 0;
234 $force_lazy = 1;
235 $do_usage = 0;
236 $do_version = 0;
237 $cron = 0;
238 $list_images = 0;
239 $output_file = undef;
240 $log_file = undef;
241 $skip_locking = 0;
242 $skip_stats = 0;
243 $stdout = 0;
244
245 $size_x = undef;
246 $size_y = undef;
247 $full_size_mode = undef;
248 $only_graph = undef;
249 $lower_limit = undef;
250 $upper_limit = undef;
251
252 # Get options
253 my ($args) = @_;
254 local @ARGV = @{$args};
255
256 # NOTE! Some of these options are available in graph_main too
257 # if you make changes here, make them there too.
258
259 my $debug;
260 &print_usage_and_exit
261 unless GetOptions (
262 "force!" => \$force_graphing,
263 "lazy!" => \$force_lazy,
264 "host=s" => \@limit_hosts,
265 "service=s" => \@limit_services,
266 "only-fqn=s" => sub{ $only_fqn = process_fqn(@_); },
267 "config=s" => \$conffile,
268 "stdout!" => \$stdout,
269 "force-run-as-root!" => \$force_run_as_root,
270 "day!" => \$draw{'day'},
271 "week!" => \$draw{'week'},
272 "month!" => \$draw{'month'},
273 "year!" => \$draw{'year'},
274 "pinpoint=s" => sub{ process_pinpoint(\$pinpoint,@_); },
275 "sumweek!" => \$draw{'sumweek'},
276 "sumyear!" => \$draw{'sumyear'},
277 "size_x=i" => \$size_x,
278 "size_y=i" => \$size_y,
279 "full_size_mode!"=> \$full_size_mode,
280 "only_graph!"=> \$only_graph,
281 "upper_limit=s" => \$upper_limit,
282 "lower_limit=s" => \$lower_limit,
283 "list-images!" => \$list_images,
284 "o|output-file=s" => \$output_file,
285 "l|log-file=s" => \$log_file,
286 "skip-locking!" => \$skip_locking,
287 "skip-stats!" => \$skip_stats,
288 "version!" => \$do_version,
289 "cron!" => \$cron,
290 "fork!" => \$do_fork,
291 "n=n" => \$max_running,
292 "help" => \$do_usage,
293 "debug!" => \$debug,
294 );
295
296 if ($do_version) {
297 print_version_and_exit();
298 }
299
300 if ($do_usage) {
301 print_usage_and_exit();
302 }
303
304 exit_if_run_by_super_user() unless $force_run_as_root;
305
306 # Only read $config once (thx Jani M.)
307 #
308 # FIXME - the loaded $config is stale within 5 minutes.
309 # we either need to die or restart ourselves when this
310 # happens.
311 if (!defined($config)) {
312 munin_readconfig_base($conffile);
313 # XXX: check if it needs datafile at that point
314 $config = munin_readconfig_part('datafile', 0);
315 }
316
317 $config->{debug} = $debug;
318
319 my $palette = &munin_get($config, "palette", "default");
320
321 $max_running = &munin_get($config, "max_graph_jobs", $max_running);
322
323 if ($config->{"rrdcached_socket"}) {
324 if ($RRDs::VERSION >= 1.3){
325 # Using the RRDCACHED_ADDRESS environnement variable, as
326 # it is way less intrusive than the command line args.
327 $ENV{RRDCACHED_ADDRESS} = $config->{"rrdcached_socket"};
328 } else {
329 ERROR "[ERROR] RRDCached feature ignored: RRD version must be at least 1.3. Version found: " . $RRDs::VERSION;
330 }
331 }
332
333
334 if ($max_running == 0) {
335 $do_fork = 0;
336 }
337
338 if (defined($PALETTE{$palette})) {
339 @COLOUR = @{$PALETTE{$palette}};
340 }
341 else {
342 die "Unknown palette named by 'palette' keyword: $palette\n";
343 }
344
345 return $config;
346 }
347
348 sub graph_check_cron {
349
350 # Are we running from cron and do we have matching graph_strategy
351 if (&munin_get($config, "graph_strategy", "cron") ne "cron" and $cron) {
352
353 # Strategy mismatch: We're run from cron, but munin.conf says
354 # we use dynamic graph generation
355 return 0;
356 }
357
358 # Strategy match:
359 return 1;
360 }
361
362
363 sub graph_main {
364
365 my ($args) = @_;
366 local @ARGV = @{$args};
367
368 # The loaded $config is stale within 5 minutes.
369 # So, we need to reread it when this happens.
370 $config = munin_readconfig_part('datafile');
371
372 # Reset an eventual custom size
373 $size_x = undef;
374 $size_y = undef;
375 $full_size_mode = undef;
376 $only_graph = undef;
377 $lower_limit = undef;
378 $upper_limit = undef;
379 $pinpoint = undef;
380
381 # XXX [DEBUG]
382 my $debug = undef;
383
384 GetOptions (
385 "host=s" => \@limit_hosts,
386 "only-fqn=s" => sub { $only_fqn = process_fqn(@_); },
387 "day!" => \$draw{'day'},
388 "week!" => \$draw{'week'},
389 "month!" => \$draw{'month'},
390 "year!" => \$draw{'year'},
391 "pinpoint=s" => sub{ process_pinpoint(\$pinpoint,@_); },
392 "sumweek!" => \$draw{'sumweek'},
393 "sumyear!" => \$draw{'sumyear'},
394 "o|output-file=s" => \$output_file,
395
396 # XXX [DEBUG]
397 "debug!" => \$debug,
398
399 "size_x=i" => \$size_x,
400 "size_y=i" => \$size_y,
401 "full_size_mode!"=> \$full_size_mode,
402 "only_graph!" => \$only_graph,
403 "upper_limit=s" => \$upper_limit,
404 "lower_limit=s" => \$lower_limit,
405 );
406
407 # XXX [DEBUG]
408 logger_debug() if $debug;
409
410 my $graph_time = Time::HiRes::time;
411
412 munin_runlock("$config->{rundir}/munin-graph.lock") unless $skip_locking;
413
414 unless ($skip_stats) {
415 open($STATS, '>', "$config->{dbdir}/munin-graph.stats.tmp")
416 or WARN "[WARNING] Unable to open $config->{dbdir}/munin-graph.stats.tmp";
417 autoflush $STATS 1;
418 }
419
420 process_work(@limit_hosts);
421
422 $graph_time = sprintf("%.2f", (Time::HiRes::time - $graph_time));
423
424 rename(
425 "$config->{dbdir}/munin-graph.stats.tmp",
426 "$config->{dbdir}/munin-graph.stats"
427 );
428 close $STATS unless $skip_stats;
429
430 munin_removelock("$config->{rundir}/munin-graph.lock") unless $skip_locking;
431
432 $running = wait_for_remaining_children($running);
433 }
434
435
436 # --------------------------------------------------------------------------
437
438 sub get_title {
439 my $service = shift;
440 my $scale = shift;
441
442 my $scale_text;
443 if ($pinpoint) {
444 my $start_text = localtime($pinpoint->{"start"});
445 my $end_text = localtime($pinpoint->{"end"});
446 $scale_text = "from $start_text to $end_text";
447 } else {
448 $scale_text = "by " . $scale;
449 }
450
451 my $title = munin_get($service, "graph_title", $service);
452
453 # Substitute ${graph_period} in title
454 my $period = munin_get($service, "graph_period", "second");
455 $title =~ s/\$\{graph_period\}/$period/g;
456
457 return ("$title - $scale_text");
458 }
459
460 sub get_custom_graph_args {
461 my $service = shift;
462 my $result = [];
463
464 my $args = munin_get($service, "graph_args");
465 if (defined $args) {
466 my $result = [ grep /\S/, "ewords('\s+', 0, $args) ];
467 return $result;
468 }
469 else {
470 return;
471 }
472 }
473
474 # insert these arguments after all others
475 # needed for your own VDEF/CDEF/DEF combinations
476 sub get_custom_graph_args_after {
477 my $service = shift;
478 my $result = [];
479
480 my $args = munin_get($service, "graph_args_after");
481 if (defined $args) {
482 my $result = ["ewords('\s+', 0, $args)];
483 return $result;
484 }
485 else {
486 return;
487 }
488 }
489
490 # set a graph end point in the future
491 # needed for CDEF TREND and PREDICT
492 sub get_end_offset {
493 my $service = shift;
494
495 # get number of seconds in future
496 return munin_get($service, "graph_future", 0);
497 }
498
499 sub get_vlabel {
500 my $service = shift;
501 my $scale = munin_get($service, "graph_period", "second");
502 my $res = munin_get($service, "graph_vlabel",
503 munin_get($service, "graph_vtitle"));
504
505 if (defined $res) {
506 $res =~ s/\$\{graph_period\}/$scale/g;
507 }
508 return $res;
509 }
510
511 sub should_scale {
512 my $service = shift;
513 my $ret;
514
515 if (!defined($ret = munin_get_bool($service, "graph_scale"))) {
516 $ret = !munin_get_bool($service, "graph_noscale", 0);
517 }
518
519 return $ret;
520 }
521
522 sub get_header {
523 my $service = shift;
524 my $scale = shift;
525 my $sum = shift;
526 my $result = [];
527 my $tmp_field;
528
529 # Picture filename
530 push @$result, get_picture_filename($service, $scale, $sum || undef);
531
532 # Title
533 push @$result, ("--title", get_title($service, $scale));
534
535 # When to start the graph
536 if ($pinpoint) {
537 push @$result, "--start", $pinpoint->{start};
538 push @$result, "--end", $pinpoint->{end};
539 } else {
540 push @$result, "--start", $times{$scale};
541 }
542
543 # Custom graph args, vlabel and graph title
544 if (defined($tmp_field = get_custom_graph_args($service))) {
545 push(@$result, @{$tmp_field});
546 }
547 if (defined($tmp_field = get_vlabel($service))) {
548 push @$result, ("--vertical-label", $tmp_field);
549 }
550
551 push @$result, '--slope-mode' if $RRDs::VERSION >= 1.2;
552
553 push @$result, "--height", ($size_y || munin_get($service, "graph_height", "175"));
554 push @$result, "--width", ($size_x || munin_get($service, "graph_width", "400"));
555
556 push @$result, "--full-size-mode" if ($full_size_mode);
557 push @$result, "--only-graph" if ($only_graph);
558
559 push @$result,"--rigid" if (defined $lower_limit || defined $upper_limit);
560
561 push @$result, "--imgformat", "PNG";
562 push @$result, "--lazy" if ($force_lazy);
563
564 push(@$result, "--units-exponent", "0") if (!should_scale($service));
565
566 return $result;
567 }
568
569 sub get_sum_command {
570 my $field = shift;
571 return munin_get($field, "sum");
572 }
573
574 sub get_stack_command {
575 my $field = shift;
576 return munin_get($field, "stack");
577 }
578
579 sub expand_specials {
580 my $service = shift;
581 my $order = shift;
582
583 my $preproc = [];
584 my $single ;
585
586 # Test if already expanded
587 {
588 my $cached = $service->{"#%#expand_specials"};
589 if (defined $cached) {
590 DEBUG "[DEBUG] expand_specials(): already processed " . munin_dumpconfig_as_str($cached);
591 return $cached;
592 }
593 DEBUG "[DEBUG] expand_specials(): not processed, proceeding for " . munin_dumpconfig_as_str($service);
594 }
595
596 # we have to compute the result;
597 my $result = [];
598
599 my $fieldnum = 0;
600
601 for my $field (@$order) { # Search for 'specials'...
602 my $tmp_field;
603
604 if ($field =~ /^-(.+)$/) { # Invisible field
605 $field = $1;
606 munin_set_var_loc($service, [$field, "graph"], "no");
607 }
608
609 $fieldnum++;
610 if ($field =~ /^([^=]+)=(.+)$/) { # Aliased in graph_order
611 my $fname = $1;
612 my $spath = $2;
613 my $src = munin_get_node_partialpath($service, $spath);
614 my $sname = munin_get_node_name($src);
615
616 if(!defined $src) {
617 ERROR "[ERROR] Failed to find $fname source at $spath, skipping field";
618 next;
619 }
620 DEBUG "[DEBUG] Copying settings from $sname to $fname.";
621
622 foreach my $foption ("draw", "type", "rrdfile", "fieldname", "info")
623 {
624 if (!defined $service->{$fname}->{$foption}) {
625 if (defined $src->{$foption}) {
626 munin_set_var_loc($service, [$fname, $foption],
627 $src->{$foption});
628 }
629 }
630 }
631
632 if (!defined $service->{$fname}->{"label"}) {
633 munin_set_var_loc($service, [$fname, "label"], $fname);
634 }
635 munin_set_var_loc(
636 $service,
637 [$fname, "filename"],
638 munin_get_rrd_filename($src));
639
640 }
641 elsif (defined($tmp_field = get_stack_command($service->{$field}))) {
642 # Aliased with .stack
643 DEBUG "[DEBUG] expand_specials ($tmp_field): Doing stack...";
644
645 my @spc_stack = ();
646 foreach my $pre (split(/\s+/, $tmp_field)) {
647 (my $name = $pre) =~ s/=.+//;
648
649 # Auto selects the .draw
650 my $draw = (!@spc_stack) ? munin_get($service->{$field}, "draw", "LINE1") : "STACK";
651 munin_set_var_loc($service, [$name, "draw"], $draw);
652
653 # Don't process this field later
654 munin_set_var_loc($service, [$field, "process"], "0");
655
656 push(@spc_stack, $name);
657 push(@$preproc, $pre);
658 push @$result, "$name.label";
659 push @$result, "$name.draw";
660 push @$result, "$name.cdef";
661
662 munin_set_var_loc($service, [$name, "label"], $name);
663 munin_set_var_loc($service, [$name, "cdef"], "$name,UN,0,$name,IF");
664 if (munin_get($service->{$field}, "cdef")
665 and !munin_get_bool($service->{$name}, "onlynullcdef", 0)) {
666 DEBUG "[DEBUG] NotOnlynullcdef ($field)...";
667 $service->{$name}->{"cdef"} .= "," . $service->{$field}->{"cdef"};
668 $service->{$name}->{"cdef"} =~ s/\b$field\b/$name/g;
669 }
670 else {
671 DEBUG "[DEBUG] Onlynullcdef ($field)...";
672 munin_set_var_loc($service, [$name, "onlynullcdef"], 1);
673 push @$result, "$name.onlynullcdef";
674 }
675 }
676 } # if get_stack_command
677 elsif (defined($tmp_field = get_sum_command($service->{$field}))) {
678 my @spc_stack = ();
679 my $last_name = "";
680 DEBUG "[DEBUG] expand_specials ($tmp_field): Doing sum...";
681
682 if (@$order == 1
683 or (@$order == 2 and munin_get($field, "negative", 0))) {
684 $single = 1;
685 }
686
687 foreach my $pre (split(/\s+/, $tmp_field)) {
688 (my $path = $pre) =~ s/.+=//;
689 my $name = "z" . $fieldnum . "_" . scalar(@spc_stack);
690 $last_name = $name;
691
692 munin_set_var_loc($service, [$name, "cdef"],
693 "$name,UN,0,$name,IF");
694 munin_set_var_loc($service, [$name, "graph"], "no");
695 munin_set_var_loc($service, [$name, "label"], $name);
696 push @$result, "$name.cdef";
697 push @$result, "$name.graph";
698 push @$result, "$name.label";
699
700 push(@spc_stack, $name);
701 push(@$preproc, "$name=$pre");
702 }
703 $service->{$last_name}->{"cdef"} .=
704 "," . join(",$AddNAN,", @spc_stack[0 .. @spc_stack - 2]) .
705 ",$AddNAN";
706
707 if (my $tc = munin_get($service->{$field}, "cdef", 0))
708 { # Oh bugger...
709 DEBUG "[DEBUG] Oh bugger...($field)...\n";
710 $tc =~ s/\b$field\b/$service->{$last_name}->{"cdef"}/;
711 $service->{$last_name}->{"cdef"} = $tc;
712 }
713 munin_set_var_loc($service, [$field, "process"], "0");
714 munin_set_var_loc(
715 $service,
716 [$last_name, "draw"],
717 munin_get($service->{$field}, "draw"));
718 munin_set_var_loc(
719 $service,
720 [$last_name, "colour"],
721 munin_get($service->{$field}, "colour"));
722 munin_set_var_loc(
723 $service,
724 [$last_name, "label"],
725 munin_get($service->{$field}, "label"));
726 munin_set_var_loc(
727 $service,
728 [$last_name, "graph"],
729 munin_get($service->{$field}, "graph", "yes"));
730
731 if (my $tmp = munin_get($service->{$field}, "negative")) {
732 munin_set_var_loc($service, [$last_name, "negative"], $tmp);
733 }
734
735 munin_set_var_loc($service, [$field, "realname"], $last_name);
736
737 }
738 elsif (my $nf = munin_get($service->{$field}, "negative", 0)) {
739 if ( !munin_get_bool($service->{$nf}, "graph", 1)
740 or munin_get_bool($service->{$nf}, "skipdraw", 0)) {
741 munin_set_var_loc($service, [$nf, "graph"], "no");
742 }
743 }
744 } # for (@$order)
745
746 # Return & save it for future use
747 $service->{"#%#expand_specials"} = {
748 "added" => $result,
749 "preprocess" => $preproc,
750 "single" => $single,
751 };
752 return $service->{"#%#expand_specials"};
753 }
754
755
756 sub single_value {
757 my $service = shift;
758
759 my $graphable = munin_get($service, "graphable", 0);
760 if (!$graphable) {
761 foreach my $field (@{munin_get_field_order($service)}) {
762 DEBUG "[DEBUG] single_value: Checking field \"$field\".";
763 $graphable++ if munin_draw_field($service->{$field});
764 }
765 munin_set_var_loc($service, ["graphable"], $graphable);
766 }
767 DEBUG "[DEBUG] service "
768 . join(' :: ', @{munin_get_node_loc($service)})
769 . " has $graphable elements.";
770 return ($graphable == 1);
771 }
772
773
774 sub get_field_name {
775 my $name = shift;
776
777 $name = substr(Digest::MD5::md5_hex($name), -15)
778 if (length $name > 15);
779
780 return $name;
781 }
782
783
784 sub process_work {
785 my (@hosts) = @_;
786
787 # Make array of what is probably needed to graph
788
789 my $work_array = [];
790
791 if ($only_fqn) {
792 push @$work_array, munin_find_node_by_fqn($config,$only_fqn);
793 } elsif (@hosts) {
794 foreach my $nodename (@hosts) {
795 push @$work_array,
796 map {@{munin_find_field($_->{$nodename}, "graph_title")}}
797 @{munin_find_field($config, $nodename)};
798 }
799 } else {
800 FATAL "[FATAL] In process_work, no fqn and no hosts!";
801 }
802
803 # @$work_array contains copy of (or pointer to) each service to be graphed.
804 for my $service (@$work_array) {
805
806 # Want to avoid forking for that
807 next if (skip_service($service));
808
809 # Fork (or not) and run the anonymous sub afterwards.
810 fork_and_work(sub {process_service($service);});
811 }
812 }
813
814
815 sub process_field {
816 my $field = shift;
817 return munin_get_bool($field, "process", 1);
818 }
819
820
821 sub fork_and_work {
822 my ($work) = @_;
823
824 if (!$do_fork) {
825
826 # We're not forking. Do work and return.
827 DEBUG "[DEBUG] Doing work synchronously";
828 &$work;
829 return;
830 }
831
832 # Make sure we don't fork too much
833 while ($running >= $max_running) {
834 DEBUG
835 "[DEBUG] Too many forks ($running/$max_running), wait for something to get done";
836 look_for_child("block");
837 --$running;
838 }
839
840 my $pid = fork();
841
842 if (!defined $pid) {
843 ERROR "[ERROR] fork failed: $!";
844 die "fork failed: $!";
845 }
846
847 if ($pid == 0) {
848
849 # This block does the real work. Since we're forking exit
850 # afterwards.
851
852 &$work;
853
854 # See?!
855
856 exit 0;
857
858 }
859 else {
860 ++$running;
861 DEBUG "[DEBUG] Forked: $pid. Now running $running/$max_running";
862 while ($running and look_for_child()) {
863 --$running;
864 }
865 }
866 }
867
868 sub remove_dups {
869 my @ret;
870 my %keys;
871 for my $order (@_) {
872 (my $name = $order) =~ s/=.+//;
873 push @ret, $order unless ($keys{$name} ++);
874 }
875
876 return @ret;
877 }
878
879 sub _sanitise_fieldname {
880 # http://munin-monitoring.org/wiki/notes_on_datasource_names
881 my ($name) = @_;
882
883 $name =~ s/^[^A-Za-z_]/_/;
884 $name =~ s/[^A-Za-z0-9_]/_/g;
885
886 return $name;
887 }
888
889 sub process_service {
890 my ($service) = @_;
891
892 # See if we should skip the service
893 return if (skip_service($service));
894
895 # Make my graphs
896 my $sname = munin_get_node_name($service);
897 my $skeypath = munin_get_keypath($service);
898 my $service_time = Time::HiRes::time;
899 my $lastupdate = 0;
900 my $now = time;
901 my $fnum = 0;
902 my @rrd;
903
904 DEBUG "[DEBUG] Node name: $sname\n";
905
906 my $field_count = 0;
907 my $max_field_len = 0;
908 my @field_order = ();
909 my $rrdname;
910
911 @field_order = @{munin_get_field_order($service)};
912
913 # Array to keep 'preprocess'ed fields.
914 DEBUG "[DEBUG] Expanding specials for $sname: \""
915 . join("\",\"", @field_order) . "\".";
916
917 my $expanded_result = expand_specials($service, \@field_order);
918 my $force_single_value = $expanded_result->{single};
919 my @added = @{ $expanded_result->{added} };
920
921 # put preprocessed fields in front
922 unshift @field_order, @{ $expanded_result->{preprocess} };
923
924 # Remove duplicates, while retaining the order
925 @field_order = remove_dups ( @field_order );
926
927 # Get max label length
928 DEBUG "[DEBUG] Checking field lengths for $sname: \"" . join('","', @field_order) . '".';
929 $max_field_len = munin_get_max_label_length($service, \@field_order);
930
931 # Global headers makes the value tables easier to read no matter how
932 # wide the labels are.
933 my $global_headers = 1;
934
935 # Default format for printing under graph.
936 my $avgformat;
937 my $rrdformat = $avgformat = "%6.2lf";
938
939 if (munin_get($service, "graph_args", "") =~ /--base\s+1024/) {
940
941 # If the base unit is 1024 then 1012.56 is a valid
942 # number to show. That's 7 positions, not 6.
943 $rrdformat = $avgformat = "%7.2lf";
944 }
945
946 # Plugin specified complete printf format
947 $rrdformat = munin_get($service, "graph_printf", $rrdformat);
948
949 my $rrdscale = '';
950 if (munin_get_bool($service, "graph_scale", 1)) {
951 $rrdscale = '%s';
952 }
953
954 # Array to keep negative data until we're finished with positive.
955 my @rrd_negatives = ();
956
957 my $filename;
958 my %total_pos;
959 my %total_neg;
960 my $autostacking = 0;
961
962 DEBUG "[DEBUG] Treating fields \"" . join("\",\"", @field_order) . "\".";
963 for my $fname (@field_order) {
964 my $path = undef;
965 my $field = undef;
966
967 if ($fname =~ s/=(.+)//) {
968 $path = $1;
969 }
970 $field = munin_get_node($service, [$fname]);
971
972 next if (!defined $field or !$field or !process_field($field));
973 DEBUG "[DEBUG] Processing field \"$fname\" ["
974 . munin_get_node_name($field) . "].";
975
976 my $fielddraw = munin_get($field, "draw", "LINE1");
977
978 if ($field_count == 0 and $fielddraw eq 'STACK') {
979
980 # Illegal -- first field is a STACK
981 DEBUG "ERROR: First field (\"$fname\") of graph "
982 . join(' :: ', munin_get_node_loc($service))
983 . " is STACK. STACK can only be drawn after a LINEx or AREA.";
984 $fielddraw = "LINE1";
985 }
986
987 if ($fielddraw eq 'AREASTACK') {
988 if ($autostacking == 0) {
989 $fielddraw = 'AREA';
990 $autostacking = 1;
991 }
992 else {
993 $fielddraw = 'STACK';
994 }
995 }
996
997 if ($fielddraw =~ /LINESTACK(|\d+|\d+\.\d+)$/) {
998 if ($autostacking == 0) {
999 $fielddraw = "LINE$1";
1000 $autostacking = 1;
1001 }
1002 else {
1003 $fielddraw = 'STACK';
1004 }
1005 }
1006
1007 # Getting name of rrd file
1008 $filename = munin_get_rrd_filename($field, $path);
1009 if (! $filename) {
1010 ERROR "[ERROR] filename is empty for " . munin_dumpconfig_as_str($field) . ", $path";
1011 # Ignore this field
1012 next;
1013 }
1014
1015 if(!defined $filename) {
1016 ERROR "[ERROR] Failed getting filename for $path, skipping field";
1017 next;
1018 }
1019 # Here it is OK to flush the rrdcached, since we'll flush it anyway
1020 # with graph
1021 my $update = RRDs::last(@rrdcached_params, $filename);
1022 $update = 0 if !defined $update;
1023 if ($update > $lastupdate) {
1024 $lastupdate = $update;
1025 }
1026
1027 # It does not look like $fieldname.rrdfield is possible to set
1028 my $rrdfield = munin_get($field, "rrdfield", "42");
1029
1030 my $single_value = $force_single_value || single_value($service);
1031
1032 # XXX - single_value is wrong for some multigraph, disabling it for now
1033 $single_value = 0;
1034
1035 my $has_negative = munin_get($field, "negative");
1036
1037 # Trim the fieldname to make room for other field names.
1038
1039 $rrdname = &get_field_name($fname);
1040
1041 reset_cdef($service, $rrdname);
1042 if ($rrdname ne $fname) {
1043
1044 # A change was made
1045 munin_set($field, "cdef_name", $rrdname);
1046 }
1047
1048 # Push will place the DEF too far down for some CDEFs to work
1049 unshift(@rrd, "DEF:g$rrdname=" . $filename . ":" . $rrdfield . ":AVERAGE");
1050 unshift(@rrd, "DEF:i$rrdname=" . $filename . ":" . $rrdfield . ":MIN");
1051 unshift(@rrd, "DEF:a$rrdname=" . $filename . ":" . $rrdfield . ":MAX");
1052
1053 if (munin_get_bool($field, "onlynullcdef", 0)) {
1054 push(@rrd,
1055 "CDEF:c$rrdname=g$rrdname"
1056 . (($now - $update) > 900 ? ",POP,UNKN" : ""));
1057 }
1058
1059 if ( munin_get($field, "type", "GAUGE") ne "GAUGE"
1060 and graph_by_minute($service)) {
1061 push(@rrd, expand_cdef($service, \$rrdname, "$fname,60,*"));
1062 }
1063 if ( munin_get($field, "type", "GAUGE") ne "GAUGE"
1064 and graph_by_hour($service)) {
1065 push(@rrd, expand_cdef($service, \$rrdname, "$fname,3600,*"));
1066 }
1067
1068 if (my $tmpcdef = munin_get($field, "cdef")) {
1069 push(@rrd, expand_cdef($service, \$rrdname, $tmpcdef));
1070 push(@rrd, "CDEF:c$rrdname=g$rrdname");
1071 DEBUG "[DEBUG] Field name after cdef set to $rrdname";
1072 }
1073 elsif (!munin_get_bool($field, "onlynullcdef", 0)) {
1074 push(@rrd,
1075 "CDEF:c$rrdname=g$rrdname"
1076 . (($now - $update) > 900 ? ",POP,UNKN" : ""));
1077 }
1078
1079 next if !munin_draw_field($field);
1080 DEBUG "[DEBUG] Drawing field \"$fname\".";
1081
1082 if ($single_value) {
1083
1084 # Only one field. Do min/max range.
1085 push(@rrd, "CDEF:min_max_diff=a$rrdname,i$rrdname,-");
1086 push(@rrd, "CDEF:re_zero=min_max_diff,min_max_diff,-")
1087 if !munin_get($field, "negative");
1088
1089 push(@rrd, "AREA:i$rrdname#ffffff");
1090 push(@rrd, "STACK:min_max_diff#$range_colour");
1091 push(@rrd, "LINE1:re_zero#000000")
1092 if !munin_get($field, "negative");
1093 }
1094
1095 # Push "global" headers or not
1096 if ($has_negative and !@rrd_negatives and $global_headers < 2) {
1097
1098 # Always for -/+ graphs
1099 push(@rrd, "COMMENT:" . (" " x $max_field_len));
1100 push(@rrd, "COMMENT:Cur (-/+)");
1101 push(@rrd, "COMMENT:Min (-/+)");
1102 push(@rrd, "COMMENT:Avg (-/+)");
1103 push(@rrd, "COMMENT:Max (-/+) \\j");
1104 $global_headers = 2; # Avoid further headers/labels
1105 }
1106 elsif ($global_headers == 1) {
1107
1108 # Or when we want to.
1109 push(@rrd, "COMMENT:" . (" " x $max_field_len));
1110 push(@rrd, "COMMENT: Cur$RRDkludge:");
1111 push(@rrd, "COMMENT:Min$RRDkludge:");
1112 push(@rrd, "COMMENT:Avg$RRDkludge:");
1113 push(@rrd, "COMMENT:Max$RRDkludge: \\j");
1114 $global_headers = 2; # Avoid further headers/labels
1115 }
1116
1117 my $colour = munin_get($field, "colour");
1118
1119 if ($colour && $colour =~ /^COLOUR(\d+)$/) {
1120 $colour = $COLOUR[$1 % @COLOUR];
1121 }
1122
1123 # Select a default colour if no explict one
1124 $colour ||= ($single_value) ? $single_colour : $COLOUR[$field_count % @COLOUR];
1125 my $warn_colour = $single_value ? "ff0000" : $colour;
1126
1127 # colour needed for transparent predictions and trends
1128 munin_set($field, "colour", $colour);
1129 $field_count++;
1130
1131 my $tmplabel = munin_get($field, "label", $fname);
1132
1133 # Substitute ${graph_period}
1134 my $period = munin_get($service, "graph_period", "second");
1135 $tmplabel =~ s/\$\{graph_period\}/$period/g;
1136
1137 push(@rrd,
1138 $fielddraw
1139 . ":g$rrdname"
1140 . "#$colour" . ":"
1141 . escape($tmplabel)
1142 . (" " x ($max_field_len + 1 - length $tmplabel)));
1143
1144 # Check for negative fields (typically network (or disk) traffic)
1145 if ($has_negative) {
1146 my $negfieldname
1147 = orig_to_cdef($service, munin_get($field, "negative"));
1148 my $negfield = $service->{_sanitise_fieldname(munin_get($field, "negative"))};
1149 if (my $tmpneg = munin_get($negfield, "realname")) {
1150 $negfieldname = $tmpneg;
1151 $negfield = $service->{$negfieldname};
1152 }
1153
1154 if (!@rrd_negatives) {
1155
1156 # zero-line, to redraw zero afterwards.
1157 push(@rrd_negatives, "CDEF:re_zero=g$negfieldname,UN,0,0,IF");
1158 }
1159
1160 push(@rrd_negatives, "CDEF:ng$negfieldname=g$negfieldname,-1,*");
1161
1162 if ($single_value) {
1163
1164 # Only one field. Do min/max range.
1165 push(@rrd,
1166 "CDEF:neg_min_max_diff=i$negfieldname,a$negfieldname,-");
1167 push(@rrd, "CDEF:ni$negfieldname=i$negfieldname,-1,*");
1168 push(@rrd, "AREA:ni$negfieldname#ffffff");
1169 push(@rrd, "STACK:neg_min_max_diff#$range_colour");
1170 }
1171
1172 push(@rrd_negatives, $fielddraw . ":ng$negfieldname#$colour");
1173
1174 # Draw HRULEs
1175 my $linedef = munin_get($negfield, "line");
1176 if ($linedef) {
1177 my ($number, $ldcolour, $label) = split(/:/, $linedef, 3);
1178 unshift(@rrd_negatives,
1179 "HRULE:" . $number . ($ldcolour ? "#$ldcolour" : "#$colour"));
1180 }
1181 elsif (my $tmpwarn = munin_get($negfield, "warning")) {
1182
1183 my ($warn_min, $warn_max) = split(':', $tmpwarn,2);
1184
1185 if (defined($warn_min) and $warn_min ne '') {
1186 unshift(@rrd, "HRULE:${warn_min}#${warn_colour}${RRDLineThresholdAttribute}");
1187 }
1188 if (defined($warn_max) and $warn_max ne '') {
1189 unshift(@rrd, "HRULE:${warn_max}#${warn_colour}${RRDLineThresholdAttribute}");
1190 }
1191 }
1192
1193 push(@rrd,
1194 "GPRINT:c$negfieldname:LAST:$rrdformat" . $rrdscale . "/\\g");
1195 push(@rrd, "GPRINT:c$rrdname:LAST:$rrdformat" . $rrdscale . "");
1196 push(@rrd,
1197 "GPRINT:i$negfieldname:MIN:$rrdformat" . $rrdscale . "/\\g");
1198 push(@rrd, "GPRINT:i$rrdname:MIN:$rrdformat" . $rrdscale . "");
1199 push(@rrd,
1200 "GPRINT:g$negfieldname:AVERAGE:$avgformat"
1201 . $rrdscale
1202 . "/\\g");
1203 push(@rrd, "GPRINT:g$rrdname:AVERAGE:$avgformat" . $rrdscale . "");
1204 push(@rrd,
1205 "GPRINT:a$negfieldname:MAX:$rrdformat" . $rrdscale . "/\\g");
1206 push(@rrd, "GPRINT:a$rrdname:MAX:$rrdformat" . $rrdscale . "\\j");
1207 push(@{$total_pos{'min'}}, "i$rrdname");
1208 push(@{$total_pos{'avg'}}, "g$rrdname");
1209 push(@{$total_pos{'max'}}, "a$rrdname");
1210 push(@{$total_neg{'min'}}, "i$negfieldname");
1211 push(@{$total_neg{'avg'}}, "g$negfieldname");
1212 push(@{$total_neg{'max'}}, "a$negfieldname");
1213 }
1214 else {
1215 push(@rrd, "COMMENT: Cur$RRDkludge:") unless $global_headers;
1216 push(@rrd, "GPRINT:c$rrdname:LAST:$rrdformat" . $rrdscale . "");
1217 push(@rrd, "COMMENT: Min$RRDkludge:") unless $global_headers;
1218 push(@rrd, "GPRINT:i$rrdname:MIN:$rrdformat" . $rrdscale . "");
1219 push(@rrd, "COMMENT: Avg$RRDkludge:") unless $global_headers;
1220 push(@rrd, "GPRINT:g$rrdname:AVERAGE:$avgformat" . $rrdscale . "");
1221 push(@rrd, "COMMENT: Max$RRDkludge:") unless $global_headers;
1222 push(@rrd, "GPRINT:a$rrdname:MAX:$rrdformat" . $rrdscale . "\\j");
1223 push(@{$total_pos{'min'}}, "i$rrdname");
1224 push(@{$total_pos{'avg'}}, "g$rrdname");
1225 push(@{$total_pos{'max'}}, "a$rrdname");
1226 }
1227
1228 # Draw HRULEs
1229 my $linedef = munin_get($field, "line");
1230 if ($linedef) {
1231 my ($number, $ldcolour, $label) = split(/:/, $linedef, 3);
1232 $label =~ s/:/\\:/g if defined $label;
1233 unshift(
1234 @rrd,
1235 "HRULE:"
1236 . $number
1237 . (
1238 $ldcolour ? "#$ldcolour"
1239 : ((defined $single_value and $single_value) ? "#ff0000"
1240 : "#$colour"))
1241 . ((defined $label and length($label)) ? ":$label" : ""),
1242 "COMMENT: \\j"
1243 );
1244 }
1245 elsif (my $tmpwarn = munin_get($field, "warning")) {
1246
1247 my ($warn_min, $warn_max) = split(':', $tmpwarn,2);
1248
1249 if (defined($warn_min) and $warn_min ne '') {
1250 unshift(@rrd, "HRULE:${warn_min}#${warn_colour}${RRDLineThresholdAttribute}");
1251 }
1252 if (defined($warn_max) and $warn_max ne '') {
1253 unshift(@rrd, "HRULE:${warn_max}#${warn_colour}${RRDLineThresholdAttribute}");
1254 }
1255 }
1256 }
1257
1258 my $graphtotal = munin_get($service, "graph_total");
1259 if (@rrd_negatives) {
1260 push(@rrd, @rrd_negatives);
1261 push(@rrd, "LINE1:re_zero#000000"); # Redraw zero.
1262 if ( defined $graphtotal
1263 and exists $total_pos{'min'}
1264 and exists $total_neg{'min'}
1265 and @{$total_pos{'min'}}
1266 and @{$total_neg{'min'}}) {
1267
1268 push(@rrd,
1269 "CDEF:ipostotal="
1270 . join(",", @{$total_pos{'min'}})
1271 . (",$AddNAN" x (@{$total_pos{'min'}} - 1)));
1272 push(@rrd,
1273 "CDEF:gpostotal="
1274 . join(",", @{$total_pos{'avg'}})
1275 . (",$AddNAN" x (@{$total_pos{'avg'}} - 1)));
1276 push(@rrd,
1277 "CDEF:apostotal="
1278 . join(",", @{$total_pos{'max'}})
1279 . (",$AddNAN" x (@{$total_pos{'max'}} - 1)));
1280 push(@rrd,
1281 "CDEF:inegtotal="
1282 . join(",", @{$total_neg{'min'}})
1283 . (",$AddNAN" x (@{$total_neg{'min'}} - 1)));
1284 push(@rrd,
1285 "CDEF:gnegtotal="
1286 . join(",", @{$total_neg{'avg'}})
1287 . (",$AddNAN" x (@{$total_neg{'avg'}} - 1)));
1288 push(@rrd,
1289 "CDEF:anegtotal="
1290 . join(",", @{$total_neg{'max'}})
1291 . (",$AddNAN" x (@{$total_neg{'max'}} - 1)));
1292 push(@rrd,
1293 "LINE1:gpostotal#000000:$graphtotal"
1294 . (" " x ($max_field_len - length($graphtotal) + 1)));
1295 push(@rrd, "GPRINT:gnegtotal:LAST:$rrdformat" . $rrdscale . "/\\g");
1296 push(@rrd, "GPRINT:gpostotal:LAST:$rrdformat" . $rrdscale . "");
1297 push(@rrd, "GPRINT:inegtotal:MIN:$rrdformat" . $rrdscale . "/\\g");
1298 push(@rrd, "GPRINT:ipostotal:MIN:$rrdformat" . $rrdscale . "");
1299 push(@rrd,
1300 "GPRINT:gnegtotal:AVERAGE:$avgformat" . $rrdscale . "/\\g");
1301 push(@rrd, "GPRINT:gpostotal:AVERAGE:$avgformat" . $rrdscale . "");
1302 push(@rrd, "GPRINT:anegtotal:MAX:$rrdformat" . $rrdscale . "/\\g");
1303 push(@rrd, "GPRINT:apostotal:MAX:$rrdformat" . $rrdscale . "\\j");
1304 }
1305 }
1306 elsif ( defined $graphtotal
1307 and exists $total_pos{'min'}
1308 and @{$total_pos{'min'}}) {
1309 push(@rrd,
1310 "CDEF:ipostotal="
1311 . join(",", @{$total_pos{'min'}})
1312 . (",$AddNAN" x (@{$total_pos{'min'}} - 1)));
1313 push(@rrd,
1314 "CDEF:gpostotal="
1315 . join(",", @{$total_pos{'avg'}})
1316 . (",$AddNAN" x (@{$total_pos{'avg'}} - 1)));
1317 push(@rrd,
1318 "CDEF:apostotal="
1319 . join(",", @{$total_pos{'max'}})
1320 . (",$AddNAN" x (@{$total_pos{'max'}} - 1)));
1321
1322 push(@rrd,
1323 "LINE1:gpostotal#000000:$graphtotal"
1324 . (" " x ($max_field_len - length($graphtotal) + 1)));
1325 push(@rrd, "COMMENT: Cur$RRDkludge:") unless $global_headers;
1326 push(@rrd, "GPRINT:gpostotal:LAST:$rrdformat" . $rrdscale . "");
1327 push(@rrd, "COMMENT: Min$RRDkludge:") unless $global_headers;
1328 push(@rrd, "GPRINT:ipostotal:MIN:$rrdformat" . $rrdscale . "");
1329 push(@rrd, "COMMENT: Avg$RRDkludge:") unless $global_headers;
1330 push(@rrd, "GPRINT:gpostotal:AVERAGE:$avgformat" . $rrdscale . "");
1331 push(@rrd, "COMMENT: Max$RRDkludge:") unless $global_headers;
1332 push(@rrd, "GPRINT:apostotal:MAX:$rrdformat" . $rrdscale . "\\j");
1333 }
1334
1335 # insert these graph args in the end
1336 if (defined(my $tmp_field = get_custom_graph_args_after($service))) {
1337 push(@rrd, @{$tmp_field});
1338 }
1339
1340
1341
1342 my $nb_graphs_drawn = 0;
1343 for my $time (keys %times) {
1344 next unless ($draw{$time});
1345 my $picfilename = get_picture_filename($service, $time);
1346 DEBUG "[DEBUG] Looking into drawing $picfilename";
1347 (my $picdirname = $picfilename) =~ s/\/[^\/]+$//;
1348
1349 DEBUG "[DEBUG] Picture filename: $picfilename";
1350
1351 my @complete = get_fonts();
1352
1353 # Watermarks introduced in RRD 1.2.13.
1354 push(@complete, '-W', $watermark) if $RRDs::VERSION >= 1.2013;
1355
1356 # Do the header (title, vtitle, size, etc...), but IN THE BEGINNING
1357 unshift @complete, @{get_header($service, $time)};
1358
1359 if ($LINEkluge) {
1360 @rrd = map {
1361 my $line = $_;
1362 $line =~ s/LINE3:/LINE2.2:/;
1363 $line =~ s/LINE2:/LINE1.6:/;
1364
1365 # LINE1 is thin enough.
1366 $line;
1367 } @rrd;
1368 }
1369 push @complete, @rrd;
1370
1371 # graph end in future
1372 push (@complete, handle_trends($time, $lastupdate, $pinpoint, $service, $RRDkludge, @added));
1373
1374 # Make sure directory exists
1375 munin_mkdir_p($picdirname, oct(777));
1376
1377 # Since version 1.3 rrdtool uses libpango which needs its input
1378 # as utf8 string. So we assume that every input is in latin1
1379 # and decode it to perl's internal representation and then to utf8.
1380
1381 if ($RRDs::VERSION >= 1.3) {
1382 @complete = map {
1383 my $str = $_;
1384 my $utf8 = guess_encoding($str, 'utf8');
1385 ref $utf8 ? $str : encode("utf8", (decode("latin1", $_)));
1386 } @complete;
1387 }
1388
1389 # Surcharging the graphing limits
1390 my ($upper_limit_overrided, $lower_limit_overrided);
1391 for (my $index = 0; $index <= $#complete; $index++) {
1392 if ($complete[$index] =~ /^(--upper-limit|-u)$/ && (defined $upper_limit)) {
1393 $upper_limit = get_scientific($upper_limit);
1394 $complete[$index + 1] = $upper_limit;
1395 $upper_limit_overrided = 1;
1396 }
1397 if ($complete[$index] =~ /^(--lower-limit|-l)$/ && (defined $lower_limit)) {
1398 $lower_limit = get_scientific($lower_limit);
1399 $complete[$index + 1] = $lower_limit;
1400 $lower_limit_overrided = 1;
1401 }
1402 }
1403
1404 # Add the limit if not present
1405 if (defined $upper_limit && ! $upper_limit_overrided) {
1406 push @complete, "--upper-limit", $upper_limit;
1407 }
1408 if (defined $lower_limit && ! $lower_limit_overrided) {
1409 push @complete, "--lower-limit", $lower_limit;
1410 }
1411
1412 DEBUG "\n\nrrdtool 'graph' '" . join("' \\\n\t'", @rrdcached_params, @complete) . "'\n";
1413 $nb_graphs_drawn ++;
1414 RRDs::graph(@rrdcached_params, @complete);
1415 if (my $ERROR = RRDs::error) {
1416 ERROR "[RRD ERROR] Unable to graph $picfilename : $ERROR";
1417 # ALWAYS dumps the cmd used when an error occurs.
1418 # Otherwise, it will be difficult to debug post-mortem
1419 ERROR "[RRD ERROR] rrdtool 'graph' '" . join("' \\\n\t'", @rrdcached_params, @complete) . "'\n";
1420 }
1421 elsif (!-f $picfilename) {
1422 ERROR "[RRD ERROR] rrdtool graph did not generate the image (make sure there are data to graph).\n";
1423 }
1424 else {
1425
1426 # Set time of png file to the time of the last update of
1427 # the rrd file. This makes http's If-Modified-Since more
1428 # reliable, esp. in combination with munin-*cgi-graph.
1429
1430 # Since this disrupts rrd's --lazy option we're disableing
1431 # it unless we (munin-graph) were specially asked --lazy.
1432 # This way --lazy continues to work as expected, and since
1433 # CGI uses --nolazy, http IMS are also working as expected.
1434 if (! $force_lazy) {
1435 DEBUG "[DEBUG] setting time on $picfilename";
1436 utime $lastupdate, $lastupdate, $picfilename;
1437 }
1438
1439 if ($list_images) {
1440 # Command-line option to list images created
1441 print $picfilename. "\n";
1442 }
1443 }
1444 }
1445
1446 if (munin_get_bool($service, "graph_sums", 0)) {
1447 foreach my $time (keys %sumtimes) {
1448 my $picfilename = get_picture_filename($service, $time, 1);
1449 DEBUG "Looking into drawing $picfilename";
1450 (my $picdirname = $picfilename) =~ s/\/[^\/]+$//;
1451 next unless ($draw{"sum" . $time});
1452 my @rrd_sum;
1453 push @rrd_sum, @{get_header($service, $time, 1)};
1454
1455 # graph end in future
1456 push (@rrd_sum, handle_trends($time, $lastupdate, $pinpoint, $service, $RRDkludge, @added));
1457
1458 push @rrd_sum, @rrd;
1459
1460 my $labelled = 0;
1461 my @defined = ();
1462 for (my $index = 0; $index <= $#rrd_sum; $index++) {
1463 if ($rrd_sum[$index] =~ /^(--vertical-label|-v)$/) {
1464 (my $label = munin_get($service, "graph_vlabel"))
1465 =~ s/\$\{graph_period\}/$sumtimes{$time}[0]/g;
1466 splice(@rrd_sum, $index, 2, ("--vertical-label", $label));
1467 $index++;
1468 $labelled++;
1469 }
1470 elsif ($rrd_sum[$index]
1471 =~ /^(LINE[123]|STACK|AREA|GPRINT):([^#:]+)([#:].+)$/) {
1472 my ($pre, $fname, $post) = ($1, $2, $3);
1473 next if $fname eq "re_zero";
1474 if ($post =~ /^:AVERAGE/) {
1475 splice(@rrd_sum, $index, 1, $pre . ":x$fname" . $post);
1476 $index++;
1477 next;
1478 }
1479 next if grep /^x$fname$/, @defined;
1480 push @defined, "x$fname";
1481 my @replace;
1482
1483 if (munin_get($service->{$fname}, "type", "GAUGE") ne
1484 "GAUGE") {
1485 if ($time eq "week") {
1486
1487 # Every plot is half an hour. Add two plots and multiply, to get per hour
1488 if (graph_by_minute($service)) {
1489
1490 # Already multiplied by 60
1491 push @replace,
1492 "CDEF:x$fname=PREV($fname),UN,0,PREV($fname),IF,$fname,+,5,*,6,*";
1493 } elsif (graph_by_hour($service)) {
1494 # Already multiplied by 3600, have to *divide* by 12
1495 push @replace,
1496 "CDEF:x$fname=PREV($fname),UN,0,PREV($fname),IF,$fname,+,12,/,6,*";
1497 }
1498 else {
1499 push @replace,
1500 "CDEF:x$fname=PREV($fname),UN,0,PREV($fname),IF,$fname,+,300,*,6,*";
1501 }
1502 }
1503 else {
1504
1505 # Every plot is one day exactly. Just multiply.
1506 if (graph_by_minute($service)) {
1507
1508 # Already multiplied by 60
1509 push @replace, "CDEF:x$fname=$fname,5,*,288,*";
1510 } elsif (graph_by_hour($service)) {
1511 # Already multiplied by 3600, have to *divide* by 12
1512 push @replace, "CDEF:x$fname=$fname,12,/,288,*";
1513 }
1514 else {
1515 push @replace,
1516 "CDEF:x$fname=$fname,300,*,288,*";
1517 }
1518 }
1519 }
1520 push @replace, $pre . ":x$fname" . $post;
1521 splice(@rrd_sum, $index, 1, @replace);
1522 $index++;
1523 }
1524 elsif (
1525 $rrd_sum[$index] =~ /^(--lower-limit|--upper-limit|-l|-u)$/)
1526 {
1527 $index++;
1528 $rrd_sum[$index]
1529 = $rrd_sum[$index] * 300 * $sumtimes{$time}->[1];
1530 }
1531 }
1532
1533
1534 unless ($labelled) {
1535 my $label = munin_get($service, "graph_vlabel_sum_$time",
1536 $sumtimes{$time}->[0]);
1537 unshift @rrd_sum, "--vertical-label", $label;
1538 }
1539
1540 DEBUG "[DEBUG] \n\nrrdtool graph '" . join("' \\\n\t'", @rrd_sum) . "'\n";
1541
1542 # Make sure directory exists
1543 munin_mkdir_p($picdirname, oct(777));
1544
1545 $nb_graphs_drawn ++;
1546 RRDs::graph(@rrdcached_params, @rrd_sum);
1547
1548 if (my $ERROR = RRDs::error) {
1549 ERROR "[RRD ERROR(sum)] Unable to graph "
1550 . get_picture_filename($service, $time)
1551 . ": $ERROR";
1552 }
1553 elsif ($list_images) {
1554 # Command-line option to list images created
1555 print get_picture_filename ($service, $time, 1), "\n";
1556 }
1557 } # foreach (keys %sumtimes)
1558 } # if graph_sums
1559
1560 $service_time = sprintf("%.2f", (Time::HiRes::time - $service_time));
1561 DEBUG "[DEBUG] Graphed service $skeypath ($service_time sec for $nb_graphs_drawn graphs)";
1562 print $STATS "GS|$service_time\n" unless $skip_stats;
1563
1564 foreach (@added) {
1565 delete $service->{$_} if exists $service->{$_};
1566 }
1567 @added = ();
1568 }
1569
1570 # sets enddate --end and draws a vertical ruler if enddate is in the future
1571 # future trends are also added to the graph here
1572 sub handle_trends {
1573 my $time = shift;
1574 my $lastupdate = shift;
1575 my $pinpoint = shift;
1576 my $service = shift;
1577 my $RRDkludge = shift;
1578 my @added = @_;
1579 my @complete;
1580
1581 # enddate possibly in future
1582 my $futuretime = $pinpoint ? 0 : $resolutions{$time} * get_end_offset($service);
1583 my $enddate = time + ($futuretime);
1584 DEBUG "[DEBUG] lastupdate: $lastupdate, enddate: $enddate\n";
1585
1586 # future begins at this horizontal ruler
1587 if ($enddate > $lastupdate) {
1588 push(@complete, "VRULE:$lastupdate#999999");
1589 }
1590
1591 # create trends/predictions
1592 foreach my $field (@{munin_find_field($service, "label")}) {
1593 my $fieldname = munin_get_node_name($field);
1594 my $colour = $single_colour;
1595
1596 # Skip virtual fieldnames, otherwise beware of $hash->{foo}{bar}.
1597 #
1598 # On a sidenote, what's the output of the following code ?
1599 # perl -e '$a = {}; if ($a->{foo}{bar}) { print "Found Foo/Bar\n"; } \
1600 # if ($a->{foo}) { print "Found Foo\n"; }'
1601 next if ! defined $service->{$fieldname};
1602
1603 if (defined $service->{$fieldname}{'colour'}) {
1604 $colour = "$service->{$fieldname}{'colour'}66";
1605 }
1606
1607 my $rrd_fieldname = get_field_name($fieldname);
1608
1609 my $cdef = "";
1610 if (defined $service->{$fieldname}{'cdef'}) {
1611 $cdef = "cdef";
1612 }
1613
1614 #trends
1615 if (defined $service->{$fieldname}{'trend'} and $service->{$fieldname}{'trend'} eq 'yes') {
1616 push (@complete, "CDEF:t$rrd_fieldname=c$cdef$rrd_fieldname,$futuretime,TRENDNAN");
1617 push (@complete, "LINE1:t$rrd_fieldname#$colour:$service->{$fieldname}->{'label'} trend\\l");
1618 DEBUG "[DEBUG] set trend for $fieldname\n";
1619 }
1620
1621 #predictions
1622 if (defined $service->{$fieldname}{'predict'}) {
1623 #arguments: pattern length (e.g. 1 day), smoothing (multiplied with timeresolution)
1624 my @predict = split(",", $service->{$fieldname}{'predict'});
1625 my $predictiontime = int ($futuretime / $predict[0]) + 2; #2 needed for 1 day
1626 my $smooth = $predict[1]*$resolutions{$time};
1627 push (@complete, "CDEF:p$rrd_fieldname=$predict[0],-$predictiontime,$smooth,c$cdef$rrd_fieldname,PREDICT");
1628 push (@complete, "LINE1:p$rrd_fieldname#$colour:$service->{$fieldname}->{'label'} prediction\\l");
1629 DEBUG "[DEBUG] set prediction for $fieldname\n";
1630 }
1631 }
1632
1633
1634 push(@complete,
1635 "COMMENT:Last update$RRDkludge: "
1636 . RRDescape(scalar localtime($lastupdate))
1637 . "\\r");
1638
1639 # If pinpointing, --end should *NOT* be changed
1640 if (! $pinpoint) {
1641 if (@added) { # stop one period earlier if it's a .sum or .stack
1642 push @complete, "--end",
1643 (int(($enddate-$resolutions{$time}) / $resolutions{$time})) * $resolutions{$time};
1644 } else {
1645 push @complete, "--end",
1646 (int($enddate / $resolutions{$time})) * $resolutions{$time};
1647 }
1648 }
1649
1650 return @complete;
1651 }
1652
1653 sub get_fonts {
1654 # Set up rrdtool graph font options according to RRD version.
1655 my @options;
1656
1657 if ($RRDs::VERSION < 1.2) {
1658 # RRD before 1.2, no font options
1659 } elsif ($RRDs::VERSION < 1.3) {
1660 # RRD 1.2
1661 # The RRD 1.2 documentation says you can identify font family
1662 # names but I never got that to work, but full font path worked
1663 @options = (
1664 '--font', "LEGEND:7:$libdir/DejaVuSansMono.ttf",
1665 '--font', "UNIT:7:$libdir/DejaVuSans.ttf",
1666 '--font', "AXIS:7:$libdir/DejaVuSans.ttf",
1667 );
1668 } else {
1669 # At least 1.3
1670 @options = (
1671 '--font', 'DEFAULT:0:DejaVuSans,DejaVu Sans,DejaVu LGC Sans,Bitstream Vera Sans',
1672 '--font', 'LEGEND:7:DejaVuSansMono,DejaVu Sans Mono,DejaVu LGC Sans Mono,Bitstream Vera Sans Mono,monospace',
1673 # Colors coordinated with CSS.
1674 '--color', 'BACK#F0F0F0', # Area around the graph
1675 '--color', 'FRAME#F0F0F0', # Line around legend spot
1676 '--color', 'CANVAS#FFFFFF', # Graph background, max contrast
1677 '--color', 'FONT#666666', # Some kind of gray
1678 '--color', 'AXIS#CFD6F8', # And axis like html boxes
1679 '--color', 'ARROW#CFD6F8', # And arrow, ditto.
1680 );
1681 }
1682
1683 if ($RRDs::VERSION >= 1.4) {
1684 # RRD 1.4 has border, adding it
1685 push @options, (
1686 '--border', '0',
1687 );
1688 }
1689
1690 return @options;
1691 };
1692
1693
1694 sub graph_by_minute {
1695 my $service = shift;
1696
1697 return (munin_get($service, "graph_period", "second") eq "minute");
1698 }
1699
1700 sub graph_by_hour {
1701 my $service = shift;
1702
1703 return (munin_get($service, "graph_period", "second") eq "hour");
1704 }
1705
1706 sub orig_to_cdef {
1707 my $service = shift;
1708 my $fieldname = shift;
1709 my $original_fieldname = shift || $fieldname;
1710
1711 return unless ref($service) eq "HASH";
1712
1713 if (defined $service->{$fieldname} && defined $service->{$fieldname}->{"cdef_name"}) {
1714 return orig_to_cdef($service, $service->{$fieldname}->{"cdef_name"}, $original_fieldname);
1715 }
1716 # For unknown reasons the sanitizing of fieldnames in the context of RRD field names is not
1717 # applied consistently (maybe it should not be applied at all).
1718 # Thus we need to apply it here in the same way, as it seems to be applied at other places.
1719 if (_sanitise_fieldname($original_fieldname) ne $original_fieldname) {
1720 return get_field_name(_sanitise_fieldname($fieldname));
1721 } else {
1722 return get_field_name($fieldname);
1723 }
1724 }
1725
1726 sub reset_cdef {
1727 my $service = shift;
1728 my $fieldname = shift;
1729 return unless ref($service) eq "HASH";
1730 if (defined $service->{$fieldname} && defined $service->{$fieldname}->{"cdef_name"}) {
1731 reset_cdef($service, $service->{$fieldname}->{"cdef_name"});
1732 delete $service->{$fieldname}->{"cdef_name"};
1733 }
1734 }
1735
1736 sub ends_with {
1737 my ($src, $searched) = @_;
1738
1739 my $is_ending = (substr($src, - length($searched)) eq $searched);
1740 return $is_ending;
1741 }
1742
1743
1744 sub skip_service {
1745 my $service = shift;
1746 my $fqn = munin_get_node_fqn($service);
1747
1748 # Skip if we've limited services with the omnipotent cli option only-fqn
1749 return 1 if ($only_fqn and ! ends_with($fqn, $only_fqn));
1750 DEBUG "[DEBUG] $fqn is in ($only_fqn)\n";
1751
1752 # Skip if we've limited services with cli options
1753 return 1
1754 if (@limit_services and
1755 ! (grep { ends_with($fqn, $_) } @limit_services));
1756
1757 DEBUG "[DEBUG] $fqn is in (" . join(",", @limit_services) . ")\n";
1758
1759 # Always graph if --force is present
1760 return 0 if $force_graphing;
1761
1762 # See if we should skip it because of conf-options
1763 return 1
1764 if (munin_get($service, "graph", "yes") eq "on-demand"
1765 or !munin_get_bool($service, "graph", 1));
1766
1767 # Don't skip
1768 return 0;
1769 }
1770
1771
1772 sub expand_cdef {
1773 my $service = shift;
1774 my $cfield_ref = shift;
1775 my $cdef = shift;
1776
1777 my $new_field = &get_field_name("cdef$$cfield_ref");
1778
1779 my ($max, $min, $avg) = (
1780 "CDEF:a$new_field=$cdef", "CDEF:i$new_field=$cdef",
1781 "CDEF:g$new_field=$cdef"
1782 );
1783
1784 foreach my $field (@{munin_find_field($service, "label")}) {
1785 my $fieldname = munin_get_node_name($field);
1786 my $rrdname = &orig_to_cdef($service, $fieldname);
1787 if ($cdef =~ /\b$fieldname\b/) {
1788 $max =~ s/(?<=[,=(])$fieldname(?=[,=)]|$)/a$rrdname/g;
1789 $min =~ s/(?<=[,=(])$fieldname(?=[,=)]|$)/i$rrdname/g;
1790 $avg =~ s/(?<=[,=(])$fieldname(?=[,=)]|$)/g$rrdname/g;
1791 }
1792 }
1793
1794 munin_set_var_loc($service, [$$cfield_ref, "cdef_name"], $new_field);
1795 $$cfield_ref = $new_field;
1796
1797 return ($max, $min, $avg);
1798 }
1799
1800 sub parse_path {
1801 my ($path, $domain, $node, $service, $field) = @_;
1802 my $filename = "unknown";
1803
1804 if ($path =~ /^\s*([^:]*):([^:]*):([^:]*):([^:]*)\s*$/) {
1805 $filename = munin_get_filename($config, $1, $2, $3, $4);
1806 }
1807 elsif ($path =~ /^\s*([^:]*):([^:]*):([^:]*)\s*$/) {
1808 $filename = munin_get_filename($config, $domain, $1, $2, $3);
1809 }
1810 elsif ($path =~ /^\s*([^:]*):([^:]*)\s*$/) {
1811 $filename = munin_get_filename($config, $domain, $node, $1, $2);
1812 }
1813 elsif ($path =~ /^\s*([^:]*)\s*$/) {
1814 $filename = munin_get_filename($config, $domain, $node, $service, $1);
1815 }
1816 return $filename;
1817 }
1818
1819 # Wrapper for munin_get_picture_filename to handle pinpoint
1820 sub get_picture_filename {
1821 my $of;
1822 if (defined $output_file) { $of=$output_file; goto exit_label; }
1823
1824 $of = munin_get_picture_filename(@_);
1825
1826 exit_label:
1827
1828 return $of;
1829 }
1830
1831 sub escape {
1832 my $text = shift;
1833 return if not defined $text;
1834 $text =~ s/\\/\\\\/g;
1835 $text =~ s/:/\\:/g;
1836 return $text;
1837 }
1838
1839 sub get_scientific {
1840 my $value = shift;
1841 $value =~ s/m/e-03/;
1842 $value =~ s/k/e+03/;
1843 $value =~ s/M/e+06/;
1844 $value =~ s/G/e+09/;
1845 return $value;
1846 }
1847
1848 sub RRDescape {
1849 my $text = shift;
1850 return $RRDs::VERSION < 1.2 ? $text : escape($text);
1851 }
1852
1853
1854 sub print_usage_and_exit {
1855 print "Usage: $0 [options]
1856
1857 Options:
1858 --[no]fork Do not fork. By default munin-graph forks sub
1859 processes for drawing graphs to utilize available
1860 cores and I/O bandwidth. [--fork]
1861 --n n Max number of concurrent processes [$max_running]
1862 --[no]force Force drawing of graphs that are not usually
1863 drawn due to options in the config file. [--noforce]
1864 --[no]lazy Only redraw graphs when needed. [--lazy]
1865 --help View this message.
1866 --version View version information.
1867 --debug View debug messages.
1868 --[no]cron Behave as expected when run from cron. (Used internally
1869 in Munin.)
1870 --host <host> Limit graphed hosts to <host>. Multiple --host options
1871 may be supplied.
1872 --only-fqn <FQN> For internal use with CGI graphing. Graph only a
1873 single fully qualified named graph, e.g. --only-fqn
1874 root/Backend/dafnes.example.com/diskstats_iops
1875 Always use with the correct --host option.
1876 --config <file> Use <file> as configuration file. [$conffile]
1877 --[no]list-images List the filenames of the images created.
1878 [--nolist-images]
1879 --output-file -o Output graph file. (used for CGI graphing)
1880 --log-file -l Output log file. (used for CGI graphing)
1881 --[no]day Create day-graphs. [--day]
1882 --[no]week Create week-graphs. [--week]
1883 --[no]month Create month-graphs. [--month]
1884 --[no]year Create year-graphs. [--year]
1885 --[no]sumweek Create summarised week-graphs. [--summweek]
1886 --[no]sumyear Create summarised year-graphs. [--sumyear]
1887 --pinpoint <start,stop> Create custom-graphs. <start,stop> is the standard unix Epoch. [not active]
1888 --size_x <pixels> Sets the X size of the graph in pixels [175]
1889 --size_y <pixels> Sets the Y size of the graph in pixels [400]
1890 --lower_limit <lim> Sets the lower limit of the graph
1891 --upper_limit <lim> Sets the upper limit of the graph
1892
1893 NOTE! --pinpoint and --only-fqn must not be combined with
1894 --[no]<day|week|month|year> options. The result of doing that is
1895 undefined.
1896
1897 ";
1898 exit 0;
1899 }
1900
1901 1;