diff --git a/Barsik.csproj b/Barsik.csproj index 3b9bef7..07a09e3 100644 --- a/Barsik.csproj +++ b/Barsik.csproj @@ -9,6 +9,7 @@ + diff --git a/Logging.cs b/Logging.cs new file mode 100644 index 0000000..810c1b0 --- /dev/null +++ b/Logging.cs @@ -0,0 +1,16 @@ +using NLog; + +namespace Barsik +{ + public static class Logging + { + public static void Setup() + { + var config = new NLog.Config.LoggingConfiguration(); + + var logConsole = new NLog.Targets.ConsoleTarget("logconsole"); + config.AddRuleForAllLevels(logConsole); + LogManager.Configuration = config; + } + } +} diff --git a/MDStatMonitoringUnit.cs b/MDStatMonitoringUnit.cs new file mode 100644 index 0000000..53ccbae --- /dev/null +++ b/MDStatMonitoringUnit.cs @@ -0,0 +1,66 @@ +using IniParser.Model; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; + +namespace Barsik +{ + /// + /// Monitors configured RAID array status via /proc/mdstat + /// Will fill registers info starting from baseAddress with + /// disk status (0 - fail, 1 - up) in following order: + /// raid #0 disk #0 + /// raid #0 disk #1 + /// ... + /// raid #0 disk #N + /// raid #1 disk #0 + /// ... + /// etc + /// + class MDStatMonitoringUnit : BaseMonitoringUnit + { + public MDStatMonitoringUnit(SectionData config, RegistersContainer registers) : base(config, registers) + { + } + + public override void Run() + { + while (true) + { + int currentAddress = baseAddress; + // the file is not so big, so read it completely + var lines = File.ReadAllLines("/proc/mdstat"); + for (int i = 0; i < lines.Length;) + { + if (lines[i].StartsWith("md") && i < lines.Length - 1) + { + // we found device description + // second line of it is as follows: + // 2930264896 blocks super 1.0 [2/2] [UU] + // we need last characters in brackets [] + currentAddress += FillDiskStatus(currentAddress, lines[i + 1]); + i += 2; + } + else i++; + } + Thread.Sleep(2000); + } + } + + private int FillDiskStatus(int address, string v) + { + int last_pos = v.LastIndexOf(']'); + int pos; + for (pos = last_pos; pos > 0 && v[pos] != '['; pos--) ; + if (v[pos] != '[') return 0; + for (int i = pos + 1; i < last_pos; i++) + { + if (v[i] == 'U') registers[address + i - (pos + 1)] = 1; + else registers[address + i - (pos + 1)] = 1; + } + return last_pos - pos - 1; + } + } +} diff --git a/ModbusServer.cs b/ModbusServer.cs index 9ea8469..0d49f18 100644 --- a/ModbusServer.cs +++ b/ModbusServer.cs @@ -1,4 +1,5 @@ -using System; +using NLog; +using System; using System.Collections.Generic; using System.IO; using System.Net; @@ -20,11 +21,14 @@ namespace Barsik internal void Run() { + var logger = LogManager.GetCurrentClassLogger(); + logger.Info("Accepting connections on port " + Port); var listener = new TcpListener(IPAddress.Any, Port); listener.Start(); while (true) { var client = listener.AcceptTcpClient(); + logger.Info("Accepted connection from " + client.Client.RemoteEndPoint); var worker = new ModbusServerWorker(registers, client); Thread clientThread = new Thread(new ThreadStart(worker.Run)); clientThread.Start(); @@ -38,6 +42,7 @@ namespace Barsik private RegistersContainer registers; private TcpClient client; + private static Logger logger = LogManager.GetCurrentClassLogger(); public ModbusServerWorker(RegistersContainer registers, TcpClient client) { this.registers = registers; @@ -52,6 +57,7 @@ namespace Barsik public void Run() { + logger.Debug("Working with client"); received = new List(); try { @@ -63,11 +69,13 @@ namespace Barsik readCompleted.Reset(); stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(this.OnReadCompleted), this); readCompleted.WaitOne(); - if (currentReceived < 0) + if (currentReceived <= 0) { + logger.Debug("Received no data, closing "); client.Close(); return; } + logger.Debug("ProcessReceivedData of " + received.Count); ProcessReceivedData(); } } @@ -75,6 +83,11 @@ namespace Barsik { client.Close(); } + finally + { + if (stream != null) stream.Dispose(); + client.Dispose(); + } } private void ProcessReceivedData() @@ -123,12 +136,20 @@ namespace Barsik private void OnReadCompleted(IAsyncResult ar) { - currentReceived = stream.EndRead(ar); - lock (received) + try { - for (int i = 0; i < currentReceived; i++) received.Add(buffer[i]); + currentReceived = stream.EndRead(ar); + lock (received) + { + for (int i = 0; i < currentReceived; i++) received.Add(buffer[i]); + } + readCompleted.Set(); + } + catch (IOException e) + { + logger.Info("Error receiving " + e); + currentReceived = -1; } - readCompleted.Set(); } } } diff --git a/MonitoringUnit.cs b/MonitoringUnit.cs index a38e1a1..88c5f4c 100644 --- a/MonitoringUnit.cs +++ b/MonitoringUnit.cs @@ -1,8 +1,5 @@ using IniParser.Model; using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; namespace Barsik { @@ -31,66 +28,15 @@ namespace Barsik { return new SensorsMonitoringUnit(config, registers); } - else + else if (type == "MDStat") + { + return new MDStatMonitoringUnit(config, registers); + } + else { Console.WriteLine("Unknown unit type " + type); return null; } } } - - class SensorsMonitoringUnit : BaseMonitoringUnit - { - public SensorsMonitoringUnit(SectionData config, RegistersContainer registers) : base(config, registers) - { - } - - public override void Run() - { - var stringToFind = config.Keys["String"]; - var multiplier = float.Parse(config.Keys["Multiplier"]); - while (true) - { - System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("sensors"); - procStartInfo.RedirectStandardOutput = true; - procStartInfo.UseShellExecute = false; - procStartInfo.CreateNoWindow = true; - System.Diagnostics.Process proc = new System.Diagnostics.Process(); - proc.StartInfo = procStartInfo; - proc.Start(); - while (!proc.HasExited) - { - Thread.Sleep(100); - } - string result = proc.StandardOutput.ReadToEnd(); - var lines = result.Split('\n'); - for (int i = 0; i < lines.Length; i++) - { - if (lines[i].Contains(stringToFind)) - { - var value = ExtractValue(lines[i]); - registers[baseAddress] = (int)(value * multiplier); - } - } - } - } - - bool Good(char c) - { - if (c == '-' || c == '+' || c == '.') return true; - if (c >= '0' && c <= '9') return true; - return false; - } - - private float ExtractValue(string v) - { - int pos = 0; - while (pos < v.Length && !Good(v[pos])) pos++; - int pos2 = pos; - while (pos2 < v.Length && Good(v[pos2])) pos2++; - float res = 0; - float.TryParse(v.Substring(pos, pos2 - pos), out res); - return res; - } - } } diff --git a/Program.cs b/Program.cs index d7da284..083a25e 100644 --- a/Program.cs +++ b/Program.cs @@ -1,6 +1,8 @@ using IniParser; -using System; +using NLog; using System.Collections.Generic; +using System.IO; +using System.Reflection; using System.Threading; namespace Barsik @@ -10,14 +12,20 @@ namespace Barsik private static RegistersContainer registers; private static List units; - static void Main(string[] args) + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + static void Main(string[] args) { + Logging.Setup(); + logger.Info("Barsik monitoring service starting"); + registers = new RegistersContainer(); var server = new ModbusServer(registers); units = new List(); var configParser = new FileIniDataParser(); - var data = configParser.ReadFile("barsik.ini"); + string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var data = configParser.ReadFile(Path.Combine(assemblyFolder, "barsik.ini")); foreach (var section in data.Sections) { if (section.SectionName.StartsWith("Unit")) @@ -31,12 +39,10 @@ namespace Barsik } } } - server.Port = int.Parse(data["Server"]["Port"]); - while (true) - { - server.Run(); - Thread.Sleep(1000); - } - } + logger.Info("Configuration loaded, we have " + units.Count + " units"); + server.Port = int.Parse(data["Server"]["Port"]); + server.Run(); + logger.Info("Exiting"); + } } } diff --git a/Properties/PublishProfiles/FolderProfile.pubxml b/Properties/PublishProfiles/FolderProfile.pubxml index 3280d7f..bc9f40e 100644 --- a/Properties/PublishProfiles/FolderProfile.pubxml +++ b/Properties/PublishProfiles/FolderProfile.pubxml @@ -10,7 +10,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. netcoreapp2.1 bin\Debug\netcoreapp2.1\publish\ linux-x64 - true - <_IsPortable>false + false + <_IsPortable>true \ No newline at end of file diff --git a/SensorsMonitoringUnit.cs b/SensorsMonitoringUnit.cs new file mode 100644 index 0000000..b5cd164 --- /dev/null +++ b/SensorsMonitoringUnit.cs @@ -0,0 +1,88 @@ +using IniParser.Model; +using System; +using System.Threading; + +namespace Barsik +{ + /// + /// Parses output of "sensors" command and puts CPU core temperatures into + /// registers + /// + class SensorsMonitoringUnit : BaseMonitoringUnit + { + const string CORE_STR = "Core{0} Temp:"; + + public SensorsMonitoringUnit(SectionData config, RegistersContainer registers) : base(config, registers) + { + } + + public override void Run() + { + int cores = int.Parse(config.Keys["Cores"]); + var multiplier = float.Parse(config.Keys["Multiplier"]); + while (true) + { + System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("sensors"); + procStartInfo.RedirectStandardOutput = true; + procStartInfo.UseShellExecute = false; + procStartInfo.CreateNoWindow = true; + using (System.Diagnostics.Process proc = new System.Diagnostics.Process()) + { + proc.StartInfo = procStartInfo; + proc.Start(); + while (!proc.HasExited) + { + Thread.Sleep(100); + } + string result = proc.StandardOutput.ReadToEnd(); + var lines = result.Split('\n'); + // lines in output will be of format: + // Core0 Temp: +29.0°C + // and there could be several lines for one core + // so let's take all of them and use maximal value as core temp + float[] t = new float[cores]; + for (int i = 0; i < cores; i++) + { + t[i] = 0; + } + for (int i = 0; i < lines.Length; i++) + { + for (int j = 0; j < cores; j++) + { + string toFind = string.Format(CORE_STR, j); + if (lines[i].Contains(toFind)) + { + var value = ExtractValue(lines[i].Substring(toFind.Length)); + t[j] = Math.Max(t[j], value); + } + } + } + // fill register data starting from baseAddress + for (int i = 0; i < cores; i++) + { + registers[baseAddress + i] = (int)(t[i] * multiplier); + } + } + Thread.Sleep(2000); + } + } + + bool Good(char c) + { + if (c == '-' || c == '+' || c == '.') return true; + if (c >= '0' && c <= '9') return true; + return false; + } + + private float ExtractValue(string v) + { + int pos = 0; + while (pos < v.Length && !Good(v[pos])) pos++; + int pos2 = pos; + while (pos2 < v.Length && Good(v[pos2])) pos2++; + float res; + float.TryParse(v.Substring(pos, pos2 - pos), out res); + return res; + } + } +} \ No newline at end of file diff --git a/barsik.ini.sample b/barsik.ini.sample index f9136f2..71280e9 100644 --- a/barsik.ini.sample +++ b/barsik.ini.sample @@ -4,5 +4,9 @@ Port = 4500 [Unit_CPU] Type = Sensors BaseAddress = 1 -String = CPU Temperature -Multiplier = 100 \ No newline at end of file +Multiplier = 100 +Cores = 2 + +[Unit_RAID] +Type = MDStat +BaseAddress = 3 diff --git a/barsik.service b/barsik.service new file mode 100644 index 0000000..67b0043 --- /dev/null +++ b/barsik.service @@ -0,0 +1,14 @@ +[Unit] +Description=Barsik monitoring service +After=network.target + +[Service] +ExecStart=dotnet /root/barsik/Barsik.dll +User=root +Group=root +RuntimeDirectory=/root/barsik +RuntimeDirectoryMode=0755 +ProtectHome=false + +[Install] +WantedBy=multi-user.target