diff --git a/.gitignore b/.gitignore
index fa281ab..d0200ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -329,4 +329,4 @@ ASALocalRun/
# MFractors (Xamarin productivity tool) working folder
.mfractor/
-.parm
\ No newline at end of file
+*.parm
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index c7cf1d7..38e142f 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,9 +1,9 @@
{
- // Use IntelliSense to find out which attributes exist for C# debugging
- // Use hover for the description of the existing attributes
- // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
- "version": "0.2.0",
- "configurations": [
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "version": "0.2.0",
+ "configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
diff --git a/src/Corsinvest.ProxmoxVE.Metrics.Exporter.Api/Corsinvest.ProxmoxVE.Metrics.Exporter.Api.csproj b/src/Corsinvest.ProxmoxVE.Metrics.Exporter.Api/Corsinvest.ProxmoxVE.Metrics.Exporter.Api.csproj
index 117ede0..d97694b 100644
--- a/src/Corsinvest.ProxmoxVE.Metrics.Exporter.Api/Corsinvest.ProxmoxVE.Metrics.Exporter.Api.csproj
+++ b/src/Corsinvest.ProxmoxVE.Metrics.Exporter.Api/Corsinvest.ProxmoxVE.Metrics.Exporter.Api.csproj
@@ -1,9 +1,9 @@
- netstandard2.1
- true
+ net6.0
+ true
- 1.4.1
+ 1.4.2
Corsinvest Srl
Daniele Corsini
Corsinvest Srl
@@ -34,7 +34,7 @@
-
+
\ No newline at end of file
diff --git a/src/Corsinvest.ProxmoxVE.Metrics.Exporter.Api/PrometheusExporter.cs b/src/Corsinvest.ProxmoxVE.Metrics.Exporter.Api/PrometheusExporter.cs
index cb8f4f0..25783a2 100644
--- a/src/Corsinvest.ProxmoxVE.Metrics.Exporter.Api/PrometheusExporter.cs
+++ b/src/Corsinvest.ProxmoxVE.Metrics.Exporter.Api/PrometheusExporter.cs
@@ -8,10 +8,13 @@
using System.Diagnostics;
using System.Globalization;
using System.Linq;
-using System.Reflection;
using System.Threading.Tasks;
using Corsinvest.ProxmoxVE.Api;
+using Corsinvest.ProxmoxVE.Api.Extension;
using Corsinvest.ProxmoxVE.Api.Extension.Utils;
+using Corsinvest.ProxmoxVE.Api.Shared.Models.Cluster;
+using Corsinvest.ProxmoxVE.Api.Shared.Models.Node;
+using Corsinvest.ProxmoxVE.Api.Shared.Models.Vm;
using Microsoft.Extensions.Logging;
using Prometheus;
@@ -22,6 +25,8 @@ namespace Corsinvest.ProxmoxVE.Metrics.Exporter.Api
///
public class PrometheusExporter
{
+ private const string KeyBalloon = "balloon: ";
+
///
/// Default host
///
@@ -33,7 +38,7 @@ public class PrometheusExporter
public static readonly int DEFAULT_PORT = 9221;
///
- /// Dewfault url
+ /// Default url
///
public static readonly string DEFAULT_URL = "metrics/";
@@ -115,40 +120,114 @@ public PrometheusExporter(CollectorRegistry registry, string prefix, bool export
//create gauges
_up = metricFactory.CreateGauge($"{prefix}_up",
- "Proxmox VE Node/Storage/VM/CT-Status is online/running/aviable",
- new GaugeConfiguration { LabelNames = new[] { "id" } });
+ "Proxmox VE Node/Storage/VM/CT-Status is online/running/available",
+ new GaugeConfiguration { LabelNames = new[] { "Id" } });
_nodeInfo = metricFactory.CreateGauge($"{prefix}_node_info",
"Node info",
- new GaugeConfiguration { LabelNames = new[] { "id", "ip", "level", "local", "name", "nodeid" } });
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(ClusterStatus.Id),
+ nameof(ClusterStatus.IpAddress),
+ nameof(ClusterStatus.Level),
+ nameof(ClusterStatus.Local),
+ nameof(ClusterStatus.Name),
+ nameof(ClusterStatus.NodeId),
+ }
+ });
_nodeDiskWearout = metricFactory.CreateGauge($"{prefix}_node_disk_Wearout",
"Node disk wearout",
- new GaugeConfiguration { LabelNames = new[] { "serial", "node", "type", "dev" } });
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(NodeDiskList.Serial),
+ "Node",
+ nameof(NodeDiskList.Type),
+ nameof(NodeDiskList.DevPath),
+ }
+ });
_nodeDiskHealth = metricFactory.CreateGauge($"{prefix}_node_disk_health",
"Node disk health",
- new GaugeConfiguration { LabelNames = new[] { "serial", "node", "type", "dev" } });
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(NodeDiskList.Serial),
+ "Node",
+ nameof(NodeDiskList.Type),
+ nameof(NodeDiskList.DevPath),
+ }
+ });
_nodeDiskSmart = metricFactory.CreateGauge($"{prefix}_node_disk_smart",
"Node disk smart",
- new GaugeConfiguration { LabelNames = new[] { "serial", "node", "type", "dev", "name" } });
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(NodeDiskList.Serial),
+ "Node",
+ nameof(NodeDiskList.Type),
+ nameof(NodeDiskList.DevPath),
+ }
+ });
+ // new GaugeConfiguration { LabelNames = new[] { "serial", "node", "type", "dev", "name" } });
_clusterInfo = metricFactory.CreateGauge($"{prefix}_cluster_info",
"Cluster info",
- new GaugeConfiguration { LabelNames = new[] { "id", "nodes", "quorate", "version" } });
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(ClusterStatus.Id),
+ nameof(ClusterStatus.Nodes),
+ nameof(ClusterStatus.Quorate),
+ nameof(ClusterStatus.Version),
+ }
+ });
_versionInfo = metricFactory.CreateGauge($"{prefix}_version_info",
"Proxmox VE version info",
- new GaugeConfiguration { LabelNames = new[] { "release", "repoid", "version" } });
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(NodeVersion.Release),
+ nameof(NodeVersion.RepositoryId),
+ nameof(NodeVersion.Version),
+ }
+ });
_guestInfo = metricFactory.CreateGauge($"{prefix}_guest_info",
"VM/CT info",
- new GaugeConfiguration { LabelNames = new[] { "id", "node", "name", "type" } });
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(IClusterResourceVm.VmId),
+ nameof(IClusterResourceVm.Node),
+ nameof(IClusterResourceVm.Name),
+ nameof(IClusterResourceVm.Type),
+ }
+ });
_storageInfo = metricFactory.CreateGauge($"{prefix}_storage_info",
"Storage info",
- new GaugeConfiguration { LabelNames = new[] { "id", "node", "storage", "shared" } });
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(IClusterResourceStorage.Id),
+ nameof(IClusterResourceStorage.Node),
+ nameof(IClusterResourceStorage.Storage),
+ nameof(IClusterResourceStorage.Shared),
+ }
+ });
// StorageDef = metricFactory.CreateGauge($"{prefix}_storage_def",
// "Storage info 1",
@@ -169,27 +248,35 @@ public PrometheusExporter(CollectorRegistry registry, string prefix, bool export
private void CreateGauges(MetricFactory metricFactory, string prefix)
{
- var defs = new (string Key, string Name, string Description)[]
+ var defs = new (string propertyName, string name, string description)[]
{
- ( "maxdisk", "disk_size_bytes", "Size of storage device" ),
- ( "disk", "disk_usage_bytes", "Disk usage in bytes" ),
- ( "maxmem", "memory_size_bytes", "Size of memory" ),
- ( "mem", "memory_usage_bytes", "Memory usage in bytes" ),
- ( "netout", "network_transmit_bytes", "Number of bytes transmitted over the network" ),
- ( "netin", "network_receive_bytes", "Number of bytes received over the network" ),
- ( "diskwrite", "disk_write_bytes", "Number of bytes written to storage" ),
- ( "diskread", "disk_read_bytes", "Number of bytes read from storage" ),
- ( "cpu", "cpu_usage_ratio",$"CPU usage (value between 0.0 and {prefix}_cpu_usage_limit)" ),
- ( "maxcpu", "cpu_usage_limit", "Maximum allowed CPU usage" ),
- ( "uptime", "uptime_seconds", "Number of seconds since the last boot" ),
+ ( nameof(IClusterResourceVm.HostMemoryUsage), "host_memory_usage_bytes", "Host memory usage" ),
+ ( nameof(IClusterResourceVm.DiskSize), "disk_size_bytes", "Size of storage device" ),
+ ( nameof(IClusterResourceVm.DiskUsage) , "disk_usage_bytes", "Disk usage in bytes" ),
+ ( nameof(IClusterResourceVm.MemorySize), "memory_size_bytes", "Size of memory" ),
+ ( nameof(IClusterResourceVm.MemoryUsage), "memory_usage_bytes", "Memory usage in bytes" ),
+ ( nameof(IClusterResourceVm.NetOut) , "network_transmit_bytes", "Number of bytes transmitted over the network" ),
+ ( nameof(IClusterResourceVm.NetIn) , "network_receive_bytes", "Number of bytes received over the network" ),
+ ( nameof(IClusterResourceVm.DiskWrite), "disk_write_bytes", "Number of bytes written to storage" ),
+ ( nameof(IClusterResourceVm.DiskRead), "disk_read_bytes", "Number of bytes read from storage" ),
+ ( nameof(IClusterResourceVm.CpuUsagePercentage) , "cpu_usage_ratio",$"CPU usage (value between 0.0 and {prefix}_cpu_usage_limit)" ),
+ ( nameof(IClusterResourceVm.CpuSize) , "cpu_usage_limit", "Maximum allowed CPU usage" ),
+ ( nameof(IClusterResourceVm.Uptime), "uptime_seconds", "Number of seconds since the last boot" ),
};
- foreach (var (Key, Name, Description) in defs)
+ foreach (var (propertyName, name, description) in defs)
{
- _resourceInfo.Add(Key,
- metricFactory.CreateGauge($"{prefix}_{Name}",
- Description,
- new GaugeConfiguration { LabelNames = new[] { "id", "node" } }));
+ _resourceInfo.Add(propertyName,
+ metricFactory.CreateGauge($"{prefix}_{name}",
+ description,
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(IClusterResourceVm.Id),
+ nameof(IClusterResourceVm.Node),
+ }
+ }));
}
var defs2 = new (string Key, string Description)[]
@@ -207,37 +294,55 @@ private void CreateGauges(MetricFactory metricFactory, string prefix)
new GaugeConfiguration { LabelNames = new[] { "id", "node" } }));
}
- var defs3 = new (string Key, string Description)[]
+ var defs3 = new (string key, string description)[]
{
( "load_avg1", "Node load avg1" ),
( "load_avg5", "Node load avg5" ),
( "load_avg15", "Node load avg15" ),
+ ( "uptime_seconds", "Number of seconds since the last boot" ),
};
- foreach (var (Key, Description) in defs3)
+ foreach (var (key, description) in defs3)
{
- _nodeExtraInfo.Add(Key,
- metricFactory.CreateGauge($"{prefix}_node_{Key}",
- Description,
- new GaugeConfiguration { LabelNames = new[] { "node" } }));
+ _nodeExtraInfo.Add(key,
+ metricFactory.CreateGauge($"{prefix}_node_{key}",
+ description,
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(IClusterResourceVm.Node),
+ }
+ }));
}
- var defs4 = new (string Key, string Description)[]
+ var defs4 = new (string key, string description)[]
{
( "memory_used", "Node memory used" ),
( "memory_total", "Node memory total" ),
( "memory_free", "Node memory free" ),
+
( "swap_used", "Node swap used" ),
( "swap_total", "Node swap total" ),
( "swap_free", "Node swap free" ),
+
+ ( "root_fs_used", "Node root fs used" ),
+ ( "root_fs_total", "Node root fs total" ),
+ ( "root_fs_free", "Node root fs free" ),
};
- foreach (var (Key, Description) in defs4)
+ foreach (var (key, description) in defs4)
{
- _nodeExtraInfo.Add(Key,
- metricFactory.CreateGauge($"{prefix}_node_{Key}_bytes",
- Description,
- new GaugeConfiguration { LabelNames = new[] { "node" } }));
+ _nodeExtraInfo.Add(key,
+ metricFactory.CreateGauge($"{prefix}_node_{key}_bytes",
+ description,
+ new GaugeConfiguration
+ {
+ LabelNames = new[]
+ {
+ nameof(IClusterResourceVm.Node),
+ }
+ }));
}
}
@@ -248,73 +353,78 @@ private void CreateGauges(MetricFactory metricFactory, string prefix)
public async Task Collect(PveClient client)
{
//todo fix in new version use decode model
- SetGauge(_versionInfo, (await client.Version.Version()).Response.data);
+ SetGauge(_versionInfo, await client.Version.Get());
var formatInfo = new NumberFormatInfo
{
NumberDecimalSeparator = "."
};
- foreach (var item in (await client.Cluster.Status.GetStatus()).ToEnumerable())
+ foreach (var item in await client.Cluster.Status.Get())
{
- switch (item.type)
+ switch (item.Type)
{
case "node":
- _up.WithLabels(item.id as string).Set(item.online);
+ _up.WithLabels(item.Id).Set(item.IsOnline ? 1 : 0);
SetGauge(_nodeInfo, item);
- if (item.online == 1)
+ if (item.IsOnline)
{
- var node = item.name as string;
- var data = (await client.Nodes[node].Status.Status()).Response.data;
+ var status = await client.Nodes[item.Name].Status.Get();
+ var loadAvg = status.LoadAvg.ToArray();
- foreach (var item1 in _nodeExtraInfo)
+ foreach (var (key, gauge) in _nodeExtraInfo)
{
- double value = item1.Key switch
+ double value = key switch
{
- "memory_used" => data.memory.used,
- "memory_total" => data.memory.total,
- "memory_free" => data.memory.free,
- "load_avg1" => double.Parse(data.loadavg[0], formatInfo),
- "load_avg5" => double.Parse(data.loadavg[1], formatInfo),
- "load_avg15" => double.Parse(data.loadavg[2], formatInfo),
- "swap_used" => data.swap.used,
- "swap_total" => data.swap.total,
- "swap_free" => data.swap.free,
+ "uptime_seconds" => status.Uptime,
+
+ "memory_used" => status.Memory.Used,
+ "memory_total" => status.Memory.Total,
+ "memory_free" => status.Memory.Free,
+
+ "swap_used" => status.Swap.Used,
+ "swap_total" => status.Swap.Total,
+ "swap_free" => status.Swap.Free,
+
+ "root_fs_used" => status.RootFs.Used,
+ "root_fs_total" => status.RootFs.Total,
+ "root_fs_free" => status.RootFs.Free,
+
+ "load_avg1" => double.Parse(loadAvg[0], formatInfo),
+ "load_avg5" => double.Parse(loadAvg[1], formatInfo),
+ "load_avg15" => double.Parse(loadAvg[2], formatInfo),
+
_ => 0.0,
};
- item1.Value.WithLabels(node).Set(value);
+
+ gauge.WithLabels(item.Name).Set(value);
}
if (_exportNodeDiskInfo)
{
//disk info
- foreach (var disk in (await client.Nodes[node].Disks.List.List()).ToEnumerable())
+ foreach (var disk in await client.Nodes[item.Name].Disks.List.Get())
{
- var labelValues = new string[] { disk.serial as string,
- node,
- disk.type as string,
- disk.devpath as string };
+ var labelValues = new string[] { disk.Serial, item.Name, disk.Type, disk.DevPath };
- if (ExistProperty(disk, "wearout") != null)
+ if (disk.Wearout != "N/A")
{
- _nodeDiskHealth.WithLabels(labelValues).Set(GetValue(disk, "wearout"));
+ _nodeDiskHealth.WithLabels(labelValues).Set(double.Parse(disk.Wearout));
}
- _nodeDiskWearout.WithLabels(labelValues).Set((disk.health as string) == "PASSED" ? 1 : 0);
+ _nodeDiskWearout.WithLabels(labelValues).Set(disk.Health == "PASSED" ? 1 : 0);
//smart
- var smart = (await client.Nodes[node].Disks.Smart.Smart(disk.devpath as string)).Response.data;
- if (ExistProperty(smart, "attributes") != null)
+ var smart = await client.Nodes[item.Name].Disks.Smart.Get(disk.DevPath);
+ foreach (var attribute in smart.Attributes)
{
- foreach (var attribute in smart.attributes)
+ var labelValuesAttr = new List(labelValues)
{
- var labelValuesAttr = new List();
- labelValuesAttr.AddRange(labelValues);
- labelValuesAttr.Add(attribute.name as string);
+ attribute.Name
+ };
- _nodeDiskSmart.WithLabels(labelValuesAttr.ToArray()).Set(attribute.value);
- }
+ _nodeDiskSmart.WithLabels(labelValuesAttr.ToArray()).Set(attribute.Value);
}
}
}
@@ -322,7 +432,7 @@ public async Task Collect(PveClient client)
break;
case "cluster":
- _up.WithLabels($"cluster/{item.name}").Set(item.quorate);
+ _up.WithLabels($"cluster/{item.Name}").Set(item.Quorate);
SetGauge(_clusterInfo, item);
break;
@@ -332,72 +442,69 @@ public async Task Collect(PveClient client)
//foreach (var item in client.Storage.Index().ToEnumerable()) { SetGauge1(StorageDef, item); }
- foreach (var item in (await client.Cluster.Resources.Resources()).ToEnumerable())
+ foreach (var item in (await client.Cluster.Resources.Get()).CalculateHostUsage())
{
- switch (item.type)
+ switch (item.ResourceType)
{
- case "qemu":
- case "lxc":
- var isRunning = item.status as string == "running";
-
- _up.WithLabels($"{item.id}").Set(isRunning ? 1 : 0);
+ case ClusterResourceType.Vm:
+ _up.WithLabels(item.Id).Set(item.IsRunning ? 1 : 0);
SetGauge(_guestInfo, item);
- if (isRunning && item.type == "qemu")
+ if (item.IsRunning && item.VmType == VmType.Qemu)
{
- var node = item.node as string;
- var vmId = item.vmid + "" as string;
- var data = ((await client.Nodes[node]
- .Qemu[vmId]
- .Monitor.Monitor("info balloon"))
- .Response.data as string)
- .Split(" ")
- .Skip(1)
- .Select(a => new
- {
- Name = a.Split("=")[0],
- Value = double.Parse(a.Split("=")[1], formatInfo)
- });
-
- foreach (var gbi in _guestBalloonInfo)
+ var data = (await client.Nodes[item.Node]
+ .Qemu[item.VmId]
+ .Monitor.Monitor("info balloon"))
+ .Response.data as string;
+
+ if (data.StartsWith(KeyBalloon))
{
- var row = data.Where(a => a.Name == gbi.Key).FirstOrDefault();
- if (row != null) { gbi.Value.WithLabels(new[] { vmId, node }).Set(row.Value * 1024 * 1024); }
+ //split data
+ var dataIb = data[(data.IndexOf(KeyBalloon) + KeyBalloon.Length)..]
+ .Split(' ')
+ .Select(a => new
+ {
+ Name = a.Split("=")[0],
+ Value = double.Parse(a.Split("=")[1], formatInfo)
+ });
+
+ foreach (var gbi in _guestBalloonInfo)
+ {
+ var row = dataIb.FirstOrDefault(a => a.Name == gbi.Key);
+ if (row != null)
+ {
+ gbi.Value.WithLabels(new[] { item.VmId.ToString(), item.Node })
+ .Set(row.Value * 1024 * 1024);
+ }
+ }
}
}
+
+ foreach (var item1 in _resourceInfo)
+ {
+ item1.Value.WithLabels(GetValues(item1.Value, item))
+ .Set(GetValue(item, item1.Key));
+ }
break;
- case "storage":
- _up.WithLabels($"{item.id}").Set((item.status == "available") ? 1 : 0);
+ case ClusterResourceType.Storage:
+ _up.WithLabels(item.Id).Set(item.IsAvailable ? 1 : 0);
SetGauge(_storageInfo, item);
break;
default: break;
}
-
- if (item.type == "qemu" || item.type == "lxc")
- {
- foreach (var item1 in _resourceInfo)
- {
- item1.Value.WithLabels(GetValues(item1.Value, item))
- .Set(GetValue(item, item1.Key));
- }
- }
}
}
- private static void SetGauge(Gauge gauge, dynamic values)
- => gauge.WithLabels(GetValues(gauge, values)).Set(1);
+ private static void SetGauge(Gauge gauge, dynamic values) => gauge.WithLabels(GetValues(gauge, values)).Set(1);
private static string[] GetValues(Gauge gauge, dynamic obj)
=> gauge.LabelNames.Select(a => GetValue