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 function collectd_walk(&$options) {
65 global $config;
67 foreach($config['datadirs'] as $datadir)
68 if ($dh = @opendir($datadir)) {
69 while (($hdent = readdir($dh)) !== false) {
70 if ($hdent == '.' || $hdent == '..' || !is_dir($datadir.'/'.$hdent))
71 continue;
72 if (!preg_match(REGEXP_HOST, $hdent))
73 continue;
74 if (isset($options['cb_host']) && ($options['cb_host'] === false || !$options['cb_host']($options, $hdent)))
75 continue;
77 if ($dp = @opendir($datadir.'/'.$hdent)) {
78 while (($pdent = readdir($dp)) !== false) {
79 if ($pdent == '.' || $pdent == '..' || !is_dir($datadir.'/'.$hdent.'/'.$pdent))
80 continue;
81 if ($i = strpos($pdent, '-')) {
82 $plugin = substr($pdent, 0, $i);
83 $pinst = substr($pdent, $i+1);
84 } else {
85 $plugin = $pdent;
86 $pinst = '';
87 }
88 if (isset($options['cb_plugin']) && ($options['cb_plugin'] === false || !$options['cb_plugin']($options, $hdent, $plugin)))
89 continue;
90 if (isset($options['cb_pinst']) && ($options['cb_pinst'] === false || !$options['cb_pinst']($options, $hdent, $plugin, $pinst)))
91 continue;
93 if ($dt = @opendir($datadir.'/'.$hdent.'/'.$pdent)) {
94 while (($tdent = readdir($dt)) !== false) {
95 if ($tdent == '.' || $tdent == '..' || !is_file($datadir.'/'.$hdent.'/'.$pdent.'/'.$tdent))
96 continue;
97 if (substr($tdent, strlen($tdent)-4) != '.rrd')
98 continue;
99 $tdent = substr($tdent, 0, strlen($tdent)-4);
100 if ($i = strpos($tdent, '-')) {
101 $type = substr($tdent, 0, $i);
102 $tinst = substr($tdent, $i+1);
103 } else {
104 $type = $tdent;
105 $tinst = '';
106 }
107 if (isset($options['cb_type']) && ($options['cb_type'] === false || !$options['cb_type']($options, $hdent, $plugin, $pinst, $type)))
108 continue;
109 if (isset($options['cb_tinst']) && ($options['cb_tinst'] === false || !$options['cb_tinst']($options, $hdent, $plugin, $pinst, $type, $tinst)))
110 continue;
111 }
112 closedir($dt);
113 }
114 }
115 closedir($dp);
116 }
117 }
118 closedir($dh);
119 } else
120 error_log('Failed to open datadir: '.$datadir);
121 return true;
122 }
124 function _collectd_list_cb_host(&$options, $host) {
125 if ($options['cb_plugin'] === false) {
126 $options['result'][] = $host;
127 return false;
128 } else if (isset($options['filter_host'])) {
129 if ($options['filter_host'] == '@all') {
130 return true; // We take anything
131 } else if (substr($options['filter_host'], 0, 2) == '@.') {
132 if ($host == substr($options['filter_host'], 2) || substr($host, 0, 1-strlen($options['filter_host'])) == substr($options['filter_host'], 1))
133 return true; // Host part of domain
134 else
135 return false;
136 } else if ($options['filter_host'] == $host) {
137 return true;
138 } else
139 return false;
140 } else
141 return true;
142 }
144 function _collectd_list_cb_plugin(&$options, $host, $plugin) {
145 if ($options['cb_pinst'] === false) {
146 $options['result'][] = $plugin;
147 return false;
148 } else if (isset($options['filter_plugin'])) {
149 if ($options['filter_plugin'] == '@all')
150 return true;
151 else if ($options['filter_plugin'] == $plugin)
152 return true;
153 else
154 return false;
155 } else
156 return true;
157 }
159 function _collectd_list_cb_pinst(&$options, $host, $plugin, $pinst) {
160 if ($options['cb_type'] === false) {
161 $options['result'][] = $pinst;
162 return false;
163 } else if (isset($options['filter_pinst'])) {
164 if ($options['filter_pinst'] == '@all')
165 return true;
166 else if (strncmp($options['filter_pinst'], '@merge_', 7) == 0)
167 return true;
168 else if ($options['filter_pinst'] == $pinst)
169 return true;
170 else
171 return false;
172 } else
173 return true;
174 }
176 function _collectd_list_cb_type(&$options, $host, $plugin, $pinst, $type) {
177 if ($options['cb_tinst'] === false) {
178 $options['result'][] = $type;
179 return false;
180 } else if (isset($options['filter_type'])) {
181 if ($options['filter_type'] == '@all')
182 return true;
183 else if ($options['filter_type'] == $type)
184 return true;
185 else
186 return false;
187 } else
188 return true;
189 }
191 function _collectd_list_cb_tinst(&$options, $host, $plugin, $pinst, $type, $tinst) {
192 $options['result'][] = $tinst;
193 return false;
194 }
196 function _collectd_list_cb_graph(&$options, $host, $plugin, $pinst, $type, $tinst) {
197 if (isset($options['filter_tinst'])) {
198 if ($options['filter_tinst'] == '@all') {
199 } else if ($options['filter_tinst'] == $tinst) {
200 } else if (strncmp($options['filter_tinst'], '@merge', 6) == 0) {
201 // Need to exclude @merge with non-existent meta graph
202 } else
203 return false;
204 }
205 if (isset($options['filter_pinst']) && strncmp($options['filter_pinst'], '@merge', 6) == 0)
206 $pinst = $options['filter_pinst'];
207 if (isset($options['filter_tinst']) && strncmp($options['filter_tinst'], '@merge', 6) == 0)
208 $tinst = $options['filter_tinst'];
209 $ident = collectd_identifier($host, $plugin, $pinst, $type, $tinst);
210 if (!in_array($ident, $options['ridentifiers'])) {
211 $options['ridentifiers'][] = $ident;
212 $options['result'][] = array('host'=>$host, 'plugin'=>$plugin, 'pinst'=>$pinst, 'type'=>$type, 'tinst'=>$tinst);
213 }
214 }
216 /**
217 * Fetch list of hosts found in collectd's datadirs.
218 * @return Sorted list of hosts (sorted by label from rigth to left)
219 */
220 function collectd_list_hosts() {
221 $options = array(
222 'result' => array(),
223 'cb_host' => '_collectd_list_cb_host',
224 'cb_plugin' => false,
225 'cb_pinst' => false,
226 'cb_type' => false,
227 'cb_tinst' => false
228 );
229 collectd_walk($options);
230 $hosts = array_unique($options['result']);
231 usort($hosts, 'collectd_compare_host');
232 return $hosts;
233 }
235 /**
236 * Fetch list of plugins found in collectd's datadirs for given host.
237 * @arg_host Name of host for which to return plugins
238 * @return Sorted list of plugins (sorted alphabetically)
239 */
240 function collectd_list_plugins($arg_host, $arg_plugin = null) {
241 $options = array(
242 'result' => array(),
243 'cb_host' => '_collectd_list_cb_host',
244 'cb_plugin' => '_collectd_list_cb_plugin',
245 'cb_pinst' => is_null($arg_plugin) ? false : '_collectd_list_cb_pinst',
246 'cb_type' => false,
247 'cb_tinst' => false,
248 'filter_host' => $arg_host,
249 'filter_plugin' => $arg_plugin
250 );
251 collectd_walk($options);
252 $plugins = array_unique($options['result']);
253 sort($plugins);
254 return $plugins;
255 }
257 /**
258 * Fetch list of types found in collectd's datadirs for given host+plugin+instance
259 * @arg_host Name of host
260 * @arg_plugin Name of plugin
261 * @arg_pinst Plugin instance
262 * @return Sorted list of types (sorted alphabetically)
263 */
264 function collectd_list_types($arg_host, $arg_plugin, $arg_pinst, $arg_type = null) {
265 $options = array(
266 'result' => array(),
267 'cb_host' => '_collectd_list_cb_host',
268 'cb_plugin' => '_collectd_list_cb_plugin',
269 'cb_pinst' => '_collectd_list_cb_pinst',
270 'cb_type' => '_collectd_list_cb_type',
271 'cb_tinst' => is_null($arg_type) ? false : '_collectd_list_cb_tinst',
272 'filter_host' => $arg_host,
273 'filter_plugin' => $arg_plugin,
274 'filter_pinst' => $arg_pinst,
275 'filter_type' => $arg_type
276 );
277 collectd_walk($options);
278 $types = array_unique($options['result']);
279 sort($types);
280 return $types;
281 }
283 function collectd_list_graphs($arg_host, $arg_plugin, $arg_pinst, $arg_type, $arg_tinst) {
284 $options = array(
285 'result' => array(),
286 'ridentifiers' => array(),
287 'cb_host' => '_collectd_list_cb_host',
288 'cb_plugin' => '_collectd_list_cb_plugin',
289 'cb_pinst' => '_collectd_list_cb_pinst',
290 'cb_type' => '_collectd_list_cb_type',
291 'cb_tinst' => '_collectd_list_cb_graph',
292 'filter_host' => $arg_host,
293 'filter_plugin' => $arg_plugin,
294 'filter_pinst' => $arg_pinst,
295 'filter_type' => $arg_type,
296 'filter_tinst' => $arg_tinst == '@' ? '@merge' : $arg_tinst
297 );
298 collectd_walk($options);
299 return $options['result'];
300 }
302 /**
303 * Parse symlinks in order to get an identifier that collectd understands
304 * (e.g. virtualisation is collected on host for individual VMs and can be
305 * symlinked to the VM's hostname, support FLUSH for these by flushing
306 * on the host-identifier instead of VM-identifier)
307 * @host Host name
308 * @plugin Plugin name
309 * @pinst Plugin instance
310 * @type Type name
311 * @tinst Type instance
312 * @return Identifier that collectd's FLUSH command understands
313 */
314 function collectd_identifier($host, $plugin, $pinst, $type, $tinst) {
315 global $config;
316 $rrd_realpath = null;
317 $orig_identifier = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, strlen($pinst) ? '-' : '', $pinst, $type, strlen($tinst) ? '-' : '', $tinst);
318 $identifier = null;
319 foreach ($config['datadirs'] as $datadir)
320 if (is_file($datadir.'/'.$orig_identifier.'.rrd')) {
321 $rrd_realpath = realpath($datadir.'/'.$orig_identifier.'.rrd');
322 break;
323 }
324 if ($rrd_realpath) {
325 $identifier = basename($rrd_realpath);
326 $identifier = substr($identifier, 0, strlen($identifier)-4);
327 $rrd_realpath = dirname($rrd_realpath);
328 $identifier = basename($rrd_realpath).'/'.$identifier;
329 $rrd_realpath = dirname($rrd_realpath);
330 $identifier = basename($rrd_realpath).'/'.$identifier;
331 }
333 if (is_null($identifier))
334 return $orig_identifier;
335 else
336 return $identifier;
337 }
339 /**
340 * Tell collectd that it should FLUSH all data it has regarding the
341 * graph we are about to generate.
342 * @host Host name
343 * @plugin Plugin name
344 * @pinst Plugin instance
345 * @type Type name
346 * @tinst Type instance
347 */
348 function collectd_flush($identifier) {
349 global $config;
351 if (!$config['collectd_sock'])
352 return false;
353 if (is_null($identifier) || (is_array($identifier) && count($identifier) == 0) || !(is_string($identifier) || is_array($identifier)))
354 return false;
356 $u_errno = 0;
357 $u_errmsg = '';
358 if ($socket = @fsockopen($config['collectd_sock'], 0, $u_errno, $u_errmsg)) {
359 $cmd = 'FLUSH plugin=rrdtool';
360 if (is_array($identifier)) {
361 foreach ($identifier as $val)
362 $cmd .= sprintf(' identifier="%s"', $val);
363 } else
364 $cmd .= sprintf(' identifier="%s"', $identifier);
365 $cmd .= "\n";
367 $r = fwrite($socket, $cmd, strlen($cmd));
368 if ($r === false || $r != strlen($cmd))
369 error_log(sprintf("graph.php: Failed to write whole command to unix-socket: %d out of %d written", $r === false ? -1 : $r, strlen($cmd)));
371 $resp = fgets($socket);
372 if ($resp === false)
373 error_log(sprintf("graph.php: Failed to read response from collectd for command: %s", trim($cmd)));
375 $n = (int)$resp;
376 while ($n-- > 0)
377 fgets($socket);
379 fclose($socket);
380 } else
381 error_log(sprintf("graph.php: Failed to open unix-socket to collectd: %d: %s", $u_errno, $u_errmsg));
382 }
384 class CollectdColor {
385 private $r = 0;
386 private $g = 0;
387 private $b = 0;
389 function __construct($value = null) {
390 if (is_null($value)) {
391 } else if (is_array($value)) {
392 if (isset($value['r']))
393 $this->r = $value['r'] > 0 ? ($value['r'] > 1 ? 1 : $value['r']) : 0;
394 if (isset($value['g']))
395 $this->g = $value['g'] > 0 ? ($value['g'] > 1 ? 1 : $value['g']) : 0;
396 if (isset($value['b']))
397 $this->b = $value['b'] > 0 ? ($value['b'] > 1 ? 1 : $value['b']) : 0;
398 } else if (is_string($value)) {
399 $matches = array();
400 if ($value == 'random') {
401 $this->randomize();
402 } 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)) {
403 $this->r = ('0x'.$matches[1]) / 255.0;
404 $this->g = ('0x'.$matches[2]) / 255.0;
405 $this->b = ('0x'.$matches[3]) / 255.0;
406 }
407 } else if (is_a($value, 'CollectdColor')) {
408 $this->r = $value->r;
409 $this->g = $value->g;
410 $this->b = $value->b;
411 }
412 }
414 function randomize() {
415 $this->r = rand(0, 255) / 255.0;
416 $this->g = rand(0, 255) / 255.0;
417 $this->b = 0.0;
418 $min = 0.0;
419 $max = 1.0;
421 if (($this->r + $this->g) < 1.0) {
422 $min = 1.0 - ($this->r + $this->g);
423 } else {
424 $max = 2.0 - ($this->r + $this->g);
425 }
426 $this->b = $min + ((rand(0, 255)/255.0) * ($max - $min));
427 }
429 function fade($bkgnd = null, $alpha = 0.25) {
430 if (is_null($bkgnd) || !is_a($bkgnd, 'CollectdColor')) {
431 $bg_r = 1.0;
432 $bg_g = 1.0;
433 $bg_b = 1.0;
434 } else {
435 $bg_r = $bkgnd->r;
436 $bg_g = $bkgnd->g;
437 $bg_b = $bkgnd->b;
438 }
440 $this->r = $alpha * $this->r + ((1.0 - $alpha) * $bg_r);
441 $this->g = $alpha * $this->g + ((1.0 - $alpha) * $bg_g);
442 $this->b = $alpha * $this->b + ((1.0 - $alpha) * $bg_b);
443 }
445 function as_array() {
446 return array('r'=>$this->r, 'g'=>$this->g, 'b'=>$this->b);
447 }
449 function as_string() {
450 $r = (int)($this->r*255);
451 $g = (int)($this->g*255);
452 $b = (int)($this->b*255);
453 return sprintf('%02x%02x%02x', $r > 255 ? 255 : $r, $g > 255 ? 255 : $g, $b > 255 ? 255 : $b);
454 }
455 }
458 /**
459 * Helper function to strip quotes from RRD output
460 * @str RRD-Info generated string
461 * @return String with one surrounding pair of quotes stripped
462 */
463 function rrd_strip_quotes($str) {
464 if ($str[0] == '"' && $str[strlen($str)-1] == '"')
465 return substr($str, 1, strlen($str)-2);
466 else
467 return $str;
468 }
470 function rrd_escape($str) {
471 return str_replace(array('\\', ':'), array('\\\\', '\\:'), $str);
472 }
474 /**
475 * Determine useful information about RRD file
476 * @file Name of RRD file to analyse
477 * @return Array describing the RRD file
478 */
479 function rrd_info($file) {
480 $info = array('filename'=>$file);
482 $rrd = popen(RRDTOOL.' info '.escapeshellarg($file), 'r');
483 if ($rrd) {
484 while (($s = fgets($rrd)) !== false) {
485 $p = strpos($s, '=');
486 if ($p === false)
487 continue;
488 $key = trim(substr($s, 0, $p));
489 $value = trim(substr($s, $p+1));
490 if (strncmp($key,'ds[', 3) == 0) {
491 /* DS definition */
492 $p = strpos($key, ']');
493 $ds = substr($key, 3, $p-3);
494 if (!isset($info['DS']))
495 $info['DS'] = array();
496 $ds_key = substr($key, $p+2);
498 if (strpos($ds_key, '[') === false) {
499 if (!isset($info['DS']["$ds"]))
500 $info['DS']["$ds"] = array();
501 $info['DS']["$ds"]["$ds_key"] = rrd_strip_quotes($value);
502 }
503 } else if (strncmp($key, 'rra[', 4) == 0) {
504 /* RRD definition */
505 $p = strpos($key, ']');
506 $rra = substr($key, 4, $p-4);
507 if (!isset($info['RRA']))
508 $info['RRA'] = array();
509 $rra_key = substr($key, $p+2);
511 if (strpos($rra_key, '[') === false) {
512 if (!isset($info['RRA']["$rra"]))
513 $info['RRA']["$rra"] = array();
514 $info['RRA']["$rra"]["$rra_key"] = rrd_strip_quotes($value);
515 }
516 } else if (strpos($key, '[') === false) {
517 $info[$key] = rrd_strip_quotes($value);
518 }
519 }
520 pclose($rrd);
521 }
522 return $info;
523 }
525 function rrd_get_color($code, $line = true) {
526 global $config;
527 $name = ($line ? 'f_' : 'h_').$code;
528 if (!isset($config['rrd_colors'][$name])) {
529 $c_f = new CollectdColor('random');
530 $c_h = new CollectdColor($c_f);
531 $c_h->fade();
532 $config['rrd_colors']['f_'.$code] = $c_f->as_string();
533 $config['rrd_colors']['h_'.$code] = $c_h->as_string();
534 }
535 return $config['rrd_colors'][$name];
536 }
538 /**
539 * Draw RRD file based on it's structure
540 * @host
541 * @plugin
542 * @pinst
543 * @type
544 * @tinst
545 * @opts
546 * @return Commandline to call RRDGraph in order to generate the final graph
547 */
548 function collectd_draw_rrd($host, $plugin, $pinst = null, $type, $tinst = null, $opts = array()) {
549 global $config;
550 $timespan_def = null;
551 if (!isset($opts['timespan']))
552 $timespan_def = reset($config['timespan']);
553 else foreach ($config['timespan'] as &$ts)
554 if ($ts['name'] == $opts['timespan'])
555 $timespan_def = $ts;
557 if (!isset($opts['rrd_opts']))
558 $opts['rrd_opts'] = array();
559 if (isset($opts['logarithmic']) && $opts['logarithmic'])
560 array_unshift($opts['rrd_opts'], '-o');
562 $rrdinfo = null;
563 $rrdfile = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, is_null($pinst) ? '' : '-', $pinst, $type, is_null($tinst) ? '' : '-', $tinst);
564 foreach ($config['datadirs'] as $datadir)
565 if (is_file($datadir.'/'.$rrdfile.'.rrd')) {
566 $rrdinfo = rrd_info($datadir.'/'.$rrdfile.'.rrd');
567 if (isset($rrdinfo['RRA']) && is_array($rrdinfo['RRA']))
568 break;
569 else
570 $rrdinfo = null;
571 }
573 if (is_null($rrdinfo))
574 return false;
576 $graph = array();
577 $has_avg = false;
578 $has_max = false;
579 $has_min = false;
580 reset($rrdinfo['RRA']);
581 $l_max = 0;
582 while (list($k, $v) = each($rrdinfo['RRA'])) {
583 if ($v['cf'] == 'MAX')
584 $has_max = true;
585 else if ($v['cf'] == 'AVERAGE')
586 $has_avg = true;
587 else if ($v['cf'] == 'MIN')
588 $has_min = true;
589 }
590 reset($rrdinfo['DS']);
591 while (list($k, $v) = each($rrdinfo['DS'])) {
592 if (strlen($k) > $l_max)
593 $l_max = strlen($k);
594 if ($has_min)
595 $graph[] = sprintf('DEF:%s_min=%s:%s:MIN', $k, rrd_escape($rrdinfo['filename']), $k);
596 if ($has_avg)
597 $graph[] = sprintf('DEF:%s_avg=%s:%s:AVERAGE', $k, rrd_escape($rrdinfo['filename']), $k);
598 if ($has_max)
599 $graph[] = sprintf('DEF:%s_max=%s:%s:MAX', $k, rrd_escape($rrdinfo['filename']), $k);
600 }
601 if ($has_min && $has_max || $has_min && $has_avg || $has_avg && $has_max) {
602 $n = 1;
603 reset($rrdinfo['DS']);
604 while (list($k, $v) = each($rrdinfo['DS'])) {
605 $graph[] = sprintf('LINE:%s_%s', $k, $has_min ? 'min' : 'avg');
606 $graph[] = sprintf('CDEF:%s_var=%s_%s,%s_%s,-', $k, $k, $has_max ? 'max' : 'avg', $k, $has_min ? 'min' : 'avg');
607 $graph[] = sprintf('AREA:%s_var#%s::STACK', $k, rrd_get_color($n++, false));
608 }
609 }
611 reset($rrdinfo['DS']);
612 $n = 1;
613 while (list($k, $v) = each($rrdinfo['DS'])) {
614 $graph[] = sprintf('LINE1:%s_avg#%s:%s ', $k, rrd_get_color($n++, true), $k.substr(' ', 0, $l_max-strlen($k)));
615 if (isset($opts['tinylegend']) && $opts['tinylegend'])
616 continue;
617 if ($has_avg)
618 $graph[] = sprintf('GPRINT:%s_avg:AVERAGE:%%5.1lf%%s Avg%s', $k, $has_max || $has_min || $has_avg ? ',' : "\\l");
619 if ($has_min)
620 $graph[] = sprintf('GPRINT:%s_min:MIN:%%5.1lf%%s Max%s', $k, $has_max || $has_avg ? ',' : "\\l");
621 if ($has_max)
622 $graph[] = sprintf('GPRINT:%s_max:MAX:%%5.1lf%%s Max%s', $k, $has_avg ? ',' : "\\l");
623 if ($has_avg)
624 $graph[] = sprintf('GPRINT:%s_avg:LAST:%%5.1lf%%s Last\\l', $k);
625 }
627 $rrd_cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $rrdfile);
628 $rrd_cmd = array_merge($rrd_cmd, $config['rrd_opts'], $opts['rrd_opts'], $graph);
630 $cmd = RRDTOOL;
631 for ($i = 1; $i < count($rrd_cmd); $i++)
632 $cmd .= ' '.escapeshellarg($rrd_cmd[$i]);
634 return $cmd;
635 }
637 /**
638 * Draw RRD file based on it's structure
639 * @timespan
640 * @host
641 * @plugin
642 * @pinst
643 * @type
644 * @tinst
645 * @opts
646 * @return Commandline to call RRDGraph in order to generate the final graph
647 */
648 function collectd_draw_generic($timespan, $host, $plugin, $pinst = null, $type, $tinst = null) {
649 global $config, $GraphDefs;
650 $timespan_def = null;
651 foreach ($config['timespan'] as &$ts)
652 if ($ts['name'] == $timespan)
653 $timespan_def = $ts;
654 if (is_null($timespan_def))
655 $timespan_def = reset($config['timespan']);
657 if (!isset($GraphDefs[$type]))
658 return false;
660 $rrd_file = sprintf('%s/%s%s%s/%s%s%s', $host, $plugin, is_null($pinst) ? '' : '-', $pinst, $type, is_null($tinst) ? '' : '-', $tinst);
661 $rrd_cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $rrd_file);
662 $rrd_cmd = array_merge($rrd_cmd, $config['rrd_opts']);
663 $rrd_args = $GraphDefs[$type];
665 foreach ($config['datadirs'] as $datadir) {
666 $file = $datadir.'/'.$rrd_file.'.rrd';
667 if (!is_file($file))
668 continue;
670 $file = str_replace(":", "\\:", $file);
671 $rrd_args = str_replace('{file}', rrd_escape($file), $rrd_args);
673 $rrdgraph = array_merge($rrd_cmd, $rrd_args);
674 $cmd = RRDTOOL;
675 for ($i = 1; $i < count($rrdgraph); $i++)
676 $cmd .= ' '.escapeshellarg($rrdgraph[$i]);
678 return $cmd;
679 }
680 return false;
681 }
683 /**
684 * Draw stack-graph for set of RRD files
685 * @opts Graph options like colors
686 * @sources List of array(name, file, ds)
687 * @return Commandline to call RRDGraph in order to generate the final graph
688 */
689 function collectd_draw_meta_stack(&$opts, &$sources) {
690 global $config;
691 $timespan_def = null;
692 if (!isset($opts['timespan']))
693 $timespan_def = reset($config['timespan']);
694 else foreach ($config['timespan'] as &$ts)
695 if ($ts['name'] == $opts['timespan'])
696 $timespan_def = $ts;
698 if (!isset($opts['title']))
699 $opts['title'] = 'Unknown title';
700 if (!isset($opts['rrd_opts']))
701 $opts['rrd_opts'] = array();
702 if (!isset($opts['colors']))
703 $opts['colors'] = array();
704 if (isset($opts['logarithmic']) && $opts['logarithmic'])
705 array_unshift($opts['rrd_opts'], '-o');
707 $cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $opts['title']);
708 $cmd = array_merge($cmd, $config['rrd_opts'], $opts['rrd_opts']);
709 $max_inst_name = 0;
711 foreach($sources as &$inst_data) {
712 $inst_name = str_replace('!', '_', $inst_data['name']);
713 $file = $inst_data['file'];
714 $ds = isset($inst_data['ds']) ? $inst_data['ds'] : 'value';
716 if (strlen($inst_name) > $max_inst_name)
717 $max_inst_name = strlen($inst_name);
719 if (!is_file($file))
720 continue;
722 $cmd[] = 'DEF:'.$inst_name.'_min='.rrd_escape($file).':'.$ds.':MIN';
723 $cmd[] = 'DEF:'.$inst_name.'_avg='.rrd_escape($file).':'.$ds.':AVERAGE';
724 $cmd[] = 'DEF:'.$inst_name.'_max='.rrd_escape($file).':'.$ds.':MAX';
725 $cmd[] = 'CDEF:'.$inst_name.'_nnl='.$inst_name.'_avg,UN,0,'.$inst_name.'_avg,IF';
726 }
727 $inst_data = end($sources);
728 $inst_name = $inst_data['name'];
729 $cmd[] = 'CDEF:'.$inst_name.'_stk='.$inst_name.'_nnl';
731 $inst_data1 = end($sources);
732 while (($inst_data0 = prev($sources)) !== false) {
733 $inst_name0 = str_replace('!', '_', $inst_data0['name']);
734 $inst_name1 = str_replace('!', '_', $inst_data1['name']);
736 $cmd[] = 'CDEF:'.$inst_name0.'_stk='.$inst_name0.'_nnl,'.$inst_name1.'_stk,+';
737 $inst_data1 = $inst_data0;
738 }
740 foreach($sources as &$inst_data) {
741 $inst_name = str_replace('!', '_', $inst_data['name']);
742 $legend = sprintf('%s', $inst_data['name']);
743 while (strlen($legend) < $max_inst_name)
744 $legend .= ' ';
745 $number_format = isset($opts['number_format']) ? $opts['number_format'] : '%6.1lf';
747 if (isset($opts['colors'][$inst_name]))
748 $line_color = new CollectdColor($opts['colors'][$inst_name]);
749 else
750 $line_color = new CollectdColor('random');
751 $area_color = new CollectdColor($line_color);
752 $area_color->fade();
754 $cmd[] = 'AREA:'.$inst_name.'_stk#'.$area_color->as_string();
755 $cmd[] = 'LINE1:'.$inst_name.'_stk#'.$line_color->as_string().':'.$legend;
756 if (!(isset($opts['tinylegend']) && $opts['tinylegend'])) {
757 $cmd[] = 'GPRINT:'.$inst_name.'_min:MIN:'.$number_format.' Min,';
758 $cmd[] = 'GPRINT:'.$inst_name.'_avg:AVERAGE:'.$number_format.' Avg,';
759 $cmd[] = 'GPRINT:'.$inst_name.'_max:MAX:'.$number_format.' Max,';
760 $cmd[] = 'GPRINT:'.$inst_name.'_avg:LAST:'.$number_format.' Last\\l';
761 }
762 }
764 $rrdcmd = RRDTOOL;
765 for ($i = 1; $i < count($cmd); $i++)
766 $rrdcmd .= ' '.escapeshellarg($cmd[$i]);
767 return $rrdcmd;
768 }
770 /**
771 * Draw stack-graph for set of RRD files
772 * @opts Graph options like colors
773 * @sources List of array(name, file, ds)
774 * @return Commandline to call RRDGraph in order to generate the final graph
775 */
776 function collectd_draw_meta_line(&$opts, &$sources) {
777 global $config;
778 $timespan_def = null;
779 if (!isset($opts['timespan']))
780 $timespan_def = reset($config['timespan']);
781 else foreach ($config['timespan'] as &$ts)
782 if ($ts['name'] == $opts['timespan'])
783 $timespan_def = $ts;
785 if (!isset($opts['title']))
786 $opts['title'] = 'Unknown title';
787 if (!isset($opts['rrd_opts']))
788 $opts['rrd_opts'] = array();
789 if (!isset($opts['colors']))
790 $opts['colors'] = array();
791 if (isset($opts['logarithmic']) && $opts['logarithmic'])
792 array_unshift($opts['rrd_opts'], '-o');
794 $cmd = array(RRDTOOL, 'graph', '-', '-a', 'PNG', '-w', $config['rrd_width'], '-h', $config['rrd_height'], '-s', -1*$timespan_def['seconds'], '-t', $opts['title']);
795 $cmd = array_merge($cmd, $config['rrd_opts'], $opts['rrd_opts']);
796 $max_inst_name = 0;
798 foreach ($sources as &$inst_data) {
799 $inst_name = str_replace('!', '_', $inst_data['name']);
800 $file = $inst_data['file'];
801 $ds = isset($inst_data['ds']) ? $inst_data['ds'] : 'value';
803 if (strlen($inst_name) > $max_inst_name)
804 $max_inst_name = strlen($inst_name);
806 if (!is_file($file))
807 continue;
809 $cmd[] = 'DEF:'.$inst_name.'_min='.rrd_escape($file).':'.$ds.':MIN';
810 $cmd[] = 'DEF:'.$inst_name.'_avg='.rrd_escape($file).':'.$ds.':AVERAGE';
811 $cmd[] = 'DEF:'.$inst_name.'_max='.rrd_escape($file).':'.$ds.':MAX';
812 }
814 foreach ($sources as &$inst_data) {
815 $inst_name = str_replace('!', '_', $inst_data['name']);
816 $legend = sprintf('%s', $inst_name);
817 while (strlen($legend) < $max_inst_name)
818 $legend .= ' ';
819 $number_format = isset($opts['number_format']) ? $opts['number_format'] : '%6.1lf';
821 if (isset($opts['colors'][$inst_name]))
822 $line_color = new CollectdColor($opts['colors'][$inst_name]);
823 else
824 $line_color = new CollectdColor('random');
826 $cmd[] = 'LINE1:'.$inst_name.'_avg#'.$line_color->as_string().':'.$legend;
827 if (!(isset($opts['tinylegend']) && $opts['tinylegend'])) {
828 $cmd[] = 'GPRINT:'.$inst_name.'_min:MIN:'.$number_format.' Min,';
829 $cmd[] = 'GPRINT:'.$inst_name.'_avg:AVERAGE:'.$number_format.' Avg,';
830 $cmd[] = 'GPRINT:'.$inst_name.'_max:MAX:'.$number_format.' Max,';
831 $cmd[] = 'GPRINT:'.$inst_name.'_avg:LAST:'.$number_format.' Last\\l';
832 }
833 }
835 $rrdcmd = RRDTOOL;
836 for ($i = 1; $i < count($cmd); $i++)
837 $rrdcmd .= ' '.escapeshellarg($cmd[$i]);
838 return $rrdcmd;
839 }
841 ?>