Merge pull request #2969 from rpv-tomsk/issue-2173
[collectd.git] / contrib / php-collection / functions.php
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  */
18
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_.-]+$/');
21
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 }
48
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 }
63
64 function collectd_walk(&$options) {
65         global $config;
66
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;
76
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;
92
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 }
123
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 }
143
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 }
158
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 }
175
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 }
190
191 function _collectd_list_cb_tinst(&$options, $host, $plugin, $pinst, $type, $tinst) {
192         $options['result'][] = $tinst;
193         return false;
194 }
195
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 }
215
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 }
234
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 }
256
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 }
282
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 }
301
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         }
332
333         if (is_null($identifier))
334                 return $orig_identifier;
335         else
336                 return $identifier;
337 }
338
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;
350
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;
355
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";
366
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)));
370
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)));
374
375                 $n = (int)$resp;
376                 while ($n-- > 0)
377                         fgets($socket);
378
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 }
383
384 class CollectdColor {
385         private $r = 0;
386         private $g = 0;
387         private $b = 0;
388
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         }
413
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;
420
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         }
428
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                 }
439
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         }
444
445         function as_array() {
446                 return array('r'=>$this->r, 'g'=>$this->g, 'b'=>$this->b);
447         }
448
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 }
456
457
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 }
469
470 function rrd_escape($str) {
471         return str_replace(array('\\', ':'), array('\\\\', '\\:'), $str);
472 }
473
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);
481
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);
497
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);
510
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 }
524
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 }
537
538 /**
539  * Draw RRD file based on its 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;
556
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');
561
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                 }
572
573         if (is_null($rrdinfo))
574                 return false;
575
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         }
610
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         }
626
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);
629
630         $cmd = RRDTOOL;
631         for ($i = 1; $i < count($rrd_cmd); $i++)
632                 $cmd .= ' '.escapeshellarg($rrd_cmd[$i]);
633
634         return $cmd;
635 }
636
637 /**
638  * Draw RRD file based on its 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']);
656
657         if (!isset($GraphDefs[$type]))
658                 return false;
659
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];
664
665         foreach ($config['datadirs'] as $datadir) {
666                 $file = $datadir.'/'.$rrd_file.'.rrd';
667                 if (!is_file($file))
668                         continue;
669
670                 $file = str_replace(":", "\\:", $file);
671                 $rrd_args = str_replace('{file}', rrd_escape($file), $rrd_args);
672
673                 $rrdgraph = array_merge($rrd_cmd, $rrd_args);
674                 $cmd = RRDTOOL;
675                 for ($i = 1; $i < count($rrdgraph); $i++)
676                         $cmd .= ' '.escapeshellarg($rrdgraph[$i]);
677
678                 return $cmd;
679         }
680         return false;
681 }
682
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;
697
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');
706
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;
710
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';
715
716                 if (strlen($inst_name) > $max_inst_name)
717                         $max_inst_name = strlen($inst_name);
718
719                 if (!is_file($file))
720                         continue;
721
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';
730
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']);
735
736                 $cmd[] = 'CDEF:'.$inst_name0.'_stk='.$inst_name0.'_nnl,'.$inst_name1.'_stk,+';
737                 $inst_data1 = $inst_data0;
738         }
739
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';
746
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();
753
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         }
763
764         $rrdcmd = RRDTOOL;
765         for ($i = 1; $i < count($cmd); $i++)
766                 $rrdcmd .= ' '.escapeshellarg($cmd[$i]);
767         return $rrdcmd;
768 }
769
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;
784
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');
793
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;
797
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';
802
803                 if (strlen($inst_name) > $max_inst_name)
804                         $max_inst_name = strlen($inst_name);
805
806                 if (!is_file($file))
807                         continue;
808
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         }
813
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';
820
821                 if (isset($opts['colors'][$inst_name]))
822                         $line_color = new CollectdColor($opts['colors'][$inst_name]);
823                 else
824                         $line_color = new CollectdColor('random');
825
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         }
834
835         $rrdcmd = RRDTOOL;
836         for ($i = 1; $i < count($cmd); $i++)
837                 $rrdcmd .= ' '.escapeshellarg($cmd[$i]);
838         return $rrdcmd;
839 }
840
841 ?>