PageRenderTime 66ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 1ms

/ganglia-3.3.7/web/graph.php

#
PHP | 1130 lines | 837 code | 132 blank | 161 comment | 183 complexity | 8e088fc14faa2243e8a9ff7fc64608a8 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. include_once "./eval_conf.php";
  3. include_once "./get_context.php";
  4. include_once "./functions.php";
  5. ///////////////////////////////////////////////////////////////////////////////
  6. // Populate $rrdtool_graph from $config (from JSON file).
  7. ///////////////////////////////////////////////////////////////////////////////
  8. function build_rrdtool_args_from_json( &$rrdtool_graph, $graph_config ) {
  9. global $context, $hostname, $range, $rrd_dir, $size, $conf;
  10. if ($conf['strip_domainname']) {
  11. $hostname = strip_domainname($hostname);
  12. }
  13. $title = sanitize( $graph_config[ 'title' ] );
  14. $rrdtool_graph[ 'title' ] = $title;
  15. // If vertical label is empty or non-existent set it to space otherwise
  16. // rrdtool will fail
  17. if ( ! isset($graph_config[ 'vertical_label' ]) ||
  18. $graph_config[ 'vertical_label' ] == "" ) {
  19. $rrdtool_graph[ 'vertical-label' ] = " ";
  20. } else {
  21. $rrdtool_graph[ 'vertical-label' ] =
  22. sanitize( $graph_config[ 'vertical_label' ] );
  23. }
  24. $rrdtool_graph['lower-limit'] = '0';
  25. if( isset($graph_config['height_adjustment']) ) {
  26. $rrdtool_graph['height'] +=
  27. ($size == 'medium') ? $graph_config['height_adjustment'] : 0;
  28. } else {
  29. $rrdtool_graph['height'] += ($size == 'medium') ? 28 : 0;
  30. }
  31. // find longest label length, so we pad the others accordingly to get
  32. // consistent column alignment
  33. $max_label_length = 0;
  34. foreach( $graph_config[ 'series' ] as $item ) {
  35. $max_label_length = max( strlen( $item[ 'label' ] ), $max_label_length );
  36. }
  37. $series = '';
  38. $stack_counter = 0;
  39. // Available line types
  40. $line_widths = array("1","2","3");
  41. // Loop through all the graph items
  42. foreach( $graph_config[ 'series' ] as $index => $item ) {
  43. // ignore item if context is not defined in json template
  44. if ( isSet($item[ 'contexts' ]) and
  45. in_array($context, $item['contexts']) == false )
  46. continue;
  47. $rrd_dir = $conf['rrds'] . "/" . $item['clustername'] . "/" . $item['hostname'];
  48. $metric = sanitize( $item[ 'metric' ] );
  49. $metric_file = $rrd_dir . "/" . $metric . ".rrd";
  50. // Make sure metric file exists. Otherwise we'll get a broken graph
  51. if ( is_file($metric_file) ) {
  52. # Need this when defining graphs that may use same metric names
  53. $unique_id = "a" . $index;
  54. $label = str_pad( sanitize( $item[ 'label' ] ), $max_label_length );
  55. // use custom DS defined in json template if it's
  56. // defined (default = 'sum')
  57. $DS = "sum";
  58. if ( isset($item[ 'ds' ]) )
  59. $DS = sanitize( $item[ 'ds' ] );
  60. $series .= " DEF:'$unique_id'='$metric_file':'$DS':AVERAGE ";
  61. // By default graph is a line graph
  62. isset( $item['type']) ?
  63. $item_type = $item['type'] : $item_type = "line";
  64. // TODO sanitize color
  65. switch ( $item_type ) {
  66. case "line":
  67. // Make sure it's a recognized line type
  68. isset($item['line_width']) &&
  69. in_array( $item['line_width'], $line_widths) ?
  70. $line_width = $item['line_width'] : $line_width = "1";
  71. $series .= "LINE" .
  72. $line_width .
  73. ":'$unique_id'#{$item['color']}:'{$label}' ";
  74. break;
  75. case "stack":
  76. // First element in a stack has to be AREA
  77. if ( $stack_counter == 0 ) {
  78. $series .= "AREA";
  79. $stack_counter++;
  80. } else {
  81. $series .= "STACK";
  82. }
  83. $series .= ":'$unique_id'#${item['color']}:'${label}' ";
  84. break;
  85. } // end of switch ( $item_type )
  86. if ( $conf['graphreport_stats'] )
  87. $series .= legendEntry($unique_id, $conf['graphreport_stat_items']);
  88. } // end of if ( is_file($metric_file) ) {
  89. } // end of foreach( $graph_config[ 'series' ] as $index => $item )
  90. // If we end up with the empty series it means that no RRD files matched.
  91. // This can happen if we are trying to create a report and metrics for
  92. // this host were not collected. If that happens we should create an
  93. // empty graph
  94. if ( $series == "" )
  95. $rrdtool_graph[ 'series' ] =
  96. 'HRULE:1#FFCC33:"No matching metrics detected"';
  97. else
  98. $rrdtool_graph[ 'series' ] = $series;
  99. return $rrdtool_graph;
  100. }
  101. ///////////////////////////////////////////////////////////////////////////////
  102. // Graphite graphs
  103. ///////////////////////////////////////////////////////////////////////////////
  104. function build_graphite_series( $config, $host_cluster = "" ) {
  105. global $context;
  106. $targets = array();
  107. $colors = array();
  108. // Keep track of stacked items
  109. $stacked = 0;
  110. foreach( $config[ 'series' ] as $item ) {
  111. if ( isSet($item[ 'contexts' ]) and in_array($context, $item['contexts'])==false )
  112. continue;
  113. if ( $item['type'] == "stack" )
  114. $stacked++;
  115. if ( isset($item['hostname']) && isset($item['clustername']) ) {
  116. $host_cluster = $item['clustername'] . "." . str_replace(".","_", $item['hostname']);
  117. }
  118. $targets[] = "target=". urlencode( "alias($host_cluster.${item['metric']}.sum,'${item['label']}')" );
  119. $colors[] = $item['color'];
  120. }
  121. $output = implode( $targets, '&' );
  122. $output .= "&colorList=" . implode( $colors, ',' );
  123. $output .= "&vtitle=" . urlencode( isset($config[ 'vertical_label' ]) ? $config[ 'vertical_label' ] : "" );
  124. // Do we have any stacked elements. We assume if there is only one element
  125. // that is stacked that rest of it is line graphs
  126. if ( $stacked > 0 ) {
  127. if ( $stacked > 1 )
  128. $output .= "&areaMode=stacked";
  129. else
  130. $output .= "&areaMode=first";
  131. }
  132. return $output;
  133. }
  134. $gweb_root = dirname(__FILE__);
  135. # RFM - Added all the isset() tests to eliminate "undefined index"
  136. # messages in ssl_error_log.
  137. # Graph specific variables
  138. # ATD - No need for escapeshellcmd or rawurldecode on $size or $graph. Not used directly in rrdtool calls.
  139. $size = isset($_GET["z"]) &&
  140. in_array($_GET[ 'z' ], $conf['graph_sizes_keys']) ? $_GET["z"] : NULL;
  141. # If graph arg is not specified default to metric
  142. $graph = isset($_GET["g"]) ? sanitize ( $_GET["g"] ) : "metric";
  143. $graph_arguments = NULL;
  144. $pos = strpos($graph, ",");
  145. if ($pos !== FALSE) {
  146. $graph_report = substr($graph, 0, $pos);
  147. $graph_arguments = substr($graph, $pos + 1);
  148. $graph = $graph_report;
  149. }
  150. $grid = isset($_GET["G"]) ? sanitize( $_GET["G"]) : NULL;
  151. $self = isset($_GET["me"]) ? sanitize( $_GET["me"]) : NULL;
  152. $vlabel = isset($_GET["vl"]) ? sanitize($_GET["vl"]) : NULL;
  153. $value = isset($_GET["v"]) ? sanitize ($_GET["v"]) : NULL;
  154. $metric_name = isset($_GET["m"]) ? sanitize ( $_GET["m"] ) : NULL;
  155. $max = isset($_GET["x"]) && is_numeric($_GET["x"]) ? $_GET["x"] : NULL;
  156. $min = isset($_GET["n"]) && is_numeric($_GET["n"]) ? $_GET["n"] : NULL;
  157. $sourcetime = isset($_GET["st"]) ? clean_number(sanitize($_GET["st"])) : NULL;
  158. $load_color = isset($_GET["l"]) &&
  159. is_valid_hex_color(rawurldecode($_GET['l'])) ?
  160. sanitize($_GET["l"]) : NULL;
  161. $summary = isset($_GET["su"]) ? 1 : 0;
  162. $debug = isset($_GET['debug']) ? clean_number(sanitize($_GET["debug"])) : 0;
  163. $showEvents = isset($_GET["event"]) ? sanitize ($_GET["event"]) : "show";
  164. $command = '';
  165. $graphite_url = '';
  166. $user['json_output'] = isset($_GET["json"]) ? 1 : NULL;
  167. $user['csv_output'] = isset($_GET["csv"]) ? 1 : NULL;
  168. $user['graphlot_output'] = isset($_GET["graphlot"]) ? 1 : NULL;
  169. $user['flot_output'] = isset($_GET["flot"]) ? 1 : NULL;
  170. $user['trend_line'] = isset($_GET["trend"]) ? 1 : NULL;
  171. # How many months ahead to extend the trend e.g. 6 months
  172. $user['trend_range'] = isset($_GET["trendrange"]) && is_numeric($_GET["trendrange"]) ? $_GET["trendrange"] : 6;
  173. #
  174. $user['trend_history'] = isset($_GET["trendhistory"]) && is_numeric($_GET["trendhistory"]) ? $_GET["trendhistory"] : 6;
  175. // Get hostname
  176. $raw_host = isset($_GET["h"]) ? sanitize($_GET["h"]) : "__SummaryInfo__";
  177. // For graphite purposes we need to replace all dots with underscore. dot is
  178. // separates subtrees in graphite
  179. $host = str_replace(".","_", $raw_host);
  180. # Assumes we have a $start variable (set in get_context.php).
  181. # $conf['graph_sizes'] and $conf['graph_sizes_keys'] defined in conf.php.
  182. # Add custom sizes there.
  183. $size = in_array($size, $conf['graph_sizes_keys']) ? $size : 'default';
  184. if (isset($_GET['height']))
  185. $height = $_GET['height'];
  186. else
  187. $height = $conf['graph_sizes'][$size]['height'];
  188. if (isset($_GET['width']))
  189. $width = $_GET['width'];
  190. else
  191. $width = $conf['graph_sizes'][$size]['width'];
  192. #$height = $conf['graph_sizes'][$size]['height'];
  193. #$width = $conf['graph_sizes'][$size]['width'];
  194. $fudge_0 = $conf['graph_sizes'][$size]['fudge_0'];
  195. $fudge_1 = $conf['graph_sizes'][$size]['fudge_1'];
  196. $fudge_2 = $conf['graph_sizes'][$size]['fudge_2'];
  197. ///////////////////////////////////////////////////////////////////////////
  198. // Set some variables depending on the context. Context is set in
  199. // get_context.php
  200. ///////////////////////////////////////////////////////////////////////////
  201. switch ($context)
  202. {
  203. case "meta":
  204. $rrd_dir = $conf['rrds'] . "/__SummaryInfo__";
  205. $rrd_graphite_link = $conf['graphite_rrd_dir'] . "/__SummaryInfo__";
  206. $title = "$self Grid";
  207. break;
  208. case "grid":
  209. $rrd_dir = $conf['rrds'] . "/$grid/__SummaryInfo__";
  210. $rrd_graphite_link = $conf['graphite_rrd_dir'] . "/$grid/__SummaryInfo__";
  211. if (preg_match('/grid/i', $gridname))
  212. $title = $gridname;
  213. else
  214. $title = "$gridname Grid";
  215. break;
  216. case "cluster":
  217. $rrd_dir = $conf['rrds'] . "/$clustername/__SummaryInfo__";
  218. $rrd_graphite_link = $conf['graphite_rrd_dir'] . "/$clustername/__SummaryInfo__";
  219. if (preg_match('/cluster/i', $clustername))
  220. $title = $clustername;
  221. else
  222. $title = "$clustername Cluster";
  223. break;
  224. case "host":
  225. $rrd_dir = $conf['rrds'] . "/$clustername/$raw_host";
  226. $rrd_graphite_link = $conf['graphite_rrd_dir'] . "/" . $clustername . "/" . $host;
  227. // Add hostname to report graphs' title in host view
  228. if ($graph != 'metric')
  229. if ($conf['strip_domainname'])
  230. $title = strip_domainname($raw_host);
  231. else
  232. $title = $raw_host;
  233. break;
  234. default:
  235. break;
  236. }
  237. $resource = GangliaAcl::ALL_CLUSTERS;
  238. if( $context == "grid" ) {
  239. $resource = $grid;
  240. } else if ( $context == "cluster" || $context == "host" ) {
  241. $resource = $clustername;
  242. }
  243. if( ! checkAccess( $resource, GangliaAcl::VIEW, $conf ) ) {
  244. header( "HTTP/1.1 403 Access Denied" );
  245. header ("Content-type: image/jpg");
  246. echo file_get_contents( $gweb_root.'/img/access-denied.jpg');
  247. die();
  248. }
  249. if ($cs)
  250. $start = $cs;
  251. if ($ce)
  252. $end = $ce;
  253. # Set some standard defaults that don't need to change much
  254. $rrdtool_graph = array(
  255. 'start' => $start,
  256. 'end' => $end,
  257. 'width' => $width,
  258. 'height' => $height,
  259. );
  260. # automatically strip domainname from small graphs where it won't fit
  261. if ($size == "small") {
  262. $conf['strip_domainname'] = true;
  263. # Let load coloring work for little reports in the host list.
  264. if (! isset($subtitle) and $load_color)
  265. $rrdtool_graph['color'] = "BACK#'$load_color'";
  266. }
  267. if ($debug) {
  268. error_log("Graph [$graph] in context [$context]");
  269. }
  270. /* If we have $graph, then a specific report was requested, such as "network_report" or
  271. * "cpu_report. These graphs usually have some special logic and custom handling required,
  272. * instead of simply plotting a single metric. If $graph is not set, then we are (hopefully),
  273. * plotting a single metric, and will use the commands in the metric.php file.
  274. *
  275. * With modular graphs, we look for a "${graph}.php" file, and if it exists, we
  276. * source it, and call a pre-defined function name. The current scheme for the function
  277. * names is: 'graph_' + <name_of_report>. So a 'cpu_report' would call graph_cpu_report(),
  278. * which would be found in the cpu_report.php file.
  279. *
  280. * These functions take the $rrdtool_graph array as an argument. This variable is
  281. * PASSED BY REFERENCE, and will be modified by the various functions. Each key/value
  282. * pair represents an option/argument, as passed to the rrdtool program. Thus,
  283. * $rrdtool_graph['title'] will refer to the --title option for rrdtool, and pass the array
  284. * value accordingly.
  285. *
  286. * There are two exceptions to: the 'extras' and 'series' keys in $rrdtool_graph. These are
  287. * assigned to $extras and $series respectively, and are treated specially. $series will contain
  288. * the various DEF, CDEF, RULE, LINE, AREA, etc statements that actually plot the charts. The
  289. * rrdtool program requires that this come *last* in the argument string; we make sure that it
  290. * is put in it's proper place. The $extras variable is used for other arguemnts that may not
  291. * fit nicely for other reasons. Complicated requests for --color, or adding --ridgid, for example.
  292. * It is simply a way for the graph writer to add an arbitrary options when calling rrdtool, and to
  293. * forcibly override other settings, since rrdtool will use the last version of an option passed.
  294. * (For example, if you call 'rrdtool' with two --title statements, the second one will be used.)
  295. *
  296. * See ${conf['graphdir']}/sample.php for more documentation, and details on the
  297. * common variables passed and used.
  298. */
  299. // Calculate time range.
  300. if ($sourcetime)
  301. {
  302. $end = $sourcetime;
  303. # Get_context makes start negative.
  304. $start = $sourcetime + $start;
  305. }
  306. // Fix from Phil Radden, but step is not always 15 anymore.
  307. if ($range == "month")
  308. $rrdtool_graph['end'] = floor($rrdtool_graph['end'] / 672) * 672;
  309. ///////////////////////////////////////////////////////////////////////////////
  310. // Are we generating aggregate graphs
  311. ///////////////////////////////////////////////////////////////////////////////
  312. if ( isset( $_GET["aggregate"] ) && $_GET['aggregate'] == 1 ) {
  313. // Set start time
  314. $start = time() + $start;
  315. // If graph type is not specified default to line graph
  316. if ( isset($_GET["gtype"]) && in_array($_GET["gtype"], array("stack","line") ) )
  317. $graph_type = $_GET["gtype"];
  318. else
  319. $graph_type = "line";
  320. // If line width not specified default to 2
  321. if ( isset($_GET["lw"]) && in_array($_GET["lw"], array("1","2", "3") ) )
  322. $line_width = $_GET["lw"];
  323. else
  324. $line_width = "2";
  325. if ( isset($_GET["glegend"]) && in_array($_GET["glegend"], array("show", "hide") ) )
  326. $graph_legend = $_GET["glegend"];
  327. else
  328. $graph_legend = "show";
  329. /////////////////////////////////////////////////////////////////////////////
  330. // In order to reduce the load on the machine when someone is doing host
  331. // compare we look whether host list has been supplied via hl arg.
  332. // That way we do not have get metric cache
  333. /////////////////////////////////////////////////////////////////////////////
  334. if ( isset($_GET['hl']) ) {
  335. $counter = 0;
  336. $color_count = sizeof($conf['graph_colors']);
  337. $metric_name = str_replace("$", "", str_replace("^", "", $_GET['mreg'][0]));
  338. $host_list = explode(",", $_GET['hl']);
  339. foreach ( $host_list as $index => $host_cluster ) {
  340. $color_index = $counter % $color_count;
  341. $parts = explode("|", $host_cluster);
  342. $hostname = $parts[0];
  343. $clustername = $parts[1];
  344. $series = array("hostname" => $hostname,
  345. "clustername" => $clustername,
  346. "fill" => "true",
  347. "metric" => $metric_name,
  348. "color" => $conf['graph_colors'][$color_index],
  349. "label" => $hostname,
  350. "type" => $graph_type);
  351. if ($graph_type == "line" || $graph_type == "area") {
  352. $series['line_width'] = $line_width;
  353. } else {
  354. $series['stack'] = "1";
  355. }
  356. $graph_config['series'][] = $series;
  357. $counter++;
  358. } // end of foreach ( $host_list as
  359. } else {
  360. $exclude_host_from_legend_label =
  361. (array_key_exists('lgnd_xh', $_GET) &&
  362. $_GET['lgnd_xh'] == "true") ? TRUE : FALSE;
  363. $graph_config = build_aggregate_graph_config ($graph_type,
  364. $line_width,
  365. $_GET['hreg'],
  366. $_GET['mreg'],
  367. $graph_legend,
  368. $exclude_host_from_legend_label);
  369. }
  370. // Set up
  371. $graph_config["report_type"] = "standard";
  372. $graph_config["vertical_label"] = $vlabel;
  373. // Reset graph title
  374. if ( isset($_GET['title']) && $_GET['title'] != "") {
  375. $title = "";
  376. $graph_config["title"] = sanitize($_GET['title']);
  377. } else {
  378. $title = "Aggregate";
  379. }
  380. }
  381. //////////////////////////////////////////////////////////////////////////////
  382. // Check what graph engine we are using
  383. //////////////////////////////////////////////////////////////////////////////
  384. switch ( $conf['graph_engine'] ) {
  385. case "flot":
  386. case "rrdtool":
  387. if ( ! isset($graph_config) ) {
  388. if ( ($graph == "metric") &&
  389. isset($_GET['title']) &&
  390. $_GET['title'] !== '')
  391. $metrictitle = sanitize($_GET['title']);
  392. $php_report_file = $conf['graphdir'] . "/" . $graph . ".php";
  393. $json_report_file = $conf['graphdir'] . "/" . $graph . ".json";
  394. if( is_file( $php_report_file ) ) {
  395. include_once $php_report_file;
  396. $graph_function = "graph_${graph}";
  397. if (isset($graph_arguments))
  398. eval('$graph_function($rrdtool_graph,' . $graph_arguments . ');');
  399. else
  400. $graph_function( $rrdtool_graph ); // Pass by reference call, $rrdtool_graph modified inplace
  401. } else if ( is_file( $json_report_file ) ) {
  402. $graph_config = json_decode( file_get_contents( $json_report_file ), TRUE );
  403. # We need to add hostname and clustername if it's not specified
  404. foreach ( $graph_config['series'] as $index => $item ) {
  405. if ( ! isset($graph_config['series'][$index]['hostname'])) {
  406. $graph_config['series'][$index]['hostname'] = $raw_host;
  407. if (isset($grid))
  408. $graph_config['series'][$index]['clustername'] = $grid;
  409. else
  410. $graph_config['series'][$index]['clustername'] = $clustername;
  411. }
  412. }
  413. build_rrdtool_args_from_json ( $rrdtool_graph, $graph_config );
  414. }
  415. } else {
  416. build_rrdtool_args_from_json ( $rrdtool_graph, $graph_config );
  417. }
  418. // We must have a 'series' value, or this is all for naught
  419. if (!array_key_exists('series', $rrdtool_graph) ||
  420. !strlen($rrdtool_graph['series']) ) {
  421. $rrdtool_graph[ 'series' ] =
  422. 'HRULE:1#FFCC33:"No matching metrics detected"';
  423. }
  424. # Make small graphs (host list) cleaner by removing the too-big
  425. # legend: it is displayed above on larger cluster summary graphs.
  426. if (($size == "small" and ! isset($subtitle)) || ($graph_config["glegend"] == "hide"))
  427. $rrdtool_graph['extras'] = "-g";
  428. # add slope-mode if rrdtool_slope_mode is set
  429. if (isset($conf['rrdtool_slope_mode']) &&
  430. $conf['rrdtool_slope_mode'] == True)
  431. $rrdtool_graph['slope-mode'] = '';
  432. if (isset($rrdtool_graph['title']) && isset($title)) {
  433. if ($conf['decorated_graph_title'])
  434. $rrdtool_graph['title'] = $title . " " .
  435. $rrdtool_graph['title'] .
  436. " last $range";
  437. else
  438. $rrdtool_graph['title'] = $rrdtool_graph['title'];
  439. }
  440. $command = $conf['rrdtool'] . " graph - $rrd_options ";
  441. // Look ahead six months
  442. if ( $user['trend_line'] ) {
  443. // We may only want to use last x months of data since for example
  444. // if we are trending disk we may have added a disk recently which will
  445. // skew a trend line. By default we'll use 6 months however we'll let
  446. // user define this if they want to.
  447. $rrdtool_graph['start'] = "-" . $user['trend_history'] * 2592000 . "s";
  448. // Project the trend line this many months ahead
  449. $rrdtool_graph['end'] = "+" . $user["trend_range"] * 2592000 . "s";
  450. }
  451. if ( $max ) {
  452. $rrdtool_graph['upper-limit'] = $max;
  453. }
  454. if ( $min )
  455. $rrdtool_graph['lower-limit'] = $min;
  456. if ( $max || $min )
  457. $rrdtool_graph['extras'] = isset($rrdtool_graph['extras']) ? $rrdtool_graph['extras'] . " --rigid" : " --rigid" ;
  458. // The order of the other arguments isn't important, except for the
  459. // 'extras' and 'series' values. These two require special handling.
  460. // Otherwise, we just loop over them later, and tack $extras and
  461. // $series onto the end of the command.
  462. foreach (array_keys ($rrdtool_graph) as $key) {
  463. if (preg_match('/extras|series/', $key))
  464. continue;
  465. $value = $rrdtool_graph[$key];
  466. if (preg_match('/\W/', $value)) {
  467. //more than alphanumerics in value, so quote it
  468. $value = "'$value'";
  469. }
  470. $command .= " --$key $value";
  471. }
  472. // And finish up with the two variables that need special handling.
  473. // See above for how these are created
  474. $command .= array_key_exists('extras', $rrdtool_graph) ? ' '.$rrdtool_graph['extras'].' ' : '';
  475. $command .= " $rrdtool_graph[series]";
  476. break;
  477. /////////////////////////////////////////////////////////////////////////////
  478. // USING Graphite
  479. /////////////////////////////////////////////////////////////////////////////
  480. case "graphite":
  481. // Check whether the link exists from Ganglia RRD tree to the graphite
  482. // storage/rrd_dir area
  483. if ( ! is_link($rrd_graphite_link) ) {
  484. // Does the directory exist for the cluster. If not create it
  485. if ( ! is_dir ($conf['graphite_rrd_dir'] . "/" . str_replace(" ", "_", $clustername)) )
  486. mkdir ( $conf['graphite_rrd_dir'] . "/" . str_replace(" ", "_", $clustername ));
  487. symlink($rrd_dir, str_replace(" ", "_", $rrd_graphite_link));
  488. }
  489. // Generate host cluster string
  490. if ( isset($clustername) ) {
  491. $host_cluster = str_replace(" ", "_", $clustername) . "." . $host;
  492. } else {
  493. $host_cluster = $host;
  494. }
  495. $height += 70;
  496. if ($size == "small") {
  497. $width += 20;
  498. }
  499. // $title = urlencode($rrdtool_graph["title"]);
  500. // If graph_config is already set we can use it immediately
  501. if ( isset($graph_config) ) {
  502. $target = build_graphite_series( $graph_config, "" );
  503. } else {
  504. if ( isset($_GET['g'])) {
  505. // if it's a report increase the height for additional 30 pixels
  506. $height += 40;
  507. $report_name = sanitize($_GET['g']);
  508. $report_definition_file = $conf['gweb_root'] . "/graph.d/" . $report_name . ".json";
  509. // Check whether report is defined in graph.d directory
  510. if ( is_file($report_definition_file) ) {
  511. $graph_config = json_decode(file_get_contents($report_definition_file), TRUE);
  512. } else {
  513. error_log("There is JSON config file specifying $report_name.");
  514. exit(1);
  515. }
  516. if ( isset($graph_config) ) {
  517. switch ( $graph_config["report_type"] ) {
  518. case "template":
  519. $target = str_replace("HOST_CLUSTER", $conf['graphite_prefix'] . $host_cluster, $graph_config["graphite"]);
  520. break;
  521. case "standard":
  522. $target = build_graphite_series( $graph_config, $conf['graphite_prefix'] . $host_cluster );
  523. break;
  524. default:
  525. error_log("No valid report_type specified in the " .
  526. $report_name .
  527. " definition.");
  528. break;
  529. }
  530. $title = $graph_config['title'];
  531. } else {
  532. error_log("Configuration file to $report_name exists however it doesn't appear it's a valid JSON file");
  533. exit(1);
  534. }
  535. } else {
  536. // It's a simple metric graph
  537. $target = "target=" . $conf['graphite_prefix'] . "$host_cluster.$metric_name.sum&hideLegend=true&vtitle=" . urlencode($vlabel) . "&areaMode=all&colorList=". $conf['default_metric_color'];
  538. $title = " ";
  539. }
  540. } // end of if ( ! isset($graph_config) ) {
  541. if ($cs) $start = date("H:i_Ymd",strtotime($cs));
  542. if ($ce) $end = date("H:i_Ymd",strtotime($ce));
  543. if ($max == 0) $max = "";
  544. $graphite_url = $conf['graphite_url_base'] . "?width=$width&height=$height&" . $target . "&from=" . $start . "&until=" . $end . "&yMin=" . $min . "&yMax=" . $max . "&bgcolor=FFFFFF&fgcolor=000000&title=" . urlencode($title . " last " . $range);
  545. break;
  546. } // end of switch ( $conf['graph_engine'])
  547. // Output to JSON
  548. if ( $user['json_output'] ||
  549. $user['csv_output'] ||
  550. $user['flot_output'] ||
  551. $user['graphlot_output'] ) {
  552. if ($conf['graph_engine'] == "graphite") {
  553. if ( $user['json_output'] == 1 ) { $output_format = "json"; }
  554. elseif ( $user['csv_output'] == 1 ) { $output_format = "csv"; }
  555. echo file_get_contents($graphite_url . "&format=" . $output_format);
  556. } else {
  557. $rrdtool_graph_args = "";
  558. // First find RRDtool DEFs by parsing $rrdtool_graph['series']
  559. preg_match_all("/([^V]DEF|CDEF):(.*)(:AVERAGE|\s)/",
  560. " " . $rrdtool_graph['series'],
  561. $matches);
  562. foreach ( $matches[0] as $key => $value ) {
  563. $rrdtool_graph_args .= $value . " ";
  564. }
  565. preg_match_all("/(LINE[0-9]*|AREA|STACK):\'[^']*\'[^']*\'[^']*\'[^ ]* /",
  566. " " . $rrdtool_graph['series'],
  567. $matches);
  568. foreach ( $matches[0] as $key => $value ) {
  569. if ( preg_match("/(LINE[0-9]*:\'|AREA:\'|STACK:\')([^']*)(\')([^']*)(\')([^']*)(')/", $value, $out ) ) {
  570. $ds_name = $out[2];
  571. $cluster_name = "";
  572. $host_name = "";
  573. $metric_type = "line";
  574. if (preg_match("/(STACK:|AREA:)/", $value, $ignore)) {
  575. $metric_type = "stack";
  576. }
  577. $metric_name = $out[6];
  578. $output_array[] = array( "ds_name" => $ds_name,
  579. "cluster_name" => $cluster_name,
  580. "graph_type" => $metric_type,
  581. "host_name" => $host_name,
  582. "metric_name" => $metric_name );
  583. $rrdtool_graph_args .= " " . "XPORT:'" . $ds_name . "':'" . $metric_name . "' ";
  584. }
  585. }
  586. // This command will export values for the specified format in XML
  587. $command = $conf['rrdtool'] . " xport --start " . $rrdtool_graph['start'] . " --end " . $rrdtool_graph['end'] . " " . $rrdtool_graph_args;
  588. // Read in the XML
  589. $fp = popen($command,"r");
  590. $string = "";
  591. while (!feof($fp)) {
  592. $buffer = fgets($fp, 4096);
  593. $string .= $buffer;
  594. }
  595. // Parse it
  596. $xml = simplexml_load_string($string);
  597. # If there are multiple metrics columns will be > 1
  598. $num_of_metrics = $xml->meta->columns;
  599. //
  600. $metric_values = array();
  601. // Build the metric_values array
  602. foreach ( $xml->data->row as $key => $objects ) {
  603. $values = get_object_vars($objects);
  604. // If $values["v"] is an array we have multiple data sources/metrics and
  605. // we need to iterate over those
  606. if ( is_array($values["v"]) ) {
  607. foreach ( $values["v"] as $key => $value ) {
  608. $output_array[$key]["datapoints"][] =
  609. array(floatval($value), intval($values['t']));
  610. }
  611. } else {
  612. $output_array[0]["datapoints"][] =
  613. array(floatval($values["v"]), intval($values['t']));
  614. }
  615. }
  616. // If JSON output request simple encode the array as JSON
  617. if ( $user['json_output'] ) {
  618. header("Content-type: application/json");
  619. header("Content-Disposition: inline; filename=\"ganglia-metrics.json\"");
  620. print json_encode($output_array);
  621. }
  622. // If Flot output massage the data JSON
  623. if ( $user['flot_output'] ) {
  624. foreach ( $output_array as $key => $metric_array ) {
  625. foreach ( $metric_array['datapoints'] as $key => $values ) {
  626. $data_array[] = array ( $values[1]*1000, $values[0]);
  627. }
  628. $gdata = array('label' => strip_domainname($metric_array['host_name']) .
  629. " " .
  630. $metric_array['metric_name'],
  631. 'data' => $data_array);
  632. if ($metric_array['graph_type'] == "stack")
  633. $gdata['stack'] = '1';
  634. $flot_array[] = $gdata;
  635. unset($data_array);
  636. }
  637. header("Content-type: application/json");
  638. print json_encode($flot_array);
  639. }
  640. if ( $user['csv_output'] ) {
  641. header("Content-Type: application/csv");
  642. header("Content-Disposition: inline; filename=\"ganglia-metrics.csv\"");
  643. print "Timestamp";
  644. // Print out headers
  645. for ( $i = 0 ; $i < sizeof($output_array) ; $i++ ) {
  646. print "," . $output_array[$i]["metric_name"];
  647. }
  648. print "\n";
  649. foreach ( $output_array[0]['datapoints'] as $key => $row ) {
  650. print date("c", $row[1]);
  651. for ( $j = 0 ; $j < $num_of_metrics ; $j++ ) {
  652. print "," . $output_array[$j]["datapoints"][$key][0];
  653. }
  654. print "\n";
  655. }
  656. }
  657. // Implement Graphite style Raw Data
  658. if ( $user['graphlot_output'] ) {
  659. header("Content-Type: application/json");
  660. $last_index = sizeof($output_array[0]["datapoints"]) - 1;
  661. $output_vals['step'] = $output_array[0]["datapoints"][1][1] - $output_array[0]["datapoints"][0][1];
  662. $output_vals['name'] = "stats." . $output_array[0]["metric_name"];
  663. $output_vals['start'] = $output_array[0]["datapoints"][0][1];
  664. $output_vals['end'] = $output_array[0]["datapoints"][$last_index][1];
  665. foreach ( $output_array[0]["datapoints"] as $index => $array ) {
  666. $output_vals['data'][] = $array[0];
  667. }
  668. print json_encode(array($output_vals, $output_vals));
  669. }
  670. }
  671. exit(0);
  672. }
  673. //////////////////////////////////////////////////////////////////////////////
  674. // Nagios event integration support
  675. //////////////////////////////////////////////////////////////////////////////
  676. $nagios_events = array();
  677. if ( $showEvents == "show" &&
  678. $conf['overlay_nagios_events'] &&
  679. ! in_array($range, $conf['overlay_events_exclude_ranges']) ) {
  680. $nagios_pull_url =
  681. $conf['overlay_nagios_base_url'] .
  682. '/cgi-bin/api.cgi?action=host.gangliaevents&host=' . urlencode($raw_host) .
  683. '&start=' . urlencode($start) .
  684. '&end=' . urlencode($end);
  685. $raw_nagios_events =
  686. @file_get_contents(
  687. $nagios_pull_url,
  688. 0,
  689. stream_context_create(
  690. array('http' => array('timeout' => 5),
  691. 'https' => array('timeout' => 5))));
  692. if (strlen($raw_nagios_events) > 3) {
  693. $nagios_events = json_decode( $raw_nagios_events, TRUE );
  694. // Handle any "ERROR" formatted messages and wipe resulting array.
  695. if (isset($nagios_events['response_type']) &&
  696. $nagios_events['response_type'] == 'ERROR') {
  697. $nagios_events = array();
  698. }
  699. }
  700. }
  701. //////////////////////////////////////////////////////////////////////////////
  702. // Check whether user wants to overlay events on graphs
  703. //////////////////////////////////////////////////////////////////////////////
  704. if ( $showEvents == "show" &&
  705. $conf['overlay_events'] &&
  706. $conf['graph_engine'] == "rrdtool" &&
  707. ! in_array($range, $conf['overlay_events_exclude_ranges']) && ! $user['trend_line'] ) {
  708. $color_count = sizeof($conf['graph_colors']);
  709. $counter = 0;
  710. $color_counter = 0;
  711. // In order not to pollute the command line with all the possible VRULEs
  712. // we need to find the time range for the graph
  713. if ( $rrdtool_graph['end'] == "-now" or $rrdtool_graph['end'] == "now") {
  714. $end = time();
  715. } else if ( is_numeric($rrdtool_graph['end']) ) {
  716. $end = $rrdtool_graph['end'];
  717. }
  718. if ( preg_match("/\-([0-9]*)(s)/", $rrdtool_graph['start'] , $out ) ) {
  719. $start = time() - $out[1];
  720. } else if ( is_numeric($rrdtool_graph['start']) ) {
  721. $start = $rrdtool_graph['start'];
  722. } else {
  723. // If it's not
  724. $start = time() - 157680000;
  725. }
  726. // Get array of events for time range
  727. $events_array = ganglia_events_get($start, $end);
  728. if (!empty($events_array)) {
  729. $event_color_json =
  730. file_get_contents($conf['overlay_events_color_map_file']);
  731. if ($debug)
  732. error_log("$event_color_json");
  733. $event_color_array = json_decode($event_color_json, TRUE);
  734. $initial_event_color_count = count($event_color_array);
  735. $event_color_map = array();
  736. foreach ($event_color_array as $event_color_entry) {
  737. $event_color_map[$event_color_entry['summary']] =
  738. $event_color_entry['color'];
  739. if ($debug)
  740. error_log("Adding event color to map: " .
  741. $event_color_entry['summary'] .
  742. ' ' .
  743. $event_color_entry['color']);
  744. }
  745. $color_count = sizeof($conf['graph_colors']);
  746. $counter = 0;
  747. // In order not to pollute the command line with all the possible VRULEs
  748. // we need to find the time range for the graph
  749. if ( $rrdtool_graph['end'] == "-now" or $rrdtool_graph['end'] == "now")
  750. $end = time();
  751. else if ( is_numeric($rrdtool_graph['end']) )
  752. $end = $rrdtool_graph['end'];
  753. else
  754. error_log("Graph does not have a specified end time");
  755. if ( preg_match("/\-([0-9]*)(s)/", $rrdtool_graph['start'] , $out ) ) {
  756. $start = time() - $out[1];
  757. } else if ( is_numeric($rrdtool_graph['start']) )
  758. $start = $rrdtool_graph['start'];
  759. else
  760. // If it's not
  761. $start = time() - 157680000;
  762. // Preserve original rrdtool command. That's the one we'll run regex checks
  763. // against
  764. $original_command = $command;
  765. // Combine the nagios_events array, if it exists
  766. if (count($nagios_events) > 0) {
  767. // World's dumbest array merge:
  768. foreach ($nagios_events AS $ne) {
  769. $events_array[] = $ne;
  770. }
  771. }
  772. foreach ($events_array as $key => $row) {
  773. $start_time[$key] = $row['start_time'];
  774. }
  775. // Sort events in reverse chronological order
  776. array_multisort($start_time, SORT_DESC, $events_array);
  777. // Default to dashed line unless events_line_type is set to solid
  778. if ( $conf['overlay_events_line_type'] == "solid" )
  779. $overlay_events_line_type = "";
  780. else
  781. $overlay_events_line_type = ":dashes";
  782. // Loop through all the events
  783. $legend_items = array();
  784. foreach ( $events_array as $id => $event) {
  785. $evt_start = $event['start_time'];
  786. // Make sure it's a number
  787. if ( ! is_numeric($evt_start) ) {
  788. continue;
  789. }
  790. unset($evt_end);
  791. if (array_key_exists('end_time', $event) &&
  792. is_numeric($event['end_time']) ) {
  793. $evt_end = $event['end_time'];
  794. }
  795. // If event start is less than start bail out of the loop since
  796. // there is nothing more to do since events are sorted in reverse
  797. // chronological order and these events are not gonna show up in
  798. // the graph
  799. $in_graph = (($evt_start >= $start) && ($evt_start <= $end)) ||
  800. (isset($evt_end) &&
  801. ($evt_end >= $start) &&
  802. ($evt_start <= $end));
  803. if (!$in_graph) {
  804. if ($debug)
  805. error_log("Event [$evt_start] does not overlap with graph [$start, $end]");
  806. continue;
  807. }
  808. // Compute the part of the event to be displayed
  809. $evt_start_in_graph_range = TRUE;
  810. if ($evt_start < $start) {
  811. $evt_start = $start;
  812. $evt_start_in_graph_range = FALSE;
  813. }
  814. $evt_end_in_graph_range = TRUE;
  815. if (isset($evt_end)) {
  816. if ($evt_end > $end) {
  817. $evt_end = $end;
  818. $evt_end_in_graph_range = FALSE;
  819. }
  820. } else
  821. $evt_end_in_graph_range = FALSE;
  822. if ( preg_match("/" . $event["host_regex"] . "/", $original_command)) {
  823. if ( $evt_start >= $start ) {
  824. // Do we have the end timestamp.
  825. if ( !isset($end) || ( $evt_start < $end ) || 'N' == $end ) {
  826. // This is a potential vector since this gets added to the
  827. // command line_width TODO: Look over sanitize
  828. $summary =
  829. isset($event['summary']) ? sanitize($event['summary']) : "";
  830. // We need to keep track of summaries so that if we have identical
  831. // summaries e.g. Deploy we can use the same color
  832. if ( array_key_exists($summary, $event_color_map) ) {
  833. $color = $event_color_map[$summary];
  834. if ($debug)
  835. error_log("Found existing color: $summary $color");
  836. // Reset summary to empty string if it is already present in
  837. // the legend
  838. if (array_key_exists($summary, $legend_items))
  839. $summary = "";
  840. else
  841. $legend_items[$summary] = TRUE;
  842. } else {
  843. // Haven't seen this summary before. Assign it a color
  844. $color_index = count($event_color_map) % $color_count;
  845. $color = $conf['graph_colors'][$color_index];
  846. $event_color_map[$summary] = $color;
  847. $event_color_array[] = array('summary' => $summary,
  848. 'color' => $color);
  849. if ($debug)
  850. error_log("Adding new event color: $summary $color");
  851. }
  852. if (isset($evt_end)) {
  853. # Attempt to draw a shaded area between start and end points.
  854. # Force solid line for ranges
  855. $overlay_events_line_type = "";
  856. $start_vrule = '';
  857. if ($evt_start_in_graph_range)
  858. $start_vrule = " VRULE:" . $evt_start .
  859. "#$color" . $conf['overlay_events_tick_alpha'] .
  860. ":\"" . $summary . "\"" . $overlay_events_line_type;
  861. $end_vrule = '';
  862. if ($evt_end_in_graph_range)
  863. $end_vrule = " VRULE:" . $evt_end .
  864. "#$color" . $conf['overlay_events_tick_alpha'] .
  865. ':""' . $overlay_events_line_type;
  866. # We need a dummpy DEF statement, because RRDtool is too stupid
  867. # to plot graphs without a DEF statement.
  868. # We can't count on a static name, so we have to "find" one.
  869. if (preg_match("/DEF:['\"]?(\w+)['\"]?=/", $command, $matches)) {
  870. # stupid rrdtool limitation.
  871. $area_cdef =
  872. " CDEF:area_$counter=$matches[1],POP," .
  873. "TIME,$evt_start,GT,1,UNKN,IF,TIME,$evt_end,LT,1,UNKN,IF,+";
  874. $area_shade = $color . $conf['overlay_events_shade_alpha'];
  875. $area = " TICK:area_$counter#$area_shade:1";
  876. if (!$evt_start_in_graph_range)
  877. $area .= ':"' . $summary . '"';
  878. $command .= "$area_cdef $area $start_vrule $end_vrule";
  879. } else {
  880. error_log("No DEF statements found in \$command?!");
  881. }
  882. } else {
  883. $command .= " VRULE:" . $evt_start . "#" . $color .
  884. ":\"" . $summary . "\"" . $overlay_events_line_type;
  885. }
  886. $counter++;
  887. } else {
  888. if ($debug)
  889. error_log("Event start [$evt_start] >= graph end [$end]");
  890. }
  891. } else {
  892. if ($debug)
  893. error_log("Event start [$evt_start] < graph start [$start]");
  894. }
  895. } // end of if ( preg_match ...
  896. else {
  897. //error_log("Doesn't match host_regex");
  898. }
  899. } // end of foreach ( $events_array ...
  900. unset($events_array);
  901. if (count($event_color_array) > $initial_event_color_count) {
  902. $event_color_json = json_encode($event_color_array);
  903. file_put_contents($conf['overlay_events_color_map_file'],
  904. $event_color_json);
  905. }
  906. } //End check for array
  907. }
  908. // Add a trend line
  909. if ( $user['trend_line'] ) {
  910. $command .= " VDEF:D2=sum,LSLSLOPE VDEF:H2=sum,LSLINT CDEF:avg2=sum,POP,D2,COUNT,*,H2,+";
  911. $command .= " 'LINE3:avg2#53E2FF:Trend:dashes'";
  912. }
  913. if ($debug) {
  914. error_log("Final rrdtool command: $command");
  915. }
  916. # Did we generate a command? Run it.
  917. if($command || $graphite_url) {
  918. /*Make sure the image is not cached*/
  919. header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
  920. header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
  921. header ("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
  922. header ("Pragma: no-cache"); // HTTP/1.0
  923. if ($debug > 2) {
  924. header ("Content-type: text/html");
  925. print "<html><body>";
  926. switch ( $conf['graph_engine'] ) {
  927. case "flot":
  928. case "rrdtool":
  929. print htmlentities( $command );
  930. break;
  931. case "graphite":
  932. print $graphite_url;
  933. break;
  934. }
  935. print "</body></html>";
  936. } else {
  937. header ("Content-type: image/png");
  938. switch ( $conf['graph_engine'] ) {
  939. case "flot":
  940. case "rrdtool":
  941. if (strlen($command) < 100000) {
  942. passthru($command);
  943. } else {
  944. $tf = tempnam("/tmp", "ganglia-graph");
  945. file_put_contents($tf, $command);
  946. passthru("/bin/bash $tf");
  947. unlink($tf);
  948. }
  949. break;
  950. case "graphite":
  951. echo file_get_contents($graphite_url);
  952. break;
  953. }
  954. }
  955. }
  956. ?>