1<?php
2
3#
4# Some common functions for the Ganglia PHP website.
5# Assumes the Gmeta XML tree has already been parsed,
6# and the global variables $metrics, $clusters, and $hosts
7# have been set.
8#
9
10include_once ( dirname(__FILE__) . "/lib/json.php" );
11
12#
13# Load event API driver.
14#
15$driver = ucfirst(strtolower( !isset($conf['overlay_events_provider']) ? "Json" : $conf['overlay_events_provider'] ));
16if (file_exists( dirname(__FILE__) . "/lib/Events/Driver_${driver}.php")) {
17  include_once( dirname(__FILE__) . "/lib/Events/Driver_${driver}.php" );
18}
19
20#------------------------------------------------------------------------------
21# Allows a form of inheritance for template files.
22# If a file does not exist in the chosen template, the
23# default is used. Cuts down on code duplication.
24function template ($name)
25{
26   global $conf;
27
28   $fn = "./templates/${conf['template_name']}/$name";
29   $default = "./templates/default/$name";
30
31   if (file_exists($fn)) {
32      return $fn;
33   }
34   else {
35      return $default;
36   }
37}
38
39#------------------------------------------------------------------------------
40# Creates a hidden input field in a form. Used to save CGI variables.
41function hiddenvar ($name, $var)
42{
43
44   $hidden = "";
45   if ($var) {
46      #$url = rawurlencode($var);
47      $hidden = "<input type=\"hidden\" name=\"$name\" value=\"$var\">\n";
48   }
49   return $hidden;
50}
51
52#------------------------------------------------------------------------------
53# Gives a readable time string, from a "number of seconds" integer.
54# Often used to compute uptime.
55function uptime($uptimeS)
56{
57   $uptimeD=intval($uptimeS/86400);
58   $uptimeS=$uptimeD ? $uptimeS % ($uptimeD*86400) : $uptimeS;
59   $uptimeH=intval($uptimeS/3600);
60   $uptimeS=$uptimeH ? $uptimeS % ($uptimeH*3600) : $uptimeS;
61   $uptimeM=intval($uptimeS/60);
62   $uptimeS=$uptimeM ? $uptimeS % ($uptimeM*60) : $uptimeS;
63
64   $s = ($uptimeD!=1) ? "s" : "";
65   return sprintf("$uptimeD day$s, %d:%02d:%02d",$uptimeH,$uptimeM,$uptimeS);
66}
67
68#------------------------------------------------------------------------------
69# Try to determine a nodes location in the cluster. Attempts to find the
70# LOCATION attribute first. Requires the host attribute array from
71# $hosts[$cluster][$name], where $name is the hostname.
72# Returns [-1,-1,-1] if we could not determine location.
73#
74function findlocation($attrs)
75{
76   $rack=$rank=$plane=-1;
77
78   $loc=$attrs['LOCATION'];
79   if ($loc) {
80      sscanf($loc, "%d,%d,%d", $rack, $rank, $plane);
81      #echo "Found LOCATION: $rack, $rank, $plane.<br>";
82   }
83   if ($rack<0 or $rank<0) {
84      # Try to parse the host name. Assumes a compute-<rack>-<rank>
85      # naming scheme.
86      $n=sscanf($attrs['NAME'], "compute-%d-%d", $rack, $rank);
87      $plane=0;
88   }
89   return array($rack,$rank,$plane);
90}
91
92
93#------------------------------------------------------------------------------
94function cluster_sum($name, $metrics)
95{
96   $sum = 0;
97
98   foreach ($metrics as $host => $val)
99      {
100         if(isset($val[$name]['VAL'])) $sum += $val[$name]['VAL'];
101      }
102
103   return $sum;
104}
105
106#------------------------------------------------------------------------------
107function cluster_min($name, $metrics)
108{
109   $min = "";
110
111   foreach ($metrics as $host => $val)
112      {
113         $v = $val[$name]['VAL'];
114         if (!is_numeric($min) or $min < $v)
115            {
116               $min = $v;
117               $minhost = $host;
118            }
119      }
120   return array($min, $minhost);
121}
122
123#------------------------------------------------------------------------------
124#
125# A useful function for giving the correct picture for a given
126# load. Scope is "node | cluster | grid". Value is 0 <= v <= 1.
127function load_image ($scope, $value)
128{
129   global $conf;
130
131   $scaled_load = $value / $conf['load_scale'];
132   if ($scaled_load>1.00) {
133      $image = template("images/${scope}_overloaded.jpg");
134   }
135   else if ($scaled_load>=0.75) {
136      $image = template("images/${scope}_75-100.jpg");
137   }
138   else if ($scaled_load >= 0.50) {
139      $image = template("images/${scope}_50-74.jpg");
140   }
141   else if ($scaled_load>=0.25) {
142      $image = template("images/${scope}_25-49.jpg");
143   }
144   else {
145      $image = template("images/${scope}_0-24.jpg");
146   }
147
148   return $image;
149}
150
151#------------------------------------------------------------------------------
152# A similar function that specifies the background color for a graph
153# based on load. Quantizes the load figure into 6 sets.
154function load_color ($value)
155{
156   global $conf;
157
158   $scaled_load = $value / $conf['load_scale'];
159   if ($scaled_load>1.00) {
160      $color = $conf['load_colors']["100+"];
161   }
162   else if ($scaled_load>=0.75) {
163      $color = $conf['load_colors']["75-100"];
164   }
165   else if ($scaled_load >= 0.50) {
166      $color = $conf['load_colors']["50-75"];
167   }
168   else if ($scaled_load>=0.25) {
169      $color = $conf['load_colors']["25-50"];
170   }
171   else if ($scaled_load < 0.0)
172      $color = $conf['load_colors']["down"];
173   else {
174      $color = $conf['load_colors']["0-25"];
175   }
176
177   return $color;
178}
179
180#------------------------------------------------------------------------------
181#
182# Just a useful function to print the HTML for
183# the load/death of a cluster node
184function node_image ($metrics)
185{
186   global $hosts_down;
187
188   # More rigorous checking if variables are set before trying to use them.
189   if ( isset($metrics['cpu_num']['VAL']) and $metrics['cpu_num']['VAL'] != 0 ) {
190		$cpu_num = $metrics['cpu_num']['VAL'];
191   } else {
192		$cpu_num = 1;
193   }
194
195   if ( isset($metrics['load_one']['VAL']) ) {
196		$load_one = $metrics['load_one']['VAL'];
197   } else {
198		$load_one = 0;
199   }
200
201   $value = $load_one / $cpu_num;
202
203   # Check if the host is down
204   # RFM - Added isset() check to eliminate error messages in ssl_error_log
205   if (isset($hosts_down) and $hosts_down)
206         $image = template("images/node_dead.jpg");
207   else
208         $image = load_image("node", $value);
209
210   return $image;
211}
212
213#------------------------------------------------------------------------------
214#
215# Finds the min/max over a set of metric graphs. Nodes is
216# an array keyed by host names.
217#
218function find_limits($clustername,
219		     $nodes,
220		     $metricname,
221		     $start,
222		     $end,
223		     $metrics,
224		     $conf,
225		     $rrd_options) {
226  if (!count($metrics))
227    return array(0, 0);
228
229  $firsthost = key($metrics);
230
231  if (array_key_exists($metricname, $metrics[$firsthost])) {
232    if ($metrics[$firsthost][$metricname]['TYPE'] == "string"
233        or $metrics[$firsthost][$metricname]['SLOPE'] == "zero")
234      return array(0,0);
235  } else {
236    return array(0,0);
237  }
238
239  $max = 0;
240  $min = 0;
241  if ($conf['graph_engine'] == "graphite") {
242    $target = $conf['graphite_prefix'] .
243      $clustername . ".[a-zA-Z0-9]*." . $metricname . ".sum";
244    $raw_highestMax = file_get_contents($conf['graphite_url_base'] . "?target=highestMax(" . $target . ",1)&from=" . $start . "&until=" . $end . "&format=json");
245    $highestMax = json_decode($raw_highestMax, TRUE);
246    $highestMaxDatapoints = $highestMax[0]['datapoints'];
247    $maxdatapoints = array();
248    foreach ($highestMaxDatapoints as $datapoint) {
249      array_push($maxdatapoints, $datapoint[0]);
250    }
251    $max = max($maxdatapoints);
252  } else {
253    foreach (array_keys($nodes) as $host) {
254      $rrd_dir = "{$conf['rrds']}/$clustername/$host";
255      $rrd_file = "$rrd_dir/$metricname.rrd";
256      if (file_exists($rrd_file)) {
257	if (extension_loaded('rrd')) {
258	  $values = rrd_fetch($rrd_file,
259			      array("--start", $start,
260				    "--end", $end,
261				    "AVERAGE"));
262
263	  $values = (array_filter(array_values($values['data']['sum']),
264				  'is_finite'));
265	  $thismax = max($values);
266	  $thismin = min($values);
267	} else {
268	  $command = $conf['rrdtool'] . " graph /dev/null $rrd_options ".
269	    "--start '$start' --end '$end' ".
270	    "DEF:limits='$rrd_dir/$metricname.rrd':'sum':AVERAGE ".
271	    "PRINT:limits:MAX:%.2lf ".
272	    "PRINT:limits:MIN:%.2lf";
273	  $out = array();
274	  exec($command, $out);
275	  if (isset($out[1])) {
276	    $thismax = $out[1];
277	  } else {
278	    $thismax = NULL;
279	  }
280	  if (!is_numeric($thismax))
281	    continue;
282	  $thismin = $out[2];
283	  if (!is_numeric($thismin))
284	    continue;
285	}
286
287	if ($max < $thismax)
288	  $max = $thismax;
289
290	if ($min > $thismin)
291	  $min = $thismin;
292	//echo "$host: $thismin - $thismax<br>\n";
293      }
294    }
295  }
296  return array($min, $max);
297}
298
299#------------------------------------------------------------------------------
300#
301# Finds the avg of the given cluster & metric from the summary rrds.
302#
303function find_avg($clustername, $hostname, $metricname)
304{
305    global $conf, $start, $end, $rrd_options;
306    $avg = 0;
307
308    if ($hostname)
309        $sum_dir = "${conf['rrds']}/$clustername/$hostname";
310    else
311        $sum_dir = "${conf['rrds']}/$clustername/__SummaryInfo__";
312
313    $command = $conf['rrdtool'] . " graph /dev/null $rrd_options ".
314        "--start $start --end $end ".
315        "DEF:avg='$sum_dir/$metricname.rrd':'sum':AVERAGE ".
316        "PRINT:avg:AVERAGE:%.2lf ";
317    exec($command, $out);
318    if ( isset($out[1]) )
319      $avg = $out[1];
320    else
321      $avg = 0;
322    #echo "$sum_dir: avg($metricname)=$avg<br>\n";
323    return $avg;
324}
325
326#------------------------------------------------------------------------------
327# Alternate between even and odd row styles.
328function rowstyle()
329{
330   static $style;
331
332   if ($style == "even") { $style = "odd"; }
333   else { $style = "even"; }
334
335   return $style;
336}
337
338#------------------------------------------------------------------------------
339# Return a version of the string which is safe for display on a web page.
340# Potentially dangerous characters are converted to HTML entities.
341# Resulting string is not URL-encoded.
342function clean_string( $string )
343{
344  return htmlentities( $string );
345}
346#------------------------------------------------------------------------------
347function sanitize ( $string ) {
348  return  escapeshellcmd( clean_string( rawurldecode( $string ) ) ) ;
349}
350
351#------------------------------------------------------------------------------
352# If arg is a valid number, return it.  Otherwise, return null.
353function clean_number( $value )
354{
355  return is_numeric( $value ) ? $value : null;
356}
357
358#------------------------------------------------------------------------------
359# Return true if string is a 3 or 6 character hex color.Return false otherwise.
360function is_valid_hex_color( $string )
361{
362  $return_value = false;
363  if( strlen( $string ) == 6 || strlen( $string ) == 3 ) {
364    if( preg_match( '/^[0-9a-fA-F]+$/', $string ) ) {
365      $return_value = true;
366    }
367  }
368  return $return_value;
369
370}
371
372#------------------------------------------------------------------------------
373# Allowed view name characters are alphanumeric plus space, dash and underscore
374function is_proper_view_name( $string )
375{
376  if(preg_match("/[^a-zA-z0-9_\-\ ]/", $string)){
377    return false;
378  } else {
379    return true;
380  }
381}
382
383
384#------------------------------------------------------------------------------
385# Return a shortened version of a FQDN
386# if "hostname" is numeric only, assume it is an IP instead
387#
388function strip_domainname( $hostname ) {
389    $postition = strpos($hostname, '.');
390    $name = substr( $hostname , 0, $postition );
391    if ( FALSE === $postition || is_numeric($name) ) {
392        return $hostname;
393    } else {
394        return $name;
395    }
396}
397
398#------------------------------------------------------------------------------
399# Read a file containing key value pairs
400function file_to_hash($filename, $sep)
401{
402
403  $lines = file($filename, FILE_IGNORE_NEW_LINES);
404
405  foreach ($lines as $line)
406  {
407    list($k, $v) = explode($sep, rtrim($line));
408    $params[$k] = $v;
409  }
410
411  return $params;
412}
413
414#------------------------------------------------------------------------------
415# Read a file containing key value pairs
416# Multiple values permitted for each key
417function file_to_hash_multi($filename, $sep)
418{
419
420  $lines = file($filename);
421
422  foreach ($lines as $line)
423  {
424    list($k, $v) = explode($sep, rtrim($line));
425    $params[$k][] = $v;
426  }
427
428  return $params;
429}
430
431#------------------------------------------------------------------------------
432# Obtain a list of distinct values from an array of arrays
433function hash_get_distinct_values($h)
434{
435  $values = array();
436  $values_done = array();
437  foreach($h as $k => $v)
438  {
439    if($values_done[$v] != "x")
440    {
441      $values_done[$v] = "x";
442      $values[] = $v;
443    }
444  }
445  return $values;
446}
447
448$filter_defs = array();
449
450#------------------------------------------------------------------------------
451# Scan $conf['filter_dir'] and populate $filter_defs
452function discover_filters()
453{
454  global $conf, $filter_defs;
455
456  # Check whether filtering is configured or not
457  if(!isset($conf['filter_dir']))
458    return;
459
460  if(!is_dir($conf['filter_dir']))
461  {
462    error_log("discover_filters(): not a directory: ${conf['filter_dir']}");
463    return;
464  }
465
466  if($dh = opendir($conf['filter_dir']))
467  {
468    while(($filter_conf_filename = readdir($dh)) !== false) {
469      if(!is_dir($filter_conf_filename))
470      {
471        # Parse the file contents
472        $full_filename = "${conf['filter_dir']}/$filter_conf_filename";
473        $filter_params = file_to_hash($full_filename, '=');
474        $filter_shortname = $filter_params["shortname"];
475        $filter_type = $filter_params["type"];
476        if($filter_type = "url")
477        {
478          $filter_data_url = $filter_params['url'];
479          $filter_defs[$filter_shortname] = $filter_params;
480          $filter_defs[$filter_shortname]["data"] = file_to_hash($filter_data_url, ',');
481          $filter_defs[$filter_shortname]["choices"] = hash_get_distinct_values($filter_defs[$filter_shortname]["data"]);
482        }
483      }
484    }
485    closedir($dh);
486  }
487}
488
489$filter_permit_list = NULL;
490
491#------------------------------------------------------------------------------
492# Initialise the filter permit list, if necessary
493function filter_init()
494{
495   global $conf, $filter_permit_list, $filter_defs, $choose_filter;
496
497   if(!is_null($filter_permit_list))
498   {
499      return;
500   }
501
502   if(!isset($conf['filter_dir']))
503   {
504      $filter_permit_list = FALSE;
505      return;
506   }
507
508   $filter_permit_list = array();
509   $filter_count = 0;
510
511   foreach($choose_filter as $filter_shortname => $filter_choice)
512   {
513      if($filter_choice == "")
514         continue;
515
516      $filter_params = $filter_defs[$filter_shortname];
517      if($filter_count == 0)
518      {
519         foreach($filter_params["data"] as $key => $value)
520         {
521            if($value == $filter_choice)
522               $filter_permit_list[$key] = $key;
523         }
524      }
525      else
526      {
527         foreach($filter_permit_list as $key => $value)
528         {
529            $remove_key = TRUE;
530            if(isset($filter_params["data"][$key]))
531            {
532               if($filter_params["data"][$key] == $filter_choice)
533               {
534                  $remove_key = FALSE;
535               }
536            }
537            if($remove_key)
538            {
539               unset($filter_permit_list[$key]);
540            }
541         }
542      }
543      $filter_count++;
544   }
545
546   if($filter_count == 0)
547      $filter_permit_list = FALSE;
548
549}
550
551#------------------------------------------------------------------------------
552# Decide whether the given source is permitted by the filters, if any
553function filter_permit($source_name)
554{
555   global $filter_permit_list;
556
557   filter_init();
558
559   # Handle the case where filtering is not active
560   if(!is_array($filter_permit_list))
561      return true;
562
563   return isset($filter_permit_list[$source_name]);
564}
565
566$VIEW_NAME_SEP = '--';
567
568function viewName($view) {
569  global $VIEW_NAME_SEP;
570
571  $vn = '';
572  if ($view['parent'] != NULL)
573    $vn = str_replace('/', $VIEW_NAME_SEP, $view['parent']) . $VIEW_NAME_SEP;
574  $vn .= $view['view_name'];
575  return $vn;
576}
577
578class ViewList {
579  private $available_views;
580
581  public function __construct() {
582    $this->available_views = get_available_views();
583  }
584
585  public function viewExists($view_name) {
586    foreach ($this->available_views as $view) {
587      if ($view['view_name'] == $view_name) {
588	return TRUE;
589      }
590    }
591    return FALSE;
592  }
593
594  public function getView($view_name) {
595    foreach ($this->available_views as $view) {
596      if (viewName($view) == $view_name) {
597	return $view;
598      }
599    }
600    return NULL;
601  }
602
603  public function removeView($view_name) {
604    foreach ($this->available_views as $key => $view) {
605      if (viewName($view) == $view_name) {
606	unset($this->available_views[$key]);
607	return;
608      }
609    }
610  }
611
612  public function getViews() {
613    return $this->available_views;
614  }
615}
616
617function getViewItems($view, $range, $cs, $ce) {
618  $view_elements = get_view_graph_elements($view);
619  $view_items = array();
620  if (count($view_elements) != 0) {
621    $graphargs = "";
622    if ($cs)
623      $graphargs .= "&amp;cs=" . rawurlencode($cs);
624    if ($ce)
625      $graphargs .= "&amp;ce=" . rawurlencode($ce);
626
627    foreach ($view_elements as $element) {
628      $canBeDecomposed = isset($element['aggregate_graph']) ||
629	((strpos($element['graph_args'], 'vn=') !== FALSE) &&
630	 (strpos($element['graph_args'], 'item_id=') !== FALSE));
631      $view_items[] =
632	array("legend" => isset($element['hostname']) ?
633	      $element['hostname'] : "Aggregate graph",
634	      "url_args" => htmlentities($element['graph_args']) .
635	      "&amp;r=" . $range . $graphargs,
636	      "aggregate_graph" => isset($element['aggregate_graph']) ? 1 : 0,
637	      "canBeDecomposed" => $canBeDecomposed ? 1 : 0);
638    }
639  }
640  return $view_items;
641}
642
643///////////////////////////////////////////////////////////////////////////////
644// Get all the available views
645///////////////////////////////////////////////////////////////////////////////
646function get_available_views() {
647  global $conf;
648
649  /* -----------------------------------------------------------------------
650  Find available views by looking in the GANGLIA_DIR/conf directory
651  anything that matches view_*.json. Read them all and build a available_views
652  array
653  ----------------------------------------------------------------------- */
654  $available_views = array();
655
656  if ($handle = opendir($conf['views_dir'])) {
657    while (false !== ($file = readdir($handle))) {
658      if (preg_match("/^view_(.*)\.json$/", $file, $out)) {
659	$view_config_file = $conf['views_dir'] . "/" . $file;
660	if (!is_file ($view_config_file)) {
661	  echo("Can't read view config file " .
662	       $view_config_file . ". Please check permissions");
663	}
664
665	$view = json_decode(file_get_contents($view_config_file), TRUE);
666	// Check whether view type has been specified ie. regex.
667	// If not it's standard view
668	$view_type =
669	  isset($view['view_type']) ? $view['view_type'] : "standard";
670	$default_size = isset($view['default_size']) ?
671	  $view['default_size'] : $conf['default_view_graph_size'];
672	$view_parent =
673	  isset($view['parent']) ? $view['parent'] : NULL;
674	$common_y_axis =
675	  isset($view['common_y_axis']) ? $view['common_y_axis'] : 0;
676
677	$available_views[] = array ("file_name" => $view_config_file,
678				    "view_name" => $view['view_name'],
679				    "default_size" => $default_size,
680				    "items" => $view['items'],
681				    "view_type" => $view_type,
682				    "parent" => $view_parent,
683				    "common_y_axis" => $common_y_axis);
684	unset($view);
685      }
686    }
687    closedir($handle);
688  }
689
690  foreach ($available_views as $key => $row) {
691    $name[$key] = strtolower($row['view_name']);
692  }
693
694  @array_multisort($name, SORT_ASC, $available_views);
695
696  return $available_views;
697}
698
699///////////////////////////////////////////////////////////////////////////////
700// Get image graph URLS
701// This function returns an array of graph URLs to be used when rendering the
702// view. It returns only the base ie. cluster, host, metric information.
703// It is up to the caller to add proper size information, time ranges etc.
704///////////////////////////////////////////////////////////////////////////////
705function get_view_graph_elements($view) {
706  global $conf, $index_array;
707
708  retrieve_metrics_cache();
709
710  $view_elements = array();
711
712  // set the default size from the view or global config
713  if ( isset($conf['default_view_graph_size']) ) {
714    $default_size = $conf['default_view_graph_size'];
715  }
716
717  if ( isset($view['default_size']) ) {
718    $default_size = $view['default_size'];
719  }
720
721
722  switch ( $view['view_type'] ) {
723  case "standard":
724    // Does view have any items/graphs defined
725    if ( count($view['items']) == 0 ) {
726      continue;
727      // print "No graphs defined for this view. Please add some";
728    } else {
729      // Loop through graph items
730      foreach ($view['items'] as $item_id => $item) {
731	// Check if item is an aggregate graph
732	if (isset($item['aggregate_graph'])) {
733	  foreach ( $item['host_regex'] as $reg_id => $regex_array ) {
734	    $graph_args_array[] = "hreg[]=" . urlencode($regex_array["regex"]);
735	  }
736
737	  if (isset($item['metric_regex'])) {
738	    foreach ( $item['metric_regex'] as $reg_id => $regex_array ) {
739	      $graph_args_array[] =
740		"mreg[]=" . urlencode($regex_array["regex"]);
741              $mreg[] = $regex_array["regex"];
742	    }
743	  }
744
745          if ( isset($item['size']) ) {
746            $graph_args_array[] = "z=" . $item['size'];
747          } else {
748            $graph_args_array[] = "z=" . $default_size;
749          }
750
751          if ( isset($item['sortit']) ) {
752            $graph_args_array[] = "sortit=" . $item['sortit'];
753          }
754
755	  // If graph type is not specified default to line graph
756	  if (isset($item['graph_type']) &&
757	      in_array($item['graph_type'], array('line', 'stack')))
758	    $graph_args_array[] = "gtype=" . $item['graph_type'];
759	  else
760	    $graph_args_array[] = "gtype=line";
761
762	  if (isset($item['upper_limit']))
763	    $graph_args_array[] = "x=" . $item['upper_limit'];
764
765	  if (isset($item['lower_limit']))
766	    $graph_args_array[] = "n=" . $item['lower_limit'];
767
768	  if (isset($item['vertical_label']))
769	    $graph_args_array[] = "vl=" . urlencode($item['vertical_label']);
770
771	  if (isset($item['title']))
772	    $graph_args_array[] = "title=" . urlencode($item['title']);
773
774	  if (isset($item['metric']))
775	    $graph_args_array[] = "m=" . $item['metric'];
776
777          if (isset($item['glegend']))
778            $graph_args_array[] = "glegend=" . $item["glegend"];
779
780	  if (isset($item['cluster']))
781	    $graph_args_array[] = "c=" . urlencode($item['cluster']);
782
783	  if (isset($item['exclude_host_from_legend_label']))
784	    $graph_args_array[] =
785	      "lgnd_xh=" . $item['exclude_host_from_legend_label'];
786
787	  $graph_args_array[] = "aggregate=1";
788	  $view_elements[] =
789	    array("graph_args" => join("&", $graph_args_array),
790		  "aggregate_graph" => 1,
791		  "name" => isset($item['title']) && $item['title'] != "" ?
792		  $item['title'] : $mreg[0] . " Aggregate graph");
793
794	  unset($graph_args_array);
795
796	  // Check whether it's a composite graph/report.
797	  // It needs to have an item id
798	} else if ($item['item_id']) {
799	  $graph_args_array[] = "vn=" . $view['view_name'];
800          $graph_args_array[] = "item_id=" . $item['item_id'];
801
802	  $view_elements[] =
803	    array("graph_args" => join("&", $graph_args_array));
804          unset($graph_args_array);
805
806	  // It's standard metric graph
807        } else {
808	  // Is it a metric or a graph(report)
809	  if (isset($item['metric'])) {
810	    $graph_args_array[] = "m=" . $item['metric'];
811	    $name = $item['metric'];
812	  } else {
813	    $graph_args_array[] = "g=" . urlencode($item['graph']);
814	    $name = $item['graph'];
815	  }
816          if ( isset($item['size']) ) {
817            $graph_args_array[] = "z=" . $item['size'];
818          } else {
819            $graph_args_array[] = "z=" . $default_size;
820          }
821
822	  if (isset($item['hostname'])) {
823            $hostname = $item['hostname'];
824            $cluster = array_key_exists($hostname, $index_array['cluster']) ?
825	      $index_array['cluster'][$hostname][0] : NULL;
826	    $graph_args_array[] = "h=" . urlencode($hostname);
827          } else if (isset($item['cluster'])) {
828	    $hostname = "";
829            $cluster = $item['cluster'];
830	  } else {
831            $hostname = "";
832            $cluster = "";
833	  }
834	  $graph_args_array[] = "c=" . urlencode($cluster);
835
836	  if (isset($item['upper_limit']))
837	    $graph_args_array[] = "x=" . $item['upper_limit'];
838
839	  if (isset($item['lower_limit']))
840	    $graph_args_array[] = "n=" . $item['lower_limit'];
841
842	  if (isset($item['vertical_label']))
843	    $graph_args_array[] = "vl=" . urlencode($item['vertical_label']);
844
845	  if (isset($item['title']))
846	    $graph_args_array[] = "title=" . urlencode($item['title']);
847
848          if (isset($item['warning'])) {
849            $view_e['warning'] = $item['warning'];
850            $graph_args_array[] = "warn=" . $item['warning'];
851          }
852          if (isset($item['critical'])) {
853            $view_e['critical'] = $item['critical'];
854            $graph_args_array[] = "crit=" . $item['critical'];
855          }
856
857          if (isset($item['alias'])) {
858	    $view_e['alias'] = $item['alias'];
859          }
860
861          $view_e["graph_args"] = join("&", $graph_args_array);
862          $view_e['hostname'] = $hostname;
863          $view_e['cluster'] = $cluster;
864          $view_e['name'] = $name;
865
866	  $view_elements[] = $view_e;
867
868          unset($view_e);
869	  unset($graph_args_array);
870	}
871      } // end of foreach ( $view['items']
872    } // end of if ( count($view['items'])
873    break;
874
875    ///////////////////////////////////////////////////////////////////////////
876    // Currently only supports matching hosts.
877    ///////////////////////////////////////////////////////////////////////////
878  case "regex":
879    foreach ($view['items'] as $item_id => $item) {
880      // Is it a metric or a graph(report)
881      if ( isset($item['metric']) ) {
882	$metric_suffix = "m=" . $item['metric'];
883	$name = $item['metric'];
884      } else {
885	$metric_suffix = "g=" . $item['graph'];
886	$name = $item['graph'];
887      }
888
889      // Find hosts matching a criteria
890      $query = $item['hostname'];
891      foreach ( $index_array['hosts'] as $key => $host_name ) {
892	if (preg_match("/$query/", $host_name)) {
893	  $clusters = $index_array['cluster'][$host_name];
894	  foreach ($clusters AS $cluster) {
895	    $graph_args_array[] = "h=" . urlencode($host_name);
896	    $graph_args_array[] = "c=" . urlencode($cluster);
897
898	    $view_elements[] =
899	      array("graph_args" => $metric_suffix . "&" . join("&", $graph_args_array),
900		    "hostname" => $host_name,
901		    "cluster" => $cluster,
902		    "name" => $name);
903
904	    unset($graph_args_array);
905	  }
906	}
907      }
908    } // end of foreach ( $view['items'] as $item_id => $item )
909    break;;
910  } // end of switch ( $view['view_type'] ) {
911  return ($view_elements);
912}
913
914function legendEntry($vname, $legend_items) {
915  $legend = "";
916  if (in_array("now", $legend_items))
917    $legend .= "VDEF:{$vname}_last={$vname},LAST ";
918
919  if (in_array("min", $legend_items))
920    $legend .= "VDEF:{$vname}_min={$vname},MINIMUM ";
921
922  if (in_array("avg", $legend_items))
923    $legend .= "VDEF:{$vname}_avg={$vname},AVERAGE ";
924
925  if (in_array("max", $legend_items))
926    $legend .= "VDEF:{$vname}_max={$vname},MAXIMUM ";
927
928  $terminate = FALSE;
929  if (in_array("now", $legend_items)) {
930    $legend .= "GPRINT:'{$vname}_last':'Now\:%5.1lf%s";
931    $terminate = TRUE;
932  }
933
934  if (in_array("min", $legend_items)) {
935    if ($terminate)
936      $legend .= "' ";
937    $legend .= "GPRINT:'{$vname}_min':'Min\:%5.1lf%s";
938    $terminate = TRUE;
939  }
940
941  if (in_array("avg", $legend_items)) {
942    if ($terminate)
943      $legend .= "' ";
944    $legend .= "GPRINT:'{$vname}_avg':'Avg\:%5.1lf%s";
945    $terminate = TRUE;
946  }
947
948  if (in_array("max", $legend_items)) {
949    if ($terminate)
950      $legend .= "' ";
951    $legend .= "GPRINT:'{$vname}_max':'Max\:%5.1lf%s";
952    $terminate = TRUE;
953  }
954
955  if ($terminate)
956    $legend .= "\\l' ";
957
958  return $legend;
959}
960
961/**
962 * Check if current user has a privilege (view, edit, etc) on a resource.
963 * If resource is unspecified, we assume GangliaAcl::ALL.
964 *
965 * Examples
966 *   checkAccess( GangliaAcl::ALL_CLUSTERS, GangliaAcl::EDIT, $conf ); // user has global edit?
967 *   checkAccess( GangliaAcl::ALL_CLUSTERS, GangliaAcl::VIEW, $conf ); // user has global view?
968 *   checkAccess( $cluster, GangliaAcl::EDIT, $conf ); // user can edit current cluster?
969 *   checkAccess( 'cluster1', GangliaAcl::EDIT, $conf ); // user has edit privilege on cluster1?
970 *   checkAccess( 'cluster1', GangliaAcl::VIEW, $conf ); // user has view privilege on cluster1?
971 */
972function checkAccess($resource, $privilege, $conf) {
973
974  if(!is_array($conf)) {
975    trigger_error('checkAccess: $conf is not an array.', E_USER_ERROR);
976  }
977  if(!isSet($conf['auth_system'])) {
978    trigger_error("checkAccess: \$conf['auth_system'] is not defined.", E_USER_ERROR);
979  }
980
981  switch( $conf['auth_system'] ) {
982    case 'readonly':
983      $out = ($privilege == GangliaAcl::VIEW);
984      break;
985
986    case 'enabled':
987      // TODO: 'edit' needs to check for writeability of data directory.  error log if edit is allowed but we're unable to due to fs problems.
988
989      $acl = GangliaAcl::getInstance();
990      $auth = GangliaAuth::getInstance();
991
992      if(!$auth->isAuthenticated()) {
993        $user = GangliaAcl::GUEST;
994      } else {
995        $user = $auth->getUser();
996      }
997
998      if(!$acl->has($resource)) {
999        $resource = GangliaAcl::ALL_CLUSTERS;
1000      }
1001
1002      $out = false;
1003      if($acl->hasRole($user)) {
1004        $out = (bool) $acl->isAllowed($user, $resource, $privilege);
1005      }
1006      // error_log("checkAccess() user=$user, resource=$resource, priv=$privilege == $out");
1007      break;
1008
1009    case 'disabled':
1010      $out = true;
1011      break;
1012
1013    default:
1014      trigger_error( "Invalid value '".$conf['auth_system']."' for \$conf['auth_system'].", E_USER_ERROR );
1015      return false;
1016  }
1017
1018  return $out;
1019}
1020
1021function viewId($view_name) {
1022  $id = 'v_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $view_name);
1023  return $id;
1024}
1025
1026///////////////////////////////////////////////////////////////////////////////
1027// Taken from
1028// http://au2.php.net/manual/en/function.json-encode.php#80339
1029// Pretty print JSON
1030///////////////////////////////////////////////////////////////////////////////
1031function json_prettyprint($json)
1032{
1033    $tab = "  ";
1034    $new_json = "";
1035    $indent_level = 0;
1036    $in_string = false;
1037
1038    $len = strlen($json);
1039
1040    for($c = 0; $c < $len; $c++)
1041    {
1042        $char = $json[$c];
1043        switch($char)
1044        {
1045            case '{':
1046            case '[':
1047                if(!$in_string)
1048                {
1049                    $new_json .= $char . "\n" . str_repeat($tab, $indent_level+1);
1050                    $indent_level++;
1051                }
1052                else
1053                {
1054                    $new_json .= $char;
1055                }
1056                break;
1057            case '}':
1058            case ']':
1059                if(!$in_string)
1060                {
1061                    $indent_level--;
1062                    $new_json .= "\n" . str_repeat($tab, $indent_level) . $char;
1063                }
1064                else
1065                {
1066                    $new_json .= $char;
1067                }
1068                break;
1069            case ',':
1070                if(!$in_string)
1071                {
1072                    $new_json .= ",\n" . str_repeat($tab, $indent_level);
1073                }
1074                else
1075                {
1076                    $new_json .= $char;
1077                }
1078                break;
1079            case ':':
1080                if(!$in_string)
1081                {
1082                    $new_json .= ": ";
1083                }
1084                else
1085                {
1086                    $new_json .= $char;
1087                }
1088                break;
1089            case '"':
1090                if($c > 0 && $json[$c-1] != '\\')
1091                {
1092                    $in_string = !$in_string;
1093                }
1094            default:
1095                $new_json .= $char;
1096                break;
1097        }
1098    }
1099
1100    return $new_json;
1101}
1102
1103function ganglia_cache_metrics() {
1104    global $conf, $index_array, $hosts, $grid, $clusters, $debug, $metrics;
1105
1106    require dirname(__FILE__) . '/lib/cache.php';
1107} // end function ganglia_cache_metrics
1108
1109
1110//////////////////////////////////////////////////////////////////////////////
1111//
1112//////////////////////////////////////////////////////////////////////////////
1113function build_aggregate_graph_config ($graph_type,
1114                                       $line_width,
1115                                       $hreg,
1116                                       $mreg,
1117                                       $glegend,
1118                                       $exclude_host_from_legend_label,
1119                                       $sortit = true) {
1120
1121  global $conf, $index_array, $hosts, $grid, $clusters, $debug, $metrics;
1122
1123  retrieve_metrics_cache();
1124
1125  $color_count = count($conf['graph_colors']);
1126
1127  $graph_config["report_name"]=isset($mreg)  ?  sanitize(implode($mreg))   : NULL;
1128  $graph_config["title"]=isset($mreg)  ?  sanitize(implode($mreg))   : NULL;
1129  $graph_config["glegend"]=isset($glegend) ? sanitize($glegend) : "show";
1130
1131  $counter = 0;
1132
1133  ///////////////////////////////////////////////////////////////////////////
1134  // Find matching hosts
1135  foreach ( $hreg as $key => $query ) {
1136    foreach ( $index_array['hosts'] as $key => $host_name ) {
1137      if ( preg_match("/$query/i", $host_name ) ) {
1138        // We can have same hostname in multiple clusters
1139        foreach ($index_array['cluster'][$host_name] AS $cluster) {
1140            $host_matches[] = $host_name . "|" . $cluster;
1141        }
1142      }
1143    }
1144  }
1145
1146  sort($host_matches);
1147
1148  if( isset($mreg)) {
1149    // Find matching metrics
1150    foreach ( $mreg as $key => $query ) {
1151      foreach ( $index_array['metrics'] as $metric_key => $m_name ) {
1152        if ( preg_match("/$query/i", $metric_key, $metric_subexpr ) ) {
1153          if (isset($metric_subexpr) && count($metric_subexpr) > 1) {
1154            $legend = array();
1155            for ($i = 1; $i < count($metric_subexpr); $i++) {
1156              $legend[] = $metric_subexpr[$i];
1157            }
1158	    $metric_matches[$metric_key] = implode(' ', $legend);
1159          } else {
1160            $metric_matches[$metric_key] = $metric_key;
1161          }
1162        }
1163      }
1164    }
1165    if($sortit) {
1166      ksort($metric_matches);
1167    }
1168  }
1169  if( isset($metric_matches)){
1170    $metric_matches_unique = array_unique($metric_matches);
1171  }
1172  else{
1173    $metric_matches_unique = array($metric_name => $metric_name);
1174  }
1175
1176  if ( isset($host_matches)) {
1177
1178    $host_matches_unique = array_unique($host_matches);
1179
1180    // Create graph_config series from matched hosts and metrics
1181    foreach ( $host_matches_unique as $key => $host_cluster ) {
1182
1183      $out = explode("|", $host_cluster);
1184
1185      $host_name = $out[0];
1186      $cluster_name = $out[1];
1187
1188      foreach ( $metric_matches_unique as $m_name => $legend ) {
1189
1190        // We need to cycle the available colors
1191        $color_index = $counter % $color_count;
1192
1193        // next loop if there is no metric for this hostname
1194        if( !in_array($host_name, $index_array['metrics'][$m_name]))
1195          continue;
1196
1197        $label = '';
1198        if ($exclude_host_from_legend_label) {
1199	  $label = $legend;
1200        } else {
1201          if ($conf['strip_domainname'] == True )
1202            $label = strip_domainname($host_name);
1203          else
1204            $label = $host_name;
1205
1206 	  if (isset($metric_matches) and count($metric_matches_unique) > 1)
1207            $label .= " $legend";
1208	}
1209
1210        $graph_config['series'][] = array ( "hostname" => $host_name , "clustername" => $cluster_name,
1211          "metric" => $m_name,  "color" => $conf['graph_colors'][$color_index], "label" => $label, "line_width" => $line_width, "type" => $graph_type);
1212
1213        $counter++;
1214
1215      }
1216      }
1217   }
1218
1219   return $graph_config;
1220
1221} // function build_aggregate_graph_config () {
1222
1223
1224//////////////////////////////////////////////////////////////////////////////
1225//
1226//////////////////////////////////////////////////////////////////////////////
1227function retrieve_metrics_cache ( $index = "all" ) {
1228
1229   global $conf, $index_array, $hosts, $grid, $clusters, $debug, $metrics, $context;
1230
1231   require dirname(__FILE__) . '/lib/cache.php';
1232   return;
1233} // end of function get_metrics_cache () {
1234
1235function getHostOverViewData($hostname,
1236                             $metrics,
1237                             $cluster,
1238                             $hosts_up,
1239                             $hosts_down,
1240                             $always_timestamp,
1241                             $always_constant,
1242                             $data) {
1243  $data->assign("extra", template("host_extra.tpl"));
1244
1245  $data->assign("host", $hostname);
1246  $data->assign("node_image", node_image($metrics));
1247
1248  if ($hosts_up)
1249    $data->assign("node_msg", "This host is up and running.");
1250  else
1251    $data->assign("node_msg", "This host is down.");
1252
1253  # No reason to go on if this node is down.
1254  if ($hosts_down)
1255    return;
1256
1257  foreach ($metrics as $name => $v) {
1258    if ($v['TYPE'] == "string" or $v['TYPE']=="timestamp" or
1259        (isset($always_timestamp[$name]) and $always_timestamp[$name])) {
1260      $s_metrics[$name] = $v;
1261    } elseif ($v['SLOPE'] == "zero" or
1262              (isset($always_constant[$name]) and $always_constant[$name])) {
1263      $c_metrics[$name] = $v;
1264    }
1265  }
1266
1267  # in case this is not defined, set to LOCALTIME so uptime will be 0 in the display
1268  $boottime = null;
1269  if (isset($metrics['boottime']['VAL']))
1270    $boottime = $metrics['boottime']['VAL'];
1271  else
1272    $boottime = $cluster['LOCALTIME'];
1273
1274  # Add the uptime metric for this host. Cannot be done in ganglia.php,
1275  # since it requires a fully-parsed XML tree. The classic contructor problem.
1276  $s_metrics['uptime']['TYPE'] = "string";
1277  $s_metrics['uptime']['VAL'] = uptime($cluster['LOCALTIME'] - $boottime);
1278  $s_metrics['uptime']['TITLE'] = "Uptime";
1279
1280  # Add the gmond started timestamps & last reported time (in uptime format) from
1281  # the HOST tag:
1282  $s_metrics['gmond_started']['TYPE'] = "timestamp";
1283  $s_metrics['gmond_started']['VAL'] = $hosts_up['GMOND_STARTED'];
1284  $s_metrics['gmond_started']['TITLE'] = "Gmond Started";
1285  $s_metrics['last_reported']['TYPE'] = "string";
1286  $s_metrics['last_reported']['VAL'] = uptime($cluster['LOCALTIME'] - $hosts_up['REPORTED']);
1287  $s_metrics['last_reported']['TITLE'] = "Last Reported";
1288
1289  $s_metrics['ip_address']['TITLE'] = "IP Address";
1290  $s_metrics['ip_address']['VAL'] = $hosts_up['IP'];
1291  $s_metrics['ip_address']['TYPE'] = "string";
1292  $s_metrics['location']['TITLE'] = "Location";
1293  $s_metrics['location']['VAL'] = $hosts_up['LOCATION'];
1294  $s_metrics['location']['TYPE'] = "string";
1295
1296  # String metrics
1297  if (is_array($s_metrics)) {
1298    $s_metrics_data = array();
1299    ksort($s_metrics);
1300    foreach ($s_metrics as $name => $v) {
1301      # RFM - If units aren't defined for metric, make it be the empty string
1302      ! array_key_exists('UNITS', $v) and $v['UNITS'] = "";
1303      if (isset($v['TITLE'])) {
1304        $s_metrics_data[$name]["name"] = $v['TITLE'];
1305      } else {
1306        $s_metrics_data[$name]["name"] = $name;
1307      }
1308      if ($v['TYPE']=="timestamp" or
1309          (isset($always_timestamp[$name]) and $always_timestamp[$name])) {
1310        $s_metrics_data[$name]["value"] = date("r", $v['VAL']);
1311      } else {
1312        $s_metrics_data[$name]["value"] = $v['VAL'] . " " . $v['UNITS'];
1313      }
1314    }
1315  }
1316  $data->assign("s_metrics_data", $s_metrics_data);
1317
1318  # Constant metrics.
1319  $c_metrics_data = null;
1320  if (isset($c_metrics) and is_array($c_metrics)) {
1321    $c_metrics_data = array();
1322    ksort($c_metrics);
1323    foreach ($c_metrics as $name => $v) {
1324      if (isset($v['TITLE']))  {
1325        $c_metrics_data[$name]["name"] =  $v['TITLE'];
1326      } else {
1327        $c_metrics_data[$name]["name"] = $name;
1328      }
1329      $c_metrics_data[$name]["value"] = "$v[VAL] $v[UNITS]";
1330    }
1331  }
1332  $data->assign("c_metrics_data", $c_metrics_data);
1333}
1334
1335function buildMetricMaps($metrics,
1336			 $always_timestamp,
1337			 $always_constant,
1338			 $baseGraphArgs) {
1339  $metricMap = NULL;
1340  $metricGroupMap = NULL;
1341  foreach ($metrics as $name => $metric) {
1342    if ($metric['TYPE'] == "string" or
1343	$metric['TYPE'] == "timestamp" or
1344	(isset($always_timestamp[$name]) and $always_timestamp[$name])) {
1345    } elseif ($metric['SLOPE'] == "zero" or
1346	      (isset($always_constant[$name]) and $always_constant[$name])) {
1347    } else {
1348      $graphArgs = $baseGraphArgs . "&amp;v=$metric[VAL]&amp;m=$name";
1349      # Adding units to graph 2003 by Jason Smith <smithj4@bnl.gov>.
1350      if ($metric['UNITS']) {
1351	$encodeUnits = rawurlencode($metric['UNITS']);
1352	$graphArgs .= "&amp;vl=$encodeUnits";
1353      }
1354      if (isset($metric['TITLE'])) {
1355	$title = $metric['TITLE'];
1356	$encodeTitle = rawurlencode($title);
1357	$graphArgs .= "&amp;ti=$encodeTitle";
1358      }
1359      // dump_var($graphArgs, "graphArgs");
1360
1361      $metricMap[$name]['graph'] = $graphArgs;
1362      $metricMap[$name]['description'] =
1363	isset($metric['DESC']) ? $metric['DESC'] : '';
1364      $metricMap[$name]['title'] =
1365	isset($metric['TITLE']) ? $metric['TITLE'] : '';
1366
1367      # Setup an array of groups that can be used for sorting in group view
1368      if ( isset($metrics[$name]['GROUP']) ) {
1369	$groups = $metrics[$name]['GROUP'];
1370      } else {
1371	$groups = array("");
1372      }
1373
1374      foreach ($groups as $group) {
1375	if (isset($metricGroupMap[$group])) {
1376	  $metricGroupMap[$group] =
1377	    array_merge($metricGroupMap[$group], (array)$name);
1378	} else {
1379	  $metricGroupMap[$group] = array($name);
1380	}
1381      }
1382    } // if
1383  } // foreach
1384  return array($metricMap, $metricGroupMap);
1385}
1386
1387// keep url decoding until it looks good
1388function heuristic_urldecode($blob) {
1389  while (substr($blob,0,1) == "%") {
1390    $blob = rawurldecode($blob);
1391  }
1392  return $blob;
1393}
1394
1395// alternative passthru() implementation to avoid incomplete images shown in
1396// browsers.
1397function my_passthru($command) {
1398  $tf = tempnam('/tmp', 'ganglia-graph.');
1399  $ret = exec("$command > $tf");
1400  $size = filesize($tf);
1401  header("Content-Length: $size");
1402  $fp = fopen($tf, 'rb');
1403  fpassthru($fp);
1404  fclose($fp);
1405  unlink($tf);
1406}
1407
1408?>
1409