1
0

MetricTimer.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Net.Sockets;
  6. using System.Timers;
  7. using NLog;
  8. using OpenHardwareMonitor.Hardware;
  9. namespace OhmGraphite
  10. {
  11. public class MetricTimer
  12. {
  13. private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
  14. private readonly Computer _computer;
  15. private readonly int _graphitePort;
  16. private readonly string _graphiteHost;
  17. private readonly Timer _timer;
  18. public MetricTimer(MetricConfig config)
  19. {
  20. _computer = config.Computer;
  21. _graphitePort = config.Port;
  22. _graphiteHost = config.Host;
  23. _timer = new Timer(config.Interval.TotalMilliseconds) { AutoReset = true };
  24. _timer.Elapsed += ReportMetrics;
  25. }
  26. public void Start()
  27. {
  28. Logger.LogAction("starting metric timer", () =>
  29. {
  30. _computer.Open();
  31. _timer.Start();
  32. });
  33. }
  34. public void Stop()
  35. {
  36. Logger.LogAction("stopping metric timer", () =>
  37. {
  38. _computer.Close();
  39. _timer.Stop();
  40. });
  41. }
  42. private void ReportMetrics(object sender, ElapsedEventArgs e)
  43. {
  44. Logger.Debug("Starting to report metrics");
  45. try
  46. {
  47. // We don't want to transmit metrics across multiple seconds as they
  48. // are being retrieved so calculate the timestamp of the signaled event
  49. // only once.
  50. long epoch = new DateTimeOffset(e.SignalTime).ToUnixTimeSeconds();
  51. string host = Environment.MachineName;
  52. SendMetrics(host, epoch);
  53. }
  54. catch (Exception ex)
  55. {
  56. Logger.Error(ex, "Unable to send metrics");
  57. }
  58. }
  59. private void SendMetrics(string host, long epoch)
  60. {
  61. int sensorCount = 0;
  62. // Every 5 seconds (or superceding interval) we connect to graphite
  63. // and poll the hardware. It may be inefficient to open a new connection
  64. // every 5 seconds, and there are ways to optimize this, but opening a
  65. // new connection is the easiest way to ensure that previous failures
  66. // don't affect future results
  67. var stopwatch = Stopwatch.StartNew();
  68. using (var client = new TcpClient(_graphiteHost, _graphitePort))
  69. using (var networkStream = client.GetStream())
  70. using (var writer = new StreamWriter(networkStream))
  71. {
  72. foreach (var hardware in ReadHardware(_computer))
  73. {
  74. foreach (var sensor in ReadSensors(hardware))
  75. {
  76. var data = Normalize(sensor);
  77. // Graphite API wants <metric> <value> <timestamp>. We prefix the metric
  78. // with `ohm` as to not overwrite potentially existing metrics
  79. writer.WriteLine($"ohm.{host}.{data.Identifier}.{data.Name} {data.Value} {epoch:d}");
  80. sensorCount++;
  81. }
  82. }
  83. }
  84. Logger.Info($"Sent {sensorCount} metrics in {stopwatch.Elapsed.TotalMilliseconds}ms");
  85. }
  86. private static Sensor Normalize(Sensor sensor)
  87. {
  88. // Take the sensor's identifier (eg. /nvidiagpu/0/load/0)
  89. // and tranform into nvidiagpu.0.load.<name> where <name>
  90. // is the name of the sensor lowercased with spaces removed.
  91. // A name like "GPU Core" is turned into "gpucore". Also
  92. // since some names are like "cpucore#2", turn them into
  93. // separate metrics by replacing "#" with "."
  94. var identifier = sensor.Identifier.Replace('/', '.').Substring(1);
  95. identifier = identifier.Remove(identifier.LastIndexOf('.'));
  96. var name = sensor.Name.ToLower().Replace(" ", null).Replace('#', '.');
  97. return new Sensor(identifier, name, sensor.Value);
  98. }
  99. private static IEnumerable<IHardware> ReadHardware(IComputer computer)
  100. {
  101. foreach (var hardware in computer.Hardware)
  102. {
  103. yield return hardware;
  104. foreach (var subHardware in hardware.SubHardware)
  105. {
  106. yield return subHardware;
  107. }
  108. }
  109. }
  110. private static IEnumerable<Sensor> ReadSensors(IHardware hardware)
  111. {
  112. hardware.Update();
  113. foreach (var sensor in hardware.Sensors)
  114. {
  115. var id = sensor.Identifier.ToString();
  116. // Only report a value if the sensor was able to get a value
  117. // as 0 is different than "didn't read". For example, are the
  118. // fans really spinning at 0 RPM or was the value not read.
  119. if (sensor.Value.HasValue)
  120. {
  121. yield return new Sensor(id, sensor.Name, sensor.Value.Value);
  122. }
  123. else
  124. {
  125. Logger.Debug($"{id} did not have a value");
  126. }
  127. }
  128. }
  129. }
  130. }