Browse Source

Working version of ohm export to graphite

Nick Babcock 7 years ago
parent
commit
f439846293

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "LibreHardwareMonitor"]
+	path = LibreHardwareMonitor
+	url = https://github.com/LibreHardwareMonitor/LibreHardwareMonitor

+ 1 - 0
LibreHardwareMonitor

@@ -0,0 +1 @@
+Subproject commit 28e20d2397573a7e733fc29c6f2b601fb599613c

+ 6 - 0
OhmGraphite.sln

@@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27004.2005
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OhmGraphite", "OhmGraphite\OhmGraphite.csproj", "{329E2785-6E88-449F-A641-6ACCBFF943CA}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenHardwareMonitorLib", "LibreHardwareMonitor\OpenHardwareMonitorLib.csproj", "{B0397530-545A-471D-BB74-027AE456DF1A}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
 		{329E2785-6E88-449F-A641-6ACCBFF943CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{329E2785-6E88-449F-A641-6ACCBFF943CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{329E2785-6E88-449F-A641-6ACCBFF943CA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B0397530-545A-471D-BB74-027AE456DF1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B0397530-545A-471D-BB74-027AE456DF1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B0397530-545A-471D-BB74-027AE456DF1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B0397530-545A-471D-BB74-027AE456DF1A}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 5 - 3
OhmGraphite/App.config

@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <configuration>
-    <startup> 
-        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
-    </startup>
+  <appSettings>
+    <add key="host" value="localhost" />
+    <add key="port" value="2003" />
+    <add key="interval" value="5" />
+  </appSettings>
 </configuration>

+ 115 - 0
OhmGraphite/MetricTimer.cs

@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net.Sockets;
+using System.Timers;
+using NLog;
+using OpenHardwareMonitor.Hardware;
+
+namespace OhmGraphite
+{
+    public class MetricTimer
+    {
+        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
+
+        private readonly Computer _computer;
+        private readonly int _graphitePort;
+        private readonly string _graphiteHost;
+        private readonly Timer _timer;
+
+        public MetricTimer(Computer computer, TimeSpan interval, string graphiteHost, int graphitePort = 2003)
+        {
+            _computer = computer;
+            _graphitePort = graphitePort;
+            _graphiteHost = graphiteHost;
+            _timer = new Timer(interval.TotalMilliseconds) {AutoReset = true};
+            _timer.Elapsed += ReportMetrics;
+        }
+
+        public void Start()
+        {
+            _computer.Open();
+            _timer.Start();
+        }
+
+        public void Stop()
+        {
+            _computer.Close();
+            _timer.Stop();
+        }
+
+        private void ReportMetrics(object sender, ElapsedEventArgs e)
+        {
+            // We don't want to transmit metrics across multiple seconds as they
+            // are being retrieved so calculate the timestamp of the signaled event
+            // only once.
+            long epoch = ((DateTimeOffset) e.SignalTime).ToUnixTimeSeconds();
+            string host = Environment.MachineName;
+            try
+            {
+                SendMetrics(host, epoch);
+            }
+            catch (Exception ex)
+            {
+                Logger.Error(ex, "Unable to send metrics");
+            }
+        }
+
+        private void SendMetrics(string host, long epoch)
+        {
+            int sensorCount = 0;
+
+            // Every 5 seconds (or superceding interval) we connect to graphite
+            // and poll the hardware. It may be inefficient to open a new connection
+            // every 5 seconds, and there are ways to optimize this, but opening a
+            // new connection is the easiest way to ensure that previous failures
+            // don't affect future results
+            var stopwatch = Stopwatch.StartNew();
+            using (var client = new TcpClient(_graphiteHost, _graphitePort))
+            using (var networkStream = client.GetStream())
+            using (var writer = new StreamWriter(networkStream))
+            {
+                foreach (var sensor in ReadSensors(_computer))
+                {
+                    var data = Normalize(sensor);
+
+                    // Graphite API wants <metric> <value> <timestamp>. We prefix the metric
+                    // with `ohm` as to not overwrite potentially existing metrics
+                    writer.WriteLine($"ohm.{host}.{data.Identifier}.{data.Name} {data.Value} {epoch:d}");
+
+                    sensorCount++;
+                }
+            }
+
+            Logger.Info($"Sent {sensorCount} metrics in {stopwatch.Elapsed.TotalMilliseconds}ms");
+        }
+
+        private static Sensor Normalize(Sensor sensor)
+        {
+            // Take the sensor's identifier (eg. /nvidiagpu/0/load/0)
+            // and tranform into nvidiagpu.0.load.<name> where <name>
+            // is the name of the sensor lowercased with spaces removed.
+            // A name like "GPU Core" is turned into "gpucore". Also
+            // since some names are like "cpucore#2", turn them into
+            // separate metrics by replacing "#" with "."
+            var identifier = sensor.Identifier.Replace('/', '.').Substring(1);
+            identifier = identifier.Remove(identifier.LastIndexOf('.'));
+            var name = sensor.Name.ToLower().Replace(" ", null).Replace('#', '.');
+            return new Sensor(identifier, name, sensor.Value);
+        }
+
+        private static IEnumerable<Sensor> ReadSensors(IComputer computer)
+        {
+            foreach (var hardware in computer.Hardware)
+            {
+                hardware.Update();
+                foreach (var hardwareSensor in hardware.Sensors)
+                {
+                    yield return new Sensor(hardwareSensor.Identifier.ToString(), hardwareSensor.Name,
+                        hardwareSensor.Value ?? 0.0f);
+                }
+            }
+        }
+    }
+}

+ 20 - 0
OhmGraphite/NLog.config

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+  <targets>
+    <target name="file" xsi:type="File"
+            fileName="OhmGraphite.log"
+            archiveFileName="log-archives/OhmGraphite.{#}.log"
+            archiveEvery="Day"
+            archiveNumbering="Rolling"
+            maxArchiveFiles="7"
+            keepFileOpen="false" />
+    <target name="console" xsi:type="Console" />
+  </targets>
+
+  <rules>
+    <logger name="*" minlevel="Info" writeTo="file" />
+    <logger name="*" minlevel="Info" writeTo="console" />
+  </rules>
+</nlog>

+ 31 - 41
OhmGraphite/OhmGraphite.csproj

@@ -1,52 +1,42 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
+
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{329E2785-6E88-449F-A641-6ACCBFF943CA}</ProjectGuid>
     <OutputType>Exe</OutputType>
-    <RootNamespace>OhmGraphite</RootNamespace>
-    <AssemblyName>OhmGraphite</AssemblyName>
-    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
-    <FileAlignment>512</FileAlignment>
-    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <TargetFramework>net46</TargetFramework>
     <PlatformTarget>AnyCPU</PlatformTarget>
-    <DebugType>pdbonly</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
-    <DefineConstants>TRACE</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
+    <Authors>Nick Babcock</Authors>
+
+
+    <Description>Extract hardware sensor data and exports it to a given host and port in a graphite compatible format</Description>
+    <Copyright>Nick Babcock</Copyright>
+    <Major>0</Major>
+    <Minor>1</Minor>
+    <Revision>0</Revision>
+    <AssemblyVersion>$(Major).$(Minor).$(Revision)</AssemblyVersion>
+    <AssemblyFileVersion>$(Major).$(Minor).$(Revision)</AssemblyFileVersion>
+    <InformationalVersion>$(Major).$(Minor).$(Revision)</InformationalVersion>
+    <Version>$(Major).$(Minor).$(Revision)</Version>
+    <FileVersion>$(Major).$(Minor).$(Revision)</FileVersion>
   </PropertyGroup>
+
+  <ItemGroup>
+    <None Remove="NLog.config" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="NLog.config">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
   <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Net.Http" />
-    <Reference Include="System.Xml" />
+    <PackageReference Include="NLog.Config" Version="4.4.12" />
+    <PackageReference Include="TopShelf" Version="4.0.3" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="Program.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
+    <ProjectReference Include="..\LibreHardwareMonitor\OpenHardwareMonitorLib.csproj" />
   </ItemGroup>
   <ItemGroup>
-    <None Include="App.config" />
+    <Reference Include="System.Configuration" />
   </ItemGroup>
-  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>

+ 55 - 4
OhmGraphite/Program.cs

@@ -1,15 +1,66 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using OpenHardwareMonitor.Hardware;
+using System.Configuration;
+using NLog;
+using Topshelf;
 
 namespace OhmGraphite
 {
     class Program
     {
+        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
         static void Main(string[] args)
         {
+            HostFactory.Run(x =>
+            {
+                x.Service<MetricTimer>(s =>
+                {
+                    // We need to know where the graphite server lives and how often
+                    // to poll the hardware
+                    ParseConfig(out string host, out int port, out int seconds);
+
+                    // We'll want to capture all available hardware metrics
+                    // to send to graphite
+                    var computer = new Computer
+                    {
+                        GPUEnabled = true,
+                        MainboardEnabled = true,
+                        CPUEnabled = true,
+                        RAMEnabled = true,
+                        FanControllerEnabled = true,
+                        HDDEnabled = true
+                    };
+
+                    s.ConstructUsing(name => new MetricTimer(computer, TimeSpan.FromSeconds(seconds), host, port));
+                    s.WhenStarted(tc => tc.Start());
+                    s.WhenStopped(tc => tc.Stop());
+                });
+                x.RunAsLocalSystem();
+
+                x.SetDescription("Extract hardware sensor data and exports it to a given host and port in a graphite compatible format");
+                x.SetDisplayName("Ohm Graphite");
+                x.SetServiceName("OhmGraphite");
+                x.OnException(ex =>
+                {
+                    Logger.Error(ex, "OhmGraphite TopShelf encountered an error");
+                });
+            });
+        }
+
+        private static void ParseConfig(out string host, out int port, out int seconds)
+        {
+            host = ConfigurationManager.AppSettings["host"] ?? "localhost";
+            if (!int.TryParse(ConfigurationManager.AppSettings["port"], out port))
+            {
+                port = 2003;
+            }
+
+            if (!int.TryParse(ConfigurationManager.AppSettings["interval"], out seconds))
+            {
+                seconds = 5;
+            }
+
+            Logger.Info($"Host: {host} port: {port} interval: {seconds}");
         }
     }
 }

+ 0 - 36
OhmGraphite/Properties/AssemblyInfo.cs

@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("OhmGraphite")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("OhmGraphite")]
-[assembly: AssemblyCopyright("Copyright ©  2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components.  If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("329e2785-6e88-449f-a641-6accbff943ca")]
-
-// Version information for an assembly consists of the following four values:
-//
-//      Major Version
-//      Minor Version
-//      Build Number
-//      Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]

+ 16 - 0
OhmGraphite/Sensor.cs

@@ -0,0 +1,16 @@
+namespace OhmGraphite
+{
+    public class Sensor
+    {
+        public string Identifier { get; }
+        public string Name { get; }
+        public float Value { get; }
+
+        public Sensor(string identifier, string name, float value)
+        {
+            Identifier = identifier;
+            Name = name;
+            Value = value;
+        }
+    }
+}