From dfa8542fb23fb3a264cbaf77f98b7875636215f2 Mon Sep 17 00:00:00 2001 From: Gregory Lirent Date: Thu, 3 Jul 2025 20:01:19 +0300 Subject: [PATCH] v 0.1.4 --- Configuration/AppSettings.cs | 4 +- Controllers/ProcessController.cs | 218 +++++++++++++++++++++-- Services/ProcessMonitor.cs | 49 ++--- Services/Scanners/AbstractScanner.cs | 8 +- Services/Scanners/IScannable.cs | 2 +- Services/Scanners/MemoryRegionScanner.cs | 60 +++---- Services/Scanners/ModuleScanner.cs | 128 ++++++------- Services/Scanners/ProcessScanner.cs | 56 +++--- Services/Scanners/ScanProvider.cs | 31 ++-- Services/Scanners/ScanQueue.cs | 8 +- Services/Scanners/ThreadScanner.cs | 70 ++++---- Utils/WindowsProcess.cs | 5 +- webmr-api.csproj | 2 +- 13 files changed, 423 insertions(+), 218 deletions(-) diff --git a/Configuration/AppSettings.cs b/Configuration/AppSettings.cs index acbc830..2c3015d 100644 --- a/Configuration/AppSettings.cs +++ b/Configuration/AppSettings.cs @@ -10,8 +10,10 @@ namespace WebmrAPI.Configuration public class MonitoringSettings { - public int ProcessScanInterval { get; set; } = 5; + public int ProcessScanInterval { get; set; } = 5; public int MemoryRegionScanTimeout { get; set; } = 30; + public int ThreadScanTimeout { get; set; } = 30; + public int ModuleScanTimeout { get; set; } = 60; public bool AllowProcessReadAccess { get; set; } = true; } diff --git a/Controllers/ProcessController.cs b/Controllers/ProcessController.cs index b9b2d08..274094d 100644 --- a/Controllers/ProcessController.cs +++ b/Controllers/ProcessController.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using WebmrAPI.Models; using WebmrAPI.Services; +using WebmrAPI.Services.Scanners; namespace WebmrAPI.Controllers { @@ -34,13 +35,41 @@ namespace WebmrAPI.Controllers [HttpGet] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public IActionResult GetProcesses([FromQuery] bool pretty = false) + public IActionResult GetProcesses( + [FromQuery] bool pretty = false, + [FromQuery] string sortBy = "", + [FromQuery] bool desc = false, + [FromQuery] int limit = 0, + [FromQuery] int offset = 0 + ) { try { - var processes = _monitor.GetBufferedProcesses(); - return Content(GetFormattedJson(processes, pretty), "application/json"); + var data = _monitor.GetBufferedProcesses(); + + if (data != null && !String.IsNullOrEmpty(sortBy)) + { + sortBy = sortBy.ToLowerInvariant(); + switch (sortBy) + { + case "pid": data = Sort(data, p => p.PID, desc); break; + case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break; + case "memsize": data = Sort(data, p => p.MemorySize, desc); break; + case "parentpid": data = Sort(data, p => p.ParentPID, desc); break; + case "name": data = Sort(data, p => p.Name, desc); break; + case "filename": data = Sort(data, p => p.FileName, desc); break; + case "commandline": data = Sort(data, p => p.CommandLine, desc); break; + case "threadcount": data = Sort(data, p => p.ThreadCount, desc); break; + case "status": data = Sort(data, p => p.Status, desc); break; + case "starttime": data = Sort(data, p => p.StartTime, desc); break; + case "cpuusage": data = Sort(data, p => p.CpuUsage, desc); break; + default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); + } + } + + return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); } catch (Exception ex) { @@ -53,11 +82,13 @@ namespace WebmrAPI.Controllers [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessInfo))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public IActionResult GetProcessById(int pid, [FromQuery] bool pretty = false) + async public Task GetProcessById(int pid, + [FromQuery] bool pretty = false + ) { try { - var data = _monitor.GetProcessDetails(pid); + var data = await _monitor.GetProcessDetails(pid, ScanTarget.ProcessDetails); if (data == null) { return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); @@ -71,12 +102,175 @@ namespace WebmrAPI.Controllers } } - // TODO: Добавить эндпоинты для: - // - /api/Process/{pid}/memory_regions - // - /api/Process/{pid}/modules - // - /api/Process/{pid}/threads - // - Поиск по имени - // - Пагинация - // - Нагрузка на CPU + [HttpGet("{pid}/base_info")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessBaseInfo))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + async public Task GetProcessBaseInfoById(int pid, + [FromQuery] bool pretty = false + ) + { + try + { + var data = await _monitor.GetProcessDetails(pid, 0); + + if (data == null) + { + return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); + } + return Content(GetFormattedJson((ProcessBaseInfo)data, pretty), "application/json"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"An error occurred while receiving memory regions for PID {pid}."); + return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving memory regions for PID {pid}."); + } + } + + [HttpGet("{pid}/memory_regions")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + async public Task GetProcessMemoryPagesById(int pid, + [FromQuery] bool pretty = false, + [FromQuery] string sortBy = "", + [FromQuery] bool desc = false, + [FromQuery] int limit = 0, + [FromQuery] int offset = 0 + ) + { + try + { + var data = (await _monitor.GetProcessDetails(pid, ScanTarget.MemoryRegions))?.MemoryRegions; + + if (data == null) + { + return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); + } + + if (!String.IsNullOrEmpty(sortBy)) + { + sortBy = sortBy.ToLowerInvariant(); + switch (sortBy) + { + case "state": data = Sort(data, p => p.MemoryState, desc); break; + case "protection": data = Sort(data, p => p.MemoryPageProtection, desc); break; + case "type": data = Sort(data, p => p.MemoryType, desc); break; + case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break; + case "memsize": data = Sort(data, p => p.MemorySize, desc); break; + default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); + } + } + + + return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"An error occurred while receiving memory regions for PID {pid}."); + return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving memory regions for PID {pid}."); + } + } + + [HttpGet("{pid}/modules")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + async public Task GetProcessModulesById(int pid, + [FromQuery] bool pretty = false, + [FromQuery] string sortBy = "", + [FromQuery] bool desc = false, + [FromQuery] int limit = 0, + [FromQuery] int offset = 0 + ) + { + try + { + var data = (await _monitor.GetProcessDetails(pid, ScanTarget.Modules))?.Modules; + + if (data == null) + { + return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); + } + + if (!String.IsNullOrEmpty(sortBy)) + { + sortBy = sortBy.ToLowerInvariant(); + switch (sortBy) + { + case "name": data = Sort(data, p => p.ModuleName, desc); break; + case "filename": data = Sort(data, p => p.FileName, desc); break; + case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break; + case "entryaddress": data = Sort(data, p => p.EntrypointRawAddress, desc); break; + case "memsize": data = Sort(data, p => p.MemorySize, desc); break; + default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); + } + } + + return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"An error occurred while receiving modules for PID {pid}."); + return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving modules for PID {pid}."); + } + } + + [HttpGet("{pid}/threads")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + async public Task GetProcessThreadsById(int pid, + [FromQuery] bool pretty = false, + [FromQuery] string sortBy = "", + [FromQuery] bool desc = false, + [FromQuery] int limit = 0, + [FromQuery] int offset = 0 + ) + { + try + { + var data = (await _monitor.GetProcessDetails(pid, ScanTarget.Threads))?.Threads; + + if (data == null) + { + return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); + } + + if (!String.IsNullOrEmpty(sortBy)) + { + sortBy = sortBy.ToLowerInvariant(); + switch (sortBy) + { + case "id": data = Sort(data, p => p.ID, desc); break; + case "priority": data = Sort(data, p => p.CurrentPriority, desc); break; + case "basepriority": data = Sort(data, p => p.BasePriority, desc); break; + case "cpuusage": data = Sort(data, p => p.CpuUsage, desc); break; + default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); + } + } + + return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); + } + catch (Exception ex) + { + _logger.LogError(ex, $"An error occurred while receiving threads for PID {pid}."); + return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving threads for PID {pid}."); + } + } + + private static IEnumerable Sort(IEnumerable data, Func selector, bool desc) + { + return desc ? data.OrderByDescending(selector) : data.OrderBy(selector); + } + private IEnumerable Paginate(IEnumerable data, int limit, int offset) + { + if (offset > 0) data = data.Skip(offset); + if (limit > 0) data = data.Take(limit); + return data; + } } } diff --git a/Services/ProcessMonitor.cs b/Services/ProcessMonitor.cs index 3971d9f..5ed526f 100644 --- a/Services/ProcessMonitor.cs +++ b/Services/ProcessMonitor.cs @@ -11,9 +11,8 @@ using WebmrAPI.Utils; namespace WebmrAPI.Services { [SupportedOSPlatform("windows")] - public class ProcessMonitor : IHostedService, IDisposable + public class ProcessMonitor : BackgroundService { - private Timer? _timer; private readonly ILogger _logger; private readonly MonitoringSettings _config; private LazyConcurrentContainer _processes = new(); @@ -28,13 +27,13 @@ namespace WebmrAPI.Services return _processes.Values; } - public ProcessInfo? GetProcessDetails(int pid, ScanTarget target = ScanTarget.ProcessDetails) + async public Task GetProcessDetails(int pid, ScanTarget target = ScanTarget.ProcessDetails) { if (_processes.Container != null && _processes.Container.TryGetValue(pid, out var info)) { try { - _provider.CreateScanTask(info, target).Scan(); + await _provider.CreateScanTask(info, target).ScanAsync(); _logger.LogInformation($"Scan details of the process {info.Name} (PID: {info.PID}) was completed."); return info; } @@ -51,34 +50,43 @@ namespace WebmrAPI.Services return null; } - public ProcessMonitor(ILogger logger, IOptions settings) { _logger = logger; _config = settings.Value.Monitoring; _provider = new(this); } - - public Task StartAsync(CancellationToken cancellationToken) + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation($"ProcessMonitor started. Scan interval: {_config.ProcessScanInterval} seconds."); - _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(_config.ProcessScanInterval)); - return Task.CompletedTask; + + await ScanAsync(); + + using var timer = new PeriodicTimer(TimeSpan.FromSeconds(_config.ProcessScanInterval)); + try + { + while (await timer.WaitForNextTickAsync(stoppingToken)) + { + await ScanAsync(); + } + } + catch (OperationCanceledException) + { + _logger.LogInformation("ProcessMonitor is stopping due to cancellation."); + } + catch (Exception ex) + { + _logger.LogError(ex, "ProcessMonitor encountered an unhandled exception and is stopping."); + } } - public Task StopAsync(CancellationToken cancellationToken) - { - _logger.LogInformation("ProcessMonitor is stopping."); - _timer?.Change(Timeout.Infinite, 0); - return Task.CompletedTask; - } - - private void DoWork(object? state) + private async Task ScanAsync() { _logger.LogDebug("Initiating process scan..."); try { - _provider.CreateScanTask().Scan(); + await _provider.CreateScanTask().ScanAsync(); _logger.LogInformation($"Process buffer updated, contains {Processes.Container?.Count} processes."); } catch (ProcessMonitorException ex) @@ -90,10 +98,5 @@ namespace WebmrAPI.Services _logger.LogError($"Unhandled error during process monitoring cycle: {ex.Message}"); } } - - public void Dispose() - { - _timer?.Dispose(); - } } } diff --git a/Services/Scanners/AbstractScanner.cs b/Services/Scanners/AbstractScanner.cs index a1614e5..155914b 100644 --- a/Services/Scanners/AbstractScanner.cs +++ b/Services/Scanners/AbstractScanner.cs @@ -34,16 +34,18 @@ namespace WebmrAPI.Services.Scanners return result; } - abstract internal bool Scan(out Dictionary? data); + abstract internal Task ScanAsync(Dictionary data); - public void Scan() + public async Task ScanAsync() { bool success = false; if (Container.Container != null && IsActual()) return; try { - if (success = Scan(out var data)) + Dictionary data = new(); + + if (success = await ScanAsync(data)) { Container.SetContainer(data); } diff --git a/Services/Scanners/IScannable.cs b/Services/Scanners/IScannable.cs index df6d3fc..ff5a622 100644 --- a/Services/Scanners/IScannable.cs +++ b/Services/Scanners/IScannable.cs @@ -5,6 +5,6 @@ namespace WebmrAPI.Services.Scanners public interface IScannable { public ScanTarget Target { get; } - public void Scan(); + public Task ScanAsync(); } } diff --git a/Services/Scanners/MemoryRegionScanner.cs b/Services/Scanners/MemoryRegionScanner.cs index 2c77b29..d644de6 100644 --- a/Services/Scanners/MemoryRegionScanner.cs +++ b/Services/Scanners/MemoryRegionScanner.cs @@ -11,45 +11,45 @@ namespace WebmrAPI.Services.Scanners override public ScanTarget Target { get => ScanTarget.MemoryRegions; } internal int PID { get; private set; } - override internal bool Scan(out Dictionary? data) + override internal async Task ScanAsync(Dictionary data) { - data = new(); - try + return await Task.Run(() => { using (var process = new WindowsProcess(PID)) { - var regions = data; - process.ForeachMemoryRegion(info => + try { - long addr = info.BaseAddress.ToInt64(); - regions.Add(addr, new MemoryRegionInfo + process.ForeachMemoryRegion(info => { - MemoryAddress = addr, - MemorySize = info.RegionSize.ToUInt64(), - MemoryState = info.State, - MemoryPageProtection = info.PageProtection, - MemoryType = info.Type + long addr = info.BaseAddress.ToInt64(); + data.Add(addr, new MemoryRegionInfo + { + MemoryAddress = addr, + MemorySize = info.RegionSize.ToUInt64(), + MemoryState = info.State, + MemoryPageProtection = info.PageProtection, + MemoryType = info.Type + }); }); - }); + return true; + } + catch (ProcessAccessDeniedException ex) + { + Provider.Logger.LogWarning($"Access denied to process {PID} for memory region scanning. Error: {ex.Message}"); + } + catch (MemoryRegionException ex) + { + Provider.Logger.LogWarning($"Error scanning memory regions for PID {PID}. Error: {ex.Message}"); + return true; + } + catch (Exception ex) + { + Provider.Logger.LogError($"An unexpected error occurred while scanning memory regions for PID {PID}. Error: {ex.Message}"); + } } - - return true; - } - catch (ProcessAccessDeniedException ex) - { - Provider.Logger.LogWarning($"Access denied to process {PID} for memory region scanning. Error: {ex.Message}"); - } - catch (MemoryRegionException ex) - { - Provider.Logger.LogWarning($"Error scanning memory regions for PID {PID}. Error: {ex.Message}"); - return true; - } - catch (Exception ex) - { - Provider.Logger.LogError($"An unexpected error occurred while scanning memory regions for PID {PID}. Error: {ex.Message}"); - } - return false; + return false; + }); } public MemoryRegionScanner(IScanProvider scanner, LazyConcurrentContainer container, int pid) diff --git a/Services/Scanners/ModuleScanner.cs b/Services/Scanners/ModuleScanner.cs index bb97cb2..9b4ff3b 100644 --- a/Services/Scanners/ModuleScanner.cs +++ b/Services/Scanners/ModuleScanner.cs @@ -12,81 +12,85 @@ namespace WebmrAPI.Services.Scanners override public ScanTarget Target { get => ScanTarget.Modules; } internal int PID { get; private set; } - override internal bool Scan(out Dictionary? data) + override async internal Task ScanAsync(Dictionary data) { - data = new(); - - try + var success = await Task.Run(() => { - using (var process = Process.GetProcessById(PID)) + try { - foreach (ProcessModule module in process.Modules) + using (var process = Process.GetProcessById(PID)) { - long addr = module.BaseAddress.ToInt64(); - data.Add(addr, new ProcessModuleInfo + foreach (ProcessModule module in process.Modules) { - MemoryAddress = addr, - ModuleName = module.ModuleName, - FileName = module.FileName, - MemorySize = (ulong)module.ModuleMemorySize, - EntrypointRawAddress = module.EntryPointAddress.ToInt64() - }); + long addr = module.BaseAddress.ToInt64(); + data.Add(addr, new ProcessModuleInfo + { + MemoryAddress = addr, + ModuleName = module.ModuleName, + FileName = module.FileName, + MemorySize = (ulong)module.ModuleMemorySize, + EntrypointRawAddress = module.EntryPointAddress.ToInt64() + }); + } + + return true; } } - - return true; - } - catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 5) - { - Provider.Logger.LogWarning($"Access denied to Process.Modules for PID {PID}. Attempting P/Invoke. Error: {ex.Message}"); - if (!Provider.ProcessReadAccess) return false; - } - catch (InvalidOperationException ex) - { - Provider.Logger.LogWarning($"Process with PID {PID} might have exited before module scanning. Error: {ex.Message}"); - return false; - } - catch (Exception ex) - { - Provider.Logger.LogError(ex, $"An unexpected error occurred while getting modules for PID {PID} using managed API."); - return false; - } - - try - { - using (var process = new WindowsProcess(PID, true)) + catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 5) { - var modules = data; - process.ForeachLoadedModule(info => + Provider.Logger.LogWarning($"Access denied to Process.Modules for PID {PID}. Attempting P/Invoke. Error: {ex.Message}"); + if (Provider.ProcessReadAccess) return null; + } + catch (InvalidOperationException ex) + { + Provider.Logger.LogWarning($"Process with PID {PID} might have exited before module scanning. Error: {ex.Message}"); + } + catch (Exception ex) + { + Provider.Logger.LogError(ex, $"An unexpected error occurred while getting modules for PID {PID} using managed API."); + } + return false; + }); + + if (success != null) return success == true ? true : false; + + return await Task.Run(() => + { + try + { + using (var process = new WindowsProcess(PID, true)) { - long addr = info.BaseAddress.ToInt64(); - modules.Add(addr, new ProcessModuleInfo + process.ForeachLoadedModule(info => { - ModuleName = info.Name.ToString(), - MemoryAddress = addr, - FileName = info.FileName.ToString(), - MemorySize = info.MemorySize, - EntrypointRawAddress = info.EntryPointAddress.ToInt64() + long addr = info.BaseAddress.ToInt64(); + data.Add(addr, new ProcessModuleInfo + { + ModuleName = info.Name.ToString(), + MemoryAddress = addr, + FileName = info.FileName.ToString(), + MemorySize = info.MemorySize, + EntrypointRawAddress = info.EntryPointAddress.ToInt64() + }); }); - }); + + return true; + } + } + catch (GettingModuleInfoException ex) + { + Provider.Logger.LogDebug(ex.Message); + } + catch (ProcessMonitorException ex) + { + Provider.Logger.LogError(ex.Message); + } + catch (Exception ex) + { + Provider.Logger.LogError(ex, $"An error occurred while getting modules for PID {PID} using P/Invoke."); } - return true; - } - catch (GettingModuleInfoException ex) - { - Provider.Logger.LogDebug(ex.Message); - } - catch (ProcessMonitorException ex) - { - Provider.Logger.LogError(ex.Message); - } - catch (Exception ex) - { - Provider.Logger.LogError(ex, $"An error occurred while getting modules for PID {PID} using P/Invoke."); - } - - return false; + return false; + }); } public ModuleScanner(IScanProvider scanner, LazyConcurrentContainer container, int pid) diff --git a/Services/Scanners/ProcessScanner.cs b/Services/Scanners/ProcessScanner.cs index c1bc973..6318dce 100644 --- a/Services/Scanners/ProcessScanner.cs +++ b/Services/Scanners/ProcessScanner.cs @@ -11,23 +11,17 @@ namespace WebmrAPI.Services.Scanners { override public ScanTarget Target { get => ScanTarget.Processes; } - private Task> _wmi; - - private Dictionary WmiData - { - get => _wmi.Result; - } + private Task> _wmiTask; async private Task> GetWmiDataAsync() { var wmi = new Dictionary(); - - try + await Task.Run(() => { - using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, Name, CommandLine, ParentProcessId FROM Win32_Process")) - using (var processes = await Task.Run(() => { return searcher.Get(); })) - await Task.Run(() => + try { + using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, Name, CommandLine, ParentProcessId FROM Win32_Process")) + using (var processes = searcher.Get()) foreach (var obj in processes) { int pid = Convert.ToInt32(obj["ProcessId"]); @@ -37,17 +31,17 @@ namespace WebmrAPI.Services.Scanners wmi[pid] = (name, cmd, ppid); } - }); - } - catch (Exception ex) - { - Provider.Logger.LogError(ex, "Failed to retrieve WMI process data for all processes."); - } + } + catch (Exception ex) + { + Provider.Logger.LogError(ex, "Failed to retrieve WMI process data for all processes."); + } + }); return wmi; } - private bool Populate(ProcessInfo info, Process process) + private bool Populate(ProcessInfo info, Process process, Dictionary wmiData) { info.TotalProcessorTime = process.TotalProcessorTime; info.MemorySize = (ulong)process.WorkingSet64; @@ -70,7 +64,7 @@ namespace WebmrAPI.Services.Scanners { try { - if (WmiData.TryGetValue(process.Id, out var entry)) + if (wmiData.TryGetValue(process.Id, out var entry)) { info.Name = entry.Name; info.CommandLine = entry.CommandLine; @@ -103,18 +97,16 @@ namespace WebmrAPI.Services.Scanners return false; } - override internal bool Scan(out Dictionary? data) + override async internal Task ScanAsync(Dictionary data) { - data = new(); - - foreach (var process in Process.GetProcesses()) + var wmiData = await _wmiTask; + foreach (var process in await Task.Run(Process.GetProcesses)) using (process) { ProcessInfo? info; if (process.Id == 0 || process.Id == 4) { - process.Dispose(); continue; } @@ -125,15 +117,17 @@ namespace WebmrAPI.Services.Scanners else { info.PID = process.Id; + info.CpuUsage = 0; } - try + if (Populate(info, process, wmiData)) { - if (Populate(info, process)) - { - data.Add(info.PID, info); - } - } catch (Exception) { } + data.Add(info.PID, info); + } + else + { + Provider.Logger.LogWarning($"Skipping process {process.Id} due to incomplete information during population."); + } } return true; @@ -142,7 +136,7 @@ namespace WebmrAPI.Services.Scanners public ProcessScanner(IScanProvider scanner, LazyConcurrentContainer container) : base(scanner, container) { - _wmi = GetWmiDataAsync(); + _wmiTask = GetWmiDataAsync(); } } } diff --git a/Services/Scanners/ScanProvider.cs b/Services/Scanners/ScanProvider.cs index 5af8ec9..d48585b 100644 --- a/Services/Scanners/ScanProvider.cs +++ b/Services/Scanners/ScanProvider.cs @@ -14,9 +14,9 @@ namespace WebmrAPI.Services.Scanners { switch (target) { - case ScanTarget.Processes: return _monitor.Config.MemoryRegionScanTimeout; - case ScanTarget.Modules: return _monitor.Config.MemoryRegionScanTimeout; - case ScanTarget.Threads: return _monitor.Config.MemoryRegionScanTimeout; + case ScanTarget.MemoryRegions: return _monitor.Config.MemoryRegionScanTimeout; + case ScanTarget.Modules: return _monitor.Config.ModuleScanTimeout; + case ScanTarget.Threads: return _monitor.Config.ThreadScanTimeout; default: return _monitor.Config.ProcessScanInterval; } } @@ -26,12 +26,16 @@ namespace WebmrAPI.Services.Scanners return new ProcessScanner(this, _monitor.Processes); } - private IScannable CreateScanTask(ScanTarget target) + async public Task CreateFullScanTaskAsync(ScanTarget target = ScanTarget.ProcessDetails) { - var scanner = new ScanQueue(); + if (target.HasFlag(ScanTarget.Processes)) + { + await CreateScanTask().ScanAsync(); + target &= ~ScanTarget.Processes; + } - CreateScanTask().Scan(); - var data = _monitor.Processes.Values; + var scanner = new ScanQueue(); + var data = _monitor.Processes.Values; if (data != null) { @@ -43,26 +47,21 @@ namespace WebmrAPI.Services.Scanners return scanner; } - public IScannable CreateScanTask(ProcessInfo? process, ScanTarget target = ScanTarget.ProcessDetails) + public IScannable CreateScanTask(ProcessInfo process, ScanTarget target = ScanTarget.ProcessDetails) { var scanner = new ScanQueue(); - if (target.HasFlag(ScanTarget.Processes)) - { - return CreateScanTask(target^ScanTarget.Processes); - } - - if (target.HasFlag(ScanTarget.MemoryRegions) && process != null) + if (target.HasFlag(ScanTarget.MemoryRegions)) { scanner.Add(new MemoryRegionScanner(this, process.MemoryRegionsContainer, process.PID)); } - if (target.HasFlag(ScanTarget.Modules) && process != null) + if (target.HasFlag(ScanTarget.Modules)) { scanner.Add(new ModuleScanner(this, process.ModulesContainer, process.PID)); } - if (target.HasFlag(ScanTarget.Threads) && process != null) + if (target.HasFlag(ScanTarget.Threads)) { scanner.Add(new ThreadScanner(this, process.ThreadsContainer, process.PID)); } diff --git a/Services/Scanners/ScanQueue.cs b/Services/Scanners/ScanQueue.cs index 336c61c..cb59907 100644 --- a/Services/Scanners/ScanQueue.cs +++ b/Services/Scanners/ScanQueue.cs @@ -1,5 +1,7 @@ // File: Services/Scanners/ScanQueue.cs +using System.Diagnostics; + namespace WebmrAPI.Services.Scanners { public class ScanQueue : IScannable @@ -16,12 +18,14 @@ namespace WebmrAPI.Services.Scanners return this; } - public void Scan() + public async Task ScanAsync() { + var tasks = new List(); while (_queue.Count > 0) { - _queue.Dequeue().Scan(); + tasks.Add(_queue.Dequeue().ScanAsync()); } + await Task.WhenAll(tasks.ToArray()); } } } diff --git a/Services/Scanners/ThreadScanner.cs b/Services/Scanners/ThreadScanner.cs index 15721ef..b3268f7 100644 --- a/Services/Scanners/ThreadScanner.cs +++ b/Services/Scanners/ThreadScanner.cs @@ -11,49 +11,49 @@ namespace WebmrAPI.Services.Scanners override public ScanTarget Target { get => ScanTarget.Threads; } internal int PID { get; private set; } - override internal bool Scan(out Dictionary? data) + override internal async Task ScanAsync(Dictionary data) { - data = new(); - - try + return await Task.Run(() => { - using (var process = Process.GetProcessById(PID)) - foreach (ProcessThread thread in process.Threads) + try { - ProcessThreadInfo info; - - if (GetFromCacheOrNew(thread.Id, out info)) + using (var process = Process.GetProcessById(PID)) + foreach (ProcessThread thread in process.Threads) { - info.CpuUsage = CalcCpuUsage(info.ProcessorTime, Container.Elapsed.TotalMilliseconds); - } - else - { - info.ID = thread.Id; - } + ProcessThreadInfo info; - info.BasePriority = thread.BasePriority; - info.CurrentPriority = thread.CurrentPriority; - info.TotalProcessorTime = thread.TotalProcessorTime; + if (GetFromCacheOrNew(thread.Id, out info)) + { + info.CpuUsage = CalcCpuUsage(info.ProcessorTime, Container.Elapsed.TotalMilliseconds); + } + else + { + info.ID = thread.Id; + info.CpuUsage = 0; + } - data.Add(info.ID, info); + info.BasePriority = thread.BasePriority; + info.CurrentPriority = thread.CurrentPriority; + info.TotalProcessorTime = thread.TotalProcessorTime; + + data.Add(info.ID, info); + } + return true; + } + catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 5) + { + Provider.Logger.LogWarning($"Access denied to process {PID} for thread scanning. Error: {ex.Message}"); + } + catch (InvalidOperationException ex) + { + Provider.Logger.LogWarning($"Process {PID} might have exited during thread enumeration. Error: {ex.Message}"); + } + catch (Exception ex) + { + Provider.Logger.LogError($"An unexpected error occurred while scanning threads for PID {PID}. Error: {ex.Message}"); } - } - catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 5) - { - Provider.Logger.LogWarning($"Access denied to process {PID} for thread scanning. Error: {ex.Message}"); - return false; - } - catch (InvalidOperationException ex) - { - Provider.Logger.LogWarning($"Process {PID} might have exited during thread enumeration. Error: {ex.Message}"); - return false; - } - catch (Exception ex) - { - Provider.Logger.LogError($"An unexpected error occurred while scanning threads for PID {PID}. Error: {ex.Message}"); return false; - } - return true; + }); } public ThreadScanner(IScanProvider scanner, LazyConcurrentContainer container, int pid) diff --git a/Utils/WindowsProcess.cs b/Utils/WindowsProcess.cs index 66fb975..da7faa2 100644 --- a/Utils/WindowsProcess.cs +++ b/Utils/WindowsProcess.cs @@ -237,7 +237,10 @@ namespace WebmrAPI.Utils public void Dispose() { - if (_ptr != IntPtr.Zero) CloseHandle(_ptr); + if (_ptr != IntPtr.Zero) + { + try { CloseHandle(_ptr); } catch (Exception) { } + } } ~WindowsProcess() diff --git a/webmr-api.csproj b/webmr-api.csproj index f0a7c38..56a857d 100644 --- a/webmr-api.csproj +++ b/webmr-api.csproj @@ -14,7 +14,7 @@ 1.0.0.0 1.0.0.0 - 0.1.2 + 0.1.4 OpenSource Process Monitoring Agent A service for detailed monitoring processes and memory regions.