Stroika Library 3.0d23x
 
Loading...
Searching...
No Matches
Frameworks/SystemPerformance/Instruments/Process.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#if qStroika_Foundation_Common_Platform_Linux
7#include <netinet/tcp.h>
8#include <sys/sysinfo.h>
9#elif qStroika_Foundation_Common_Platform_Windows
10#include <Windows.h>
11
12#include <Wdbgexts.h>
13#include <psapi.h>
14#endif
15
16#include <filesystem>
17
19#include "Stroika/Foundation/Characters/String2Int.h"
23#include "Stroika/Foundation/Containers/Mapping.h"
29#include "Stroika/Foundation/Execution/Exceptions.h"
30#include "Stroika/Foundation/Execution/Module.h"
31#include "Stroika/Foundation/Execution/ProcessRunner.h"
39#include "Stroika/Foundation/Streams/MemoryStream.h"
40#include "Stroika/Foundation/Streams/iostream/FStreamSupport.h"
42
43#if qStroika_Foundation_Common_Platform_POSIX
44#include "Stroika/Foundation/Execution/Platform/POSIX/Users.h"
45#elif qStroika_Foundation_Common_Platform_Windows
46#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
47#include "Stroika/Foundation/Execution/Platform/Windows/Users.h"
48#endif
49
51
52#include "Process.h"
53
54// Comment this in to turn on aggressive noisy DbgTrace in this module
55// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
56
57using namespace Stroika::Foundation;
61using namespace Stroika::Foundation::Execution;
62using namespace Stroika::Foundation::Memory;
63using namespace Stroika::Foundation::Streams;
64
65using namespace Stroika::Frameworks;
66using namespace Stroika::Frameworks::SystemPerformance;
67using namespace Stroika::Frameworks::SystemPerformance::Instruments;
68
69using std::byte;
70
73
74using Instruments::Process::CachePolicy;
76using Instruments::Process::MemorySizeType;
77using Instruments::Process::Options;
80
81// --LGP 2016-03-11
82// Sadly - though ALMOST working - not quite.
83// Produces bogus PIDs, and misses many, and thread counts sometimes right, and sometimes wrong (maybe due to WOW64?)
84// Anyhow - disable til working reliably...
85// Or permanently. But keep around a bit, as lots of good stuff there and gives one quick copy to get out alot of data we want,
86// avoiding other calls
87#define qUseWinInternalSupport_ 0
88#ifndef qUseWinInternalSupport_
89#define qUseWinInternalSupport_ qStroika_Foundation_Common_Platform_Windows
90#endif
91
92// This appears to work, but I fear (not tested) its not super performant - performance not tested -- LGP 2016-03-11
93//#define qUseCreateToolhelp32SnapshotToCountThreads 0
94#ifndef qUseCreateToolhelp32SnapshotToCountThreads
95#define qUseCreateToolhelp32SnapshotToCountThreads qStroika_Foundation_Common_Platform_Windows
96#endif
97
98// Still maybe needed for thread count -- but check with ifdefs
99#define qUseWMICollectionSupport_ 0
100#ifndef qUseWMICollectionSupport_
101#define qUseWMICollectionSupport_ \
102 qStroika_Foundation_Common_Platform_Windows && (!qUseCreateToolhelp32SnapshotToCountThreads and !qUseWinInternalSupport_)
103#endif
104
105#if qUseWinInternalSupport_
106#if 1
107//avoid redef warnings... maybe not needed -
108#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
109#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
110#else
111#include <ntstatus.h>
112#endif
113#include <Winternl.h>
114#if defined(_MSC_VER)
115#pragma comment(lib, "Ntdll.lib") // Use #pragma comment lib instead of explicit entry in the lib entry of the project file
116#endif
117#endif
118
119#if qUseCreateToolhelp32SnapshotToCountThreads
120#include <tlhelp32.h>
121#if defined(_MSC_VER)
122#pragma comment(lib, "Ntdll.lib") // Use #pragma comment lib instead of explicit entry in the lib entry of the project file
123#endif
124#endif
125
126#if qUseWMICollectionSupport_
128
130#endif
131
132#if defined(_MSC_VER)
133#pragma comment(lib, "psapi.lib") // Use #pragma comment lib instead of explicit entry in the lib entry of the project file
134#endif
135
136#if qStroika_Foundation_Common_Platform_Windows
137namespace {
138 struct SetPrivilegeInContext_ {
139 enum IgnoreError {
140 eIgnoreError
141 };
142 HANDLE fToken_{INVALID_HANDLE_VALUE};
143 SDKString fPrivilege_;
144 SetPrivilegeInContext_ (LPCTSTR privilege)
145 : fPrivilege_{privilege}
146 {
147 try {
148 setupToken_ ();
149 Execution::Platform::Windows::ThrowIfZeroGetLastError (SetPrivilege_ (fToken_, fPrivilege_.c_str (), true));
150 }
151 catch (...) {
152 if (fToken_ != INVALID_HANDLE_VALUE) {
153 ::CloseHandle (fToken_); // no nee dto clear fToken_ cuz never fully constructed
154 }
155 ReThrow ();
156 }
157 }
158 SetPrivilegeInContext_ (LPCTSTR privilege, IgnoreError)
159 : fPrivilege_{privilege}
160 {
161 bool failed{false};
162 try {
163 setupToken_ ();
164 if (not SetPrivilege_ (fToken_, fPrivilege_.c_str (), true)) {
165 DbgTrace ("Failed to set privilege: error#: {}"_f, ::GetLastError ()); // avoid through so we don't pollute log with throw/catch stuff
166 failed = true; // IgnoreError
167 }
168 }
169 catch (...) {
170 failed = true; // IgnoreError
171 }
172 if (failed) {
173 if (fToken_ != INVALID_HANDLE_VALUE) {
174 ::CloseHandle (fToken_);
175 fToken_ = INVALID_HANDLE_VALUE; // do not double closed in DTOR
176 }
177 }
178 }
179 SetPrivilegeInContext_ (const SetPrivilegeInContext_&) = delete;
180 SetPrivilegeInContext_& operator= (const SetPrivilegeInContext_&) = delete;
181 ~SetPrivilegeInContext_ ()
182 {
183 if (fToken_ != INVALID_HANDLE_VALUE) {
184 SetPrivilege_ (fToken_, fPrivilege_.c_str (), false);
185 Verify (::CloseHandle (fToken_));
186 }
187 }
188
189 private:
190 void setupToken_ ()
191 {
192 if (not::OpenThreadToken (::GetCurrentThread (), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, &fToken_)) {
193 if (::GetLastError () == ERROR_NO_TOKEN) {
194 Execution::Platform::Windows::ThrowIfZeroGetLastError (::ImpersonateSelf (SecurityImpersonation));
196 ::OpenThreadToken (::GetCurrentThread (), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &fToken_));
197 }
198 else {
199 ThrowSystemErrNo ();
200 }
201 }
202 }
203 bool SetPrivilege_ (HANDLE hToken, LPCTSTR privilege, bool bEnablePrivilege) noexcept
204 {
205 LUID luid;
206 if (::LookupPrivilegeValue (nullptr, privilege, &luid) == 0) {
207 return false;
208 }
209
210 //
211 // first pass. get current privilege setting
212 //
213 TOKEN_PRIVILEGES tp;
214 tp.PrivilegeCount = 1;
215 tp.Privileges[0].Luid = luid;
216 tp.Privileges[0].Attributes = 0;
217
218 TOKEN_PRIVILEGES tpPrevious;
219 DWORD cbPrevious = sizeof (tpPrevious);
220 ::AdjustTokenPrivileges (hToken, false, &tp, sizeof (tp), &tpPrevious, &cbPrevious);
221 if (::GetLastError () != ERROR_SUCCESS) {
222 // weird but docs for AdjustTokenPrivileges unclear if you can check for failure with return value - or rahter if not updating all privs
223 // counts as failure...
224 return false;
225 }
226
227 //
228 // second pass. set privilege based on previous setting
229 //
230 tpPrevious.PrivilegeCount = 1;
231 tpPrevious.Privileges[0].Luid = luid;
232
233 if (bEnablePrivilege) {
234 tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
235 }
236 else {
237 tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED & tpPrevious.Privileges[0].Attributes);
238 }
239 ::AdjustTokenPrivileges (hToken, false, &tpPrevious, cbPrevious, nullptr, nullptr);
240 if (::GetLastError () != ERROR_SUCCESS) {
241 // weird but docs for AdjustTokenPrivileges unclear if you can check for failure with return value - or rahter if not updating all privs
242 // counts as failure...
243 return false;
244 }
245 return true;
246 }
247 };
248}
249#endif
250
251#if qUseCreateToolhelp32SnapshotToCountThreads
252namespace {
253 class ThreadCounter_ {
254 private:
255 MultiSet<pid_t> fThreads_;
256
257 public:
258 ThreadCounter_ ()
259 {
260 HANDLE hThreadSnap = ::CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0);
261 if (hThreadSnap == INVALID_HANDLE_VALUE) {
262 DbgTrace (L"CreateToolhelp32Snapshot failed: {}"_f, ::GetLastError ());
263 return;
264 }
265 [[maybe_unused]] auto&& cleanup = Finally ([hThreadSnap] () noexcept { ::CloseHandle (hThreadSnap); });
266
267 // Fill in the size of the structure before using it.
268 THREADENTRY32 te32{};
269 te32.dwSize = sizeof (THREADENTRY32);
270
271 // Retrieve information about the first thread, and exit if unsuccessful
272 if (not::Thread32First (hThreadSnap, &te32)) {
273 DbgTrace (L"CreateToolhelp32Snapshot failed: {}"_f, ::GetLastError ());
274 return;
275 }
276 // Now walk the thread list of the system,
277 do {
278 fThreads_.Add (te32.th32OwnerProcessID);
279 } while (::Thread32Next (hThreadSnap, &te32));
280 }
281
282 public:
283 optional<unsigned int> CountThreads (pid_t pid) const
284 {
285 return fThreads_.OccurrencesOf (pid);
286 }
287 };
288}
289#endif
290
291/*
292 ********************************************************************************
293 ****************** Instruments::Process::ProcessType ***************************
294 ********************************************************************************
295 */
300
301namespace {
302 struct ModuleCommonContext_ : SystemPerformance::Support::Context {
303 // skip reporting static (known at process start) data on subsequent reports
304 // only used if fCachePolicy == CachePolicy::eOmitUnchangedValues
305 Set<pid_t> fStaticSuppressedAgain;
306 };
307 template <typename CONTEXT>
309}
310
311#if qStroika_Foundation_Common_Platform_Linux
312namespace {
313 /*
314 * Missing items we don't currently capture:
315 * > fWorkingSetSize
316 *
317 * Fragile or broken:
318 * > fPrivateBytes doesn't work on RedHat5 - must use /proc/PID/map (see http://stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps)
319 */
320 struct PerfStats_ {
321 TimePointSeconds fCapturedAt;
322 optional<DurationSeconds> fTotalCPUTimeEverUsed;
323 optional<double> fCombinedIOReadBytes;
324 optional<double> fCombinedIOWriteBytes;
325 };
326 struct _Context : ModuleCommonContext_ {
328 };
329
330 struct InstrumentRep_Linux_ : InstrumentRepBase_<_Context> {
331
332 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
333
334 ProcessMapType _InternalCapture ()
335 {
336 ProcessMapType result{};
337 if (_fOptions.fAllowUse_ProcFS) {
338 result = ExtractFromProcFS_ ();
339 }
340 else if (_fOptions.fAllowUse_PS) {
341 result = capture_using_ps_ ();
342 }
343 return result;
344 }
345
346 private:
347 // One character from the string "RSDZTW" where R is running,
348 // S is sleeping in an interruptible wait, D is waiting in uninterruptible disk sleep,
349 // Z is zombie, T is traced or stopped (on a signal), and W is paging.
350 static optional<ProcessType::RunStatus> cvtStatusCharToStatus_ (char state)
351 {
352 switch (state) {
353 case 'R':
354 return ProcessType::RunStatus::eRunning;
355 case 'S':
356 return ProcessType::RunStatus::eSleeping;
357 case 'D':
358 return ProcessType::RunStatus::eWaitingOnDisk;
359 case 'Z':
360 return ProcessType::RunStatus::eZombie;
361 case 'T':
362 return ProcessType::RunStatus::eSuspended;
363 case 'W':
364 return ProcessType::RunStatus::eWaitingOnPaging;
365 }
366 return nullopt;
367 }
368
369 ProcessMapType ExtractFromProcFS_ ()
370 {
371 //
372 /// Most status - like time - come from http://linux.die.net/man/5/proc
373 ///proc/[pid]/stat
374 // Status information about the process. This is used by ps(1). It is defined in /usr/src/linux/fs/proc/array.c.
375 //
376 static const filesystem::path kCWDFilename_{"cwd"sv};
377 static const filesystem::path kEXEFilename_{"exe"sv};
378 static const filesystem::path kEnvironFilename_{"environ"sv};
379 static const filesystem::path kRootFilename_{"root"sv};
380 static const filesystem::path kCmdLineFilename_{"cmdline"sv};
381 static const filesystem::path kStatFilename_{"stat"sv};
382 static const filesystem::path kStatusFilename_{"status"sv};
383 static const filesystem::path kIOFilename_{"io"sv};
384 static const filesystem::path kNetTCPFilename_{"net/tcp"sv};
385
386 ProcessMapType results;
387
388 Mapping<pid_t, PerfStats_> newContextStats;
389
390 /*
391 * NOTE: the Linux procfs allows access to PROCESS info or THREADINFO (what linux calls lightweight processes).
392 *
393 * You can tell if a process is a real process id or thread id, by seeing if the tgid (sometimes tid) or task
394 * group id) is the same as the PID. If yes, its a process. If no, its a thread in the tgid process.
395 *
396 * However, iterating over the files in the /proc filesystem APPEARS to only produce real processes, and to skip
397 * the lightweight process thread ids, so we don't need to specially filter them out. However, I've not found
398 * this claim documented anywhere, so beware...
399 */
400 for (const auto& p :
401 filesystem::directory_iterator{"/proc", filesystem::directory_options{filesystem::directory_options::skip_permission_denied}}) {
402 const filesystem::path& dir = p.path (); // full-path
403 String dirFileNameString{dir.filename ()};
404 bool isAllNumeric = not dirFileNameString.Find ([] (Character c) -> bool { return not c.IsDigit (); });
405#if USE_NOISY_TRACE_IN_THIS_MODULE_
406 Debug::TraceContextBumper ctx{"...SystemPerformance::Instruments::Process::{}::ExtractFromProcFS_::reading /proc files"};
407 DbgTrace (L"isAllNumeric=%d, dir= %s, is_dir=%d", isAllNumeric, ToString (dir).c_str (), p.is_directory ());
408#endif
409 if (isAllNumeric and p.is_directory ()) {
410 pid_t pid{String2Int<pid_t> (dirFileNameString)};
411 TimePointSeconds now{Time::GetTickCount ()};
412#if USE_NOISY_TRACE_IN_THIS_MODULE_
413 DbgTrace ("reading for pid = %d", pid);
414#endif
415 if (_fOptions.fRestrictToPIDs) {
416 if (not _fOptions.fRestrictToPIDs->Contains (pid)) {
417 continue;
418 }
419 }
420 if (_fOptions.fOmitPIDs) {
421 if (_fOptions.fOmitPIDs->Contains (pid)) {
422 continue;
423 }
424 }
425
426 bool grabStaticData = _fOptions.fCachePolicy == CachePolicy::eIncludeAllRequestedValues or
427 not _fContext.cget ().cref ()->fStaticSuppressedAgain.Contains (pid);
428
429 ProcessType processDetails;
430
431 if (grabStaticData) {
432 processDetails.fEXEPath = OptionallyResolveShortcut_ (dir / kEXEFilename_);
433 if (processDetails.fEXEPath and String{*processDetails.fEXEPath}.EndsWith (" (deleted)"sv)) {
434 processDetails.fEXEPath = String{*processDetails.fEXEPath}.SubString (0, -10).As<filesystem::path> ();
435 }
436
437 if (_fOptions.fProcessNameReadPolicy == Options::eAlways or
438 (_fOptions.fProcessNameReadPolicy == Options::eOnlyIfEXENotRead and not processDetails.fEXEPath.has_value ())) {
439 processDetails.fProcessName =
440 OptionallyReadIfFileExists_<String> (dir / "comm"sv, [] (const Streams::InputStream::Ptr<byte>& in) {
441 return BinaryToText::Reader::New (in).ReadAll ().Trim ();
442 });
443 }
444
445 /*
446 * \note In POSIX/fAllowUse_ProcFS mode Fix EXEPath/commandline for 'kernel' processes.
447 * http://unix.stackexchange.com/questions/191594/how-can-i-determine-if-a-process-is-a-system-process
448 * Can use value from reading EXE or is parent process id is 0, or parent process id is kernel process
449 * (means kernel process/thread).
450 *
451 * Improve this logic below - checking for exact error code from readlink..as they say in that article.
452 */
453 processDetails.fKernelProcess = not processDetails.fEXEPath.has_value ();
454 // Note - many kernel processes have commandline, so don't filter here based on that
455 if (_fOptions.fCaptureCommandLine and _fOptions.fCaptureCommandLine (pid, NullCoalesce (processDetails.fEXEPath))) {
456 processDetails.fCommandLine = ReadCmdLineString_ (dir / kCmdLineFilename_);
457 }
458 // kernel process cannot chroot (as far as I know) --LGP 2015-05-21
459 if (_fOptions.fCaptureRoot and processDetails.fKernelProcess == false) {
460 processDetails.fRoot = OptionallyResolveShortcut_ (dir / kRootFilename_);
461 }
462 // kernel process cannot have environment variables (as far as I know) --LGP 2015-05-21
463 if (_fOptions.fCaptureEnvironmentVariables and processDetails.fKernelProcess == false) {
464 processDetails.fEnvironmentVariables = OptionallyReadFileStringsMap_ (dir / kEnvironFilename_);
465 }
466 }
467
468 // kernel process cannot have current directory (as far as I know) --LGP 2015-05-21
469 if (_fOptions.fCaptureCurrentWorkingDirectory and processDetails.fKernelProcess == false) {
470 processDetails.fCurrentWorkingDirectory = OptionallyResolveShortcut_ (dir / kCWDFilename_);
471 }
472
473 static const double kClockTick_ = ::sysconf (_SC_CLK_TCK);
474
475 try {
476 StatFileInfo_ stats = ReadStatFile_ (dir / kStatFilename_);
477
478 processDetails.fRunStatus = cvtStatusCharToStatus_ (stats.state);
479
480 static const size_t kPageSizeInBytes_ = ::sysconf (_SC_PAGESIZE);
481
482 if (grabStaticData) {
483 static const time_t kUNIXEpochTimeOfBoot_ = [] () {
484 struct sysinfo info;
485 ::sysinfo (&info);
486 return ::time (NULL) - info.uptime;
487 }();
488 //starttime %llu (was %lu before Linux 2.6)
489 //(22) The time the process started after system boot. In kernels before Linux 2.6,
490 // this value was expressed in jiffies. Since Linux 2.6,
491 // the value is expressed in clock ticks (divide by sysconf(_SC_CLK_TCK)).
492 processDetails.fProcessStartedAt = DateTime{static_cast<time_t> (stats.start_time / kClockTick_ + kUNIXEpochTimeOfBoot_)};
493 }
494
495 processDetails.fTotalCPUTimeEverUsed = DurationSeconds{(double (stats.utime) + double (stats.stime)) / kClockTick_};
496 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
497 auto diffTime = now - p->fCapturedAt;
498 if (p->fTotalCPUTimeEverUsed and (diffTime >= _fOptions.fMinimumAveragingInterval)) {
499 processDetails.fAverageCPUTimeUsed =
500 DurationSeconds{(*processDetails.fTotalCPUTimeEverUsed - *p->fTotalCPUTimeEverUsed) / diffTime.count ()};
501 }
502 }
503 if (stats.nlwp != 0) {
504 processDetails.fThreadCount = stats.nlwp;
505 }
506 if (grabStaticData) {
507 processDetails.fParentProcessID = stats.ppid;
508 }
509
510 processDetails.fPrivateVirtualMemorySize = stats.vsize;
511
512 // Don't know how to easily compute, but I'm sure not hard (add in shared memory of various sorts at worst) - or look at memory map, but
513 // very low priority (like smaps file below)
514 //processDetails.fTotalVirtualMemorySize = stats.vsize;
515
516 processDetails.fResidentMemorySize = stats.rss * kPageSizeInBytes_;
517
518 processDetails.fPageFaultCount = stats.minflt + stats.majflt;
519 processDetails.fMajorPageFaultCount = stats.majflt;
520
521 /*
522 * Probably best to compute fPrivateBytes from:
523 * grep Private /proc/1912/smaps
524 */
525 processDetails.fPrivateBytes = ReadPrivateBytes_ (dir / "smaps");
526
527#if USE_NOISY_TRACE_IN_THIS_MODULE_
528 DbgTrace ("loaded processDetails.fProcessStartedAt={} wuit stats.start_time = {}"_f,
529 processDetails.fProcessStartedAt, stats.start_time);
530 DbgTrace ("loaded processDetails.fTotalCPUTimeEverUsed={} wuit stats.utime = {}, stats.stime = {}"_f,
531 processDetails.fTotalCPUTimeEverUsed->count (), stats.utime, stats.stime);
532#endif
533 }
534 catch (...) {
535 }
536
537 if (_fOptions.fCaptureTCPStatistics) {
538 IgnoreExceptionsForCall (processDetails.fTCPStats = ReadTCPStats_ (dir / kNetTCPFilename_));
539 }
540
541 if (grabStaticData) {
542 try {
543 if (processDetails.fKernelProcess == true) {
544 // I think these are always running as root -- LGP 2015-05-21
545 processDetails.fUserName = "root"sv;
546 }
547 else {
548 proc_status_data_ stats = Readproc_proc_status_data_ (dir / kStatusFilename_);
549 processDetails.fUserName = Execution::Platform::POSIX::uid_t2UserName (stats.ruid);
550 }
551 }
552 catch (...) {
553 }
554 }
555
556 try {
557 // @todo maybe able to optimize and not check this if processDetails.fKernelProcess == true
558 optional<proc_io_data_> stats = Readproc_io_data_ (dir / kIOFilename_);
559 if (stats.has_value ()) {
560 processDetails.fCombinedIOReadBytes = (*stats).read_bytes;
561 processDetails.fCombinedIOWriteBytes = (*stats).write_bytes;
562 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
563 DurationSeconds diffTime = now - p->fCapturedAt;
564 if (diffTime >= _fOptions.fMinimumAveragingInterval) {
565 if (p->fCombinedIOReadBytes) {
566 processDetails.fCombinedIOReadRate =
567 (*processDetails.fCombinedIOReadBytes - *p->fCombinedIOReadBytes) / diffTime.count ();
568 }
569 if (p->fCombinedIOWriteBytes) {
570 processDetails.fCombinedIOWriteRate =
571 (*processDetails.fCombinedIOWriteBytes - *p->fCombinedIOWriteBytes) / diffTime.count ();
572 }
573 }
574 }
575 }
576 }
577 catch (...) {
578 DbgTrace ("ignored: {}"_f, current_exception ());
579 }
580
581 if (processDetails.fTotalCPUTimeEverUsed or processDetails.fCombinedIOReadBytes or processDetails.fCombinedIOWriteBytes) {
582 newContextStats.Add (pid, PerfStats_{now, processDetails.fTotalCPUTimeEverUsed, processDetails.fCombinedIOReadBytes,
583 processDetails.fCombinedIOWriteBytes});
584 }
585 results.Add (pid, processDetails);
586 }
587 }
588 _NoteCompletedCapture ();
589 _fContext.rwget ().rwref ()->fMap = newContextStats;
590 if (_fOptions.fCachePolicy == CachePolicy::eOmitUnchangedValues) {
591 _fContext.rwget ().rwref ()->fStaticSuppressedAgain = Set<pid_t>{results.Keys ()};
592 }
593 return results;
594 }
595 template <typename T>
596 static optional<T> OptionallyReadIfFileExists_ (const filesystem::path& fullPath,
597 const function<T (const Streams::InputStream::Ptr<byte>&)>& reader)
598 {
599 if (IO::FileSystem::Default ().Access (fullPath)) {
600 IgnoreExceptionsExceptThreadAbortForCall (
601 return reader (IO::FileSystem::FileInputStream::New (fullPath, IO::FileSystem::FileInputStream::eNotSeekable)));
602 }
603 return nullopt;
604 }
605 static Sequence<String> ReadFileStrings_ (const filesystem::path& fullPath)
606 {
607 Sequence<String> results;
608 Streams::InputStream::Ptr<byte> in = IO::FileSystem::FileInputStream::New (fullPath, IO::FileSystem::FileInputStream::eNotSeekable);
609 StringBuilder sb;
610 for (optional<byte> b; (b = in.ReadBlocking ()).has_value ();) {
611 if ((*b) == byte{0}) {
612 results.Append (sb.As<String> ());
613 sb.clear ();
614 }
615 else {
616 sb.Append ((char)(*b)); // for now assume no charset
617 }
618 }
619 return results;
620 }
621 static Mapping<String, String> ReadFileStringsMap_ (const filesystem::path& fullPath)
622 {
624 for (const String& i : ReadFileStrings_ (fullPath)) {
625 auto tokens = i.Tokenize ({'='});
626 if (tokens.size () == 2) {
627 results.Add (tokens[0], tokens[1]);
628 }
629 }
630 return results;
631 }
632 // if fails (cuz not readable) don't throw but return missing, but avoid noisy stroika exception logging
633 static optional<String> ReadCmdLineString_ (const filesystem::path& fullPath2CmdLineFile)
634 {
635 // this reads /proc format files - meaning that a trialing nul-byte is the EOS
636 auto ReadFileString_ = [] (const Streams::InputStream::Ptr<byte>& in) -> String {
637#if USE_NOISY_TRACE_IN_THIS_MODULE_
638 Debug::TraceContextBumper ctx{"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadCmdLineString_"};
639#endif
640 StringBuilder sb;
641 bool lastCharNullRemappedToSpace = false;
642 for (optional<byte> b; (b = in.ReadBlocking ()).has_value ();) {
643 if (*b == byte{0}) {
644 sb.Append (' '); // frequently - especially for kernel processes - we see nul bytes that really SB spaces
645 lastCharNullRemappedToSpace = true;
646 }
647 else {
648 sb.Append ((char)(*b)); // for now assume no charset
649 lastCharNullRemappedToSpace = false;
650 }
651 }
652 if (lastCharNullRemappedToSpace) {
653 Assert (sb.length () > 0 and sb.GetAt (sb.length () - 1) == ' ');
654 return sb.str ().SubString (0, sb.length () - 1);
655 }
656 else {
657 return sb.As<String> ();
658 }
659 };
660 if (IO::FileSystem::Default ().Access (fullPath2CmdLineFile)) {
661 IgnoreExceptionsExceptThreadAbortForCall (return ReadFileString_ (
662 IO::FileSystem::FileInputStream::New (fullPath2CmdLineFile, IO::FileSystem::FileInputStream::eNotSeekable)));
663 }
664 return nullopt;
665 }
666 // if fails (cuz not readable) don't throw but return missing, but avoid noisy stroika exception logging
667 static optional<filesystem::path> OptionallyResolveShortcut_ (const filesystem::path& shortcutPath)
668 {
669 std::error_code ec{};
670 if (filesystem::exists (shortcutPath, ec) and filesystem::is_symlink (shortcutPath, ec)) {
671 auto r = filesystem::read_symlink (shortcutPath, ec);
672 if (not ec) {
673 return r;
674 }
675 }
676 return nullopt;
677 }
678 static optional<Mapping<String, String>> OptionallyReadFileStringsMap_ (const filesystem::path& fullPath)
679 {
680 if (IO::FileSystem::Default ().Access (fullPath)) {
681 IgnoreExceptionsExceptThreadAbortForCall (return ReadFileStringsMap_ (fullPath));
682 }
683 return nullopt;
684 }
685 struct StatFileInfo_ {
686 /*
687 * From http://linux.die.net/man/5/proc, search for '/proc/[pid]/stat'
688 *
689 * (1) pid %d
690 * The process ID.
691 *
692 * (2) comm %s
693 * The filename of the executable, in parentheses. This is visible whether or not the executable is swapped out.
694 *
695 * (3) state %c
696 * One character from the string "RSDZTW" where R is running, S is sleeping in an interruptible wait, D is waiting in uninterruptible disk sleep, Z is zombie, T is traced or stopped (on a signal), and W is paging.
697 *
698 * (4) ppid %d
699 * The PID of the parent.
700 *
701 * (5) pgrp %d
702 * The process group ID of the process.
703 *
704 * (6) session %d
705 * The session ID of the process.
706 *
707 * (7) tty_nr %d
708 * The controlling terminal of the process. (The minor device number is contained in the combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.)
709 *
710 * (8) tpgid %d
711 * The ID of the foreground process group of the controlling terminal of the process.
712 *
713 * (9) flags %u (%lu before Linux 2.6.22)
714 * The kernel flags word of the process. For bit meanings, see the PF_* defines in the Linux kernel source file include/linux/sched.h. Details depend on the kernel version.
715 *
716 * (10) minflt %lu
717 * The number of minor faults the process has made which have not required loading a memory page from disk.
718 *
719 * (11) cminflt %lu
720 * The number of minor faults that the process's waited-for children have made.
721 *
722 * (12) majflt %lu
723 * The number of major faults the process has made which have required loading a memory page from disk.
724 *
725 * (13) cmajflt %lu
726 * The number of major faults that the process's waited-for children have made.
727 *
728 * (14) utime %lu
729 * Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time spent running a virtual CPU, see below), so that applications that are not aware of the guest time field do not lose that time from their calculations.
730 *
731 * (15) stime %lu
732 * Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
733 *
734 * (16) cutime %ld
735 * Amount of time that this process's waited-for children have been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). (See also times(2).) This includes guest time, cguest_time (time spent running a virtual CPU, see below).
736 *
737 * (17) cstime %ld
738 * Amount of time that this process's waited-for children have been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
739 *
740 * (18) priority %ld
741 * (Explanation for Linux 2.6) For processes running a real-time scheduling policy (policy below; see sched_setscheduler(2)), this is the negated scheduling priority, minus one; that is, a number in the range -2 to -100, corresponding to real-time priorities 1 to 99. For processes running under a non-real-time scheduling policy, this is the raw nice value (setpriority(2)) as represented in the kernel. The kernel stores nice values as numbers in the range 0 (high) to 39 (low), corresponding to the user-visible nice range of -20 to 19.
742 * Before Linux 2.6, this was a scaled value based on the scheduler weighting given to this process.
743 *
744 * (19) nice %ld
745 * The nice value (see setpriority(2)), a value in the range 19 (low priority) to -20 (high priority).
746 *
747 * (20) num_threads %ld
748 * Number of threads in this process (since Linux 2.6). Before kernel 2.6, this field was hard coded to 0 as a placeholder for an earlier removed field.
749 *
750 * (21) itrealvalue %ld
751 * The time in jiffies before the next SIGALRM is sent to the process due to an interval timer. Since kernel 2.6.17, this field is no longer maintained, and is hard coded as 0.
752 *
753 * (22) starttime %llu (was %lu before Linux 2.6)
754 * The time the process started after system boot. In kernels before Linux 2.6, this value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks (divide by sysconf(_SC_CLK_TCK)).
755 *
756 * (23) vsize %lu
757 * Virtual memory size in bytes.
758 *
759 * from ps docs:
760 * size in physical pages of the core image of the process. This includes text, data, and stack space. Device mappings are currently excluded;
761 * Empirically thats what this appears to be.
762 *
763 * (24) rss %ld
764 * Resident Set Size: number of pages the process has in real memory. This is just the pages which count toward text, data, or stack space. This does not include pages which have not been demand-loaded in, or which are swapped out.
765 *
766 * (25) rsslim %lu
767 * Current soft limit in bytes on the rss of the process; see the description of RLIMIT_RSS in getrlimit(2).
768 *
769 * (26) startcode %lu
770 * The address above which program text can run.
771 *
772 * (27) endcode %lu
773 * The address below which program text can run.
774 *
775 * (28) startstack %lu
776 * The address of the start (i.e., bottom) of the stack.
777 *
778 * (29) kstkesp %lu
779 * The current value of ESP (stack pointer), as found in the kernel stack page for the process.
780 *
781 * (30) kstkeip %lu
782 * The current EIP (instruction pointer).
783 *
784 * (31) signal %lu
785 * The bitmap of pending signals, displayed as a decimal number. Obsolete, because it does not provide information on real-time signals; use /proc/[pid]/status instead.
786 *
787 * (32) blocked %lu
788 * The bitmap of blocked signals, displayed as a decimal number. Obsolete, because it does not provide information on real-time signals; use /proc/[pid]/status instead.
789 *
790 * (33) sigignore %lu
791 * The bitmap of ignored signals, displayed as a decimal number. Obsolete, because it does not provide information on real-time signals; use /proc/[pid]/status instead.
792 *
793 * (34) sigcatch %lu
794 * The bitmap of caught signals, displayed as a decimal number. Obsolete, because it does not provide information on real-time signals; use /proc/[pid]/status instead.
795 *
796 * (35) wchan %lu
797 * This is the "channel" in which the process is waiting. It is the address of a system call, and can be looked up in a namelist if you need a textual name. (If you have an up-to-date /etc/psdatabase, then try ps -l to see the WCHAN field in action.)
798 *
799 * (36) nswap %lu
800 * Number of pages swapped (not maintained).
801 *
802 * (37) cnswap %lu
803 * Cumulative nswap for child processes (not maintained).
804 *
805 * (38) exit_signal %d (since Linux 2.1.22)
806 * Signal to be sent to parent when we die.
807 *
808 * (39) processor %d (since Linux 2.2.8)
809 * CPU number last executed on.
810 *
811 * (40) rt_priority %u (since Linux 2.5.19; was %lu before Linux 2.6.22)
812 * Real-time scheduling priority, a number in the range 1 to 99 for processes scheduled under a real-time policy, or 0, for non-real-time processes (see sched_setscheduler(2)).
813 *
814 * (41) policy %u (since Linux 2.5.19; was %lu before Linux 2.6.22)
815 * Scheduling policy (see sched_setscheduler(2)). Decode using the SCHED_* constants in linux/sched.h.
816 *
817 * (42) delayacct_blkio_ticks %llu (since Linux 2.6.18)
818 * Aggregated block I/O delays, measured in clock ticks (centiseconds).
819 *
820 * (43) guest_time %lu (since Linux 2.6.24)
821 * Guest time of the process (time spent running a virtual CPU for a guest operating system), measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
822 *
823 * (44) cguest_time %ld (since Linux 2.6.24)
824 * Guest time of the process's children, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
825 */
826 char state; // (3)
827 int ppid; // (4)
828 unsigned long long utime; // (14)
829 unsigned long long stime; // (15)
830 int nlwp; // (20)
831 unsigned long long start_time; // (22)
832 unsigned long long vsize; // (23)
833 unsigned long long rss; // (24)
834 unsigned long minflt;
835 unsigned long majflt;
836 };
837 static StatFileInfo_ ReadStatFile_ (const filesystem::path& fullPath)
838 {
839#if USE_NOISY_TRACE_IN_THIS_MODULE_
840 Debug::TraceContextBumper ctx{L"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadStatFile_", "fullPath={}"_f, fullPath};
841#endif
842 StatFileInfo_ result{};
843 Streams::InputStream::Ptr<byte> in = IO::FileSystem::FileInputStream::New (fullPath, IO::FileSystem::FileInputStream::eNotSeekable);
844 byte data[10 * 1024];
845 size_t nBytes = in.ReadAll (span{data}).size ();
846 Assert (nBytes <= std::size (data));
847#if USE_NOISY_TRACE_IN_THIS_MODULE_
848 DbgTrace ("nBytes read = {}"_f, nBytes);
849#endif
850 if (nBytes == std::size (data)) {
851 --nBytes; // ignore trailing byte so we can nul-terminate
852 }
853 data[nBytes] = byte{0}; // null-terminate so we can treat as c-string
854
855 const char* S = reinterpret_cast<const char*> (data);
856 {
857 ///@TODO - FIX - THIS CODE UNSAFE - CAN CRASH! what if S not nul-terminated!
858 S = ::strchr (S, '(') + 1;
859 Assert (S < reinterpret_cast<const char*> (end (data)));
860#if USE_NOISY_TRACE_IN_THIS_MODULE_
861 DbgTrace ("S = %x", S);
862#endif
863 const char* tmp = ::strrchr (S, ')');
864#if USE_NOISY_TRACE_IN_THIS_MODULE_
865 DbgTrace ("S(tmp) = %x", tmp);
866#endif
867 S = tmp + 2; // skip ") "
868 Assert (S < reinterpret_cast<const char*> (end (data)));
869 }
870
871 // MSVC SILLY WARNING ABOUT USING swscanf_s
872 // (warning doesn't appear to check if we have mismatch between types and format args provided.
873 // --LGP 2015-01-07
874 DISABLE_COMPILER_MSC_WARNING_START (4996)
875 [[maybe_unused]] int ignoredInt{};
876 [[maybe_unused]] long ignoredLong{};
877 [[maybe_unused]] unsigned long ignoredUnsignedLong{};
878 [[maybe_unused]] unsigned long long ignoredUnsignedLongLong{};
879 [[maybe_unused]] unsigned long int ignored_unsigned_long{};
880 [[maybe_unused]] int num =
881 ::sscanf (S,
882
883 // (3 - char state)...
884 "%c "
885
886 // (4 - 'int' - ppid,pgrp,session,tty_nr,tpgid)...
887 "%d %d %d %d %d "
888
889 // (9 unsigned long - flags,minflt,cminflt,majflt,cmajflt - NB: flags now unsigned int not unsigned long but unsigned long fits new and old)...
890 "%lu %lu %lu %lu %lu "
891
892 // (14 - unint but use ulonglong for safety - utime stime)...
893 "%llu %llu "
894
895 // (16 - unint but use ulonglong for safety- cutime cstime - docs say signed int but thats crazy--LGP2015-09-16)...
896 "%llu %llu "
897
898 // (18 long priority, nice)...
899 "%ld %ld "
900
901 // (20 docs say long but thats nuts %ld num_threads)...
902 "%d "
903
904 // (21 %ld - itrealvalue)...
905 "%d "
906
907 // (22 llu - starttime %llu)...
908 "%llu "
909
910 // (23 unsigned long by docs but use ull vsize, rss)...
911 "%llu %llu ",
912
913 // (3 - char state)...
914 &result.state,
915
916 // (4 - 'int' - ppid,pgrp,session,tty_nr,tpgid)...
917 &result.ppid, &ignoredInt, &ignoredInt, &ignoredInt, &ignoredInt,
918
919 // (9 unsigned long - flags,minflt,cminflt,majflt,cmajflt - NB: flags now unsigned int not unsigned long but unsigned long fits new and old)...
920 &ignoredUnsignedLong, &result.minflt, &ignoredUnsignedLong, &result.majflt, &ignoredUnsignedLong,
921
922 // (14 - unint but use ulonglong for safety - utime stime)...
923 &result.utime, &result.stime,
924
925 // (16 - unint but use ulonglong for safety- cutime cstime - docs say signed int but thats crazy--LGP2015-09-16)...
926 &ignoredUnsignedLongLong, &ignoredUnsignedLongLong,
927
928 // (18 long priority, nice)
929 &ignoredLong, &ignoredLong,
930
931 // (20 docs say long but thats nuts %ld num_threads)
932 &result.nlwp,
933
934 // (21 %ld - itrealvalue)
935 &ignoredInt,
936
937 // (22 llu - starttime %llu)...
938 &result.start_time,
939
940 // (23 unsigned long by docs but use ull vsize, rss)...
941 &result.vsize, &result.rss);
942 DISABLE_COMPILER_MSC_WARNING_END (4996) // MSVC SILLY WARNING ABOUT USING swscanf_s
943
944 Assert (num == 22); // if not probably throw away???
945
946#if USE_NOISY_TRACE_IN_THIS_MODULE_
947 DbgTrace ("result.start_time=%lld", result.start_time);
948 DbgTrace ("result.vsize=%ld", result.vsize);
949 DbgTrace ("result.rss=%ld", result.rss);
950 DbgTrace ("result.utime=%lld", result.utime);
951#endif
952
953 return result;
954 }
955 // https://www.kernel.org/doc/Documentation/filesystems/proc.txt
956 // search for 'cat /proc/3828/io'
957 struct proc_io_data_ {
958 uint64_t read_bytes;
959 uint64_t write_bytes;
960 };
961 static optional<proc_io_data_> Readproc_io_data_ (const filesystem::path& fullPath)
962 {
963#if USE_NOISY_TRACE_IN_THIS_MODULE_
964 Debug::TraceContextBumper ctx{"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::Readproc_io_data_", "fullPath={}"_f, fullPath};
965#endif
966 if (not IO::FileSystem::Default ().Access (fullPath)) {
967#if USE_NOISY_TRACE_IN_THIS_MODULE_
968 DbgTrace ("Skipping read cuz no access");
969#endif
970 return nullopt;
971 }
972 proc_io_data_ result{};
973 ifstream r{fullPath, ios_base::binary | ios_base::in}; // no excption on failed open- just returns false immediately
974 while (r) {
975 char buf[1024];
976 buf[0] = '\0';
977 if (r.getline (buf, sizeof (buf))) {
978 constexpr char kReadLbl_[] = "read_bytes:";
979 constexpr char kWriteLbl_[] = "write_bytes:";
980 if (::strncmp (buf, kReadLbl_, ::strlen (kReadLbl_)) == 0) {
981 result.read_bytes = CString::String2Int<decltype (result.read_bytes)> (buf + ::strlen (kReadLbl_));
982 }
983 else if (::strncmp (buf, kWriteLbl_, ::strlen (kWriteLbl_)) == 0) {
984 result.write_bytes = CString::String2Int<decltype (result.write_bytes)> (buf + ::strlen (kWriteLbl_));
985 }
986 }
987 }
988 return result;
989 }
990 static optional<ProcessType::TCPStats> ReadTCPStats_ (const filesystem::path& fullPath)
991 {
992 /**
993 * root@q7imx6:/opt/BLKQCL# cat /proc/3431/net/tcp
994 * sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
995 * 0: 00000000:1F90 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12925 1 e4bc8de0 300 0 0 2 -1
996 * 1: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 6059 1 e466d720 300 0 0 2 -1
997 */
998#if USE_NOISY_TRACE_IN_THIS_MODULE_
999 Debug::TraceContextBumper ctx{"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadTCPStats_", "fullPath={}"_f, fullPath};
1000#endif
1001
1002 if (not IO::FileSystem::Default ().Access (fullPath)) {
1003#if USE_NOISY_TRACE_IN_THIS_MODULE_
1004 DbgTrace (L"Skipping read cuz no access");
1005#endif
1006 return nullopt;
1007 }
1008 ProcessType::TCPStats stats;
1009 bool didSkip = false;
1010 for (const String& i :
1011 BinaryToText::Reader::New (IO::FileSystem::FileInputStream::New (fullPath, IO::FileSystem::FileInputStream::eNotSeekable)).ReadLines ()) { // @todo redo using .Skip(1) but crashes --LGP 2016-05-17
1012 if (not didSkip) {
1013 didSkip = true;
1014 continue;
1015 }
1016 Sequence<String> splits = i.Tokenize ({' '});
1017 if (splits.size () >= 4) {
1018 int st = HexString2Int (splits[3]);
1019 /*
1020 * The enum in ./include/net/tcp_states.h is hopefully (apparently) the same as that in netinet/tcp.h
1021 */
1022 if (st == TCP_ESTABLISHED) {
1023 ++stats.fEstablished;
1024 }
1025 else if (st == TCP_LISTEN) {
1026 ++stats.fListening;
1027 }
1028 else {
1029 ++stats.fOther;
1030 }
1031 }
1032 }
1033 return stats;
1034 }
1035 static optional<MemorySizeType> ReadPrivateBytes_ (const filesystem::path& fullPath)
1036 {
1037#if USE_NOISY_TRACE_IN_THIS_MODULE_
1038 Debug::TraceContextBumper ctx{"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadPrivateBytes_", "fullPath={}"_f, fullPath};
1039#endif
1040
1041 if (not IO::FileSystem::Default ().Access (fullPath)) {
1042#if USE_NOISY_TRACE_IN_THIS_MODULE_
1043 DbgTrace (L"Skipping read cuz no access");
1044#endif
1045 return nullopt;
1046 }
1047 MemorySizeType result{};
1048 ifstream r;
1049 r.open (fullPath, ios_base::in); // no exception on failed open- just returns false immediately
1050 while (r) {
1051 char buf[1024];
1052 buf[0] = '\0';
1053 if (r.getline (buf, sizeof (buf))) {
1054 // I think always in KB
1055 constexpr char kPrivate1Lbl_[] = "Private_Clean:";
1056 constexpr char kPrivate2Lbl_[] = "Private_Dirty:";
1057 // @todo - SHOULD pay attention to the labelm after the number. It may not always be kB? BUt not sure what it can be
1058 if (::strncmp (buf, kPrivate1Lbl_, ::strlen (kPrivate1Lbl_)) == 0) {
1059 result += CString::String2Int<MemorySizeType> (buf + strlen (kPrivate1Lbl_)) * 1024;
1060 }
1061 else if (::strncmp (buf, kPrivate2Lbl_, ::strlen (kPrivate2Lbl_)) == 0) {
1062 result += CString::String2Int<MemorySizeType> (buf + ::strlen (kPrivate2Lbl_)) * 1024;
1063 }
1064 }
1065 }
1066 return result;
1067 }
1068 // https://www.kernel.org/doc/Documentation/filesystems/proc.txt
1069 // search for 'cat /proc/PID/status'
1070 struct proc_status_data_ {
1071 uid_t ruid;
1072 };
1073 static proc_status_data_ Readproc_proc_status_data_ (const filesystem::path& fullPath)
1074 {
1075#if USE_NOISY_TRACE_IN_THIS_MODULE_
1076 Debug::TraceContextBumper ctx{"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::Readproc_proc_status_data_",
1077 "fullPath={}"_f, fullPath};
1078#endif
1079 proc_status_data_ result{};
1080 ifstream r;
1081 r.open (fullPath, ios_base::in); // no exception on failed open- just returns false immediately
1082 while (r) {
1083 char buf[1024];
1084 buf[0] = '\0';
1085 if (r.getline (buf, sizeof (buf))) {
1086 constexpr char kUidLbl[] = "Uid:";
1087 if (::strncmp (buf, kUidLbl, ::strlen (kUidLbl)) == 0) {
1088 Assert (::strlen (buf) >= strlen (kUidLbl)); // because istream::getline returns valid C=string, and strncmp assures first 4 bytes match so must be NUL byte later
1089 char* S = buf + ::strlen (kUidLbl);
1090 Assert (S < std::end (buf));
1091 int ruid = ::strtol (S, &S, 10);
1092 Assert (S < std::end (buf));
1093 [[maybe_unused]] int euid = ::strtol (S, &S, 10);
1094 Assert (S < std::end (buf));
1095 [[maybe_unused]] int suid = ::strtol (S, &S, 10);
1096 Assert (S < std::end (buf));
1097 [[maybe_unused]] int fuid = ::strtol (S, &S, 10);
1098 Assert (S < std::end (buf));
1099 result.ruid = ruid;
1100 }
1101 }
1102 }
1103 return result;
1104 }
1105 // consider using this as a backup if /procfs/ not present...
1106 nonvirtual ProcessMapType capture_using_ps_ ()
1107 {
1108 Debug::TraceContextBumper ctx{"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::capture_using_ps_"};
1109 ProcessMapType result;
1110 /*
1111 * Thought about STIME but too hard to parse???
1112 *
1113 * EXAMPLE OUTPUT:
1114 * $ ps -e -o "pid,ppid,s,time,rss,vsz,user,nlwp,cmd" | head
1115 * PID PPID S TIME RSS VSZ USER NLWP CMD
1116 * 1 0 S 00:00:01 3696 116896 root 1 /lib/systemd/systemd --system --deserialize 18
1117 * 2 0 S 00:00:00 0 0 root 1 [kthreadd]
1118 * 3 2 S 00:00:00 0 0 root 1 [ksoftirqd/0]
1119 * 5 2 S 00:00:00 0 0 root 1 [kworker/0:0H]
1120 * 7 2 S 00:00:08 0 0 root 1 [rcu_sched]
1121 * 8 2 S 00:00:00 0 0 root 1 [rcu_bh]
1122 * 9 2 S 00:00:06 0 0 root 1 [rcuos/0]
1123 * 10 2 S 00:00:00 0 0 root 1 [rcuob/0]
1124 * 11 2 S 00:00:00 0 0 root 1 [migration/0]
1125 */
1126 constexpr size_t kVSZ_Idx_{5};
1127 constexpr size_t kUser_Idx_{6};
1128 constexpr size_t kThreadCnt_Idx_{7};
1129 constexpr size_t kColCountIncludingCmd_{9};
1130 ProcessRunner pr{"ps -A -o \"pid,ppid,s,time,rss,vsz,user,nlwp,cmd\""sv};
1131 Streams::MemoryStream::Ptr<byte> useStdOut = Streams::MemoryStream::New<byte> ();
1132 pr.Run (nullptr, useStdOut);
1133 String out;
1135 bool skippedHeader = false;
1136 size_t headerLen = 0;
1137 for (String i = stdOut.ReadLine (); not i.empty (); i = stdOut.ReadLine ()) {
1138 if (not skippedHeader) {
1139 skippedHeader = true;
1140 headerLen = i.RTrim ().length ();
1141 continue;
1142 }
1143 Sequence<String> l = i.Tokenize ();
1144 if (l.size () < kColCountIncludingCmd_) {
1145 DbgTrace ("skipping line cuz len={}"_f, l.size ());
1146 continue;
1147 }
1148 ProcessType processDetails;
1149 pid_t pid = String2Int<int> (l[0].Trim ());
1150 processDetails.fParentProcessID = String2Int<int> (l[1].Trim ());
1151 {
1152 String s = l[2].Trim ();
1153 if (s.length () == 1) {
1154 processDetails.fRunStatus = cvtStatusCharToStatus_ (static_cast<char> (s[0].As<wchar_t> ()));
1155 }
1156 }
1157 {
1158 string tmp = l[3].AsUTF8<string> ();
1159 int hours = 0;
1160 int minutes = 0;
1161 int seconds = 0;
1162 sscanf (tmp.c_str (), "%d:%d:%d", &hours, &minutes, &seconds);
1163 processDetails.fTotalCPUTimeEverUsed = DurationSeconds{hours * 60 * 60 + minutes * 60 + seconds};
1164 }
1165 processDetails.fResidentMemorySize = String2Int<int> (l[4].Trim ()) * 1024; // RSS in /proc/xx/stat is * pagesize but this is *1024
1166 processDetails.fPrivateVirtualMemorySize = String2Int<int> (l[kVSZ_Idx_].Trim ()) * 1024;
1167 processDetails.fUserName = l[kUser_Idx_].Trim ();
1168 processDetails.fThreadCount = String2Int<unsigned int> (l[kThreadCnt_Idx_].Trim ());
1169 String cmdLine;
1170 {
1171 // wrong - must grab EVERYHTING from i past a certain point
1172 // Since our first line has headings, its length is our target, minus the 3 chars for CMD
1173 const size_t kCmdNameStartsAt_ = headerLen - 3;
1174 cmdLine = i.size () <= kCmdNameStartsAt_ ? String{} : i.SubString (kCmdNameStartsAt_).RTrim ();
1175 }
1176 {
1177 processDetails.fKernelProcess = not cmdLine.empty () and cmdLine[0] == '[';
1178 // Fake but usable answer
1179 Sequence<String> t = cmdLine.Tokenize ();
1180 if (not t.empty () and not t[0].empty () and t[0][0] == '/') {
1181 processDetails.fEXEPath = t[0].As<filesystem::path> ();
1182 }
1183 }
1184 if (_fOptions.fCaptureCommandLine and _fOptions.fCaptureCommandLine (pid, NullCoalesce (processDetails.fEXEPath))) {
1185 processDetails.fCommandLine = cmdLine;
1186 }
1187 result.Add (pid, processDetails);
1188 }
1189 return result;
1190 }
1191 };
1192};
1193#endif
1194
1195#if qStroika_Foundation_Common_Platform_Windows
1196namespace {
1197 struct UNICODE_STRING {
1198 USHORT Length;
1199 USHORT MaximumLength;
1200 PWSTR Buffer;
1201 };
1202 struct PROCESS_BASIC_INFORMATION {
1203 PVOID Reserved1;
1204 PVOID /*PPEB*/ PebBaseAddress;
1205 PVOID Reserved2[2];
1206 ULONG_PTR UniqueProcessId;
1207 PVOID Reserved3;
1208 };
1209 PVOID GetPebAddress_ (HANDLE ProcessHandle)
1210 {
1211 static LONG (WINAPI * NtQueryInformationProcess) (HANDLE ProcessHandle, ULONG ProcessInformationClass, PVOID ProcessInformation,
1212 ULONG ProcessInformationLength, PULONG ReturnLength) =
1213 (LONG (WINAPI*) (HANDLE, ULONG, PVOID, ULONG, PULONG))::GetProcAddress (::LoadLibraryA ("NTDLL.DLL"),
1214 "NtQueryInformationProcess");
1215 PROCESS_BASIC_INFORMATION pbi{};
1216 NtQueryInformationProcess (ProcessHandle, 0, &pbi, sizeof (pbi), NULL);
1217 return pbi.PebBaseAddress;
1218 }
1219}
1220#endif
1221
1222#if qUseWMICollectionSupport_
1223namespace {
1224 const String kProcessID_{"ID Process"sv};
1225 const String kThreadCount_{"Thread Count"sv};
1226 const String kIOReadBytesPerSecond_{"IO Read Bytes/sec"sv};
1227 const String kIOWriteBytesPerSecond_{"IO Write Bytes/sec"sv};
1228 const String kPercentProcessorTime_{"% Processor Time"sv}; // % Processor Time is the percentage of elapsed time that all of process threads
1229 // used the processor to execution instructions. An instruction is the basic unit of
1230 // execution in a computer, a thread is the object that executes instructions, and a
1231 // process is the object created when a program is run. Code executed to handle some
1232 // hardware interrupts and trap conditions are included in this count.
1233 const String kElapsedTime_{"Elapsed Time"sv}; // The total elapsed time, in seconds, that this process has been running.
1234}
1235#endif
1236
1237#if qStroika_Foundation_Common_Platform_Windows
1238namespace {
1239 struct PerfStats_ {
1240 TimePointSeconds fCapturedAt;
1241 optional<DurationSeconds> fTotalCPUTimeEverUsed;
1242 optional<double> fCombinedIOReadBytes;
1243 optional<double> fCombinedIOWriteBytes;
1244 };
1245 struct _Context : ModuleCommonContext_ {
1246#if qUseWMICollectionSupport_
1247 WMICollector fProcessWMICollector_{"Process"sv,
1248 {WMICollector::kWildcardInstance},
1249 { kProcessID_,
1250 kThreadCount_,
1251 kIOReadBytesPerSecond_,
1252 kIOWriteBytesPerSecond_,
1253 kPercentProcessorTime_,
1254 kElapsedTime_ }};
1255#endif
1257 };
1258
1259 struct InstrumentRep_Windows_ : InstrumentRepBase_<_Context> {
1260
1261 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
1262
1263 ProcessMapType _InternalCapture ()
1264 {
1265#if qUseWMICollectionSupport_
1266 processWMICollectorLock = fProcessWMICollector_.rwget ();
1267 TimePointSeconds timeOfPrevCollection = processWMICollectorLock.rwref ().GetTimeOfLastCollection ();
1268 IgnoreExceptionsForCall (processWMICollectorLock.rwref ().Collect ()); // hack cuz no way to copy
1269 DurationSeconds timeCollecting{processWMICollectorLock.rwref ().GetTimeOfLastCollection () - timeOfPrevCollection};
1270
1271#if USE_NOISY_TRACE_IN_THIS_MODULE_
1272 for (const String& i : processWMICollectorLock.rwref ().GetAvailableInstances ()) {
1273 DbgTrace (L"WMI instance name %s", i.c_str ());
1274 }
1275#endif
1276
1277 // NOTE THIS IS BUGGY - MUST READ BACK AS INT NOT DOUBLE
1278 Mapping<pid_t, String> pid2InstanceMap;
1279 for (const KeyValuePair<String, double>& i : processWMICollectorLock.rwref ().GetCurrentValues (kProcessID_)) {
1280 pid2InstanceMap.Add (static_cast<int> (i.fValue), i.fKey);
1281 }
1282#endif
1283
1284 SetPrivilegeInContext_ s{SE_DEBUG_NAME, SetPrivilegeInContext_::eIgnoreError};
1285 ProcessMapType results;
1286
1287#if qUseWMICollectionSupport_
1288 Mapping<String, double> threadCounts_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kThreadCount_);
1289 Mapping<String, double> ioReadBytesPerSecond_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kIOReadBytesPerSecond_);
1290 Mapping<String, double> ioWriteBytesPerSecond_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kIOWriteBytesPerSecond_);
1291 Mapping<String, double> pctProcessorTime_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kPercentProcessorTime_);
1292 Mapping<String, double> processStartAt_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kElapsedTime_);
1293#endif
1294
1295 TimePointSeconds now{Time::GetTickCount ()};
1296
1297 Mapping<pid_t, PerfStats_> newContextStats;
1298
1299#if qUseWinInternalSupport_
1300 struct AllSysInfo_ {
1301 AllSysInfo_ ()
1302 : fBuf_ (2 * 0x4000) // arbitrary, but empirically seems to work pretty often
1303 {
1304 Again:
1305 ULONG returnLength{};
1306 NTSTATUS status = ::NtQuerySystemInformation (mSystemProcessInformation, fBuf_.begin (),
1307 static_cast<ULONG> (fBuf_.GetSize ()), &returnLength);
1308 if (status == STATUS_BUFFER_TOO_SMALL or status == STATUS_INFO_LENGTH_MISMATCH) {
1309 fBuf_.GrowToSize (returnLength);
1310 goto Again;
1311 }
1312 if (status != 0) {
1313 Throw (Exception{"Bad result from NtQuerySystemInformation"sv});
1314 }
1315 fActualNumElts_ = returnLength / sizeof (SYSTEM_PROCESS_INFORMATION);
1316 }
1317 Buffer<byte> fBuf_;
1318 const SYSTEM_PROCESS_INFORMATION* GetProcessInfo () const
1319 {
1320 return reinterpret_cast<const SYSTEM_PROCESS_INFORMATION*> (fBuf_.begin ());
1321 }
1322 static bool IsValidPID_ (pid_t p)
1323 {
1324 return static_cast<make_signed_t<pid_t>> (p) > 0;
1325 }
1326 Set<pid_t> GetAllProcessIDs_ () const
1327 {
1328 const SYSTEM_PROCESS_INFORMATION* start = GetProcessInfo ();
1329 const SYSTEM_PROCESS_INFORMATION* end = start + fActualNumElts_;
1330 Set<pid_t> result;
1331 for (const SYSTEM_PROCESS_INFORMATION* i = start; i < end; ++i) {
1332 pid_t pid = reinterpret_cast<pid_t> (i->UniqueProcessId);
1333 if (IsValidPID_ (pid)) {
1334 result.Add (pid);
1335 }
1336 }
1337 return result;
1338 }
1339 Mapping<pid_t, unsigned int> GetThreadCountMap () const
1340 {
1341 if (not fThreadCntMap_.has_value ()) {
1342 const SYSTEM_PROCESS_INFORMATION* start = GetProcessInfo ();
1343 const SYSTEM_PROCESS_INFORMATION* end = start + fActualNumElts_;
1345 for (const SYSTEM_PROCESS_INFORMATION* i = start; i < end; ++i) {
1346 pid_t pid = reinterpret_cast<pid_t> (i->UniqueProcessId);
1347 if (IsValidPID_ (pid)) {
1348 struct PRIVATE_SYSTEM_PROCESS_INFORMATION_ {
1349 ULONG NextEntryOffset;
1350 ULONG NumberOfThreads; // from ProcessHacker include/ntexapi.h
1351 //...
1352 };
1353 ULONG threadCount = reinterpret_cast<const PRIVATE_SYSTEM_PROCESS_INFORMATION_*> (i)->NumberOfThreads;
1354 tmp.Add (pid, threadCount);
1355 }
1356 }
1357 fThreadCntMap_ = tmp;
1358 }
1359 return *fThreadCntMap_;
1360 }
1361 unsigned int fActualNumElts_;
1362 mutable optional<Mapping<pid_t, unsigned int>> fThreadCntMap_;
1363 };
1364 AllSysInfo_ allSysInfo;
1365 Iterable<pid_t> allPids = allSysInfo.GetAllProcessIDs_ ();
1366#else
1367 Iterable<pid_t> allPids = GetAllProcessIDs_ ();
1368#endif
1369
1370#if qUseCreateToolhelp32SnapshotToCountThreads
1371 ThreadCounter_ threadCounter;
1372#endif
1373
1374 for (pid_t pid : allPids) {
1375 if (_fOptions.fRestrictToPIDs) {
1376 if (not _fOptions.fRestrictToPIDs->Contains (pid)) {
1377 continue;
1378 }
1379 }
1380 if (_fOptions.fOmitPIDs) {
1381 if (_fOptions.fOmitPIDs->Contains (pid)) {
1382 continue;
1383 }
1384 }
1385 ProcessType processInfo;
1386 bool grabStaticData = _fOptions.fCachePolicy == CachePolicy::eIncludeAllRequestedValues or
1387 not _fContext.cget ().cref ()->fStaticSuppressedAgain.Contains (pid);
1388 {
1389 HANDLE hProcess = ::OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
1390 if (hProcess != nullptr) {
1391 [[maybe_unused]] auto&& cleanup = Finally ([hProcess] () noexcept { Verify (::CloseHandle (hProcess)); });
1392 if (grabStaticData) {
1393 optional<String> processName;
1394 optional<filesystem::path> processEXEPath;
1395 optional<pid_t> parentProcessID;
1396 optional<String> cmdLine;
1397 optional<String> userName;
1398 LookupProcessPath_ (pid, hProcess, &processName, &processEXEPath, &parentProcessID,
1399 _fOptions.fCaptureCommandLine ? &cmdLine : nullptr, &userName);
1400 if (_fOptions.fProcessNameReadPolicy == Options::eAlways or
1401 (_fOptions.fProcessNameReadPolicy == Options::eOnlyIfEXENotRead and not processEXEPath.has_value ())) {
1402 Memory::CopyToIf (&processInfo.fProcessName, processName);
1403 }
1404 Memory::CopyToIf (&processInfo.fEXEPath, processEXEPath);
1405 Memory::CopyToIf (&processInfo.fParentProcessID, parentProcessID);
1406 Memory::CopyToIf (&processInfo.fCommandLine, cmdLine);
1407 Memory::CopyToIf (&processInfo.fUserName, userName);
1408 }
1409 {
1410 PROCESS_MEMORY_COUNTERS_EX memInfo{};
1411 if (::GetProcessMemoryInfo (hProcess, reinterpret_cast<PROCESS_MEMORY_COUNTERS*> (&memInfo), sizeof (memInfo))) {
1412 processInfo.fWorkingSetSize = memInfo.WorkingSetSize;
1413 processInfo.fPrivateBytes = memInfo.PrivateUsage;
1414 processInfo.fPageFaultCount = memInfo.PageFaultCount; // docs not 100% clear but I think this is total # pagefaults
1415 }
1416 }
1417 {
1418 auto convertFILETIME2DurationSeconds = [] (FILETIME ft) -> Time::DurationSeconds {
1419 // From https://msdn.microsoft.com/en-us/library/windows/desktop/ms683223%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
1420 // Process kernel mode and user mode times are amounts of time.
1421 // For example, if a process has spent one second in kernel mode, this function
1422 // will fill the FILETIME structure specified by lpKernelTime with a 64-bit value of ten million.
1423 // That is the number of 100-nanosecond units in one second.
1424 //
1425 // Note - we go through this instead of a cast, since the FILETIME might not be 64-bit aligned
1426 ULARGE_INTEGER tmp;
1427 tmp.LowPart = ft.dwLowDateTime;
1428 tmp.HighPart = ft.dwHighDateTime;
1429 return Time::DurationSeconds{static_cast<TimePointSeconds::rep> (tmp.QuadPart) * 100e-9};
1430 };
1431 FILETIME creationTime{};
1432 FILETIME exitTime{};
1433 FILETIME kernelTime{};
1434 FILETIME userTime{};
1435 if (::GetProcessTimes (hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {
1436 if (grabStaticData) {
1437
1438 processInfo.fProcessStartedAt = DateTime{creationTime};
1439 }
1440 processInfo.fTotalCPUTimeEverUsed =
1441 convertFILETIME2DurationSeconds (kernelTime) + convertFILETIME2DurationSeconds (userTime);
1442 }
1443 else {
1444 DbgTrace (L"error calling GetProcessTimes: {}"_f, ::GetLastError ());
1445 }
1446 }
1447 {
1448 IO_COUNTERS ioCounters{};
1449 if (::GetProcessIoCounters (hProcess, &ioCounters)) {
1450 processInfo.fCombinedIOReadBytes = static_cast<double> (ioCounters.ReadTransferCount);
1451 processInfo.fCombinedIOWriteBytes = static_cast<double> (ioCounters.WriteTransferCount);
1452 }
1453 else {
1454 DbgTrace (L"error calling GetProcessIoCounters: {}"_f, ::GetLastError ());
1455 }
1456 }
1457
1458#if qUseCreateToolhelp32SnapshotToCountThreads
1459 processInfo.fThreadCount = threadCounter.CountThreads (pid);
1460#endif
1461
1462#if qUseWinInternalSupport_
1463 {
1464 if (auto i = allSysInfo.GetThreadCountMap ().Lookup (pid)) {
1465 processInfo.fThreadCount = *i;
1466 }
1467 }
1468#endif
1469 }
1470 }
1471#if qUseWMICollectionSupport_
1472 {
1473 String instanceVal = pid2InstanceMap.LookupValue (pid);
1474 if (auto o = threadCounts_ByPID.Lookup (instanceVal)) {
1475 processInfo.fThreadCount = static_cast<unsigned int> (*o);
1476 }
1477 if (auto o = ioReadBytesPerSecond_ByPID.Lookup (instanceVal)) {
1478 processInfo.fCombinedIOReadRate = *o;
1479 }
1480 if (auto o = ioWriteBytesPerSecond_ByPID.Lookup (instanceVal)) {
1481 processInfo.fCombinedIOWriteRate = *o;
1482 }
1483 if (auto o = pctProcessorTime_ByPID.Lookup (instanceVal)) {
1484 processInfo.fAverageCPUTimeUsed = *o * 100.0;
1485 }
1486 if (grabStaticData) {
1487 if (auto o = processStartAt_ByPID.Lookup (instanceVal)) {
1488 processInfo.fProcessStartedAt = DateTime::Now ().AddSeconds (-static_cast<time_t> (*o));
1489 }
1490 }
1491 }
1492#endif
1493 if (not processInfo.fCombinedIOReadRate.has_value () or not processInfo.fCombinedIOWriteRate.has_value () or
1494 not processInfo.fAverageCPUTimeUsed.has_value ()) {
1495 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
1496 auto diffTime = now - p->fCapturedAt;
1497 if (diffTime >= _fOptions.fMinimumAveragingInterval) {
1498 if (p->fCombinedIOReadBytes and processInfo.fCombinedIOReadBytes) {
1499 processInfo.fCombinedIOReadRate = (*processInfo.fCombinedIOReadBytes - *p->fCombinedIOReadBytes) / diffTime.count ();
1500 }
1501 if (p->fCombinedIOWriteBytes and processInfo.fCombinedIOWriteBytes) {
1502 processInfo.fCombinedIOWriteRate =
1503 (*processInfo.fCombinedIOWriteBytes - *p->fCombinedIOWriteBytes) / diffTime.count ();
1504 }
1505 if (p->fTotalCPUTimeEverUsed and processInfo.fTotalCPUTimeEverUsed) {
1506 processInfo.fAverageCPUTimeUsed =
1507 (*processInfo.fTotalCPUTimeEverUsed - *p->fTotalCPUTimeEverUsed) / diffTime.count ();
1508 }
1509 }
1510 }
1511#if 0
1512 else {
1513 /*
1514 * Considered something like:
1515 * if (not processInfo.fCombinedIOReadRate.has_value () and processInfo.fCombinedIOReadBytes.has_value ()) {
1516 * processInfo.fCombinedIOReadRate = *processInfo.fCombinedIOReadBytes / (now - _GetCaptureContextTime ());
1517 * }
1518 *
1519 * But cannot do, because we do capture_() once at CTOR, so if we get here and havent seen this process before
1520 * it started SOMETIME during this capture, but we don't know when (so we don't know the divisor).
1521 */
1522 }
1523#endif
1524 }
1525
1526 // So next time we can compute 'diffs'
1527 newContextStats.Add (pid, PerfStats_{now, processInfo.fTotalCPUTimeEverUsed, processInfo.fCombinedIOReadBytes,
1528 processInfo.fCombinedIOWriteBytes});
1529
1530 results.Add (pid, processInfo);
1531 }
1532 _NoteCompletedCapture (now);
1533 _fContext.rwget ().rwref ()->fMap = newContextStats;
1534 if (_fOptions.fCachePolicy == CachePolicy::eOmitUnchangedValues) {
1535 _fContext.rwget ().rwref ()->fStaticSuppressedAgain = Set<pid_t>{results.Keys ()};
1536 }
1537 return results;
1538 }
1539
1540 private:
1541 static Set<pid_t> GetAllProcessIDs_ ()
1542 {
1543 DWORD aProcesses[10 * 1024];
1544 DWORD cbNeeded;
1545
1546 Set<pid_t> result;
1547 if (not::EnumProcesses (aProcesses, sizeof (aProcesses), &cbNeeded)) {
1549 return result;
1550 }
1551
1552 // Calculate how many process identifiers were returned.
1553 DWORD cProcesses = cbNeeded / sizeof (DWORD);
1554 for (DWORD i = 0; i < cProcesses; ++i) {
1555 result.Add (aProcesses[i]);
1556 }
1557 return result;
1558 }
1559 nonvirtual void LookupProcessPath_ (pid_t pid, HANDLE hProcess, optional<String>* processName, optional<filesystem::path>* processEXEPath,
1560 optional<pid_t>* parentProcessID, optional<String>* cmdLine, optional<String>* userName)
1561 {
1562 RequireNotNull (hProcess);
1563 RequireNotNull (processEXEPath);
1564 RequireNotNull (parentProcessID);
1565 //CANBENULL (cmdLine);
1566 RequireNotNull (userName);
1567 HMODULE hMod{}; // note no need to free handles returned by EnumProcessModules () accorind to man-page for EnumProcessModules
1568 DWORD cbNeeded{};
1569 if (::EnumProcessModules (hProcess, &hMod, sizeof (hMod), &cbNeeded)) {
1570 TCHAR moduleFullPath[MAX_PATH];
1571 moduleFullPath[0] = '\0';
1572 if (::GetModuleFileNameEx (hProcess, hMod, moduleFullPath, static_cast<DWORD> (std::size (moduleFullPath))) != 0) {
1573 *processEXEPath = filesystem::path{moduleFullPath};
1574 }
1575 if (processName != nullptr) {
1576 TCHAR moduleBaseName[MAX_PATH];
1577 moduleBaseName[0] = '\0';
1578 if (::GetModuleBaseName (hProcess, hMod, moduleBaseName, static_cast<DWORD> (std::size (moduleBaseName))) != 0) {
1579 *processName = String::FromSDKString (moduleBaseName);
1580 }
1581 }
1582 }
1583 if (cmdLine != nullptr) {
1584 if (_fOptions.fCaptureCommandLine == nullptr or not _fOptions.fCaptureCommandLine (pid, NullCoalesce (*processEXEPath))) {
1585 cmdLine = nullptr;
1586 }
1587 }
1588 {
1589 const ULONG ProcessBasicInformation = 0;
1590 static LONG (WINAPI * NtQueryInformationProcess) (HANDLE ProcessHandle, ULONG ProcessInformationClass, PVOID ProcessInformation,
1591 ULONG ProcessInformationLength, PULONG ReturnLength) =
1592 (LONG (WINAPI*) (HANDLE, ULONG, PVOID, ULONG, PULONG))::GetProcAddress (::LoadLibraryA ("NTDLL.DLL"),
1593 "NtQueryInformationProcess");
1594 if (NtQueryInformationProcess) {
1595 ULONG_PTR pbi[6];
1596 ULONG ulSize = 0;
1597 if (NtQueryInformationProcess (hProcess, ProcessBasicInformation, &pbi, sizeof (pbi), &ulSize) >= 0 && ulSize == sizeof (pbi)) {
1598 *parentProcessID = static_cast<pid_t> (pbi[5]);
1599
1600 if (cmdLine != nullptr) {
1601 // Cribbed from http://windows-config.googlecode.com/svn-history/r59/trunk/doc/cmdline/cmdline.cpp
1602 void* pebAddress = GetPebAddress_ (hProcess);
1603 if (pebAddress != nullptr) {
1604#ifdef _WIN64
1605 constexpr int kUserProcParamsOffset_ = 0x20;
1606 constexpr int kCmdLineOffset_ = 112;
1607#else
1608 constexpr int kUserProcParamsOffset_ = 0x10;
1609 constexpr int kCmdLineOffset_ = 0x40;
1610#endif
1611 /* get the address of ProcessParameters */
1612 void* rtlUserProcParamsAddress{};
1613 if (not::ReadProcessMemory (hProcess, (PCHAR)pebAddress + kUserProcParamsOffset_, &rtlUserProcParamsAddress,
1614 sizeof (PVOID), NULL)) {
1615 goto SkipCmdLine_;
1616 }
1617 UNICODE_STRING commandLine;
1618
1619 /* read the CommandLine UNICODE_STRING structure */
1620 if (not::ReadProcessMemory (hProcess, (PCHAR)rtlUserProcParamsAddress + kCmdLineOffset_, &commandLine,
1621 sizeof (commandLine), NULL)) {
1622 goto SkipCmdLine_;
1623 }
1624 {
1625 size_t strLen = commandLine.Length / sizeof (WCHAR); // length field in bytes
1626 Memory::StackBuffer<WCHAR> commandLineContents{strLen + 1};
1627 /* read the command line */
1628 if (not::ReadProcessMemory (hProcess, commandLine.Buffer, commandLineContents.begin (), commandLine.Length, NULL)) {
1629 goto SkipCmdLine_;
1630 }
1631 commandLineContents[strLen] = 0;
1632 *cmdLine = commandLineContents.begin ();
1633 }
1634 SkipCmdLine_:;
1635 }
1636 }
1637 }
1638 }
1639 }
1640 {
1641 /*
1642 * This can fail for a variety of reasons - mostly lack of security access. Capture the data if we can, and just don't
1643 * if we cannot.
1644 */
1645 HANDLE processToken = 0;
1646 if (::OpenProcessToken (hProcess, TOKEN_QUERY, &processToken) != 0) {
1647 [[maybe_unused]] auto&& cleanup = Finally ([processToken] () noexcept { Verify (::CloseHandle (processToken)); });
1648 DWORD nlen{};
1649 // no idea why needed, but TOKEN_USER buffer not big enuf empirically - LGP 2015-04-30
1650 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa379626(v=vs.85).aspx
1651 // TokenUser
1652 // The buffer receives a TOKEN_USER structure that contains the user account of the token.
1653 byte tokenUserBuf[1024];
1654 TOKEN_USER* tokenUser = reinterpret_cast<TOKEN_USER*> (begin (tokenUserBuf));
1655 if (::GetTokenInformation (processToken, TokenUser, tokenUser, sizeof (tokenUserBuf), &nlen) != 0) {
1656 Assert (nlen >= sizeof (TOKEN_USER));
1657 // @todo not sure we need this IgnoreExceptionsForCall
1658 *userName = Execution::Platform::Windows::SID22UserName (tokenUser->User.Sid);
1659 //IgnoreExceptionsForCall (*userName = Execution::Platform::Windows::SID22UserName (tokenUser->User.Sid));
1660 }
1661 }
1662 }
1663 }
1664 };
1665};
1666#endif
1667
1668namespace {
1669 struct ProcessInstrumentRep_
1670#if qStroika_Foundation_Common_Platform_Linux
1671 : InstrumentRep_Linux_
1672#elif qStroika_Foundation_Common_Platform_Windows
1673 : InstrumentRep_Windows_
1674#else
1675 : InstrumentRepBase_<ModuleCommonContext_>
1676#endif
1677 {
1678#if qStroika_Foundation_Common_Platform_Linux
1679 using inherited = InstrumentRep_Linux_;
1680#elif qStroika_Foundation_Common_Platform_Windows
1681 using inherited = InstrumentRep_Windows_;
1682#else
1683 using inherited = InstrumentRepBase_<ModuleCommonContext_>;
1684#endif
1685 ProcessInstrumentRep_ (const Options& options, const shared_ptr<_Context>& context = Memory::MakeSharedPtr<_Context> ())
1686 : inherited{options, context}
1687 {
1688 Require (_fOptions.fMinimumAveragingInterval > 0s);
1689 }
1690 virtual MeasurementSet Capture () override
1691 {
1692 Debug::TraceContextBumper ctx{"SystemPerformance::Instrument...Process...ProcessInstrumentRep_::Capture ()"};
1693 MeasurementSet results;
1694 Measurement m{Instruments::Process::kProcessMapMeasurement,
1695 Process::Instrument::kObjectVariantMapper.FromObject (Capture_Raw (&results.fMeasuredAt))};
1696 results.fMeasurements.Add (m);
1697 return results;
1698 }
1699 nonvirtual Info Capture_Raw (Range<TimePointSeconds>* outMeasuredAt)
1700 {
1701 auto before = _GetCaptureContextTime ();
1702 Info rawMeasurement = _InternalCapture ();
1703 if (outMeasuredAt != nullptr) {
1704 using Traversal::Openness;
1705 *outMeasuredAt = Range<TimePointSeconds> (before, _GetCaptureContextTime (), Openness::eClosed, Openness::eClosed);
1706 }
1707 return rawMeasurement;
1708 }
1709 virtual unique_ptr<IRep> Clone () const override
1710 {
1711 return make_unique<ProcessInstrumentRep_> (_fOptions, _fContext.load ());
1712 }
1713 ProcessMapType _InternalCapture ()
1714 {
1715 AssertExternallySynchronizedMutex::WriteContext declareContext{*this};
1716#if USE_NOISY_TRACE_IN_THIS_MODULE_
1717 Debug::TraceContextBumper ctx{"Instruments::ProcessDetails _InternalCapture"};
1718#endif
1719#if qStroika_Foundation_Common_Platform_Linux or qStroika_Foundation_Common_Platform_Windows
1720 return inherited::_InternalCapture ();
1721#else
1722 return ProcessMapType{};
1723#endif
1724 }
1725 };
1726}
1727
1728/*
1729 ********************************************************************************
1730 ********************** Instruments::Process::Instrument ************************
1731 ********************************************************************************
1732 */
1734 ObjectVariantMapper mapper;
1735 mapper.Add (mapper.MakeCommonSerializer_NamedEnumerations<ProcessType::RunStatus> ());
1736 mapper.AddCommonType<optional<ProcessType::RunStatus>> ();
1737 mapper.AddCommonType<optional<pid_t>> ();
1738 mapper.AddCommonType<optional<MemorySizeType>> ();
1739 mapper.AddCommonType<optional<Time::Duration>> ();
1740 mapper.AddCommonType<optional<Mapping<String, String>>> ();
1741 mapper.AddClass<ProcessType::TCPStats> ({
1742 {"Established"sv, &ProcessType::TCPStats::fEstablished},
1743 {"Listening"sv, &ProcessType::TCPStats::fListening},
1744 {"Other"sv, &ProcessType::TCPStats::fOther},
1745 });
1746 mapper.AddCommonType<optional<ProcessType::TCPStats>> ();
1747 mapper.AddClass<ProcessType> ({
1748 {"Kernel-Process"sv, &ProcessType::fKernelProcess},
1749 {"Parent-Process-ID"sv, &ProcessType::fParentProcessID},
1750 {"Process-Name"sv, &ProcessType::fProcessName},
1751 {"User-Name"sv, &ProcessType::fUserName},
1752 {"Command-Line"sv, &ProcessType::fCommandLine},
1753 {"Current-Working-Directory"sv, &ProcessType::fCurrentWorkingDirectory},
1754 {"Environment-Variables"sv, &ProcessType::fEnvironmentVariables},
1755 {"EXE-Path"sv, &ProcessType::fEXEPath},
1756 {"Root"sv, &ProcessType::fRoot},
1757 {"Process-Started-At"sv, &ProcessType::fProcessStartedAt},
1758 {"Run-Status"sv, &ProcessType::fRunStatus},
1759 {"Private-Virtual-Memory-Size"sv, &ProcessType::fPrivateVirtualMemorySize},
1760 {"Total-Virtual-Memory-Size"sv, &ProcessType::fTotalVirtualMemorySize},
1761 {"Resident-Memory-Size"sv, &ProcessType::fResidentMemorySize},
1762 {"Private-Bytes"sv, &ProcessType::fPrivateBytes},
1763 {"Page-Fault-Count"sv, &ProcessType::fPageFaultCount},
1764 {"Major-Page-Fault-Count"sv, &ProcessType::fMajorPageFaultCount},
1765 {"Working-Set-Size"sv, &ProcessType::fWorkingSetSize},
1766 {"Private-Working-Set-Size"sv, &ProcessType::fPrivateWorkingSetSize},
1767 {"Average-CPUTime-Used"sv, &ProcessType::fAverageCPUTimeUsed},
1768 {"Thread-Count"sv, &ProcessType::fThreadCount},
1769 {"Combined-IO-Read-Rate"sv, &ProcessType::fCombinedIOReadRate},
1770 {"Combined-IO-Write-Rate"sv, &ProcessType::fCombinedIOWriteRate},
1771 {"Combined-IO-Read-Bytes"sv, &ProcessType::fCombinedIOReadBytes},
1772 {"Combined-IO-Write-Bytes"sv, &ProcessType::fCombinedIOWriteBytes},
1773 {"TCP-Stats"sv, &ProcessType::fTCPStats},
1774 });
1775 mapper.AddCommonType<ProcessMapType> ();
1776 return mapper;
1777}();
1778
1779Instruments::Process::Instrument::Instrument (const Options& options)
1780 : SystemPerformance::Instrument{InstrumentNameType{"Process"sv},
1781 make_unique<ProcessInstrumentRep_> (options),
1782 {kProcessMapMeasurement},
1783 {KeyValuePair<type_index, MeasurementType>{typeid (Info), kProcessMapMeasurement}},
1784 kObjectVariantMapper}
1785{
1786}
1787
1788/*
1789 ********************************************************************************
1790 ********* SystemPerformance::Instrument::CaptureOneMeasurement *****************
1791 ********************************************************************************
1792 */
1793template <>
1795{
1796 Debug::TraceContextBumper ctx{"SystemPerformance::Instrument::CaptureOneMeasurement<Process::Info>"};
1797 ProcessInstrumentRep_* myCap = dynamic_cast<ProcessInstrumentRep_*> (fCaptureRep_.get ());
1798 AssertNotNull (myCap);
1799 return myCap->Capture_Raw (measurementTimeOut);
1800}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
#define Verify(c)
Definition Assertions.h:419
wstring Capture(const Options &options={})
Definition BackTrace.cpp:46
size_t Length(const T *p)
Measure the length of the argument c-string (NUL-terminated string).
const OT & NullCoalesce(const OT &l, const OT &r)
return one of l, or r, with first preference for which is engaged, and second preference for left-to-...
Definition Optional.inl:134
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:309
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual size_t length() const noexcept
number of characters, not bytes or code-points
nonvirtual Character GetAt(size_t index) const noexcept
nonvirtual void Append(span< const CHAR_T > s)
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual size_t length() const noexcept
Definition String.inl:1051
nonvirtual size_t size() const noexcept
Definition String.inl:534
nonvirtual bool EndsWith(const Character &c, CompareOptions co=eWithCase) const
Definition String.cpp:1089
nonvirtual String SubString(SZ from) const
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1593
nonvirtual String RTrim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1509
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
Definition String.inl:685
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:188
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
Definition Mapping.inl:142
nonvirtual mapped_type LookupValue(ArgByValueType< key_type > key, ArgByValueType< mapped_type > defaultValue=mapped_type{}) const
Definition Mapping.inl:166
nonvirtual void Add(ArgByValueType< T > item)
Definition MultiSet.inl:248
nonvirtual CounterType OccurrencesOf(ArgByValueType< T > item) const
Definition MultiSet.inl:327
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual void Append(ArgByValueType< value_type > item)
Definition Sequence.inl:330
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
An Atom is like a String, except that its much cheaper to copy/store/compare, and the semantics of co...
Definition Atom.h:133
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
nonvirtual void AddClass(const Traversal::Iterable< StructFieldInfo > &fieldDescriptions, const ClassMapperOptions< CLASS > &mapperOptions={})
nonvirtual void AddCommonType(ARGS &&... args)
nonvirtual VariantValue FromObject(const T &from) const
nonvirtual String WriteAsString(const VariantValue &v) const
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
Run the given command, and optionally support stdin/stdout/stderr as streams (either sync with Run,...
nonvirtual bool Access(const filesystem::path &fileFullPath, AccessMode accessMode=AccessMode::eRead) const noexcept
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual String ReadAll(size_t upTo=numeric_limits< size_t >::max()) const
nonvirtual optional< ElementType > ReadBlocking() const
ReadBlocking () reads either a single element, or fills in argument intoBuffer - but never blocks (no...
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:303
nonvirtual bool empty() const
Returns true iff size() == 0.
Definition Iterable.inl:309
An Instrument is a stateful object from which you can Capture () a series of measurements about a sys...
Definition Instrument.h:69
nonvirtual T CaptureOneMeasurement(Range< TimePointSeconds > *measurementTimeOut=nullptr)
basic_string< SDKChar > SDKString
Definition SDKString.h:38
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
Definition ToString.inl:465
unsigned int HexString2Int(const String &s)
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
int pid_t
TODO - maybe move this to configuraiotn module???
Definition Module.h:34
Ptr New(const InputStream::Ptr< byte > &src, optional< AutomaticCodeCvtFlags > codeCvtFlags={}, optional< SeekableFlag > seekable={}, ReadAhead readAhead=eReadAheadAllowed)
Create an InputStream::Ptr<Character> from the arguments (usually binary source) - which can be used ...