123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693 |
- using System.Text;
- using LibreHardwareMonitor.Hardware;
- using MinecraftStatus;
- using SteamQueryNet;
- using SteamQueryNet.Interfaces;
- using System.Text.Json;
- using Microsoft.Extensions.Configuration;
- using VeloeMonitorDataCollector.DatabaseConnectors;
- using VeloeMonitorDataCollector.Models;
- using Serilog;
- using VeloeMonitorDataCollector.Dependencies;
- namespace VeloeMonitorDataCollector
- {
- public class DataCollector
- {
- private Dictionary<string, Task> _updaterTasks; //TODO i can use an array here
- CancellationToken _token;
- CancellationTokenSource _cancellationTokenSource;
- Serilog.ILogger _logger;
-
- Computer? _computerHardware;
- float prevIdle = 0f;
- float prevTotal = 0f;
- List<DriveInfo> _drives;
- Dictionary<string, int> _deviceLoadSensorIndex = new()
- {
- {"cpuload", -1},
- {"ramavailable", -1},
- {"ramused", -1},
- {"ramload", -1}
- };
-
- private List<IDataSendable> _sendToDb; //TODO and here
-
-
- //Exception thrown on checking values
- /// <exception cref="ArgumentNullException">when there is no value in INI file or this value is not declared.</exception>
- /// <exception cref="ArgumentOutOfRangeException">when value in INI file does not match in range. Example: Port value range is 1..65565]</exception>
- /// <exception cref="FormatException">when value can not be parsed to needed type. Expample: Port value can't be NaN</exception>
- /// <exception cref="OverflowException">when provided value caused overflowed return variable in parse method.</exception>
- public DataCollector(in IConfiguration data,in Serilog.ILogger logger)
- {
- _updaterTasks = new Dictionary<string, Task>();
- _cancellationTokenSource = new CancellationTokenSource();
- _token = _cancellationTokenSource.Token;
- _logger = logger;
-
- //ini file check
- //is it needed?
- if (!File.Exists("config.ini"))
- {
- File.WriteAllText("config.ini",
- "#[Hardware]\n#hardware = true\n#hardwareUpdateInterval = true\n\n#[MySQL]\n#server = 127.0.0.1\n#port = 8806\n#uid = User\n#pwd = Password\n#database = values\n\n#[WebSoket]\n#url = http://192.168.1.2:5000\n\n#[MinecraftServer]\n#Ip = 127.0.0.1\n#Port = 25565\n#Type = Minecraft\n#updateInterval = 30\n\n#[SteamAPIServer]\n#Ip = 127.0.0.1\n#Port = 27015\n#Type = Steam\n#updateInterval = 30\n\n#[Gamespy3Server]\n#Ip = 127.0.0.1\n#Port = 5446\n#Type = Gamespy3\n#updateInterval = 30\n\n#[Gamespy2Server]\n#Ip = 127.0.0.1\n#Port = 10480\n#Type = Gamespy2\n#updateInterval = 30");
- throw new FileNotFoundException("config.ini does not exist! Default config was created as an example.");
- }
- //check read/write
- //is it needed
- FileStream config = File.Open("config.ini", FileMode.Open, FileAccess.ReadWrite);
- config.Close();
- config.Dispose();
- var hardware = data.GetSection("Hardware");
- //ini hardvare section validation
-
- if (hardware["hardware"] != "true" && hardware["hardware"] != "false" || hardware["hardware"] is null)
- {
- hardware["hardware"] = "false";
- }
- //configuring database connections
- var dbSections = data
- .GetChildren()
- .Where(section =>
- section.Path is ("MySQL" or "InfluxDB" or "TimescaleDB" or "WebSoket"))
- .ToArray();
- _sendToDb = new List<IDataSendable>();
- if (!dbSections.Any())
- {
- _logger.Information("No databases detected.");
- }
- else
- {
- foreach (var dbSection in dbSections)
- {
- //init db connections
- switch (dbSection.Path)
- {
- case "MySQL":
- try
- {
- var mySqlDb = new VeloeMonitorDataCollector.DatabaseConnectors.MySqlConnector(dbSection, logger);
- _sendToDb.Add(mySqlDb);
- }
- catch (Exception ex)
- {
- logger.Warning("Database connector not confugured properly. It won't be added in working configuration.");
- logger.Error(ex.Message);
- }
- break;
- case "WebSoket":
- try
- {
- SignalRConnector signalRWebApp = new(dbSection, logger);
- _sendToDb.Add(signalRWebApp);
- }
- catch (Exception ex)
- {
- logger.Warning("SignalR connector not confugured properly. It won't be added in working configuration.");
- logger.Error(ex.Message);
- }
- break;
- case "TimescaleDB":
- throw new NotImplementedException();
- case "InfluxDB":
- throw new NotImplementedException();
- }
- }
- }
- // checking params in game servers sections
- // configuring game servers
- var gameServersSections = data
- .GetChildren()
- .Where (section =>
- section.Key is not ("MySQL" or "InfluxDB" or"TimescaleDB" or "WebSoket" or "Hardware"))
- .ToArray();
- if (!gameServersSections.Any())
- {
- //add commented block with default example
- _logger.Information("No game servers detected.");
- }
- else
-
- foreach (var gameServer in gameServersSections)
- {
- //Console.WriteLine(gameServer.Key);
- if (gameServer["Ip"] == null ||
- gameServer["Port"] == null ||
- gameServer["Type"] == null)
- {
- throw new ArgumentNullException($"Some parameters for {gameServer.Key} are missing.");
- }
-
- if (!(Int32.Parse(gameServer["Port"]) is >= 1 and <= 65565))
- throw new ArgumentOutOfRangeException(gameServer["Port"]);
- foreach (char c in gameServer.Key)
- {
- if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
- {
- throw new ArgumentException($"Not allowed symbols in {gameServer.Key}. Only letters and numbers are allowed.");
- }
- }
- }
- // create task for hardware update
- if (bool.Parse(hardware["hardware"]))
- {
- _computerHardware = new Computer()
- {
- IsCpuEnabled = true,
- IsMemoryEnabled = true,
- };
- _drives = new List<DriveInfo>();
- DriveInfo[] allDrives = DriveInfo.GetDrives();
- foreach (DriveInfo d in allDrives)
- {
- if (d.IsReady == true && (d.DriveType is DriveType.Fixed or DriveType.Network) && d.TotalSize != 0 && !(d.Name.StartsWith("/boot") || d.Name.StartsWith("/dev")))
- {
- _drives.Add(d);
- }
- }
- _computerHardware.Open();
- SetValuesTimeZero(); //no history
- CreateHardwareInfoFile();
- //check database table configuration
- foreach (var database in _sendToDb)
- {
- var hardwareConfiguration = UpdateHardware();
- if (database.CheckHardware(hardwareConfiguration) is false)
- {
- throw new Exception(
- "Table configuration for hardware is invalid. Repair it manually or drop table and restart program.");
- }
- }
- int updateIntervalHardware;
- if (!Int32.TryParse(hardware["updateIntervalHardware"], out updateIntervalHardware))
- {
- _logger.Warning("Unable to parse updateIntervalHardware. Used default value.");
- updateIntervalHardware = 1;
- }
- _updaterTasks.Add("hardware", new Task(async () =>
- {
- while (!_token.IsCancellationRequested)
- {
- //Console.WriteLine(_token.IsCancellationRequested);
- var data = UpdateHardware();
- if (_sendToDb != null)
- foreach (var dbController in _sendToDb)
- {
- dbController.SendHardware(data);
- }
-
- await Task.Delay(TimeSpan.FromSeconds(updateIntervalHardware));
- }
- }, _token));
- }
- //create tasks for game servers updates
- foreach (var section in gameServersSections)
- {
- //check database table configuration
- foreach (var database in _sendToDb)
- { //Gamespy3 servers ignores it
- //Tablecheck in BuildUpdater method
- if (database.CheckGameServer(section.Key, section["Type"]) is false)
- {
- throw new Exception(
- $"Table {section.Key} configuration is invalid. Repair it manually or drop table and restart program.");
- }
- }
- int interval;
- if (!Int32.TryParse(section["updateInterval"], out interval))
- {
- _logger.Information("Unable to parse updateInterval. Used default value.");
- interval = 1;
- }
- BuildUpdater(section.Path,
- section["Ip"],
- Int32.Parse(section["Port"]),
- section["Type"], interval);
- }
- }
- public void Start()
- {
- foreach (var task in _updaterTasks)
- {
- _logger.Information("{0} started!",task.Key);
- task.Value.Start();
- }
- }
- public void Stop()
- {
- _cancellationTokenSource.Cancel();
- bool tasksStillActive = true;
- while (tasksStillActive)
- {
- tasksStillActive = false;
- foreach (var task in _updaterTasks)
- if (task.Value.Status == TaskStatus.Running) tasksStillActive = true;
- Task.Delay(TimeSpan.FromMilliseconds(250));
- }
- if (_computerHardware is not null)
- _computerHardware.Close();
- //wait to Tasks to end their execution
- //close all db connections
- foreach (var database in _sendToDb)
- {
- database.Close();
- }
-
- }
- private void SetValuesTimeZero()
- {
- if (_computerHardware is null)
- return;
- foreach (var hardware in _computerHardware.Hardware)
- {
- foreach (var sensor in hardware.Sensors)
- {
- sensor.ValuesTimeWindow = TimeSpan.Zero;
- }
- }
- }
- private void CreateHardwareInfoFile()
- {
- File.Create("sysinfo.txt").Close();
- StreamWriter sw = File.AppendText("sysinfo.txt");
- if (OperatingSystem.IsLinux())
- {
- var cpuLine = File
- .ReadAllLines("/proc/stat")
- .First()
- .Split(' ', StringSplitOptions.RemoveEmptyEntries)
- .Skip(1)
- .Select(float.Parse)
- .ToArray();
- var idle = cpuLine[3];
- var total = cpuLine.Sum();
- var percent = 100 * (1.0f - (idle - prevIdle) / (total - prevTotal));
- sw.WriteLine("CPU Load: {0}", percent);
- prevIdle = idle;
- prevTotal = total;
- }
- foreach (var hardware in _computerHardware.Hardware)
- {
- hardware.Update();
- //foreach (var sensor in hardware.Sensors)
- // _logger.Warning("{0}: {1}", sensor.Name, sensor.Value);
- sw.WriteLine(hardware.Name);
- sw.WriteLine(" Type: {0}", hardware.HardwareType);
- sw.WriteLine(" Type: {0}", hardware.Identifier);
- sw.WriteLine(" Sensors:");
- foreach (var sensor in hardware.Sensors)
- {
- sw.WriteLine(" {0,-25} {1,-15} {2,-15}",sensor.Name, sensor.SensorType, sensor.Value);
- }
- }
- foreach (DriveInfo d in DriveInfo.GetDrives())
- {
- if (d.IsReady == true && (d.DriveType is DriveType.Fixed or DriveType.Network) && d.TotalSize != 0 && !(d.Name.StartsWith("/boot") || d.Name.StartsWith("/dev")))
- {
- sw.WriteLine("Drive {0}", d.Name);
- sw.WriteLine(" File type: {0}", d.DriveType);
- sw.WriteLine(" Volume label: {0}", d.VolumeLabel);
- sw.WriteLine(" File system: {0}", d.DriveFormat);
- sw.WriteLine(
- " Available space to current user:{0, 15} bytes",
- d.AvailableFreeSpace);
- sw.WriteLine(
- " Total available space: {0, 15} bytes",
- d.TotalFreeSpace);
- sw.WriteLine(
- " Total size of drive: {0, 15} bytes ",
- d.TotalSize);
- }
- }
- sw.Close();
- }
- private Dictionary<string, float> UpdateHardware()
- {
- Dictionary<string, float> output = new Dictionary<string, float>();
- if (_computerHardware is null)
- return output;
- if (OperatingSystem.IsLinux())
- {
- var cpuLine = File
- .ReadAllLines("/proc/stat")
- .First()
- .Split(' ', StringSplitOptions.RemoveEmptyEntries)
- .Skip(1)
- .Select(float.Parse)
- .ToArray();
- var idle = cpuLine[3];
- var total = cpuLine.Sum();
- var percent = 100 * (1.0f - (idle - prevIdle) / (total - prevTotal));
- //for the first calc number can be inf or nan
- if (float.IsFinite(percent))
- output.Add("cpuload", percent);
- prevIdle = idle;
- prevTotal = total;
- }
- foreach (var hardware in _computerHardware.Hardware)
- {
- hardware.Update();
- //foreach (var sensor in hardware.Sensors)
- // _logger.Warning("{0}: {1}", sensor.Name, sensor.Value);
-
- if (hardware.HardwareType is HardwareType.Cpu && OperatingSystem.IsWindows())
- if (_deviceLoadSensorIndex["cpuload"] is not -1)
- output.Add("cpuload", hardware.Sensors[_deviceLoadSensorIndex["cpuload"]].Value.GetValueOrDefault());
- else
- {
- for(int i = 0; i < hardware.Sensors.Length; i++)
- if (hardware.Sensors[i].SensorType is SensorType.Load && hardware.Sensors[i].Name == "CPU Total")
- {
- output.Add("cpuload", hardware.Sensors[i].Value.GetValueOrDefault());
- _deviceLoadSensorIndex["cpuload"] = i;
- }
- }
- if (hardware.HardwareType is HardwareType.Memory)
- {
- //output.Add("ramavailable", hardware.Sensors[1].Value.GetValueOrDefault());
- if (_deviceLoadSensorIndex["ramavailable"] is not -1)
- output.Add("ramavailable", hardware.Sensors[_deviceLoadSensorIndex["ramavailable"]].Value.GetValueOrDefault());
- else
- {
- for (int i = 0; i < hardware.Sensors.Length; i++)
- if (hardware.Sensors[i].SensorType is SensorType.Data && hardware.Sensors[i].Name == "Memory Available")
- {
- output.Add("ramavailable", hardware.Sensors[i].Value.GetValueOrDefault());
- _deviceLoadSensorIndex["ramavailable"] = i;
- }
- }
- //output.Add("ramused", hardware.Sensors[0].Value.GetValueOrDefault());
- if (_deviceLoadSensorIndex["ramused"] is not -1)
- output.Add("ramused", hardware.Sensors[_deviceLoadSensorIndex["ramused"]].Value.GetValueOrDefault());
- else
- {
- for (int i = 0; i < hardware.Sensors.Length; i++)
- if (hardware.Sensors[i].SensorType is SensorType.Data && hardware.Sensors[i].Name == "Memory Used")
- {
- output.Add("ramused", hardware.Sensors[i].Value.GetValueOrDefault());
- _deviceLoadSensorIndex["ramused"] = i;
- }
- }
- //output.Add("ramload", hardware.Sensors[2].Value.GetValueOrDefault());
- if (_deviceLoadSensorIndex["ramload"] is not -1)
- output.Add("ramload", hardware.Sensors[_deviceLoadSensorIndex["ramload"]].Value.GetValueOrDefault());
- else
- {
- for (int i = 0; i < hardware.Sensors.Length; i++)
- if (hardware.Sensors[i].SensorType is SensorType.Load && hardware.Sensors[i].Name == "Memory")
- {
- output.Add("ramload", hardware.Sensors[i].Value.GetValueOrDefault());
- _deviceLoadSensorIndex["ramload"] = i;
- }
- }
- }
- }
- for (int i = 0; i < _drives.Count; i++)
- {
- _drives[i] = new DriveInfo(_drives[i].Name);
- var name = "";
-
- if (OperatingSystem.IsWindows())
- name = _drives[i].Name.ToLower().Replace(":\\", "").Replace('\\','_');
- else
- if (_drives[i].Name == "/")
- name = "root_directory";
- else
- name = _drives[i].Name.ToLower().Replace('/','_');
- output.Add(name , (float)_drives[i].TotalFreeSpace / _drives[i].TotalSize);
- }
- return output;
- }
- private void BuildUpdater(string name ,string ip, int port, string type, int interval)
- {
- switch (type)
- {
- case "Minecraft":
- _updaterTasks.Add(name, new Task(async () => {
- while (!_token.IsCancellationRequested)
- {
- await Task.Delay(TimeSpan.FromSeconds(interval));
- McStatus? data = null;
- data = UpdateMinecraft(ip, port);
-
- if (data is null)
- continue;
- if (_sendToDb is null)
- continue;
-
- foreach (var dbController in _sendToDb)
- {
- //_logger.Debug("Sending {0}", name);
- dbController.SendMinecraft(data,name);
- }
- }
- }, _token));
- break;
- case "Steam":
- _updaterTasks.Add(name, new Task(async () => {
- while (!_token.IsCancellationRequested)
- {
- await Task.Delay(TimeSpan.FromSeconds(interval));
- SteamData? data = null;
- data = await UpdateSteam(ip, port, interval);
- if (data is null)
- continue;
-
- if (_sendToDb is null)
- continue;
-
- foreach (var dbController in _sendToDb)
- {
- dbController.SendSteam(data.Value,name);
- }
- }
- }, _token));
- break;
- case "Gamespy3":
- _updaterTasks.Add(name, new Task(async () => {
- try
- {
- Gs3Status server = new Gs3Status(ip, port);
- /*
- bool dataIsNull = true;
- while (dataIsNull && !_token.IsCancellationRequested)
- {
- var data = server.GetStatus();
- if (data is null)
- continue;
- if (_sendToDb is null)
- dataIsNull = false;
- foreach(var dbController in _sendToDb)
- {
- dbController.CheckGamespy3(data, name);
- }
- }
- */
- while (!_token.IsCancellationRequested)
- {
- try
- {
- await Task.Delay(TimeSpan.FromSeconds(interval));
- var data = server.GetStatus();
- if (data is null)
- continue;
- if (_sendToDb is null)
- continue;
- foreach (var dbController in _sendToDb)
- {
- dbController.SendGamespy3(data, name);
- }
- }
- catch (System.Net.Sockets.SocketException ex)
- {
- _logger.Debug("{0}:{1} {2}", ip, port, ex.Message);
- }
- }
- }
- catch(System.Net.Sockets.SocketException ex)
- {
- _logger.Debug("{0}:{1} {2}", ip, port, ex.Message);
- }
- }, _token));
- break;
- case "Gamespy2":
- _updaterTasks.Add(name, new Task(async () => {
- try
- {
- Gs2Status server = new Gs2Status(ip, port);
- while (!_token.IsCancellationRequested)
- {
- try
- {
- await Task.Delay(TimeSpan.FromSeconds(interval));
- var data = server.GetStatus();
- if (data is null)
- continue;
- if (_sendToDb is null)
- continue;
- foreach (var dbController in _sendToDb)
- {
- dbController.SendGamespy2(data, name);
- }
- }
- catch (System.Net.Sockets.SocketException ex)
- {
- _logger.Debug("{0}:{1} {2}", ip, port, ex.Message);
- }
- }
- }
- catch (System.Net.Sockets.SocketException ex)
- {
- _logger.Debug("{0}:{1} {2}", ip, port, ex.Message);
- }
- }, _token));
- break;
- }
- }
- private McStatus? UpdateMinecraft(string ip, int port)
- {
- McStatus? status = null;
- try
- {
- status = McStatus.GetStatus(ip, port);
- }
- catch (Exception ex)
- {
- _logger.Debug("{0}:{1} {2}", ip, port, ex.Message);
- }
- if (status != null)
- return status;
- return null;
- }
- private async Task<SteamData?> UpdateSteam(string ip, int port, int interval)
- {
- ServerQuery serverQuery = new ServerQuery();
- try
- {
- serverQuery.SendTimeout = 2000;
- serverQuery.ReceiveTimeout = 2000;
- SteamData? output = null;
- serverQuery.Connect(ip, (ushort)port);
- var serverInfo = await serverQuery.GetServerInfoAsync();
- var serverPlayers = await serverQuery.GetPlayersAsync();
- output = new SteamData
- {
- ServerInfo = serverInfo,
- Players = serverPlayers
- };
-
- if (output is null)
- return null;
-
- return output;
- }
- catch (TimeoutException ex)
- {
- _logger.Debug("{0}:{1} {2}", ip, port, ex.Message);
- return null;
- }
- catch (Exception ex)
- {
- _logger.Debug("{0}:{1} {2}", ip, port, ex.Message);
- return null;
- }
- }
-
- }
- }
|