1 <?php // vim:fenc=utf-8:filetype=php:ts=4
2 /*
3 * Copyright (C) 2009 Bruno Prémont <bonbons AT linux-vserver.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation; only version 2 of the License is applicable.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12 * details.
13 *
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
19 define('REGEXP_HOST', '/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/');
20 define('REGEXP_PLUGIN', '/^[a-zA-Z0-9_.-]+$/');
22 /**
23 * Read input variable from GET, POST or COOKIE taking
24 * care of magic quotes
25 * @name Name of value to return
26 * @array User-input array ($_GET, $_POST or $_COOKIE)
27 * @default Default value
28 * @return $default if name in unknown in $array, otherwise
29 * input value with magic quotes stripped off
30 */
31 function read_var($name, &$array, $default = null) {
32 if (isset($array[$name])) {
33 if (is_array($array[$name])) {
34 if (get_magic_quotes_gpc()) {
35 $ret = array();
36 while (list($k, $v) = each($array[$name]))
37 $ret[stripslashes($k)] = stripslashes($v);
38 return $ret;
39 } else
40 return $array[$name];
41 } else if (is_string($array[$name]) && get_magic_quotes_gpc()) {
42 return stripslashes($array[$name]);
43 } else
44 return $array[$name];
45 } else
46 return $default;
47 }
49 /**
50 * Alphabetically compare host names, comparing label
51 * from tld to node name
52 */
53 function collectd_compare_host($a, $b) {
54 $ea = explode('.', $a);
55 $eb = explode('.', $b);
56 $i = count($ea) - 1;
57 $j = count($eb) - 1;
58 while ($i >= 0 && $j >= 0)
59 if (($r = strcmp($ea[$i--], $eb[$j--])) != 0)
60 return $r;
61 return 0;
62 }
64 /**
65 * Fetch list of hosts found in collectd's datadirs.
66 * @return Sorted list of hosts (sorted by label from rigth to left)
67 */
68 function collectd_list_hosts() {
69 global $config;
71 $hosts = array();
72 foreach($config['datadirs'] as $datadir)
73 if ($d = @opendir($datadir)) {
74 while (($dent = readdir($d)) !== false)
75 if ($dent != '.' && $dent != '..' && is_dir($datadir.'/'.$dent) && preg_match(REGEXP_HOST, $dent))
76 $hosts[] = $dent;
77 closedir($d);
78 } else
79 error_log('Failed to open datadir: '.$datadir);
80 $hosts = array_unique($hosts);
81 usort($hosts, 'collectd_compare_host');
82 return $hosts;
83 }
85 /**
86 * Fetch list of plugins found in collectd's datadirs for given host.
87 * @arg_host Name of host for which to return plugins
88 * @return Sorted list of plugins (sorted alphabetically)
89 */
90 function collectd_list_plugins($arg_host) {
91 global $config;
93 $plugins = array();
94 foreach ($config['datadirs'] as $datadir)
95 if (preg_match(REGEXP_HOST, $arg_host) && ($d = @opendir($datadir.'/'.$arg_host))) {
96 while (($dent = readdir($d)) !== false)
97 if ($dent != '.' && $dent != '..' && is_dir($datadir.'/'.$arg_host.'/'.$dent)) {
98 if ($i = strpos($dent, '-'))
99 $plugins[] = substr($dent, 0, $i);
100 else
101 $plugins[] = $dent;
102 }
103 closedir($d);
104 }
105 $plugins = array_unique($plugins);
106 sort($plugins);
107 return $plugins;
108 }
110 /**
111 * Fetch list of plugin instances found in collectd's datadirs for given host+plugin
112 * @arg_host Name of host
113 * @arg_plugin Name of plugin
114 * @return Sorted list of plugin instances (sorted alphabetically)
115 */
116 function collectd_list_pinsts($arg_host, $arg_plugin) {
117 global $config;
119 $pinsts = array();
120 foreach ($config['datadirs'] as $datadir)
121 if (preg_match(REGEXP_HOST, $arg_host) && ($d = opendir($datadir.'/'.$arg_host))) {
122 while (($dent = readdir($d)) !== false)
123 if ($dent != '.' && $dent != '..' && is_dir($datadir.'/'.$arg_host.'/'.$dent)) {
124 if ($i = strpos($dent, '-')) {
125 $plugin = substr($dent, 0, $i);
126 $pinst = substr($dent, $i+1);
127 } else {
128 $plugin = $dent;
129 $pinst = '';
130 }
131 if ($plugin == $arg_plugin)
132 $pinsts[] = $pinst;
133 }
134 closedir($d);
135 }
136 $pinsts = array_unique($pinsts);
137 sort($pinsts);
138 return $pinsts;
139 }
141 /**
142 * Fetch list of types found in collectd's datadirs for given host+plugin+instance
143 * @arg_host Name of host
144 * @arg_plugin Name of plugin
145 * @arg_pinst Plugin instance
146 * @return Sorted list of types (sorted alphabetically)
147 */
148 function collectd_list_types($arg_host, $arg_plugin, $arg_pinst) {
149 global $config;
151 $types = array();
152 $my_plugin = $arg_plugin . (strlen($arg_pinst) ? '-'.$arg_pinst : '');
153 if (!preg_match(REGEXP_PLUGIN, $my_plugin))
154 return $types;
155 foreach ($config['datadirs'] as $datadir)
156 if (preg_match(REGEXP_HOST, $arg_host) && ($d = @opendir($datadir.'/'.$arg_host.'/'.$my_plugin))) {
157 while (($dent = readdir($d)) !== false)
158 if ($dent != '.' && $dent != '..' && is_file($datadir.'/'.$arg_host.'/'.$my_plugin.'/'.$dent) && substr($dent, strlen($dent)-4) == '.rrd') {
159 $dent = substr($dent, 0, strlen($dent)-4);
160 if ($i = strpos($dent, '-'))
161 $types[] = substr($dent, 0, $i);
162 else
163 $types[] = $dent;
164 }
165 closedir($d);
166 }
167 $types = array_unique($types);
168 sort($types);
169 return $types;
170 }
172 /**
173 * Fetch list of type instances found in collectd's datadirs for given host+plugin+instance+type
174 * @arg_host Name of host
175 * @arg_plugin Name of plugin
176 * @arg_pinst Plugin instance
177 * @arg_type Type
178 * @return Sorted list of type instances (sorted alphabetically)
179 */
180 function collectd_list_tinsts($arg_host, $arg_plugin, $arg_pinst, $arg_type) {
181 global $config;
183 $tinsts = array();
184 $my_plugin = $arg_plugin . (strlen($arg_pinst) ? '-'.$arg_pinst : '');
185 if (!preg_match(REGEXP_PLUGIN, $my_plugin))
186 return $types;
187 foreach ($config['datadirs'] as $datadir)
188 if (preg_match(REGEXP_HOST, $arg_host) && ($d = @opendir($datadir.'/'.$arg_host.'/'.$my_plugin))) {
189 while (($dent = readdir($d)) !== false)
190 if ($dent != '.' && $dent != '..' && is_file($datadir.'/'.$arg_host.'/'.$my_plugin.'/'.$dent) && substr($dent, strlen($dent)-4) == '.rrd') {
191 $dent = substr($dent, 0, strlen($dent)-4);
192 if ($i = strpos($dent, '-')) {
193 $type = substr($dent, 0, $i);
194 $tinst = substr($dent, $i+1);
195 } else {
196 $type = $dent;
197 $tinst = '';
198 }
199 if ($type == $arg_type)
200 $tinsts[] = $tinst;
201 }
202 closedir($d);
203 }
204 $tinsts = array_unique($tinsts);
205 sort($tinsts);
206 return $tinsts;
207 }
209 /**
210 * Parse symlinks in order to get an identifier that collectd understands
211 * (e.g. virtualisation is collected on host for individual VMs and can be
212 * symlinked to the VM's hostname, support FLUSH for these by flushing
213 * on the host-identifier instead of VM-identifier)
214 * @host Host name
215 * @plugin Plugin name
216 * @pinst Plugin instance
217 * @type Type name
218 * @tinst Type instance
219 * @return Identifier that collectd's FLUSH command understands
220 */
221 function collectd_identifier($host, $plugin, $pinst, $type, $tinst) {
222 global $config;
223 $rrd_realpath = null;
224 $orig_identifier = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, strlen($pinst) ? '-' : '', $pinst, $type, strlen($tinst) ? '-' : '', $tinst);
225 $identifier = null;
226 foreach ($config['datadirs'] as $datadir)
227 if (is_file($datadir.'/'.$orig_identifier.'.rrd')) {
228 $rrd_realpath = realpath($datadir.'/'.$orig_identifier.'.rrd');
229 break;
230 }
231 if ($rrd_realpath) {
232 $identifier = basename($rrd_realpath);
233 $identifier = substr($identifier, 0, strlen($identifier)-4);
234 $rrd_realpath = dirname($rrd_realpath);
235 $identifier = basename($rrd_realpath).'/'.$identifier;
236 $rrd_realpath = dirname($rrd_realpath);
237 $identifier = basename($rrd_realpath).'/'.$identifier;
238 }
240 if (is_null($identifier))
241 return $orig_identifier;
242 else
243 return $identifier;
244 }
246 /**
247 * Tell collectd that it should FLUSH all data it has regarding the
248 * graph we are about to generate.
249 * @host Host name
250 * @plugin Plugin name
251 * @pinst Plugin instance
252 * @type Type name
253 * @tinst Type instance
254 */
255 function collectd_flush($identifier) {
256 global $config;
258 if (!$config['collectd_sock'])
259 return false;
260 if (is_null($identifier) || (is_array($identifier) && count($identifier) == 0) || !(is_string($identifier) || is_array($identifier)))
261 return false;
263 $u_errno = 0;
264 $u_errmsg = '';
265 if ($socket = @fsockopen($config['collectd_sock'], 0, $u_errno, $u_errmsg)) {
266 $cmd = 'FLUSH plugin=rrdtool';
267 if (is_array($identifier)) {
268 foreach ($identifier as $val)
269 $cmd .= sprintf(' identifier="%s"', $val);
270 } else
271 $cmd .= sprintf(' identifier="%s"', $identifier);
272 $cmd .= "\n";
274 $r = fwrite($socket, $cmd, strlen($cmd));
275 if ($r === false || $r != strlen($cmd))
276 error_log(sprintf("graph.php: Failed to write whole command to unix-socket: %d out of %d written", $r === false ? -1 : $r, strlen($cmd)));
278 $resp = fgets($socket);
279 if ($resp === false)
280 error_log(sprintf("graph.php: Failed to read response from collectd for command: %s", trim($cmd)));
282 $n = (int)$resp;
283 while ($n-- > 0)
284 fgets($socket);
286 fclose($socket);
287 } else
288 error_log(sprintf("graph.php: Failed to open unix-socket to collectd: %d: %s", $u_errno, $u_errmsg));
289 }
291 class CollectdColor {
292 private $r = 0;
293 private $g = 0;
294 private $b = 0;
296 function __construct($value = null) {
297 if (is_null($value)) {
298 } else if (is_array($value)) {
299 if (isset($value['r']))
300 $this->r = $value['r'] > 0 ? ($value['r'] > 1 ? 1 : $value['r']) : 0;
301 if (isset($value['g']))
302 $this->g = $value['g'] > 0 ? ($value['g'] > 1 ? 1 : $value['g']) : 0;
303 if (isset($value['b']))
304 $this->b = $value['b'] > 0 ? ($value['b'] > 1 ? 1 : $value['b']) : 0;
305 } else if (is_string($value)) {
306 $matches = array();
307 if ($value == 'random') {
308 $this->randomize();
309 } else if (preg_match('/([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])/', $value, $matches)) {
310 $this->r = ('0x'.$matches[1]) / 255.0;
311 $this->g = ('0x'.$matches[2]) / 255.0;
312 $this->b = ('0x'.$matches[3]) / 255.0;
313 }
314 } else if (is_a($value, 'CollectdColor')) {
315 $this->r = $value->r;
316 $this->g = $value->g;
317 $this->b = $value->b;
318 }
319 }
321 function randomize() {
322 $this->r = rand(0, 255) / 255.0;
323 $this->g = rand(0, 255) / 255.0;
324 $this->b = 0.0;
325 $min = 0.0;
326 $max = 1.0;
328 if (($this->r + $this->g) < 1.0) {
329 $min = 1.0 - ($this->r + $this->g);
330 } else {
331 $max = 2.0 - ($this->r + $this->g);
332 }
333 $this->b = $min + ((rand(0, 255)/255.0) * ($max - $min));
334 }
336 function fade($bkgnd = null, $alpha = 0.25) {
337 if (is_null($bkgnd) || !is_a($bkgnd, 'CollectdColor')) {
338 $bg_r = 1.0;
339 $bg_g = 1.0;
340 $bg_b = 1.0;
341 } else {
342 $bg_r = $bkgnd->r;
343 $bg_g = $bkgnd->g;
344 $bg_b = $bkgnd->b;
345 }
347 $this->r = $alpha * $this->r + ((1.0 - $alpha) * $bg_r);
348 $this->g = $alpha * $this->g + ((1.0 - $alpha) * $bg_g);
349 $this->b = $alpha * $this->b + ((1.0 - $alpha) * $bg_b);
350 }
352 function as_array() {
353 return array('r'=>$this->r, 'g'=>$this->g, 'b'=>$this->b);
354 }
356 function as_string() {
357 $r = (int)($this->r*255);
358 $g = (int)($this->g*255);
359 $b = (int)($this->b*255);
360 return sprintf('%02x%02x%02x', $r > 255 ? 255 : $r, $g > 255 ? 255 : $g, $b > 255 ? 255 : $b);
361 }
362 }
365 /**
366 * Helper function to strip quotes from RRD output
367 * @str RRD-Info generated string
368 * @return String with one surrounding pair of quotes stripped
369 */
370 function rrd_strip_quotes($str) {
371 if ($str[0] == '"' && $str[strlen($str)-1] == '"')
372 return substr($str, 1, strlen($str)-2);
373 else
374 return $str;
375 }
377 function rrd_escape($str) {
378 return str_replace(array('\\', ':'), array('\\\\', '\\:'), $str);
379 }
381 /**
382 * Determine useful information about RRD file
383 * @file Name of RRD file to analyse
384 * @return Array describing the RRD file
385 */
386 function rrd_info($file) {
387 $info = array('filename'=>$file);
389 $rrd = popen(RRDTOOL.' info '.escapeshellarg($file), 'r');
390 if ($rrd) {
391 while (($s = fgets($rrd)) !== false) {
392 $p = strpos($s, '=');
393 if ($p === false)
394 continue;
395 $key = trim(substr($s, 0, $p));
396 $value = trim(substr($s, $p+1));
397 if (strncmp($key,'ds[', 3) == 0) {
398 /* DS definition */
399 $p = strpos($key, ']');
400 $ds = substr($key, 3, $p-3);
401 if (!isset($info['DS']))
402 $info['DS'] = array();
403 $ds_key = substr($key, $p+2);
405 if (strpos($ds_key, '[') === false) {
406 if (!isset($info['DS']["$ds"]))
407 $info['DS']["$ds"] = array();
408 $info['DS']["$ds"]["$ds_key"] = rrd_strip_quotes($value);
409 }
410 } else if (strncmp($key, 'rra[', 4) == 0) {
411 /* RRD definition */
412 $p = strpos($key, ']');
413 $rra = substr($key, 4, $p-4);
414 if (!isset($info['RRA']))
415 $info['RRA'] = array();
416 $rra_key = substr($key, $p+2);
418 if (strpos($rra_key, '[') === false) {
419 if (!isset($info['RRA']["$rra"]))
420 $info['RRA']["$rra"] = array();
421 $info['RRA']["$rra"]["$rra_key"] = rrd_strip_quotes($value);
422 }
423 } else if (strpos($key, '[') === false) {
424 $info[$key] = rrd_strip_quotes($value);
425 }
426 }
427 pclose($rrd);
428 }
429 return $info;
430 }
432 function rrd_get_color($code, $line = true) {
433 global $config;
434 $name = ($line ? 'f_' : 'h_').$code;
435 if (!isset($config['rrd_colors'][$name])) {
436 $c_f = new CollectdColor('random');
437 $c_h = new CollectdColor($c_f);
438 $c_h->fade();
439 $config['rrd_colors']['f_'.$code] = $c_f->as_string();
440 $config['rrd_colors']['h_'.$code] = $c_h->as_string();
441 }
442 return $config['rrd_colors'][$name];
443 }
445 /**
446 * Draw RRD file based on it's structure
447 * @host
448 * @plugin
449 * @pinst
450 * @type
451 * @tinst
452 * @opts
453 * @return Commandline to call RRDGraph in order to generate the final graph
454 */
455 function collectd_draw_rrd($host, $plugin, $pinst = null, $type, $tinst = null, $opts = array()) {
456 global $config;
457 $timespan_def = null;
458 if (!isset($opts['timespan']))
459 $timespan_def = reset($config['timespan']);
460 else foreach ($config['timespan'] as &$ts)
461 if ($ts['name'] == $opts['timespan'])
462 $timespan_def = $ts;
464 if (!isset($opts['rrd_opts']))
465 $opts['rrd_opts'] = array();
466 if (isset($opts['logarithmic']) && $opts['logarithmic'])
467 array_unshift($opts['rrd_opts'], '-o');
469 $rrdinfo = null;
470 $rrdfile = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, is_null($pinst) ? '' : '-', $pinst, $type, is_null($tinst) ? '' : '-', $tinst);
471 foreach ($config['datadirs'] as $datadir)
472 if (is_file($datadir.'/'.$rrdfile.'.rrd')) {
473 $rrdinfo = rrd_info($datadir.'/'.$rrdfile.'.rrd');
474 if (isset($rrdinfo['RRA']) && is_array($rrdinfo['RRA']))
475 break;
476 else
477 $rrdinfo = null;
478 }
480 if (is_null($rrdinfo))
481 return false;
483 $graph = array();
484 $has_avg = false;
485 $has_max = false;
486 $has_min = false;
487 reset($rrdinfo['RRA']);
488 $l_max = 0;
489 while (list($k, $v) = each($rrdinfo['RRA'])) {
490 if ($v['cf'] == 'MAX')
491 $has_max = true;
492 else if ($v['cf'] == 'AVERAGE')
493 $has_avg = true;
494 else if ($v['cf'] == 'MIN')
495 $has_min = true;
496 }
497 reset($rrdinfo['DS']);
498 while (list($k, $v) = each($rrdinfo['DS'])) {
499 if (strlen($k) > $l_max)
500 $l_max = strlen($k);
501 if ($has_min)
502 $graph[] = sprintf('DEF:%s_min=%s:%s:MIN', $k, rrd_escape($rrdinfo['filename']), $k);
503 if ($has_avg)
504 $graph[] = sprintf('DEF:%s_avg=%s:%s:AVERAGE', $k, rrd_escape($rrdinfo['filename']), $k);
505 if ($has_max)
506 $graph[] = sprintf('DEF:%s_max=%s:%s:MAX', $k, rrd_escape($rrdinfo['filename']), $k);
507 }
508 if ($has_min && $has_max || $has_min && $has_avg || $has_avg && $has_max) {
509 $n = 1;
510 reset($rrdinfo['DS']);
511 while (list($k, $v) = each($rrdinfo['DS'])) {
512 $graph[] = sprintf('LINE:%s_%s', $k, $has_min ? 'min' : 'avg');
513 $graph[] = sprintf('CDEF:%s_var=%s_%s,%s_%s,-', $k, $k, $has_max ? 'max' : 'avg', $k, $has_min ? 'min' : 'avg');
514 $graph[] = sprintf('AREA:%s_var#%s::STACK', $k, rrd_get_color($n++, false));
515 }
516 }
518 reset($rrdinfo['DS']);
519 $n = 1;
520 while (list($k, $v) = each($rrdinfo['DS'])) {
521 $graph[] = sprintf('LINE1:%s_avg#%s:%s ', $k, rrd_get_color($n++, true), $k.substr(' ', 0, $l_max-strlen($k)));
522 if (isset($opts['tinylegend']) && $opts['tinylegend'])
523 continue;
524 if ($has_avg)
525 $graph[] = sprintf('GPRINT:%s_avg:AVERAGE:%%5.1lf%%s Avg%s', $k, $has_max || $has_min || $has_avg ? ',' : "\\l");
526 if ($has_min)
527 $graph[] = sprintf('GPRINT:%s_min:MIN:%%5.1lf%%s Max%s', $k, $has_max || $has_avg ? ',' : "\\l");
528 if ($has_max)
529 $graph[] = sprintf('GPRINT:%s_max:MAX:%%5.1lf%%s Max%s', $k, $has_avg ? ',' : "\\l");
530 if ($has_avg)
531 $graph[] = sprintf('GPRINT:%s_avg:LAST:%%5.1lf%%s Last\\l', $k);
532 }
534 $rrd_cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $rrdfile);
535 $rrd_cmd = array_merge($rrd_cmd, $config['rrd_opts'], $opts['rrd_opts'], $graph);
537 $cmd = RRDTOOL;
538 for ($i = 1; $i < count($rrd_cmd); $i++)
539 $cmd .= ' '.escapeshellarg($rrd_cmd[$i]);
541 return $cmd;
542 }
544 /**
545 * Draw RRD file based on it's structure
546 * @timespan
547 * @host
548 * @plugin
549 * @pinst
550 * @type
551 * @tinst
552 * @opts
553 * @return Commandline to call RRDGraph in order to generate the final graph
554 */
555 function collectd_draw_generic($timespan, $host, $plugin, $pinst = null, $type, $tinst = null) {
556 global $config, $GraphDefs;
557 $timespan_def = null;
558 foreach ($config['timespan'] as &$ts)
559 if ($ts['name'] == $timespan)
560 $timespan_def = $ts;
561 if (is_null($timespan_def))
562 $timespan_def = reset($config['timespan']);
564 if (!isset($GraphDefs[$type]))
565 return false;
567 $rrd_file = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, is_null($pinst) ? '' : '-', $pinst, $type, is_null($tinst) ? '' : '-', $tinst);
568 $rrd_cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $rrd_file);
569 $rrd_cmd = array_merge($rrd_cmd, $config['rrd_opts']);
570 $rrd_args = $GraphDefs[$type];
572 foreach ($config['datadirs'] as $datadir) {
573 $file = $datadir.'/'.$rrd_file.'.rrd';
574 if (!is_file($file))
575 continue;
577 $file = str_replace(":", "\\:", $file);
578 $rrd_args = str_replace('{file}', rrd_escape($file), $rrd_args);
580 $rrdgraph = array_merge($rrd_cmd, $rrd_args);
581 $cmd = RRDTOOL;
582 for ($i = 1; $i < count($rrdgraph); $i++)
583 $cmd .= ' '.escapeshellarg($rrdgraph[$i]);
585 return $cmd;
586 }
587 return false;
588 }
590 /**
591 * Draw stack-graph for set of RRD files
592 * @opts Graph options like colors
593 * @sources List of array(name, file, ds)
594 * @return Commandline to call RRDGraph in order to generate the final graph
595 */
596 function collectd_draw_meta_stack(&$opts, &$sources) {
597 global $config;
598 $timespan_def = null;
599 if (!isset($opts['timespan']))
600 $timespan_def = reset($config['timespan']);
601 else foreach ($config['timespan'] as &$ts)
602 if ($ts['name'] == $opts['timespan'])
603 $timespan_def = $ts;
605 if (!isset($opts['title']))
606 $opts['title'] = 'Unknown title';
607 if (!isset($opts['rrd_opts']))
608 $opts['rrd_opts'] = array();
609 if (!isset($opts['colors']))
610 $opts['colors'] = array();
611 if (isset($opts['logarithmic']) && $opts['logarithmic'])
612 array_unshift($opts['rrd_opts'], '-o');
614 $cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $opts['title']);
615 $cmd = array_merge($cmd, $config['rrd_opts'], $opts['rrd_opts']);
616 $max_inst_name = 0;
618 foreach($sources as &$inst_data) {
619 $inst_name = str_replace('!', '_', $inst_data['name']);
620 $file = $inst_data['file'];
621 $ds = isset($inst_data['ds']) ? $inst_data['ds'] : 'value';
623 if (strlen($inst_name) > $max_inst_name)
624 $max_inst_name = strlen($inst_name);
626 if (!is_file($file))
627 continue;
629 $cmd[] = 'DEF:'.$inst_name.'_min='.rrd_escape($file).':'.$ds.':MIN';
630 $cmd[] = 'DEF:'.$inst_name.'_avg='.rrd_escape($file).':'.$ds.':AVERAGE';
631 $cmd[] = 'DEF:'.$inst_name.'_max='.rrd_escape($file).':'.$ds.':MAX';
632 $cmd[] = 'CDEF:'.$inst_name.'_nnl='.$inst_name.'_avg,UN,0,'.$inst_name.'_avg,IF';
633 }
634 $inst_data = end($sources);
635 $inst_name = $inst_data['name'];
636 $cmd[] = 'CDEF:'.$inst_name.'_stk='.$inst_name.'_nnl';
638 $inst_data1 = end($sources);
639 while (($inst_data0 = prev($sources)) !== false) {
640 $inst_name0 = str_replace('!', '_', $inst_data0['name']);
641 $inst_name1 = str_replace('!', '_', $inst_data1['name']);
643 $cmd[] = 'CDEF:'.$inst_name0.'_stk='.$inst_name0.'_nnl,'.$inst_name1.'_stk,+';
644 $inst_data1 = $inst_data0;
645 }
647 foreach($sources as &$inst_data) {
648 $inst_name = str_replace('!', '_', $inst_data['name']);
649 $legend = sprintf('%s', $inst_data['name']);
650 while (strlen($legend) < $max_inst_name)
651 $legend .= ' ';
652 $number_format = isset($opts['number_format']) ? $opts['number_format'] : '%6.1lf';
654 if (isset($opts['colors'][$inst_name]))
655 $line_color = new CollectdColor($opts['colors'][$inst_name]);
656 else
657 $line_color = new CollectdColor('random');
658 $area_color = new CollectdColor($line_color);
659 $area_color->fade();
661 $cmd[] = 'AREA:'.$inst_name.'_stk#'.$area_color->as_string();
662 $cmd[] = 'LINE1:'.$inst_name.'_stk#'.$line_color->as_string().':'.$legend;
663 if (!(isset($opts['tinylegend']) && $opts['tinylegend'])) {
664 $cmd[] = 'GPRINT:'.$inst_name.'_min:MIN:'.$number_format.' Min,';
665 $cmd[] = 'GPRINT:'.$inst_name.'_avg:AVERAGE:'.$number_format.' Avg,';
666 $cmd[] = 'GPRINT:'.$inst_name.'_max:MAX:'.$number_format.' Max,';
667 $cmd[] = 'GPRINT:'.$inst_name.'_avg:LAST:'.$number_format.' Last\\l';
668 }
669 }
671 $rrdcmd = RRDTOOL;
672 for ($i = 1; $i < count($cmd); $i++)
673 $rrdcmd .= ' '.escapeshellarg($cmd[$i]);
674 return $rrdcmd;
675 }
677 /**
678 * Draw stack-graph for set of RRD files
679 * @opts Graph options like colors
680 * @sources List of array(name, file, ds)
681 * @return Commandline to call RRDGraph in order to generate the final graph
682 */
683 function collectd_draw_meta_line(&$opts, &$sources) {
684 global $config;
685 $timespan_def = null;
686 if (!isset($opts['timespan']))
687 $timespan_def = reset($config['timespan']);
688 else foreach ($config['timespan'] as &$ts)
689 if ($ts['name'] == $opts['timespan'])
690 $timespan_def = $ts;
692 if (!isset($opts['title']))
693 $opts['title'] = 'Unknown title';
694 if (!isset($opts['rrd_opts']))
695 $opts['rrd_opts'] = array();
696 if (!isset($opts['colors']))
697 $opts['colors'] = array();
698 if (isset($opts['logarithmic']) && $opts['logarithmic'])
699 array_unshift($opts['rrd_opts'], '-o');
701 $cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $opts['title']);
702 $cmd = array_merge($cmd, $config['rrd_opts'], $opts['rrd_opts']);
703 $max_inst_name = 0;
705 foreach ($sources as &$inst_data) {
706 $inst_name = str_replace('!', '_', $inst_data['name']);
707 $file = $inst_data['file'];
708 $ds = isset($inst_data['ds']) ? $inst_data['ds'] : 'value';
710 if (strlen($inst_name) > $max_inst_name)
711 $max_inst_name = strlen($inst_name);
713 if (!is_file($file))
714 continue;
716 $cmd[] = 'DEF:'.$inst_name.'_min='.rrd_escape($file).':'.$ds.':MIN';
717 $cmd[] = 'DEF:'.$inst_name.'_avg='.rrd_escape($file).':'.$ds.':AVERAGE';
718 $cmd[] = 'DEF:'.$inst_name.'_max='.rrd_escape($file).':'.$ds.':MAX';
719 }
721 foreach ($sources as &$inst_data) {
722 $inst_name = str_replace('!', '_', $inst_data['name']);
723 $legend = sprintf('%s', $inst_name);
724 while (strlen($legend) < $max_inst_name)
725 $legend .= ' ';
726 $number_format = isset($opts['number_format']) ? $opts['number_format'] : '%6.1lf';
728 if (isset($opts['colors'][$inst_name]))
729 $line_color = new CollectdColor($opts['colors'][$inst_name]);
730 else
731 $line_color = new CollectdColor('random');
733 $cmd[] = 'LINE1:'.$inst_name.'_avg#'.$line_color->as_string().':'.$legend;
734 if (!(isset($opts['tinylegend']) && $opts['tinylegend'])) {
735 $cmd[] = 'GPRINT:'.$inst_name.'_min:MIN:'.$number_format.' Min,';
736 $cmd[] = 'GPRINT:'.$inst_name.'_avg:AVERAGE:'.$number_format.' Avg,';
737 $cmd[] = 'GPRINT:'.$inst_name.'_max:MAX:'.$number_format.' Max,';
738 $cmd[] = 'GPRINT:'.$inst_name.'_avg:LAST:'.$number_format.' Last\\l';
739 }
740 }
742 $rrdcmd = RRDTOOL;
743 for ($i = 1; $i < count($cmd); $i++)
744 $rrdcmd .= ' '.escapeshellarg($cmd[$i]);
745 return $rrdcmd;
746 }
748 ?>