diff --git a/doc/Extensions/Proxmox.md b/doc/Extensions/Proxmox.md
new file mode 100644
index 0000000000..0691e1d273
--- /dev/null
+++ b/doc/Extensions/Proxmox.md
@@ -0,0 +1,23 @@
+# Proxmox graphing
+
+It is possible to create graphs of the Proxmox VMs that run on your monitored machines. Currently, only trafficgraphs are created. One for each interface on each VM. Possibly, IO grahps will be added later on.
+
+The ultimate goal is to be able to create traffic bills for VMs, no matter on which physical machine that VM runs.
+
+### Enabling Proxmox graphs
+
+To enable Proxmox graphs, do the following:
+
+In config.php, enable Proxmox:
+```php
+$config['enable_proxmox'] = 1
+```
+
+Then, install librenms-agent on the machines running Proxmox, and enable the Proxmox-plugin using:
+```bash
+mk_enplug proxmox
+```
+
+Then, enable the unix-agent on the machines running Proxmox.
+
+You should now see an application in LibreNMS, as well as a new menu-item in the topmenu, allowing you to choose which cluster you want to look at.
diff --git a/html/includes/application/proxmox.inc.php b/html/includes/application/proxmox.inc.php
new file mode 100644
index 0000000000..bd775247b9
--- /dev/null
+++ b/html/includes/application/proxmox.inc.php
@@ -0,0 +1,50 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 dated June,
+ * 1991.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * See http://www.gnu.org/licenses/gpl.txt for the full license
+ */
+
+/**
+ * Fetch all VM's in a Proxmox Cluster
+ * @param string $c Clustername
+ * @return array An array with all the VM's on this cluster
+ */
+function proxmox_cluster_vms($c) {
+ return dbFetchRows("SELECT * FROM proxmox WHERE cluster = ? ORDER BY vmid", array($c));
+}
+
+/**
+ * Fetch all VM's on a Proxmox node
+ * @param integer $n device_id
+ * @return array An array with all the VM's on this node
+ */
+function proxmox_node_vms($n) {
+ return dbFetchRows("SELECT * FROM proxmox WHERE device_id = ? ORDER BY vmid", array($n));
+}
+
+/**
+ * Fetch all info about a Proxmox VM
+ * @param integer $vmid Proxmox VM ID
+ * @param string $c Clustername
+ * @return array An array with all info of this VM on this cluster, including ports
+ */
+function proxmox_vm_info($vmid, $c) {
+ $vm = dbFetchRow("SELECT pm.*, d.hostname AS host, d.device_id FROM proxmox pm, devices d WHERE pm.device_id = d.device_id AND pm.vmid = ? AND pm.cluster = ?", array($vmid, $c));
+ $appid = dbFetchRow("SELECT app_id FROM applications WHERE device_id = ? AND app_type = ?", array($vm['device_id'], 'proxmox'));
+
+ $vm['ports'] = dbFetchRows("SELECT * FROM proxmox_ports WHERE vm_id = ?", array($vm['id']));
+ $vm['app_id'] = $appid['app_id'];
+ return $vm;
+}
diff --git a/html/includes/functions.inc.php b/html/includes/functions.inc.php
index b1c44481ba..7b7ea62ca5 100644
--- a/html/includes/functions.inc.php
+++ b/html/includes/functions.inc.php
@@ -12,6 +12,35 @@
* @copyright (C) 2013 LibreNMS Group
*/
+/**
+ * Compare $t with the value of $vars[$v], if that exists
+ * @param string $v Name of the var to test
+ * @param string $t Value to compare $vars[$v] to
+ * @return boolean true, if values are the same, false if $vars[$v] is unset or values differ
+ */
+function var_eq($v, $t) {
+ global $vars;
+ if (isset($vars[$v]) && $vars[$v] == $t) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Get the value of $vars[$v], if it exists
+ * @param string $v Name of the var to get
+ * @return string|boolean The value of $vars[$v] if it exists, false if it does not exist
+ */
+function var_get($v) {
+ global $vars;
+ if (isset($vars[$v])) {
+ return $vars[$v];
+ }
+
+ return false;
+}
+
function data_uri($file, $mime) {
$contents = file_get_contents($file);
diff --git a/html/includes/graphs/application/auth.inc.php b/html/includes/graphs/application/auth.inc.php
index 15c8f01ca1..719145015d 100644
--- a/html/includes/graphs/application/auth.inc.php
+++ b/html/includes/graphs/application/auth.inc.php
@@ -3,7 +3,12 @@
if (is_numeric($vars['id']) && ($auth || application_permitted($vars['id']))) {
$app = get_application_by_id($vars['id']);
$device = device_by_id_cache($app['device_id']);
- $title = generate_device_link($device);
- $title .= $graph_subtype;
+ if ($app['app_type'] != 'proxmox') {
+ $title = generate_device_link($device);
+ $title .= $graph_subtype;
+ }
+ else {
+ $title = $vars['port'].'@'.$vars['hostname'].' on '.generate_device_link($device);
+ }
$auth = true;
}
diff --git a/html/includes/graphs/application/proxmox_traffic.inc.php b/html/includes/graphs/application/proxmox_traffic.inc.php
new file mode 100644
index 0000000000..26d6e66823
--- /dev/null
+++ b/html/includes/graphs/application/proxmox_traffic.inc.php
@@ -0,0 +1,30 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 dated June,
+ * 1991.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * See http://www.gnu.org/licenses/gpl.txt for the full license
+ */
+
+require 'includes/graphs/common.inc.php';
+
+$proxmox_rrd = $config['rrd_dir'].'/proxmox/'.$vars['cluster'].'/'.$vars['vmid'].'_netif_'.$vars['port'].'.rrd';
+
+if (is_file($proxmox_rrd)) {
+ $rrd_filename = $proxmox_rrd;
+}
+
+$ds_in = 'INOCTETS';
+$ds_out = 'OUTOCTETS';
+
+require 'includes/graphs/generic_data.inc.php';
diff --git a/html/includes/print-menubar.php b/html/includes/print-menubar.php
index 3e43f6c759..1cf1747e0f 100644
--- a/html/includes/print-menubar.php
+++ b/html/includes/print-menubar.php
@@ -360,21 +360,32 @@ foreach (array_keys($menu_sensors) as $item) {
= '5' && ($app_count) > "0") {
+if ($_SESSION['userlevel'] >= '5' && count($app_list) > "0") {
?>
Apps
');
+ if (count($app_i_list) > 1) {
+ echo '';
+ }
+ else {
+ echo(' '.nicecase($app['app_type']).' ');
+ }
}
}
?>
diff --git a/html/pages/apps/proxmox.inc.php b/html/pages/apps/proxmox.inc.php
new file mode 100644
index 0000000000..d20350a1ff
--- /dev/null
+++ b/html/pages/apps/proxmox.inc.php
@@ -0,0 +1,60 @@
+Proxmox » ";
+
+unset($sep);
+
+foreach ($pmxcl as $pmxc) {
+ if (isset($sep)) {
+ echo $sep;
+ };
+
+ if (var_eq('instance', $pmxc['app_instance'])) {
+ echo "';
+ }
+
+ $sep = ' | ';
+}
+
+print_optionbar_end();
+
+echo '';
+
+if (!isset($vars['instance'])) {
+ echo 'Select a cluster:';
+ echo '
';
+ foreach ($pmxcl as $pmxc) {
+ echo '- '.generate_link(nicecase($pmxc['app_instance']), array('page' => 'apps', 'app' => 'proxmox', 'instance' => $pmxc['app_instance'])).'
';
+ }
+ echo '
';
+}
+elseif (!isset($vars['vmid'])) {
+ echo '
';
+ foreach (proxmox_cluster_vms(var_get('instance')) as $pmxvm) {
+ echo '- '.generate_link($pmxvm['vmid']." (".$pmxvm['description'].")", array('page' => 'apps', 'app' => 'proxmox', 'instance' => var_get('instance'), 'vmid' => $pmxvm['vmid'])).'
';
+ }
+ echo '
';
+}
+else {
+ include("pages/apps/proxmox/vm.inc.php");
+}
+
+echo '
';
+
+$pagetitle[] = 'Proxmox';
diff --git a/html/pages/apps/proxmox/vm.inc.php b/html/pages/apps/proxmox/vm.inc.php
new file mode 100644
index 0000000000..5bd7a50f38
--- /dev/null
+++ b/html/pages/apps/proxmox/vm.inc.php
@@ -0,0 +1,32 @@
+ 'Traffic',
+);
+
+foreach ($vm['ports'] as $port) {
+ foreach ($graphs as $key => $text) {
+ $graph_type = 'proxmox_traffic';
+
+ $graph_array['height'] = '100';
+ $graph_array['width'] = '215';
+ $graph_array['to'] = $config['time']['now'];
+ $graph_array['id'] = $vm['app_id'];
+ $graph_array['device_id'] = $vm['device_id'];
+ $graph_array['type'] = 'application_'.$key;
+ $graph_array['port'] = $port['port'];
+ $graph_array['vmid'] = $vm['vmid'];
+ $graph_array['cluster'] = $vm['cluster'];
+ $graph_array['hostname'] = $vm['description'];
+
+ echo ''.$text.' '.$port['port'].'@'.$vm['description'].'
';
+
+ echo "| ";
+
+ include 'includes/print-graphrow.inc.php';
+
+ echo ' |
';
+ }
+}
diff --git a/html/pages/device/apps/proxmox.inc.php b/html/pages/device/apps/proxmox.inc.php
new file mode 100644
index 0000000000..625d543615
--- /dev/null
+++ b/html/pages/device/apps/proxmox.inc.php
@@ -0,0 +1,58 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 dated June,
+ * 1991.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * See http://www.gnu.org/licenses/gpl.txt for the full license
+ */
+
+include('includes/application/proxmox.inc.php');
+
+if (!isset($config['enable_proxmox']) || !$config['enable_proxmox']) {
+ print_error('Proxmox agent was discovered on this host. Please enable Proxmox in your config.');
+}
+else {
+
+ $graphs = array(
+ 'proxmox_traffic' => 'Traffic',
+ );
+
+ foreach (proxmox_node_vms(var_get('device')) as $nvm) {
+ $vm = proxmox_vm_info($nvm['vmid'], $nvm['cluster']);
+
+ foreach ($vm['ports'] as $port) {
+ foreach ($graphs as $key => $text) {
+ $graph_type = 'proxmox_traffic';
+
+ $graph_array['height'] = '100';
+ $graph_array['width'] = '215';
+ $graph_array['to'] = $config['time']['now'];
+ $graph_array['id'] = $vm['app_id'];
+ $graph_array['device_id'] = $vm['device_id'];
+ $graph_array['type'] = 'application_'.$key;
+ $graph_array['port'] = $port['port'];
+ $graph_array['vmid'] = $vm['vmid'];
+ $graph_array['cluster'] = $vm['cluster'];
+ $graph_array['hostname'] = $vm['description'];
+
+ echo ''.$text.' '.$port['port'].'@'.$vm['description'].'
';
+
+ echo "| ";
+
+ include 'includes/print-graphrow.inc.php';
+
+ echo ' |
';
+ }
+ }
+ }
+}
diff --git a/includes/polling/applications/proxmox.inc.php b/includes/polling/applications/proxmox.inc.php
new file mode 100644
index 0000000000..64668f5263
--- /dev/null
+++ b/includes/polling/applications/proxmox.inc.php
@@ -0,0 +1,93 @@
+ 0) {
+ return true;
+ }
+ if ($row = dbFetchRow("SELECT id FROM proxmox WHERE vmid = ? AND cluster = ?", array($i, $c))) {
+ $pmxcache[$c][$i] = (integer) $row['id'];
+ return true;
+ }
+
+ return false;
+}
+
+$pmxlines = explode("\n", $proxmox);
+
+if (count($pmxlines) > 2) {
+ $pmxcluster = array_shift($pmxlines);
+
+ $pmxcdir = join('/', array($config['rrd_dir'],'proxmox',$pmxcluster));
+ if (!is_dir($pmxcdir)) {
+ mkdir($pmxcdir, 0775, true);
+ }
+
+ dbUpdate(array('device_id' => $device['device_id'], 'app_type' => 'proxmox', 'app_instance' => $pmxcluster), 'applications', '`device_id` = ? AND `app_type` = ?', array($device['device_id'], 'proxmox'));
+
+ $pmxcache = [];
+
+ foreach ($pmxlines as $vm) {
+ list($vmid, $vmport, $vmpin, $vmpout, $vmdesc) = explode('/', $vm, 5);
+
+ $rrd_filename = join('/', array(
+ $pmxcdir,
+ $vmid.'_netif_'.$vmport.'.rrd'));
+
+ if (!is_file($rrd_filename)) {
+ rrdtool_create(
+ $rrd_filename,
+ ' --step 300 \
+ DS:INOCTETS:DERIVE:600:0:12500000000 \
+ DS:OUTOCTETS:DERIVE:600:0:12500000000 '.$config['rrd_rra']);
+ }
+
+ rrdtool_update($rrd_filename, 'N:'.$vmpin.':'.$vmpout);
+ if (proxmox_vm_exists($vmid, $pmxcluster, $pmxcache) === true) {
+ dbUpdate(array('device_id' => $device['device_id'], 'last_seen' => array('NOW()'), 'description' => $vmdesc), 'proxmox', '`vmid` = ? AND `cluster` = ?', array($vmid, $pmxcluster));
+ }
+ else {
+ $pmxcache[$pmxcluster][$vmid] = dbInsert(array('cluster' => $pmxcluster, 'vmid' => $vmid, 'description' => $vmdesc, 'device_id' => $device['device_id']), 'proxmox');
+ }
+
+ if ($portid = proxmox_port_exists($vmid, $pmxcluster, $vmport) !== false) {
+ dbUpdate(array('last_seen' => array('NOW()')), 'proxmox_ports', '`vm_id` = ? AND `port` = ?', array($pmxcache[$pmxcluster][$vmid], $vmport));
+ }
+ else {
+ dbInsert(array('vm_id' => $pmxcache[$pmxcluster][$vmid], 'port' => $vmport), 'proxmox_ports');
+ }
+
+ }
+
+ unset($pmxlines);
+ unset($pmxcluster);
+ unset($pmxcdir);
+ unset($proxmox);
+ unset($pmxcache);
+}
diff --git a/includes/polling/unix-agent.inc.php b/includes/polling/unix-agent.inc.php
index 3d1754498c..5b78b682c9 100644
--- a/includes/polling/unix-agent.inc.php
+++ b/includes/polling/unix-agent.inc.php
@@ -50,6 +50,7 @@ if ($device['os_group'] == 'unix') {
"mysql",
"nginx",
"bind",
+ "proxmox",
"tinydns");
if (in_array($section, $agentapps)) {
@@ -99,7 +100,7 @@ if ($device['os_group'] == 'unix') {
if (file_exists("includes/polling/applications/$key.inc.php")) {
d_echo("Enabling $key for ".$device['hostname']." if not yet enabled\n");
- if (in_array($key, array('apache', 'mysql', 'nginx'))) {
+ if (in_array($key, array('apache', 'mysql', 'nginx', 'proxmox'))) {
if (dbFetchCell('SELECT COUNT(*) FROM `applications` WHERE `device_id` = ? AND `app_type` = ?', array($device['device_id'], $key)) == '0') {
echo "Found new application '$key'\n";
dbInsert(array('device_id' => $device['device_id'], 'app_type' => $key), 'applications');
diff --git a/sql-schema/067.sql b/sql-schema/067.sql
new file mode 100644
index 0000000000..5ad0781bdf
--- /dev/null
+++ b/sql-schema/067.sql
@@ -0,0 +1,3 @@
+CREATE TABLE `proxmox` ( `id` int(11) NOT NULL AUTO_INCREMENT, `device_id` int(11) NOT NULL DEFAULT '0', `vmid` int(11) NOT NULL, `cluster` varchar(255) NOT NULL, `description` varchar(255) DEFAULT NULL, `last_seen` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `cluster_vm` (`cluster`,`vmid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE `proxmox_ports` ( `id` int(11) NOT NULL AUTO_INCREMENT, `vm_id` int(11) NOT NULL, `port` varchar(10) NOT NULL, `last_seen` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `vm_port` (`vm_id`,`port`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+ALTER TABLE `applications` ADD COLUMN `app_instance` varchar(255) NOT NULL;