This commit is contained in:
Gregory Lirent 2025-07-03 20:01:19 +03:00
parent 802598be79
commit dfa8542fb2
13 changed files with 423 additions and 218 deletions

View File

@ -10,8 +10,10 @@ namespace WebmrAPI.Configuration
public class MonitoringSettings public class MonitoringSettings
{ {
public int ProcessScanInterval { get; set; } = 5; public int ProcessScanInterval { get; set; } = 5;
public int MemoryRegionScanTimeout { get; set; } = 30; public int MemoryRegionScanTimeout { get; set; } = 30;
public int ThreadScanTimeout { get; set; } = 30;
public int ModuleScanTimeout { get; set; } = 60;
public bool AllowProcessReadAccess { get; set; } = true; public bool AllowProcessReadAccess { get; set; } = true;
} }

View File

@ -5,6 +5,7 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using WebmrAPI.Models; using WebmrAPI.Models;
using WebmrAPI.Services; using WebmrAPI.Services;
using WebmrAPI.Services.Scanners;
namespace WebmrAPI.Controllers namespace WebmrAPI.Controllers
{ {
@ -34,13 +35,41 @@ namespace WebmrAPI.Controllers
[HttpGet] [HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<ProcessBaseInfo>))] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<ProcessBaseInfo>))]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)] [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 try
{ {
var processes = _monitor.GetBufferedProcesses(); var data = _monitor.GetBufferedProcesses();
return Content(GetFormattedJson(processes, pretty), "application/json");
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) catch (Exception ex)
{ {
@ -53,11 +82,13 @@ namespace WebmrAPI.Controllers
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessInfo))] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessInfo))]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)] [ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult GetProcessById(int pid, [FromQuery] bool pretty = false) async public Task<IActionResult> GetProcessById(int pid,
[FromQuery] bool pretty = false
)
{ {
try try
{ {
var data = _monitor.GetProcessDetails(pid); var data = await _monitor.GetProcessDetails(pid, ScanTarget.ProcessDetails);
if (data == null) if (data == null)
{ {
return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); 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: Добавить эндпоинты для: [HttpGet("{pid}/base_info")]
// - /api/Process/{pid}/memory_regions [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessBaseInfo))]
// - /api/Process/{pid}/modules [ProducesResponseType(StatusCodes.Status404NotFound)]
// - /api/Process/{pid}/threads [ProducesResponseType(StatusCodes.Status500InternalServerError)]
// - Поиск по имени async public Task<IActionResult> GetProcessBaseInfoById(int pid,
// - Пагинация [FromQuery] bool pretty = false
// - Нагрузка на CPU )
{
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<MemoryRegionInfo>))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
async public Task<IActionResult> 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<MemoryRegionInfo>))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
async public Task<IActionResult> 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<MemoryRegionInfo>))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
async public Task<IActionResult> 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<T1> Sort<T1, T2>(IEnumerable<T1> data, Func<T1, T2> selector, bool desc)
{
return desc ? data.OrderByDescending(selector) : data.OrderBy(selector);
}
private IEnumerable<T> Paginate<T>(IEnumerable<T> data, int limit, int offset)
{
if (offset > 0) data = data.Skip(offset);
if (limit > 0) data = data.Take(limit);
return data;
}
} }
} }

View File

@ -11,9 +11,8 @@ using WebmrAPI.Utils;
namespace WebmrAPI.Services namespace WebmrAPI.Services
{ {
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public class ProcessMonitor : IHostedService, IDisposable public class ProcessMonitor : BackgroundService
{ {
private Timer? _timer;
private readonly ILogger<ProcessMonitor> _logger; private readonly ILogger<ProcessMonitor> _logger;
private readonly MonitoringSettings _config; private readonly MonitoringSettings _config;
private LazyConcurrentContainer<ProcessInfo> _processes = new(); private LazyConcurrentContainer<ProcessInfo> _processes = new();
@ -28,13 +27,13 @@ namespace WebmrAPI.Services
return _processes.Values; return _processes.Values;
} }
public ProcessInfo? GetProcessDetails(int pid, ScanTarget target = ScanTarget.ProcessDetails) async public Task<ProcessInfo?> GetProcessDetails(int pid, ScanTarget target = ScanTarget.ProcessDetails)
{ {
if (_processes.Container != null && _processes.Container.TryGetValue(pid, out var info)) if (_processes.Container != null && _processes.Container.TryGetValue(pid, out var info))
{ {
try 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."); _logger.LogInformation($"Scan details of the process {info.Name} (PID: {info.PID}) was completed.");
return info; return info;
} }
@ -51,34 +50,43 @@ namespace WebmrAPI.Services
return null; return null;
} }
public ProcessMonitor(ILogger<ProcessMonitor> logger, IOptions<AppSettings> settings) public ProcessMonitor(ILogger<ProcessMonitor> logger, IOptions<AppSettings> settings)
{ {
_logger = logger; _logger = logger;
_config = settings.Value.Monitoring; _config = settings.Value.Monitoring;
_provider = new(this); _provider = new(this);
} }
public Task StartAsync(CancellationToken cancellationToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
_logger.LogInformation($"ProcessMonitor started. Scan interval: {_config.ProcessScanInterval} seconds."); _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) private async Task ScanAsync()
{
_logger.LogInformation("ProcessMonitor is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWork(object? state)
{ {
_logger.LogDebug("Initiating process scan..."); _logger.LogDebug("Initiating process scan...");
try try
{ {
_provider.CreateScanTask().Scan(); await _provider.CreateScanTask().ScanAsync();
_logger.LogInformation($"Process buffer updated, contains {Processes.Container?.Count} processes."); _logger.LogInformation($"Process buffer updated, contains {Processes.Container?.Count} processes.");
} }
catch (ProcessMonitorException ex) catch (ProcessMonitorException ex)
@ -90,10 +98,5 @@ namespace WebmrAPI.Services
_logger.LogError($"Unhandled error during process monitoring cycle: {ex.Message}"); _logger.LogError($"Unhandled error during process monitoring cycle: {ex.Message}");
} }
} }
public void Dispose()
{
_timer?.Dispose();
}
} }
} }

View File

@ -34,16 +34,18 @@ namespace WebmrAPI.Services.Scanners
return result; return result;
} }
abstract internal bool Scan(out Dictionary<long, T>? data); abstract internal Task<bool> ScanAsync(Dictionary<long, T> data);
public void Scan() public async Task ScanAsync()
{ {
bool success = false; bool success = false;
if (Container.Container != null && IsActual()) return; if (Container.Container != null && IsActual()) return;
try try
{ {
if (success = Scan(out var data)) Dictionary<long, T> data = new();
if (success = await ScanAsync(data))
{ {
Container.SetContainer(data); Container.SetContainer(data);
} }

View File

@ -5,6 +5,6 @@ namespace WebmrAPI.Services.Scanners
public interface IScannable public interface IScannable
{ {
public ScanTarget Target { get; } public ScanTarget Target { get; }
public void Scan(); public Task ScanAsync();
} }
} }

View File

@ -11,45 +11,45 @@ namespace WebmrAPI.Services.Scanners
override public ScanTarget Target { get => ScanTarget.MemoryRegions; } override public ScanTarget Target { get => ScanTarget.MemoryRegions; }
internal int PID { get; private set; } internal int PID { get; private set; }
override internal bool Scan(out Dictionary<long, MemoryRegionInfo>? data) override internal async Task<bool> ScanAsync(Dictionary<long, MemoryRegionInfo> data)
{ {
data = new();
try return await Task.Run<bool>(() =>
{ {
using (var process = new WindowsProcess(PID)) using (var process = new WindowsProcess(PID))
{ {
var regions = data; try
process.ForeachMemoryRegion(info =>
{ {
long addr = info.BaseAddress.ToInt64(); process.ForeachMemoryRegion(info =>
regions.Add(addr, new MemoryRegionInfo
{ {
MemoryAddress = addr, long addr = info.BaseAddress.ToInt64();
MemorySize = info.RegionSize.ToUInt64(), data.Add(addr, new MemoryRegionInfo
MemoryState = info.State, {
MemoryPageProtection = info.PageProtection, MemoryAddress = addr,
MemoryType = info.Type 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 false;
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;
} }
public MemoryRegionScanner(IScanProvider scanner, LazyConcurrentContainer<MemoryRegionInfo> container, int pid) public MemoryRegionScanner(IScanProvider scanner, LazyConcurrentContainer<MemoryRegionInfo> container, int pid)

View File

@ -12,81 +12,85 @@ namespace WebmrAPI.Services.Scanners
override public ScanTarget Target { get => ScanTarget.Modules; } override public ScanTarget Target { get => ScanTarget.Modules; }
internal int PID { get; private set; } internal int PID { get; private set; }
override internal bool Scan(out Dictionary<long, ProcessModuleInfo>? data) override async internal Task<bool> ScanAsync(Dictionary<long, ProcessModuleInfo> data)
{ {
data = new(); var success = await Task.Run<bool?>(() =>
try
{ {
using (var process = Process.GetProcessById(PID)) try
{ {
foreach (ProcessModule module in process.Modules) using (var process = Process.GetProcessById(PID))
{ {
long addr = module.BaseAddress.ToInt64(); foreach (ProcessModule module in process.Modules)
data.Add(addr, new ProcessModuleInfo
{ {
MemoryAddress = addr, long addr = module.BaseAddress.ToInt64();
ModuleName = module.ModuleName, data.Add(addr, new ProcessModuleInfo
FileName = module.FileName, {
MemorySize = (ulong)module.ModuleMemorySize, MemoryAddress = addr,
EntrypointRawAddress = module.EntryPointAddress.ToInt64() ModuleName = module.ModuleName,
}); FileName = module.FileName,
MemorySize = (ulong)module.ModuleMemorySize,
EntrypointRawAddress = module.EntryPointAddress.ToInt64()
});
}
return true;
} }
} }
catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 5)
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))
{ {
var modules = data; Provider.Logger.LogWarning($"Access denied to Process.Modules for PID {PID}. Attempting P/Invoke. Error: {ex.Message}");
process.ForeachLoadedModule(info => 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(); process.ForeachLoadedModule(info =>
modules.Add(addr, new ProcessModuleInfo
{ {
ModuleName = info.Name.ToString(), long addr = info.BaseAddress.ToInt64();
MemoryAddress = addr, data.Add(addr, new ProcessModuleInfo
FileName = info.FileName.ToString(), {
MemorySize = info.MemorySize, ModuleName = info.Name.ToString(),
EntrypointRawAddress = info.EntryPointAddress.ToInt64() 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; return false;
} });
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;
} }
public ModuleScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessModuleInfo> container, int pid) public ModuleScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessModuleInfo> container, int pid)

View File

@ -11,23 +11,17 @@ namespace WebmrAPI.Services.Scanners
{ {
override public ScanTarget Target { get => ScanTarget.Processes; } override public ScanTarget Target { get => ScanTarget.Processes; }
private Task<Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>> _wmi; private Task<Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>> _wmiTask;
private Dictionary<int, (string? Name, string? CommandLine, int ParentPID)> WmiData
{
get => _wmi.Result;
}
async private Task<Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>> GetWmiDataAsync() async private Task<Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>> GetWmiDataAsync()
{ {
var wmi = new Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>(); var wmi = new Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>();
await Task.Run(() =>
try
{ {
using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, Name, CommandLine, ParentProcessId FROM Win32_Process")) try
using (var processes = await Task.Run(() => { return searcher.Get(); }))
await Task.Run(() =>
{ {
using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, Name, CommandLine, ParentProcessId FROM Win32_Process"))
using (var processes = searcher.Get())
foreach (var obj in processes) foreach (var obj in processes)
{ {
int pid = Convert.ToInt32(obj["ProcessId"]); int pid = Convert.ToInt32(obj["ProcessId"]);
@ -37,17 +31,17 @@ namespace WebmrAPI.Services.Scanners
wmi[pid] = (name, cmd, ppid); wmi[pid] = (name, cmd, ppid);
} }
}); }
} catch (Exception ex)
catch (Exception ex) {
{ Provider.Logger.LogError(ex, "Failed to retrieve WMI process data for all processes.");
Provider.Logger.LogError(ex, "Failed to retrieve WMI process data for all processes."); }
} });
return wmi; return wmi;
} }
private bool Populate(ProcessInfo info, Process process) private bool Populate(ProcessInfo info, Process process, Dictionary<int, (string? Name, string? CommandLine, int ParentPID)> wmiData)
{ {
info.TotalProcessorTime = process.TotalProcessorTime; info.TotalProcessorTime = process.TotalProcessorTime;
info.MemorySize = (ulong)process.WorkingSet64; info.MemorySize = (ulong)process.WorkingSet64;
@ -70,7 +64,7 @@ namespace WebmrAPI.Services.Scanners
{ {
try try
{ {
if (WmiData.TryGetValue(process.Id, out var entry)) if (wmiData.TryGetValue(process.Id, out var entry))
{ {
info.Name = entry.Name; info.Name = entry.Name;
info.CommandLine = entry.CommandLine; info.CommandLine = entry.CommandLine;
@ -103,18 +97,16 @@ namespace WebmrAPI.Services.Scanners
return false; return false;
} }
override internal bool Scan(out Dictionary<long, ProcessInfo>? data) override async internal Task<bool> ScanAsync(Dictionary<long, ProcessInfo> data)
{ {
data = new(); var wmiData = await _wmiTask;
foreach (var process in await Task.Run(Process.GetProcesses))
foreach (var process in Process.GetProcesses())
using (process) using (process)
{ {
ProcessInfo? info; ProcessInfo? info;
if (process.Id == 0 || process.Id == 4) if (process.Id == 0 || process.Id == 4)
{ {
process.Dispose();
continue; continue;
} }
@ -125,15 +117,17 @@ namespace WebmrAPI.Services.Scanners
else else
{ {
info.PID = process.Id; info.PID = process.Id;
info.CpuUsage = 0;
} }
try if (Populate(info, process, wmiData))
{ {
if (Populate(info, process)) data.Add(info.PID, info);
{ }
data.Add(info.PID, info); else
} {
} catch (Exception) { } Provider.Logger.LogWarning($"Skipping process {process.Id} due to incomplete information during population.");
}
} }
return true; return true;
@ -142,7 +136,7 @@ namespace WebmrAPI.Services.Scanners
public ProcessScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessInfo> container) public ProcessScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessInfo> container)
: base(scanner, container) : base(scanner, container)
{ {
_wmi = GetWmiDataAsync(); _wmiTask = GetWmiDataAsync();
} }
} }
} }

View File

@ -14,9 +14,9 @@ namespace WebmrAPI.Services.Scanners
{ {
switch (target) switch (target)
{ {
case ScanTarget.Processes: return _monitor.Config.MemoryRegionScanTimeout; case ScanTarget.MemoryRegions: return _monitor.Config.MemoryRegionScanTimeout;
case ScanTarget.Modules: return _monitor.Config.MemoryRegionScanTimeout; case ScanTarget.Modules: return _monitor.Config.ModuleScanTimeout;
case ScanTarget.Threads: return _monitor.Config.MemoryRegionScanTimeout; case ScanTarget.Threads: return _monitor.Config.ThreadScanTimeout;
default: return _monitor.Config.ProcessScanInterval; default: return _monitor.Config.ProcessScanInterval;
} }
} }
@ -26,12 +26,16 @@ namespace WebmrAPI.Services.Scanners
return new ProcessScanner(this, _monitor.Processes); return new ProcessScanner(this, _monitor.Processes);
} }
private IScannable CreateScanTask(ScanTarget target) async public Task<IScannable> CreateFullScanTaskAsync(ScanTarget target = ScanTarget.ProcessDetails)
{ {
var scanner = new ScanQueue(); if (target.HasFlag(ScanTarget.Processes))
{
await CreateScanTask().ScanAsync();
target &= ~ScanTarget.Processes;
}
CreateScanTask().Scan(); var scanner = new ScanQueue();
var data = _monitor.Processes.Values; var data = _monitor.Processes.Values;
if (data != null) if (data != null)
{ {
@ -43,26 +47,21 @@ namespace WebmrAPI.Services.Scanners
return scanner; return scanner;
} }
public IScannable CreateScanTask(ProcessInfo? process, ScanTarget target = ScanTarget.ProcessDetails) public IScannable CreateScanTask(ProcessInfo process, ScanTarget target = ScanTarget.ProcessDetails)
{ {
var scanner = new ScanQueue(); var scanner = new ScanQueue();
if (target.HasFlag(ScanTarget.Processes)) if (target.HasFlag(ScanTarget.MemoryRegions))
{
return CreateScanTask(target^ScanTarget.Processes);
}
if (target.HasFlag(ScanTarget.MemoryRegions) && process != null)
{ {
scanner.Add(new MemoryRegionScanner(this, process.MemoryRegionsContainer, process.PID)); 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)); 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)); scanner.Add(new ThreadScanner(this, process.ThreadsContainer, process.PID));
} }

View File

@ -1,5 +1,7 @@
// File: Services/Scanners/ScanQueue.cs // File: Services/Scanners/ScanQueue.cs
using System.Diagnostics;
namespace WebmrAPI.Services.Scanners namespace WebmrAPI.Services.Scanners
{ {
public class ScanQueue : IScannable public class ScanQueue : IScannable
@ -16,12 +18,14 @@ namespace WebmrAPI.Services.Scanners
return this; return this;
} }
public void Scan() public async Task ScanAsync()
{ {
var tasks = new List<Task>();
while (_queue.Count > 0) while (_queue.Count > 0)
{ {
_queue.Dequeue().Scan(); tasks.Add(_queue.Dequeue().ScanAsync());
} }
await Task.WhenAll(tasks.ToArray());
} }
} }
} }

View File

@ -11,49 +11,49 @@ namespace WebmrAPI.Services.Scanners
override public ScanTarget Target { get => ScanTarget.Threads; } override public ScanTarget Target { get => ScanTarget.Threads; }
internal int PID { get; private set; } internal int PID { get; private set; }
override internal bool Scan(out Dictionary<long, ProcessThreadInfo>? data) override internal async Task<bool> ScanAsync(Dictionary<long, ProcessThreadInfo> data)
{ {
data = new(); return await Task.Run(() =>
try
{ {
using (var process = Process.GetProcessById(PID)) try
foreach (ProcessThread thread in process.Threads)
{ {
ProcessThreadInfo info; using (var process = Process.GetProcessById(PID))
foreach (ProcessThread thread in process.Threads)
if (GetFromCacheOrNew(thread.Id, out info))
{ {
info.CpuUsage = CalcCpuUsage(info.ProcessorTime, Container.Elapsed.TotalMilliseconds); ProcessThreadInfo info;
}
else
{
info.ID = thread.Id;
}
info.BasePriority = thread.BasePriority; if (GetFromCacheOrNew(thread.Id, out info))
info.CurrentPriority = thread.CurrentPriority; {
info.TotalProcessorTime = thread.TotalProcessorTime; 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 false;
} });
return true;
} }
public ThreadScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessThreadInfo> container, int pid) public ThreadScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessThreadInfo> container, int pid)

View File

@ -237,7 +237,10 @@ namespace WebmrAPI.Utils
public void Dispose() public void Dispose()
{ {
if (_ptr != IntPtr.Zero) CloseHandle(_ptr); if (_ptr != IntPtr.Zero)
{
try { CloseHandle(_ptr); } catch (Exception) { }
}
} }
~WindowsProcess() ~WindowsProcess()

View File

@ -14,7 +14,7 @@
<AssemblyVersion>1.0.0.0</AssemblyVersion> <AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion> <FileVersion>1.0.0.0</FileVersion>
<Version>0.1.2</Version> <Version>0.1.4</Version>
<Company>OpenSource</Company> <Company>OpenSource</Company>
<Product>Process Monitoring Agent</Product> <Product>Process Monitoring Agent</Product>
<Description>A service for detailed monitoring processes and memory regions.</Description> <Description>A service for detailed monitoring processes and memory regions.</Description>