datadir = $config['datadir']; $this->rrdtool = $config['rrdtool']; if (!empty($config['rrdtool_opts'])) { if (is_array($config['rrdtool_opts'])) { $this->rrdtool_opts = $config['rrdtool_opts']; } else { $this->rrdtool_opts = explode(' ', $config['rrdtool_opts']); } } $this->rrd_url = $config['rrd_url']; $this->cache = $config['cache']; $this->parse_get($_get); $this->rrd_title = sprintf( '%s%s%s%s', $this->args['plugin'], $this->args['type'] != $this->args['plugin'] ? sprintf(' %s', $this->args['type']) : '', (isset($this->args['pinstance']) and $this->args['pinstance'] != '') ? sprintf(' (%s)', $this->args['pinstance']) : '', (isset($this->args['category']) and $this->args['category'] != '') ? sprintf(' (%s)', $this->args['category']) : '' ); $this->rrd_files(); $this->width = isset($_get['x']) ? $_get['x'] : $config['width']; $this->height = isset($_get['y']) ? $_get['y'] : $config['height']; $this->graph_type = $config['graph_type']; $this->negative_io = $config['negative_io']; $this->graph_smooth = $config['graph_smooth']; $this->graph_minmax = $config['graph_minmax']; $this->flush_socket = $config['socket']; $this->flush_type = $config['flush_type']; } function rainbow_colors() { $c = 0; $sources = count($this->rrd_get_sources()); foreach ($this->rrd_get_sources() as $ds) { # hue (saturnation=1, value=1) $h = $sources > 1 ? 360 - ($c * (330/($sources-1))) : 360; $h = ($h %= 360) / 60; $f = $h - floor($h); $q[0] = $q[1] = 0; $q[2] = 1*(1-1*(1-$f)); $q[3] = $q[4] = 1; $q[5] = 1*(1-1*$f); $hex = ''; foreach(array(4,2,0) as $j) { $hex .= sprintf('%02x', $q[(floor($h)+$j)%6] * 255); } $this->colors[$ds] = $hex; $c++; } } # parse $_GET values function parse_get($_get) { $this->args = array( 'host' => isset($_get['h']) ? $_get['h'] : null, 'plugin' => isset($_get['p']) ? $_get['p'] : null, 'pinstance' => isset($_get['pi']) ? $_get['pi'] : null, 'category' => isset($_get['c']) ? $_get['c'] : null, 'type' => isset($_get['t']) ? $_get['t'] : null, 'tinstance' => isset($_get['ti']) ? $_get['ti'] : null, ); $this->seconds = isset($_get['s']) ? $_get['s'] : null; } function validate_color($color) { if (!preg_match('/^[0-9a-f]{6}$/', $color)) return '000000'; else return $color; } function get_faded_color($fgc, $bgc='ffffff', $percent=0.25) { $fgc = $this->validate_color($fgc); if (!is_numeric($percent)) $percent=0.25; $rgb = array('r', 'g', 'b'); $fg['r'] = hexdec(substr($fgc,0,2)); $fg['g'] = hexdec(substr($fgc,2,2)); $fg['b'] = hexdec(substr($fgc,4,2)); $bg['r'] = hexdec(substr($bgc,0,2)); $bg['g'] = hexdec(substr($bgc,2,2)); $bg['b'] = hexdec(substr($bgc,4,2)); foreach ($rgb as $pri) { $c[$pri] = dechex(round($percent * $fg[$pri]) + ((1.0 - $percent) * $bg[$pri])); if ($c[$pri] == '0') $c[$pri] = '00'; } return $c['r'].$c['g'].$c['b']; } function rrd_escape($value) { # In case people have really bizarre URLs in $CONFIG['rrd_url'], # it should not be dropped. $value = str_replace('\\', '\\\\', $value); # http://oss.oetiker.ch/rrdtool/doc/rrdgraph_graph.en.html#IEscaping_the_colon return str_replace(':', '\:', $value); } function parse_filename($file) { if ($this->graph_type == 'canvas') { $file = str_replace($this->datadir . '/', '', $file); $file = str_replace( array('{file}', '{file_escaped}'), array($file, urlencode($file)), $this->rrd_url ); } return $this->rrd_escape($file); } function rrd_files() { $files = $this->get_filenames(); $this->tinstances = array(); $this->files = array(); $this->identifiers = array(); $datadir_prefix = preg_quote($this->datadir, '#'); foreach($files as $filename) { $basename=basename($filename,'.rrd'); $instance = strpos($basename,'-') ? substr($basename, strpos($basename,'-') + 1) : 'value'; $this->tinstances[] = $instance; $this->files[$instance] = $filename; $this->identifiers[$instance] = preg_replace( "#^{$datadir_prefix}/(.*)\.rrd$#", '$1', $filename); } sort($this->tinstances); ksort($this->files); ksort($this->identifiers); } function get_filenames() { $identifier = sprintf('%s/%s%s%s%s%s/%s%s%s', $this->args['host'], $this->args['plugin'], strlen($this->args['category']) ? '-' : '', $this->args['category'], strlen($this->args['pinstance']) ? '-' : '', $this->args['pinstance'], $this->args['type'], strlen($this->args['tinstance']) ? '-' : '', $this->args['tinstance'] ); $identifier = preg_replace("/([*?[])/", '[$1]', $identifier); $wildcard = strlen($this->args['tinstance']) ? '.' : '[-.]*'; $files = glob($this->datadir .'/'. $identifier . $wildcard . 'rrd'); return $files ? $files : array(); } function rrd_graph($debug = false) { $this->collectd_flush(); $colors = $this->colors; $this->rainbow_colors(); $this->colors = $colors + $this->colors; $graphoptions = $this->rrd_gen_graph(); # $shellcmd contains escaped rrdtool arguments $shellcmd = array(); foreach ($graphoptions as $arg) $shellcmd[] = escapeshellarg($arg); $style = $debug !== false ? $debug : $this->graph_type; switch ($style) { case 'cmd': print '
'; foreach ($shellcmd as $d) { printf("%s \\\n", htmlentities($d)); } print ''; break; case 'canvas': printf(''; break; case 'debug': case 1: print '
'; print htmlentities(print_r($shellcmd, TRUE)); print ''; break; case 'svg': case 'png': default: # caching if (is_numeric($this->cache) && $this->cache > 0) header("Expires: " . date(DATE_RFC822,strtotime($this->cache." seconds"))); if ($style === 'svg') header("content-type: image/svg+xml"); else header("content-type: image/png"); $shellcmd = array_merge( $this->rrd_graph_command($style), $shellcmd ); $shellcmd = implode(' ', $shellcmd); passthru($shellcmd, $exitcode); if ($exitcode !== 0) { header('HTTP/1.1 500 Internal Server Error'); } break; } } function rrd_graph_command($imgformat) { if (!in_array($imgformat, array('png', 'svg'))) $imgformat = 'png'; return array( $this->rrdtool, 'graph', '-', '-a', strtoupper($imgformat) ); } function rrd_options() { $rrdgraph = array(); foreach($this->rrdtool_opts as $opt) $rrdgraph[] = $opt; if ($this->graph_smooth) $rrdgraph[] = '-E'; if ($this->base) { $rrdgraph[] = '--base'; $rrdgraph[] = $this->base; } if (!array_search('-l', $rrdgraph)) { $rrdgraph[] = '-l'; $rrdgraph[] = '0'; } $rrdgraph = array_merge($rrdgraph, array( '-w', is_numeric($this->width) ? $this->width : 400, '-h', is_numeric($this->height) ? $this->height : 175, '-t', $this->rrd_title )); if ($this->rrd_vertical) { $rrdgraph[] = '-v'; $rrdgraph[] = $this->rrd_vertical; } $rrdgraph[] = '-s'; $rrdgraph[] = sprintf('e-%d', is_numeric($this->seconds) ? $this->seconds : 86400); return $rrdgraph; } function rrd_get_sources() { # is the source spread over multiple files? if (is_array($this->files) && count($this->files)>1) { # and must it be ordered? if (is_array($this->order)) { $this->tinstances = array_merge(array_unique(array_merge(array_intersect($this->order, $this->tinstances), $this->tinstances))); } # use tinstances as sources if(is_array($this->data_sources) && count($this->data_sources)>1) { $sources = array(); foreach($this->tinstances as $f) { foreach($this->data_sources as $s) { $sources[] = $f . '-' . $s; } } } else { $sources = $this->tinstances; } } # or one file with multiple data_sources else { if(is_array($this->data_sources) && count($this->data_sources)==1 && in_array('value', $this->data_sources)) { # use tinstances as sources $sources = $this->tinstances; } else { # use data_sources as sources $sources = $this->data_sources; } } $this->parse_legend($sources); return $sources; } function parse_legend($sources) { # fill up legend by items that are not defined by plugin $this->legend = $this->legend + array_combine($sources, $sources); # detect length of longest legend $max = 0; foreach ($this->legend as $legend) { if(strlen((string)$legend) > $max) $max = strlen((string)$legend); } # make all legend equal in lenght $format = sprintf("%%-%ds", $max); foreach ($this->legend as $index => $value) { $this->legend[$index] = sprintf($format, $value); } } function socket_cmd($socket, $cmd) { $r = fwrite($socket, $cmd, strlen($cmd)); if ($r === false || $r != strlen($cmd)) { error_log(sprintf('ERROR: Failed to write full command to unix-socket: %d out of %d written', $r === false ? -1 : $r, strlen($cmd))); return FALSE; } $resp = fgets($socket,128); if ($resp === false) { error_log(sprintf('ERROR: Failed to read response from collectd for command: %s', trim($cmd))); return FALSE; } $n = (int)$resp; while ($n-- > 0) fgets($socket,128); return TRUE; } # tell collectd to FLUSH all data of the identifier(s) function collectd_flush() { $identifier = $this->identifiers; if (!$this->flush_socket) return FALSE; if (!$identifier || (is_array($identifier) && count($identifier) == 0) || !(is_string($identifier) || is_array($identifier))) return FALSE; if (!is_array($identifier)) $identifier = array($identifier); $u_errno = 0; $u_errmsg = ''; if (! $socket = @fsockopen($this->flush_socket, 0, $u_errno, $u_errmsg)) { error_log(sprintf('ERROR: Failed to open unix-socket to %s (%d: %s)', $this->flush_socket, $u_errno, $u_errmsg)); return FALSE; } if ($this->flush_type == 'collectd'){ $cmd = 'FLUSH'; foreach ($identifier as $val) $cmd .= sprintf(' identifier="%s"', $val); $cmd .= "\n"; $this->socket_cmd($socket, $cmd); } elseif ($this->flush_type == 'rrdcached') { foreach ($identifier as $val) { $cmd = sprintf("FLUSH %s.rrd\n", str_replace(' ', '\\ ', $val)); $this->socket_cmd($socket, $cmd); } } fclose($socket); return TRUE; } }