diff --git a/.gitignore b/.gitignore index 45407ac..9427922 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ obj .vs/LibreHardwareMonitorExporter +bin/Debug/net8.0 diff --git a/LibreHardwareMonitorExporter.csproj b/LibreHardwareMonitorExporter.csproj index 2150e37..6cf7f06 100644 --- a/LibreHardwareMonitorExporter.csproj +++ b/LibreHardwareMonitorExporter.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Program.cs b/Program.cs index 3751555..a2057c7 100644 --- a/Program.cs +++ b/Program.cs @@ -1,2 +1,143 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using System.Net; +using System.Text; +using LibreHardwareMonitor.Hardware; + +var port = Environment.GetEnvironmentVariable("PORT"); +if (!int.TryParse(port, out var httpPort)) +{ + httpPort = 9646; // default port +} + +var listener = new HttpListener(); +listener.Prefixes.Add($"http://*:{httpPort}/"); +listener.Start(); + +var computer = new Computer +{ + IsCpuEnabled = true, + IsGpuEnabled = true, + IsMemoryEnabled = true, + IsMotherboardEnabled = true, + IsControllerEnabled = true, + IsNetworkEnabled = true, + IsStorageEnabled = true +}; + +computer.Open(); + +Console.WriteLine($"LibreHardwareMonitorExporter listening on http://localhost:{httpPort}/metrics"); + +while (true) +{ + var context = await listener.GetContextAsync(); + if (context.Request.HttpMethod != "GET") + { + context.Response.StatusCode = 405; + context.Response.Close(); + continue; + } + + if (context.Request.Url?.AbsolutePath != "/metrics") + { + context.Response.StatusCode = 404; + context.Response.Close(); + continue; + } + + var body = new StringBuilder(); + AppendMetrics(body, computer); + var bytes = Encoding.UTF8.GetBytes(body.ToString()); + context.Response.StatusCode = 200; + context.Response.ContentType = "text/plain; version=0.0.4"; + context.Response.ContentEncoding = Encoding.UTF8; + context.Response.ContentLength64 = bytes.Length; + await context.Response.OutputStream.WriteAsync(bytes, 0, bytes.Length); + context.Response.Close(); +} + +static void AppendMetrics(StringBuilder output, Computer computer) +{ + var updateVisitor = new UpdateVisitor(); + computer.Accept(updateVisitor); + + // Basic example metrics for temperature, load, clock, power, fan + foreach (var hardware in computer.Hardware) + { + EmitHardwareMetrics(output, hardware); + foreach (var sub in hardware.SubHardware) + { + EmitHardwareMetrics(output, sub); + } + } +} + +static void EmitHardwareMetrics(StringBuilder output, IHardware hardware) +{ + var hardwareLabel = SanitizeLabel(hardware.Name); + foreach (var sensor in hardware.Sensors) + { + if (sensor.Value is null) continue; + var sensorLabel = SanitizeLabel(sensor.Name); + var value = sensor.Value.Value; + var metricName = sensor.SensorType switch + { + SensorType.Temperature => "lhm_temperature_celsius", + SensorType.Load => "lhm_load_percent", + SensorType.Clock => "lhm_clock_mhz", + SensorType.Power => "lhm_power_watts", + SensorType.Fan => "lhm_fan_rpm", + SensorType.Voltage => "lhm_voltage_volts", + SensorType.Data => "lhm_data", + SensorType.Level => "lhm_level", + SensorType.Throughput => "lhm_throughput_bytes_per_sec", + SensorType.Factor => "lhm_factor", + SensorType.Flow => "lhm_flow_lph", + SensorType.Control => "lhm_control_percent", + _ => null + }; + + if (metricName == null) continue; + output.Append(metricName); + output.Append("{"); + output.Append($"hardware=\"{hardwareLabel}\",sensor=\"{sensorLabel}\",type=\"{sensor.SensorType}\""); + output.Append("} "); + output.AppendLine(value.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } +} + +static string SanitizeLabel(string raw) +{ + var sb = new StringBuilder(raw.Length); + foreach (var ch in raw) + { + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == ':' || ch == '.') + { + sb.Append(ch); + } + else + { + sb.Append('_'); + } + } + return sb.ToString(); +} + +sealed class UpdateVisitor : IVisitor +{ + public void VisitComputer(IComputer computer) + { + computer.Traverse(this); + } + + public void VisitHardware(IHardware hardware) + { + hardware.Update(); + foreach (var sub in hardware.SubHardware) + { + sub.Accept(this); + } + } + + public void VisitSensor(ISensor sensor) { } + public void VisitParameter(IParameter parameter) { } +}