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(obj, a) + "") .Cast() .ToArray(); - private static bool ExistProperty(dynamic obj, string propertyName) - => ((IDictionary)obj).ContainsKey(propertyName); - private static T GetValue(object obj, string propertyName) - => (T)Convert.ChangeType(((IDictionary)obj)[propertyName], typeof(T)); + => (T)Convert.ChangeType(obj.GetType().GetProperty(propertyName).GetValue(obj), typeof(T)); } } \ No newline at end of file diff --git a/src/Corsinvest.ProxmoxVE.Metrics.Exporter/Corsinvest.ProxmoxVE.Metrics.Exporter.csproj b/src/Corsinvest.ProxmoxVE.Metrics.Exporter/Corsinvest.ProxmoxVE.Metrics.Exporter.csproj index 2b01a0d..3697a1f 100644 --- a/src/Corsinvest.ProxmoxVE.Metrics.Exporter/Corsinvest.ProxmoxVE.Metrics.Exporter.csproj +++ b/src/Corsinvest.ProxmoxVE.Metrics.Exporter/Corsinvest.ProxmoxVE.Metrics.Exporter.csproj @@ -1,7 +1,7 @@  Exe - 1.4.0 + 1.4.1 net6.0 cv4pve-metrics-exporter Corsinvest Srl @@ -10,14 +10,17 @@ Corsinvest for Proxmox VE Metrics Exporter Corsinvest for Proxmox VE Metrics Exporter Corsinvest for Proxmox VE Metrics Exporter + Github + GPL-3.0-only true + false - + - + \ No newline at end of file