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