php-collection: add basic support for meta-selections
authorBruno Prémont <bonbons@linux-vserver.org>
Mon, 20 Apr 2009 20:37:42 +0000 (22:37 +0200)
committerFlorian Forster <octo@huhu.verplant.org>
Thu, 23 Apr 2009 09:44:03 +0000 (11:44 +0200)
Add support for new meta selections allowing addition of graphs
with wildcard behavior.
This adds support for @all selection which matches any values
for the given identifier part.
In addition those types for which meta graphs exist now also list the
individual type instances in addition to the meta graph key for separate
graphing.

In order to support such new groups the lookup code has been refactored
to use a single scanning function which recursively traveses the
collectd RRD output directory for hosts, plugins, types and passing the
discovered data to callback functions for use. The callbacks returns
true to indicate traversal should continue on to next depth level and
false to tell it to continue with next element.
e.g. true on a host means it should look for plugins for given host,
false to continue with next host.

Signed-off-by: Florian Forster <octo@huhu.verplant.org>
contrib/php-collection/browser.js
contrib/php-collection/functions.php
contrib/php-collection/graph.php
contrib/php-collection/index.php

index 376f025..4ddc424 100644 (file)
@@ -111,9 +111,22 @@ function refillSelect(options, select) {
                        newOption.value = options[i].firstChild ? options[i].firstChild.data : '';
                        if (keepSelection && newOption.value == oldValue)
                                newOption.setAttribute('selected', 'selected');
-                       if (keepSelection && optCnt == 1 && newOption.value == '@') {
+                       if (newOption.value[0] == '@') {
                                newOption.setAttribute('style', 'font-style: italic');
-                               newOption.appendChild(document.createTextNode('Meta graph'));
+                               if (newOption.value == '@' || newOption.value == '@merge')
+                                       newOption.appendChild(document.createTextNode('Meta graph'));
+                               else if (newOption.value == '@all')
+                                       newOption.appendChild(document.createTextNode('All entries'));
+                               else if (newOption.value == '@merge_sum')
+                                       newOption.appendChild(document.createTextNode('Meta summed graph'));
+                               else if (newOption.value == '@merge_avg')
+                                       newOption.appendChild(document.createTextNode('Meta averaged graph'));
+                               else if (newOption.value == '@merge_stack')
+                                       newOption.appendChild(document.createTextNode('Meta stacked graph'));
+                               else if (newOption.value == '@merge_line')
+                                       newOption.appendChild(document.createTextNode('Meta lines graph'));
+                               else
+                                       newOption.appendChild(document.createTextNode(newOption.value));
                        } else
                                newOption.appendChild(document.createTextNode(newOption.value));
                        select.appendChild(newOption);
@@ -285,8 +298,25 @@ function GraphAppend() {
        var time_list   = document.getElementById('timespan');
        var timespan    = time_list.selectedIndex >= 0 ? time_list.options[time_list.selectedIndex].value : '';
        var tinyLegend  = document.getElementById('tinylegend').checked;
-       var logarithmic = document.getElementById('logarithmic').checked
-       GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic);
+       var logarithmic = document.getElementById('logarithmic').checked;
+       if (host[0] == '@' || plugin[0] == '@' || pinst[0] == '@' || type[0] == '@' || (tinst[0] == '@' && tinst.substr(0, 5) != '@merge')) {
+               var query = 'action=list_graphs&host='+encodeURIComponent(host)+'&plugin='+encodeURIComponent(plugin)+'&plugin_instance='+encodeURIComponent(pinst);
+               query = query+'&type='+encodeURIComponent(type)+'&type_instance='+encodeURIComponent(tinst)+'&timespan='+encodeURIComponent(timespan);
+               query = query+(logarithmic ? '&logarithmic=1' : '')+(tinyLegend ? '&tinylegend=1' : '');
+               loadXMLDoc(dhtml_url, query);
+       } else
+               GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic);
+}
+
+function ListOfGraph(response) {
+       var graphs = response ? response.getElementsByTagName('graph') : null;
+       if (graphs && graphs.length > 0) {
+               for (i = 0; i < graphs.length; i++)
+                       GraphDoAppend(graphs[i].getAttribute('host'), graphs[i].getAttribute('plugin'), graphs[i].getAttribute('plugin_instance'),
+                                     graphs[i].getAttribute('type'), graphs[i].getAttribute('type_instance'), graphs[i].getAttribute('timespan'),
+                                     graphs[i].getAttribute('tinyLegend') == '1', graphs[i].getAttribute('logarithmic') == '1');
+       } else
+               alert('No graph found for adding');
 }
 
 function GraphDoAppend(host, plugin, pinst, type, tinst, timespan, tinyLegend, logarithmic) {
index f555751..fa2badc 100644 (file)
@@ -61,23 +61,173 @@ function collectd_compare_host($a, $b) {
        return 0;
 }
 
-/**
- * Fetch list of hosts found in collectd's datadirs.
- * @return Sorted list of hosts (sorted by label from rigth to left)
- */
-function collectd_list_hosts() {
+function collectd_walk(&$options) {
        global $config;
 
-       $hosts = array();
        foreach($config['datadirs'] as $datadir)
-               if ($d = @opendir($datadir)) {
-                       while (($dent = readdir($d)) !== false)
-                               if ($dent != '.' && $dent != '..' && is_dir($datadir.'/'.$dent) && preg_match(REGEXP_HOST, $dent))
-                                       $hosts[] = $dent;
-                       closedir($d);
+               if ($dh = @opendir($datadir)) {
+                       while (($hdent = readdir($dh)) !== false) {
+                               if ($hdent == '.' || $hdent == '..' || !is_dir($datadir.'/'.$hdent))
+                                       continue;
+                               if (!preg_match(REGEXP_HOST, $hdent))
+                                       continue;
+                               if (isset($options['cb_host']) && ($options['cb_host'] === false || !$options['cb_host']($options, $hdent)))
+                                       continue;
+
+                               if ($dp = @opendir($datadir.'/'.$hdent)) {
+                                       while (($pdent = readdir($dp)) !== false) {
+                                               if ($pdent == '.' || $pdent == '..' || !is_dir($datadir.'/'.$hdent.'/'.$pdent))
+                                                       continue;
+                                               if ($i = strpos($pdent, '-')) {
+                                                       $plugin = substr($pdent, 0, $i);
+                                                       $pinst  = substr($pdent, $i+1);
+                                               } else {
+                                                       $plugin = $pdent;
+                                                       $pinst  = '';
+                                               }
+                                               if (isset($options['cb_plugin']) && ($options['cb_plugin'] === false || !$options['cb_plugin']($options, $hdent, $plugin)))
+                                                       continue;
+                                               if (isset($options['cb_pinst']) && ($options['cb_pinst'] === false || !$options['cb_pinst']($options, $hdent, $plugin, $pinst)))
+                                                       continue;
+
+                                               if ($dt = @opendir($datadir.'/'.$hdent.'/'.$pdent)) {
+                                                       while (($tdent = readdir($dt)) !== false) {
+                                                               if ($tdent == '.' || $tdent == '..' || !is_file($datadir.'/'.$hdent.'/'.$pdent.'/'.$tdent))
+                                                                       continue;
+                                                               if (substr($tdent, strlen($tdent)-4) != '.rrd')
+                                                                       continue;
+                                                               $tdent = substr($tdent, 0, strlen($tdent)-4);
+                                                               if ($i = strpos($tdent, '-')) {
+                                                                       $type  = substr($tdent, 0, $i);
+                                                                       $tinst = substr($tdent, $i+1);
+                                                               } else {
+                                                                       $type  = $tdent;
+                                                                       $tinst = '';
+                                                               }
+                                                               if (isset($options['cb_type']) && ($options['cb_type'] === false || !$options['cb_type']($options, $hdent, $plugin, $pinst, $type)))
+                                                                       continue;
+                                                               if (isset($options['cb_tinst']) && ($options['cb_tinst'] === false || !$options['cb_tinst']($options, $hdent, $plugin, $pinst, $type, $tinst)))
+                                                                       continue;
+                                                       }
+                                                       closedir($dt);
+                                               }
+                                       }
+                                       closedir($dp);
+                               }
+                       }
+                       closedir($dh);
                } else
                        error_log('Failed to open datadir: '.$datadir);
-       $hosts = array_unique($hosts);
+               return true;
+}
+
+function _collectd_list_cb_host(&$options, $host) {
+       if ($options['cb_plugin'] === false) {
+               $options['result'][] = $host;
+               return false;
+       } else if (isset($options['filter_host'])) {
+               if ($options['filter_host'] == '@all') {
+                       return true; // We take anything
+               } else if (substr($options['filter_host'], 0, 2) == '@.') {
+                       if ($host == substr($options['filter_host'], 2) || substr($host, 0, 1-strlen($options['filter_host'])) == substr($options['filter_host'], 1))
+                               return true; // Host part of domain
+                       else
+                               return false;
+               } else if ($options['filter_host'] == $host) {
+                       return true;
+               } else
+                       return false;
+       } else
+               return true;
+}
+
+function _collectd_list_cb_plugin(&$options, $host, $plugin) {
+       if ($options['cb_pinst'] === false) {
+               $options['result'][] = $plugin;
+               return false;
+       } else if (isset($options['filter_plugin'])) {
+               if ($options['filter_plugin'] == '@all')
+                       return true;
+               else if ($options['filter_plugin'] == $plugin)
+                       return true;
+               else
+                       return false;
+       } else
+               return true;
+}
+
+function _collectd_list_cb_pinst(&$options, $host, $plugin, $pinst) {
+       if ($options['cb_type'] === false) {
+               $options['result'][] = $pinst;
+               return false;
+       } else if (isset($options['filter_pinst'])) {
+               if ($options['filter_pinst'] == '@all')
+                       return true;
+               else if (strncmp($options['filter_pinst'], '@merge_', 7) == 0)
+                       return true;
+               else if ($options['filter_pinst'] == $pinst)
+                       return true;
+               else
+                       return false;
+       } else
+               return true;
+}
+
+function _collectd_list_cb_type(&$options, $host, $plugin, $pinst, $type) {
+       if ($options['cb_tinst'] === false) {
+               $options['result'][] = $type;
+               return false;
+       } else if (isset($options['filter_type'])) {
+               if ($options['filter_type'] == '@all')
+                       return true;
+               else if ($options['filter_type'] == $type)
+                       return true;
+               else
+                       return false;
+       } else
+               return true;
+}
+
+function _collectd_list_cb_tinst(&$options, $host, $plugin, $pinst, $type, $tinst) {
+       $options['result'][] = $tinst;
+       return false;
+}
+
+function _collectd_list_cb_graph(&$options, $host, $plugin, $pinst, $type, $tinst) {
+       if (isset($options['filter_tinst'])) {
+               if ($options['filter_tinst'] == '@all') {
+               } else if ($options['filter_tinst'] == $tinst) {
+               } else if (strncmp($options['filter_tinst'], '@merge', 6) == 0) {
+                       // Need to exclude @merge with non-existent meta graph
+               } else
+                       return false;
+       }
+       if (isset($options['filter_pinst']) && strncmp($options['filter_pinst'], '@merge', 6) == 0)
+               $pinst = $options['filter_pinst'];
+       if (isset($options['filter_tinst']) && strncmp($options['filter_tinst'], '@merge', 6) == 0)
+               $tinst = $options['filter_tinst'];
+       $ident = collectd_identifier($host, $plugin, $pinst, $type, $tinst);
+       if (!in_array($ident, $options['ridentifiers'])) {
+               $options['ridentifiers'][] = $ident;
+               $options['result'][] = array('host'=>$host, 'plugin'=>$plugin, 'pinst'=>$pinst, 'type'=>$type, 'tinst'=>$tinst);
+       }
+}
+
+/**
+ * Fetch list of hosts found in collectd's datadirs.
+ * @return Sorted list of hosts (sorted by label from rigth to left)
+ */
+function collectd_list_hosts() {
+       $options = array(
+               'result' => array(),
+               'cb_host' => '_collectd_list_cb_host',
+               'cb_plugin' => false,
+               'cb_pinst' => false,
+               'cb_type' => false,
+               'cb_tinst' => false
+       );
+       collectd_walk($options);
+       $hosts = array_unique($options['result']);
        usort($hosts, 'collectd_compare_host');
        return $hosts;
 }
@@ -87,123 +237,66 @@ function collectd_list_hosts() {
  * @arg_host Name of host for which to return plugins
  * @return Sorted list of plugins (sorted alphabetically)
  */
-function collectd_list_plugins($arg_host) {
-       global $config;
-
-       $plugins = array();
-       foreach ($config['datadirs'] as $datadir)
-               if (preg_match(REGEXP_HOST, $arg_host) && ($d = @opendir($datadir.'/'.$arg_host))) {
-                       while (($dent = readdir($d)) !== false)
-                               if ($dent != '.' && $dent != '..' && is_dir($datadir.'/'.$arg_host.'/'.$dent)) {
-                                       if ($i = strpos($dent, '-'))
-                                               $plugins[] = substr($dent, 0, $i);
-                                       else
-                                               $plugins[] = $dent;
-                               }
-                       closedir($d);
-               }
-       $plugins = array_unique($plugins);
+function collectd_list_plugins($arg_host, $arg_plugin = null) {
+       $options = array(
+               'result' => array(),
+               'cb_host' => '_collectd_list_cb_host',
+               'cb_plugin' => '_collectd_list_cb_plugin',
+               'cb_pinst' => is_null($arg_plugin) ? false : '_collectd_list_cb_pinst',
+               'cb_type' => false,
+               'cb_tinst' => false,
+               'filter_host' => $arg_host,
+               'filter_plugin' => $arg_plugin
+       );
+       collectd_walk($options);
+       $plugins = array_unique($options['result']);
        sort($plugins);
        return $plugins;
 }
 
 /**
- * Fetch list of plugin instances found in collectd's datadirs for given host+plugin
- * @arg_host Name of host
- * @arg_plugin Name of plugin
- * @return Sorted list of plugin instances (sorted alphabetically)
- */
-function collectd_list_pinsts($arg_host, $arg_plugin) {
-       global $config;
-
-       $pinsts = array();
-       foreach ($config['datadirs'] as $datadir)
-               if (preg_match(REGEXP_HOST, $arg_host) && ($d = opendir($datadir.'/'.$arg_host))) {
-                       while (($dent = readdir($d)) !== false)
-                               if ($dent != '.' && $dent != '..' && is_dir($datadir.'/'.$arg_host.'/'.$dent)) {
-                                       if ($i = strpos($dent, '-')) {
-                                               $plugin = substr($dent, 0, $i);
-                                               $pinst  = substr($dent, $i+1);
-                                       } else {
-                                               $plugin = $dent;
-                                               $pinst  = '';
-                                       }
-                                       if ($plugin == $arg_plugin)
-                                               $pinsts[] = $pinst;
-                               }
-                       closedir($d);
-               }
-       $pinsts = array_unique($pinsts);
-       sort($pinsts);
-       return $pinsts;
-}
-
-/**
  * Fetch list of types found in collectd's datadirs for given host+plugin+instance
  * @arg_host Name of host
  * @arg_plugin Name of plugin
  * @arg_pinst Plugin instance
  * @return Sorted list of types (sorted alphabetically)
  */
-function collectd_list_types($arg_host, $arg_plugin, $arg_pinst) {
-       global $config;
-
-       $types = array();
-       $my_plugin = $arg_plugin . (strlen($arg_pinst) ? '-'.$arg_pinst : '');
-       if (!preg_match(REGEXP_PLUGIN, $my_plugin))
-               return $types;
-       foreach ($config['datadirs'] as $datadir)
-               if (preg_match(REGEXP_HOST, $arg_host) && ($d = @opendir($datadir.'/'.$arg_host.'/'.$my_plugin))) {
-                       while (($dent = readdir($d)) !== false)
-                               if ($dent != '.' && $dent != '..' && is_file($datadir.'/'.$arg_host.'/'.$my_plugin.'/'.$dent) && substr($dent, strlen($dent)-4) == '.rrd') {
-                                       $dent = substr($dent, 0, strlen($dent)-4);
-                                       if ($i = strpos($dent, '-'))
-                                               $types[] = substr($dent, 0, $i);
-                                       else
-                                               $types[] = $dent;
-                               }
-                       closedir($d);
-               }
-       $types = array_unique($types);
+function collectd_list_types($arg_host, $arg_plugin, $arg_pinst, $arg_type = null) {
+       $options = array(
+               'result' => array(),
+               'cb_host' => '_collectd_list_cb_host',
+               'cb_plugin' => '_collectd_list_cb_plugin',
+               'cb_pinst' => '_collectd_list_cb_pinst',
+               'cb_type' => '_collectd_list_cb_type',
+               'cb_tinst' => is_null($arg_type) ? false : '_collectd_list_cb_tinst',
+               'filter_host' => $arg_host,
+               'filter_plugin' => $arg_plugin,
+               'filter_pinst' => $arg_pinst,
+               'filter_type' => $arg_type
+       );
+       collectd_walk($options);
+       $types = array_unique($options['result']);
        sort($types);
        return $types;
 }
 
-/**
- * Fetch list of type instances found in collectd's datadirs for given host+plugin+instance+type
- * @arg_host Name of host
- * @arg_plugin Name of plugin
- * @arg_pinst Plugin instance
- * @arg_type Type
- * @return Sorted list of type instances (sorted alphabetically)
- */
-function collectd_list_tinsts($arg_host, $arg_plugin, $arg_pinst, $arg_type) {
-       global $config;
-
-       $tinsts = array();
-       $my_plugin = $arg_plugin . (strlen($arg_pinst) ? '-'.$arg_pinst : '');
-       if (!preg_match(REGEXP_PLUGIN, $my_plugin))
-               return $types;
-       foreach ($config['datadirs'] as $datadir)
-               if (preg_match(REGEXP_HOST, $arg_host) && ($d = @opendir($datadir.'/'.$arg_host.'/'.$my_plugin))) {
-                       while (($dent = readdir($d)) !== false)
-                               if ($dent != '.' && $dent != '..' && is_file($datadir.'/'.$arg_host.'/'.$my_plugin.'/'.$dent) && substr($dent, strlen($dent)-4) == '.rrd') {
-                                       $dent = substr($dent, 0, strlen($dent)-4);
-                                       if ($i = strpos($dent, '-')) {
-                                               $type  = substr($dent, 0, $i);
-                                               $tinst = substr($dent, $i+1);
-                                       } else {
-                                               $type  = $dent;
-                                               $tinst = '';
-                                       }
-                                       if ($type == $arg_type)
-                                               $tinsts[] = $tinst;
-                               }
-                       closedir($d);
-               }
-       $tinsts = array_unique($tinsts);
-       sort($tinsts);
-       return $tinsts;
+function collectd_list_graphs($arg_host, $arg_plugin, $arg_pinst, $arg_type, $arg_tinst) {
+       $options = array(
+               'result' => array(),
+               'ridentifiers' => array(),
+               'cb_host' => '_collectd_list_cb_host',
+               'cb_plugin' => '_collectd_list_cb_plugin',
+               'cb_pinst' => '_collectd_list_cb_pinst',
+               'cb_type' => '_collectd_list_cb_type',
+               'cb_tinst' => '_collectd_list_cb_graph',
+               'filter_host' => $arg_host,
+               'filter_plugin' => $arg_plugin,
+               'filter_pinst' => $arg_pinst,
+               'filter_type' => $arg_type,
+               'filter_tinst' => $arg_tinst == '@' ? '@merge' : $arg_tinst
+       );
+       collectd_walk($options);
+       return $options['result'];
 }
 
 /**
index 17749e0..b9fefa6 100644 (file)
@@ -164,7 +164,7 @@ $logscale   = (boolean)read_var('logarithmic', $_GET, false);
 $tinylegend = (boolean)read_var('tinylegend', $_GET, false);
 
 // Check that at least 1 RRD exists for the specified request
-$all_tinst = collectd_list_tinsts($host, $plugin, $pinst, $type);
+$all_tinst = collectd_list_types($host, $plugin, $pinst, $type);
 if (count($all_tinst) == 0)
        return error404($graph_identifier, "No rrd file found for graphing");
 
@@ -182,7 +182,7 @@ if ($tinylegend)
        $opts['tinylegend']  = 1;
 
 $rrd_cmd = false;
-if (isset($MetaGraphDefs[$type])) {
+if ((is_null($tinst) || $tinst == '@merge') && isset($MetaGraphDefs[$type])) {
        $identifiers = array();
        foreach ($all_tinst as &$atinst)
                $identifiers[] = collectd_identifier($host, $plugin, is_null($pinst) ? '' : $pinst, $type, $atinst);
index 837d261..5953fbd 100644 (file)
@@ -39,6 +39,22 @@ function dhtml_response_list(&$items, $method) {
        print("</response>");
 }
 
+function dhtml_response_graphs(&$graphs, $method) {
+       header("Content-Type: text/xml");
+
+       print('<?xml version="1.0" encoding="utf-8" ?>'."\n");
+       print("<response>\n");
+       printf(" <method>%s</method>\n", htmlspecialchars($method));
+       print(" <result>\n");
+       foreach ($graphs as &$graph)
+               printf('  <graph host="%s" plugin="%s" plugin_instance="%s" type="%s" type_instance="%s" timespan="%s" logarithmic="%d" tinyLegend="%d" />'."\n",
+                      htmlspecialchars($graph['host']), htmlspecialchars($graph['plugin']), htmlspecialchars($graph['pinst']),
+                      htmlspecialchars($graph['type']), htmlspecialchars($graph['tinst']), htmlspecialchars($graph['timespan']),
+                      htmlspecialchars($graph['logarithmic']), htmlspecialchars($graph['tinyLegend']));
+       print(" </result>\n");
+       print("</response>");
+}
+
 /**
  * Product page body with selection fields
  */
@@ -225,54 +241,87 @@ switch ($action) {
        case 'list_hosts':
                // Generate a list of hosts
                $hosts = collectd_list_hosts();
+               if (count($hosts) > 1)
+                       array_unshift($hosts, '@all');
                return dhtml_response_list($hosts, 'ListOfHost');
 
        case 'list_plugins':
                // Generate list of plugins for selected hosts
-               $arg_hosts = read_var('host', $_POST, array());
-               if (!is_array($arg_hosts))
-                       $arg_hosts = array($arg_hosts);
-               $plugins = collectd_list_plugins(reset($arg_hosts));
+               $arg_hosts = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
+               $plugins = collectd_list_plugins($arg_hosts);
+               if (count($plugins) > 1)
+                       array_unshift($plugins, '@all');
                return dhtml_response_list($plugins, 'ListOfPlugin');
 
        case 'list_pinsts':
                // Generate list of plugin_instances for selected hosts and plugin
-               $arg_hosts = read_var('host', $_POST, array());
-               if (!is_array($arg_hosts))
-                       $arg_hosts = array($arg_hosts);
+               $arg_hosts = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
                $arg_plugin = read_var('plugin', $_POST, '');
-               $pinsts = collectd_list_pinsts(reset($arg_hosts), $arg_plugin);
+               $pinsts = collectd_list_plugins($arg_hosts, $arg_plugin);
+               if (count($pinsts) > 1)
+                       array_unshift($pinsts, '@all' /* , '@merge_sum', '@merge_avg', '@merge_stack', '@merge_line' */);
                return dhtml_response_list($pinsts, 'ListOfPluginInstance');
 
        case 'list_types':
                // Generate list of types for selected hosts, plugin and plugin-instance
-               $arg_hosts  = read_var('host', $_POST, array());
-               if (!is_array($arg_hosts))
-                       $arg_hosts = array($arg_hosts);
+               $arg_hosts  = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
                $arg_plugin = read_var('plugin', $_POST, '');
                $arg_pinst  = read_var('plugin_instance', $_POST, '');
-               $types = collectd_list_types(reset($arg_hosts), $arg_plugin, $arg_pinst);
+               $types = collectd_list_types($arg_hosts, $arg_plugin, $arg_pinst);
+               if (count($types) > 1)
+                       array_unshift($types, '@all');
                return dhtml_response_list($types, 'ListOfType');
 
        case 'list_tinsts':
                // Generate list of types for selected hosts, plugin and plugin-instance
-               $arg_hosts  = read_var('host', $_POST, array());
-               if (!is_array($arg_hosts))
-                       $arg_hosts = array($arg_hosts);
+               $arg_hosts  = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
                $arg_plugin = read_var('plugin', $_POST, '');
                $arg_pinst  = read_var('plugin_instance', $_POST, '');
                $arg_type   = read_var('type', $_POST, '');
-               $tinsts = collectd_list_tinsts(reset($arg_hosts), $arg_plugin, $arg_pinst, $arg_type);
-               if (count($tinsts)) {
-                       require('definitions.php');
-                       load_graph_definitions();
-                       if (isset($MetaGraphDefs[$arg_type])) {
-                               $meta_tinsts = array('@');
-                               return dhtml_response_list($meta_tinsts, 'ListOfTypeInstance');
+               $tinsts = collectd_list_types($arg_hosts, $arg_plugin, $arg_pinst, $arg_type);
+               if (count($tinsts))
+                       if ($arg_type != '@all') {
+                               require('definitions.php');
+                               load_graph_definitions();
+                               if (isset($MetaGraphDefs[$arg_type]))
+                                       array_unshift($tinsts, '@merge');
+                               if (count($tinsts) > 1)
+                                       array_unshift($tinsts, '@all');
+                       } else {
+                               array_unshift($tinsts, /* '@merge_sum', '@merge_avg', '@merge_stack', '@merge_line', */ '@merge');
+                               if (count($tinsts) > 1)
+                                       array_unshift($tinsts, '@all');
                        }
-               }
                return dhtml_response_list($tinsts, 'ListOfTypeInstance');
 
+       case 'list_graphs':
+               // Generate list of types for selected hosts, plugin and plugin-instance
+               $arg_hosts  = read_var('host', $_POST, '');
+               if (is_array($arg_hosts))
+                       $arg_hosts = reset($arg_hosts);
+               $arg_plugin = read_var('plugin', $_POST, '');
+               $arg_pinst  = read_var('plugin_instance', $_POST, '');
+               $arg_type   = read_var('type', $_POST, '');
+               $arg_tinst  = read_var('type_instance', $_POST, '');
+               $arg_log    = (int)read_var('logarithmic', $_POST, '0');
+               $arg_legend = (int)read_var('tinyLegend', $_POST, '0');
+               $arg_period = read_var('timespan', $_POST, '');
+               $graphs = collectd_list_graphs($arg_hosts, $arg_plugin, $arg_pinst, $arg_type, $arg_tinst);
+               foreach ($graphs as &$graph) {
+                       $graph['logarithmic'] = $arg_log;
+                       $graph['tinyLegend']  = $arg_legend;
+                       $graph['timespan']    = $arg_period;
+               }
+               return dhtml_response_graphs($graphs, 'ListOfGraph');
+
        case 'overview':
        default:
                return build_page();