DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
ProcessStreamer.cpp
Go to the documentation of this file.
1#include "pch.h"
11#include <vector>
12#include <string>
13#include <atomic>
14#include <algorithm>
15#include <cctype>
16
17#if defined(_WIN32)
18#include <windows.h>
19#include <tlhelp32.h>
20#include <psapi.h>
21#pragma comment(lib, "psapi.lib")
22#else
23#include <dirent.h>
24#include <fstream>
25#include <unistd.h>
26#endif
27
28namespace DotNetDupe {
29 namespace System {
30 namespace Diagnostics {
31
32#if defined(_WIN32)
34 static void FastPopulateProc(PROCESSENTRY32W* pe32, ProcessInfo& proc) {
35 proc.iProcessId = pe32->th32ProcessID;
36 proc.sName = String(pe32->szExeFile);
38 proc.memory.lPrivateBytes = 0;
39 DWORD dwSess = 0;
40 if (::ProcessIdToSessionId(pe32->th32ProcessID, &dwSess)) proc.iSessionId = static_cast<int>(dwSess);
41 if (pe32->th32ProcessID == 0) return;
42 HANDLE hProc = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32->th32ProcessID);
43 if (!hProc) return;
44 WCHAR szPath[MAX_PATH] = { 0 }; DWORD dwLen = MAX_PATH;
45 if (::QueryFullProcessImageNameW(hProc, 0, szPath, &dwLen)) proc.sPath = String(szPath);
46 PROCESS_MEMORY_COUNTERS pmc;
47 if (::GetProcessMemoryInfo(hProc, &pmc, sizeof(pmc))) {
48 proc.memory.lPhysicalMemoryBytes = static_cast<long long>(pmc.WorkingSetSize);
49 proc.memory.lPrivateBytes = static_cast<long long>(pmc.PagefileUsage);
50 }
51 ::CloseHandle(hProc);
52 }
53
55 static void CollectTier1Processes(std::vector<ProcessInfo>& vecProcs, int iSessionId) {
56 HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
57 if (hSnapshot == INVALID_HANDLE_VALUE) throw SystemException("Failed to create system process snapshot.");
58 PROCESSENTRY32W pe32; pe32.dwSize = sizeof(PROCESSENTRY32W);
59 if (::Process32FirstW(hSnapshot, &pe32)) {
60 do {
61 if (pe32.th32ProcessID == 0) continue;
62 ProcessInfo proc;
63 FastPopulateProc(&pe32, proc);
64 if (proc.iProcessId > 0 && (iSessionId == -1 || proc.iSessionId == iSessionId)) {
65 vecProcs.push_back(proc);
66 }
67 } while (::Process32NextW(hSnapshot, &pe32));
68 }
69 ::CloseHandle(hSnapshot);
70 }
71#else
73 static void FastPopulateLinuxProc(const std::string& dname, ProcessInfo& proc) {
74 proc.iProcessId = std::stoi(dname);
75 std::ifstream commFile("/proc/" + dname + "/comm");
76 std::string procComm;
77 if (commFile.is_open() && std::getline(commFile, procComm)) proc.sName = String(procComm.c_str());
78 std::ifstream statmFile("/proc/" + dname + "/statm");
79 unsigned long size = 0, resident = 0;
80 if (statmFile >> size >> resident) {
81 long pageSize = sysconf(_SC_PAGE_SIZE);
82 proc.memory.lPhysicalMemoryBytes = static_cast<long long>(resident * pageSize);
83 }
84 }
85
87 static void CollectTier1Processes(std::vector<ProcessInfo>& vecProcs, int iSessionId) {
88 DIR* dir = ::opendir("/proc");
89 if (!dir) throw SystemException("Failed to open /proc directory.");
90 struct dirent* entry = nullptr;
91 while ((entry = ::readdir(dir)) != nullptr) {
92 if (entry->d_type == DT_DIR) {
93 std::string dname = entry->d_name;
94 if (std::all_of(dname.begin(), dname.end(), ::isdigit)) {
95 ProcessInfo proc;
96 FastPopulateLinuxProc(dname, proc);
97 if (iSessionId == -1 || proc.iSessionId == iSessionId) vecProcs.push_back(proc);
98 }
99 }
100 }
101 ::closedir(dir);
102 }
103#endif
104
105 static void DeepEnrichProc(ProcessInfo& proc, bool bIncludeNetwork) {
107 SystemMetrics::EnrichProcessInfo(proc, bIncludeNetwork);
108 }
109
110 class ProcessStreamer::Impl : public Object {
111 public:
112 ProcessStreamOptions m_options;
118
120 std::atomic<bool> m_bRunning;
121 std::atomic<bool> m_bCancelled;
122 SmartPointer<Threading::Thread> m_spWorkerThread;
123
124 explicit Impl(const ProcessStreamOptions& options)
125 : m_options(options), m_bRunning(false), m_bCancelled(false) {}
126
127 ~Impl() override {
128 Cancel();
129 }
130
131 void DispatchProcess(const ProcessInfo& proc) {
132 ProcessEventArgs args(proc);
133 ProcessDiscovered.Invoke(this, args);
134 }
135
136 void DispatchBatch(const Collections::Generic::List<ProcessInfo>& lstBatch) {
137 ProcessBatchEventArgs args(lstBatch);
138 BatchReady.Invoke(this, args);
139 }
140
141 void DispatchUpdated(const ProcessInfo& proc) {
142 ProcessEventArgs args(proc);
143 ProcessUpdated.Invoke(this, args);
144 }
145
146 void DispatchCompleted() {
147 Completed.Invoke(this, EventArgs::Empty());
148 }
149
150 void DispatchError(const Exception& ex) {
151 ProcessStreamErrorEventArgs args(ex.What());
152 Error.Invoke(this, args);
153 }
154
155 void RunTier1(std::vector<ProcessInfo>& vecProcs) {
156 Collections::Generic::List<ProcessInfo> batch;
157 int iBatchLimit = (m_options.iBatchSize > 0) ? m_options.iBatchSize : 25;
158 for (size_t i = 0; i < vecProcs.size(); ++i) {
159 if (m_bCancelled.load()) break;
160 DispatchProcess(vecProcs[i]);
161 batch.Add(vecProcs[i]);
162 if (batch.GetCount() >= iBatchLimit) {
163 DispatchBatch(batch);
164 batch.Clear();
165 if (m_options.iBatchIntervalMs > 0) Threading::Thread::Sleep(m_options.iBatchIntervalMs);
166 }
167 }
168 if (batch.GetCount() > 0 && !m_bCancelled.load()) DispatchBatch(batch);
169 }
170
171 void RunTier2(std::vector<ProcessInfo>& vecProcs) {
172 for (size_t i = 0; i < vecProcs.size(); ++i) {
173 if (m_bCancelled.load()) break;
174 DeepEnrichProc(vecProcs[i], m_options.bIncludeNetworkInfo);
175 DispatchUpdated(vecProcs[i]);
176 }
177 }
178
179 void ExecuteStream() {
181 try {
182 std::vector<ProcessInfo> vecProcs;
183 CollectTier1Processes(vecProcs, m_options.iSessionId);
184 RunTier1(vecProcs);
185 if (!m_bCancelled.load() && m_options.eDetailLevel != ProcessMetricsDetail::FastDiscoveryOnly) {
186 RunTier2(vecProcs);
187 }
188 m_bRunning.store(false);
189 if (!m_bCancelled.load()) DispatchCompleted();
190 } catch (const Exception& ex) {
191 m_bRunning.store(false);
192 DispatchError(ex);
193 }
194 }
195
196 void Start(const SmartPointer<Impl>& spSelf) {
198 if (m_options.iBatchSize < 0 || m_options.iBatchIntervalMs < 0) {
199 throw ArgumentException("ProcessStreamOptions batch parameters cannot be negative.");
200 }
201 if (m_bRunning.exchange(true)) throw InvalidOperationException("ProcessStreamer is already running.");
202
204 m_bCancelled.store(false);
205 m_spWorkerThread = SmartPointer<Threading::Thread>::NewShared([spSelf]() {
206 if (spSelf) spSelf->ExecuteStream();
207 });
208 m_spWorkerThread->Start();
209 }
210
211 void Cancel() {
213 m_bCancelled.store(true);
214 if (m_spWorkerThread && m_spWorkerThread->IsAlive()) {
215 if (Threading::Thread::GetCurrentThreadId() != m_spWorkerThread->GetCurrentThreadId()) {
216 m_spWorkerThread->Join();
217 }
218 }
219 m_bRunning.store(false);
220 }
221 };
222
224 : m_pImpl(SmartPointer<Impl>::NewShared(options)),
226 BatchReady(m_pImpl->BatchReady),
228 Completed(m_pImpl->Completed),
229 Error(m_pImpl->Error) {}
230
232
234 if (!m_pImpl) throw InvalidOperationException("ProcessStreamer implementation is null.");
235 m_pImpl->Start(m_pImpl);
236 }
237
239 if (m_pImpl) m_pImpl->Cancel();
240 }
241
243 return m_pImpl ? m_pImpl->m_bRunning.load() : false;
244 }
245
247 return m_pImpl ? m_pImpl->m_options : ProcessStreamOptions();
248 }
249
250 }
251 }
252}
Defines the exception thrown when an invalid argument is provided to a method.
Defines the exception thrown when a null reference is passed to a method that does not accept it.
Provides a re-entrant mutual exclusion primitive for thread synchronization.
Defines the exception thrown when a method call is invalid for the object's current state.
Provides an RAII-style scoped lock wrapper around synchronization primitives.
Serves as the base class for system exceptions across the library.
Creates and controls a thread, sets its priority, and gets its status mirroring .NET System....
ArgumentException(const String &sMessage)
Initializes a new instance of the ArgumentException class with a specified error message.
EventHandler< ProcessBatchEventArgs > & BatchReady
ProcessStreamOptions GetOptions() const
Gets the configuration options.
void Cancel()
Cancels background streaming.
EventHandler< ProcessEventArgs > & ProcessUpdated
void Start()
Starts background process streaming.
~ProcessStreamer() override
Destructor stopping background worker thread.
EventHandler< ProcessStreamErrorEventArgs > & Error
bool IsRunning() const
Gets whether streaming is active.
ProcessStreamer(const ProcessStreamOptions &options=ProcessStreamOptions())
Initializes a new instance of the ProcessStreamer class with options.
EventHandler< ProcessEventArgs > & ProcessDiscovered
static void EnrichProcessInfo(ProcessInfo &proc, bool bIncludeNetwork=true)
Enriches an existing ProcessInfo instance with CPU, memory, disk, and network telemetry.
static const EventArgs & Empty()
Provides a value to use with events that do not have event data.
Definition EventArgs.cpp:11
Represents the method that will handle an event when the event provides data.
InvalidOperationException(const String &sMessage)
Initializes a new instance of the InvalidOperationException class with a specified error message.
Supports all classes in the DotNetDupe class hierarchy.
Definition Object.h:18
A unified smart pointer that supports both unique and shared ownership semantics.
static SmartPointer< T > NewShared()
Creates a Shared SmartPointer, default constructing T.
String()
Initializes a new instance of the String class to an empty string.
Definition String.cpp:64
SystemException()
Initializes a new instance of the SystemException class with a default message.
Definition Exception.cpp:48
Provides a re-entrant mutual exclusion primitive for thread synchronization.
static int GetCurrentThreadId()
Returns an integer identifier for the current managed thread.
Definition Thread.cpp:148
static void Sleep(int millisecondsTimeout)
Suspends the current thread for the specified number of milliseconds.
Definition Thread.cpp:120
static void FastPopulateProc(PROCESSENTRY32W *pe32, ProcessInfo &proc)
Populate fast tier-1 process metadata on Windows.
@ FastDiscoveryOnly
Minimal discovery retrieving PID, name, and paths without deep inspection.
static void CollectTier1Processes(std::vector< ProcessInfo > &vecProcs, int iSessionId)
Collect process snapshot using Toolhelp32 on Windows.
static void DeepEnrichProc(ProcessInfo &proc, bool bIncludeNetwork)
long long lPhysicalMemoryBytes
Working set size (physical RAM resident) for the target process.
long long lPrivateBytes
Private bytes allocated by the target process (-1 if unavailable).
Comprehensive telemetry snapshot for an operating system process.
String sName
Process executable name without path.
int iProcessId
Operating system process identifier (PID).
int iSessionId
Terminal Services session ID.
String sPath
Full filesystem path to the executable image.
MemoryInfo memory
Memory allocation statistics.
Configuration options controlling the execution of a ProcessStreamer.
ProcessStreamOptions()
Default constructor configuring progressive telemetry defaults.