4#include "Stroika/Frameworks/StroikaPreComp.h"
6#if qStroika_Foundation_Common_Platform_Linux
7#include <netinet/tcp.h>
8#include <sys/sysinfo.h>
9#elif qStroika_Foundation_Common_Platform_Windows
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"
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"
61using namespace Stroika::Foundation::Memory;
62using namespace Stroika::Foundation::Streams;
64using namespace Stroika::Frameworks;
65using namespace Stroika::Frameworks::SystemPerformance;
66using namespace Stroika::Frameworks::SystemPerformance::Instruments;
73using Instruments::Process::CachePolicy;
75using Instruments::Process::MemorySizeType;
76using Instruments::Process::Options;
86#define qUseWinInternalSupport_ 0
87#ifndef qUseWinInternalSupport_
88#define qUseWinInternalSupport_ qStroika_Foundation_Common_Platform_Windows
93#ifndef qUseCreateToolhelp32SnapshotToCountThreads
94#define qUseCreateToolhelp32SnapshotToCountThreads qStroika_Foundation_Common_Platform_Windows
98#define qUseWMICollectionSupport_ 0
99#ifndef qUseWMICollectionSupport_
100#define qUseWMICollectionSupport_ \
101 qStroika_Foundation_Common_Platform_Windows && (!qUseCreateToolhelp32SnapshotToCountThreads and !qUseWinInternalSupport_)
104#if qUseWinInternalSupport_
107#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
108#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
114#pragma comment(lib, "Ntdll.lib")
118#if qUseCreateToolhelp32SnapshotToCountThreads
121#pragma comment(lib, "Ntdll.lib")
125#if qUseWMICollectionSupport_
132#pragma comment(lib, "psapi.lib")
135#if qStroika_Foundation_Common_Platform_Windows
137 struct SetPrivilegeInContext_ {
141 HANDLE fToken_{INVALID_HANDLE_VALUE};
143 SetPrivilegeInContext_ (LPCTSTR privilege)
144 : fPrivilege_{privilege}
151 if (fToken_ != INVALID_HANDLE_VALUE) {
152 ::CloseHandle (fToken_);
157 SetPrivilegeInContext_ (LPCTSTR privilege, IgnoreError)
158 : fPrivilege_{privilege}
163 if (not SetPrivilege_ (fToken_, fPrivilege_.c_str (),
true)) {
164 DbgTrace (
"Failed to set privilege: error#: {}"_f, ::GetLastError ());
172 if (fToken_ != INVALID_HANDLE_VALUE) {
173 ::CloseHandle (fToken_);
174 fToken_ = INVALID_HANDLE_VALUE;
178 SetPrivilegeInContext_ (
const SetPrivilegeInContext_&) =
delete;
179 SetPrivilegeInContext_& operator= (
const SetPrivilegeInContext_&) =
delete;
180 ~SetPrivilegeInContext_ ()
182 if (fToken_ != INVALID_HANDLE_VALUE) {
183 SetPrivilege_ (fToken_, fPrivilege_.c_str (),
false);
184 Verify (::CloseHandle (fToken_));
191 if (not::OpenThreadToken (::GetCurrentThread (), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
false, &fToken_)) {
192 if (::GetLastError () == ERROR_NO_TOKEN) {
195 ::OpenThreadToken (::GetCurrentThread (), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &fToken_));
202 bool SetPrivilege_ (HANDLE hToken, LPCTSTR privilege,
bool bEnablePrivilege)
noexcept
205 if (::LookupPrivilegeValue (
nullptr, privilege, &luid) == 0) {
213 tp.PrivilegeCount = 1;
214 tp.Privileges[0].Luid = luid;
215 tp.Privileges[0].Attributes = 0;
217 TOKEN_PRIVILEGES tpPrevious;
218 DWORD cbPrevious =
sizeof (tpPrevious);
219 ::AdjustTokenPrivileges (hToken,
false, &tp,
sizeof (tp), &tpPrevious, &cbPrevious);
220 if (::GetLastError () != ERROR_SUCCESS) {
229 tpPrevious.PrivilegeCount = 1;
230 tpPrevious.Privileges[0].Luid = luid;
232 if (bEnablePrivilege) {
233 tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
236 tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED & tpPrevious.Privileges[0].Attributes);
238 ::AdjustTokenPrivileges (hToken,
false, &tpPrevious, cbPrevious,
nullptr,
nullptr);
239 if (::GetLastError () != ERROR_SUCCESS) {
250#if qUseCreateToolhelp32SnapshotToCountThreads
252 class ThreadCounter_ {
259 HANDLE hThreadSnap = ::CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0);
260 if (hThreadSnap == INVALID_HANDLE_VALUE) {
261 DbgTrace (L
"CreateToolhelp32Snapshot failed: {}"_f, ::GetLastError ());
264 [[maybe_unused]]
auto&& cleanup =
Finally ([hThreadSnap] ()
noexcept { ::CloseHandle (hThreadSnap); });
267 THREADENTRY32 te32{};
268 te32.dwSize =
sizeof (THREADENTRY32);
271 if (not::Thread32First (hThreadSnap, &te32)) {
272 DbgTrace (L
"CreateToolhelp32Snapshot failed: {}"_f, ::GetLastError ());
277 fThreads_.
Add (te32.th32OwnerProcessID);
278 }
while (::Thread32Next (hThreadSnap, &te32));
282 optional<unsigned int> CountThreads (
pid_t pid)
const
306 template <
typename CONTEXT>
310#if qStroika_Foundation_Common_Platform_Linux
320 TimePointSeconds fCapturedAt;
321 optional<DurationSeconds> fTotalCPUTimeEverUsed;
322 optional<double> fCombinedIOReadBytes;
323 optional<double> fCombinedIOWriteBytes;
325 struct _Context : ModuleCommonContext_ {
329 struct InstrumentRep_Linux_ : InstrumentRepBase_<_Context> {
331 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
333 ProcessMapType _InternalCapture ()
335 ProcessMapType result{};
336 if (_fOptions.fAllowUse_ProcFS) {
337 result = ExtractFromProcFS_ ();
339 else if (_fOptions.fAllowUse_PS) {
340 result = capture_using_ps_ ();
349 static optional<ProcessType::RunStatus> cvtStatusCharToStatus_ (
char state)
353 return ProcessType::RunStatus::eRunning;
355 return ProcessType::RunStatus::eSleeping;
357 return ProcessType::RunStatus::eWaitingOnDisk;
359 return ProcessType::RunStatus::eZombie;
361 return ProcessType::RunStatus::eSuspended;
363 return ProcessType::RunStatus::eWaitingOnPaging;
368 ProcessMapType ExtractFromProcFS_ ()
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};
385 ProcessMapType results;
400 filesystem::directory_iterator{
"/proc", filesystem::directory_options{filesystem::directory_options::skip_permission_denied}}) {
401 const filesystem::path& dir = p.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 ());
408 if (isAllNumeric and p.is_directory ()) {
409 pid_t pid{String2Int<pid_t> (dirFileNameString)};
411#if USE_NOISY_TRACE_IN_THIS_MODULE_
412 DbgTrace (
"reading for pid = %d", pid);
414 if (_fOptions.fRestrictToPIDs) {
415 if (not _fOptions.fRestrictToPIDs->Contains (pid)) {
419 if (_fOptions.fOmitPIDs) {
420 if (_fOptions.fOmitPIDs->Contains (pid)) {
425 bool grabStaticData = _fOptions.fCachePolicy == CachePolicy::eIncludeAllRequestedValues or
426 not _fContext.cget ().cref ()->fStaticSuppressedAgain.Contains (pid);
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> ();
436 if (_fOptions.fProcessNameReadPolicy == Options::eAlways or
437 (_fOptions.fProcessNameReadPolicy == Options::eOnlyIfEXENotRead and not processDetails.fEXEPath.has_value ())) {
438 processDetails.fProcessName =
452 processDetails.
fKernelProcess = not processDetails.fEXEPath.has_value ();
454 if (_fOptions.fCaptureCommandLine and _fOptions.fCaptureCommandLine (pid,
NullCoalesce (processDetails.fEXEPath))) {
455 processDetails.fCommandLine = ReadCmdLineString_ (dir / kCmdLineFilename_);
458 if (_fOptions.fCaptureRoot and processDetails.
fKernelProcess ==
false) {
459 processDetails.
fRoot = OptionallyResolveShortcut_ (dir / kRootFilename_);
462 if (_fOptions.fCaptureEnvironmentVariables and processDetails.
fKernelProcess ==
false) {
463 processDetails.fEnvironmentVariables = OptionallyReadFileStringsMap_ (dir / kEnvironFilename_);
468 if (_fOptions.fCaptureCurrentWorkingDirectory and processDetails.
fKernelProcess ==
false) {
469 processDetails.fCurrentWorkingDirectory = OptionallyResolveShortcut_ (dir / kCWDFilename_);
472 static const double kClockTick_ = ::sysconf (_SC_CLK_TCK);
475 StatFileInfo_ stats = ReadStatFile_ (dir / kStatFilename_);
477 processDetails.fRunStatus = cvtStatusCharToStatus_ (stats.state);
479 static const size_t kPageSizeInBytes_ = ::sysconf (_SC_PAGESIZE);
481 if (grabStaticData) {
482 static const time_t kUNIXEpochTimeOfBoot_ = [] () {
485 return ::time (NULL) - info.uptime;
491 processDetails.fProcessStartedAt = DateTime{
static_cast<time_t
> (stats.start_time / kClockTick_ + kUNIXEpochTimeOfBoot_)};
495 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
496 auto diffTime = now - p->fCapturedAt;
497 if (p->fTotalCPUTimeEverUsed and (diffTime >= _fOptions.fMinimumAveragingInterval)) {
502 if (stats.nlwp != 0) {
503 processDetails.fThreadCount = stats.nlwp;
505 if (grabStaticData) {
524 processDetails.fPrivateBytes = ReadPrivateBytes_ (dir /
"smaps");
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,
536 if (_fOptions.fCaptureTCPStatistics) {
537 IgnoreExceptionsForCall (processDetails.fTCPStats = ReadTCPStats_ (dir / kNetTCPFilename_));
540 if (grabStaticData) {
544 processDetails.fUserName =
"root"sv;
547 proc_status_data_ stats = Readproc_proc_status_data_ (dir / kStatusFilename_);
548 processDetails.fUserName = Execution::Platform::POSIX::uid_t2UserName (stats.ruid);
557 optional<proc_io_data_> stats = Readproc_io_data_ (dir / kIOFilename_);
558 if (stats.has_value ()) {
561 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
563 if (diffTime >= _fOptions.fMinimumAveragingInterval) {
564 if (p->fCombinedIOReadBytes) {
568 if (p->fCombinedIOWriteBytes) {
569 processDetails.fCombinedIOWriteRate =
577 DbgTrace (
"ignored: {}"_f, current_exception ());
584 results.Add (pid, processDetails);
587 _NoteCompletedCapture ();
588 _fContext.rwget ().rwref ()->fMap = newContextStats;
589 if (_fOptions.fCachePolicy == CachePolicy::eOmitUnchangedValues) {
590 _fContext.rwget ().rwref ()->fStaticSuppressedAgain =
Set<pid_t>{results.Keys ()};
594 template <
typename T>
595 static optional<T> OptionallyReadIfFileExists_ (
const filesystem::path& fullPath,
599 IgnoreExceptionsExceptThreadAbortForCall (
600 return reader (IO::FileSystem::FileInputStream::New (fullPath, IO::FileSystem::FileInputStream::eNotSeekable)));
609 for (optional<byte> b; (b = in.
ReadBlocking ()).has_value ();) {
610 if ((*b) ==
byte{0}) {
623 for (
const String& i : ReadFileStrings_ (fullPath)) {
624 auto tokens = i.Tokenize ({
'='});
625 if (tokens.size () == 2) {
626 results.
Add (tokens[0], tokens[1]);
632 static optional<String> ReadCmdLineString_ (
const filesystem::path& fullPath2CmdLineFile)
636#if USE_NOISY_TRACE_IN_THIS_MODULE_
640 bool lastCharNullRemappedToSpace =
false;
641 for (optional<byte> b; (b = in.
ReadBlocking ()).has_value ();) {
644 lastCharNullRemappedToSpace =
true;
648 lastCharNullRemappedToSpace =
false;
651 if (lastCharNullRemappedToSpace) {
660 IgnoreExceptionsExceptThreadAbortForCall (
return ReadFileString_ (
661 IO::FileSystem::FileInputStream::New (fullPath2CmdLineFile, IO::FileSystem::FileInputStream::eNotSeekable)));
666 static optional<filesystem::path> OptionallyResolveShortcut_ (
const filesystem::path& shortcutPath)
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);
677 static optional<Mapping<String, String>> OptionallyReadFileStringsMap_ (
const filesystem::path& fullPath)
680 IgnoreExceptionsExceptThreadAbortForCall (
return ReadFileStringsMap_ (fullPath));
684 struct StatFileInfo_ {
827 unsigned long long utime;
828 unsigned long long stime;
830 unsigned long long start_time;
831 unsigned long long vsize;
832 unsigned long long rss;
833 unsigned long minflt;
834 unsigned long majflt;
836 static StatFileInfo_ ReadStatFile_ (
const filesystem::path& fullPath)
838#if USE_NOISY_TRACE_IN_THIS_MODULE_
839 Debug::TraceContextBumper ctx{L
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadStatFile_",
"fullPath={}"_f, fullPath};
841 StatFileInfo_ result{};
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);
849 if (nBytes == NEltsOf (data)) {
852 data[nBytes] =
byte{0};
854 const char* S =
reinterpret_cast<const char*
> (data);
857 S = ::strchr (S,
'(') + 1;
858 Assert (S <
reinterpret_cast<const char*
> (end (data)));
859#if USE_NOISY_TRACE_IN_THIS_MODULE_
862 const char* tmp = ::strrchr (S,
')');
863#if USE_NOISY_TRACE_IN_THIS_MODULE_
867 Assert (S <
reinterpret_cast<const char*
> (end (data)));
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 =
889 "%lu %lu %lu %lu %lu "
916 &result.ppid, &ignoredInt, &ignoredInt, &ignoredInt, &ignoredInt,
919 &ignoredUnsignedLong, &result.minflt, &ignoredUnsignedLong, &result.majflt, &ignoredUnsignedLong,
922 &result.utime, &result.stime,
925 &ignoredUnsignedLongLong, &ignoredUnsignedLongLong,
928 &ignoredLong, &ignoredLong,
940 &result.vsize, &result.rss);
941 DISABLE_COMPILER_MSC_WARNING_END (4996)
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);
956 struct proc_io_data_ {
958 uint64_t write_bytes;
960 static optional<proc_io_data_> Readproc_io_data_ (
const filesystem::path& fullPath)
962#if USE_NOISY_TRACE_IN_THIS_MODULE_
963 Debug::TraceContextBumper ctx{
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::Readproc_io_data_",
"fullPath={}"_f, fullPath};
966#if USE_NOISY_TRACE_IN_THIS_MODULE_
967 DbgTrace (
"Skipping read cuz no access");
971 proc_io_data_ result{};
972 ifstream r{fullPath, ios_base::binary | ios_base::in};
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_));
982 else if (::strncmp (buf, kWriteLbl_, ::strlen (kWriteLbl_)) == 0) {
983 result.write_bytes = CString::String2Int<
decltype (result.write_bytes)> (buf + ::strlen (kWriteLbl_));
989 static optional<ProcessType::TCPStats> ReadTCPStats_ (
const filesystem::path& fullPath)
997#if USE_NOISY_TRACE_IN_THIS_MODULE_
998 Debug::TraceContextBumper ctx{
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadTCPStats_",
"fullPath={}"_f, fullPath};
1002#if USE_NOISY_TRACE_IN_THIS_MODULE_
1003 DbgTrace (L
"Skipping read cuz no access");
1007 ProcessType::TCPStats stats;
1008 bool didSkip =
false;
1010 BinaryToText::Reader::New (IO::FileSystem::FileInputStream::New (fullPath, IO::FileSystem::FileInputStream::eNotSeekable)).ReadLines ()) {
1016 if (splits.
size () >= 4) {
1021 if (st == TCP_ESTABLISHED) {
1022 ++stats.fEstablished;
1024 else if (st == TCP_LISTEN) {
1034 static optional<MemorySizeType> ReadPrivateBytes_ (
const filesystem::path& fullPath)
1036#if USE_NOISY_TRACE_IN_THIS_MODULE_
1037 Debug::TraceContextBumper ctx{
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadPrivateBytes_",
"fullPath={}"_f, fullPath};
1041#if USE_NOISY_TRACE_IN_THIS_MODULE_
1042 DbgTrace (L
"Skipping read cuz no access");
1046 MemorySizeType result{};
1048 Streams::iostream::OpenInputFileStream (&r, fullPath);
1052 if (r.getline (buf, sizeof (buf))) {
1054 constexpr char kPrivate1Lbl_[] =
"Private_Clean:";
1055 constexpr char kPrivate2Lbl_[] =
"Private_Dirty:";
1057 if (::strncmp (buf, kPrivate1Lbl_, ::strlen (kPrivate1Lbl_)) == 0) {
1058 result += CString::String2Int<MemorySizeType> (buf + strlen (kPrivate1Lbl_)) * 1024;
1060 else if (::strncmp (buf, kPrivate2Lbl_, ::strlen (kPrivate2Lbl_)) == 0) {
1061 result += CString::String2Int<MemorySizeType> (buf + ::strlen (kPrivate2Lbl_)) * 1024;
1069 struct proc_status_data_ {
1072 static proc_status_data_ Readproc_proc_status_data_ (
const filesystem::path& fullPath)
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};
1078 proc_status_data_ result{};
1080 Streams::iostream::OpenInputFileStream (&r, fullPath);
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));
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));
1105 nonvirtual ProcessMapType capture_using_ps_ ()
1108 ProcessMapType result;
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};
1131 pr.Run (
nullptr, useStdOut);
1134 bool skippedHeader =
false;
1135 size_t headerLen = 0;
1137 if (not skippedHeader) {
1138 skippedHeader =
true;
1139 headerLen = i.RTrim ().length ();
1143 if (l.
size () < kColCountIncludingCmd_) {
1148 pid_t pid = String2Int<int> (l[0].Trim ());
1153 processDetails.fRunStatus = cvtStatusCharToStatus_ (
static_cast<char> (s[0].As<wchar_t> ()));
1157 string tmp = l[3].AsUTF8<
string> ();
1161 sscanf (tmp.c_str (),
"%d:%d:%d", &hours, &minutes, &seconds);
1166 processDetails.fUserName = l[kUser_Idx_].Trim ();
1167 processDetails.fThreadCount = String2Int<unsigned int> (l[kThreadCnt_Idx_].Trim ());
1172 const size_t kCmdNameStartsAt_ = headerLen - 3;
1176 processDetails.
fKernelProcess = not cmdLine.empty () and cmdLine[0] ==
'[';
1179 if (not t.
empty () and not t[0].empty () and t[0][0] ==
'/') {
1180 processDetails.fEXEPath = t[0].As<filesystem::path> ();
1183 if (_fOptions.fCaptureCommandLine and _fOptions.fCaptureCommandLine (pid,
NullCoalesce (processDetails.fEXEPath))) {
1184 processDetails.fCommandLine = cmdLine;
1186 result.Add (pid, processDetails);
1194#if qStroika_Foundation_Common_Platform_Windows
1196 struct UNICODE_STRING {
1198 USHORT MaximumLength;
1201 struct PROCESS_BASIC_INFORMATION {
1203 PVOID PebBaseAddress;
1205 ULONG_PTR UniqueProcessId;
1208 PVOID GetPebAddress_ (HANDLE ProcessHandle)
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;
1221#if qUseWMICollectionSupport_
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};
1232 const String kElapsedTime_{
"Elapsed Time"sv};
1236#if qStroika_Foundation_Common_Platform_Windows
1240 optional<DurationSeconds> fTotalCPUTimeEverUsed;
1241 optional<double> fCombinedIOReadBytes;
1242 optional<double> fCombinedIOWriteBytes;
1244 struct _Context : ModuleCommonContext_ {
1245#if qUseWMICollectionSupport_
1246 WMICollector fProcessWMICollector_{
"Process"sv,
1247 {WMICollector::kWildcardInstance},
1250 kIOReadBytesPerSecond_,
1251 kIOWriteBytesPerSecond_,
1252 kPercentProcessorTime_,
1258 struct InstrumentRep_Windows_ : InstrumentRepBase_<_Context> {
1260 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
1262 ProcessMapType _InternalCapture ()
1264#if qUseWMICollectionSupport_
1265 processWMICollectorLock = fProcessWMICollector_.rwget ();
1266 TimePointSeconds timeOfPrevCollection = processWMICollectorLock.rwref ().GetTimeOfLastCollection ();
1267 IgnoreExceptionsForCall (processWMICollectorLock.rwref ().Collect ());
1268 DurationSeconds timeCollecting{processWMICollectorLock.rwref ().GetTimeOfLastCollection () - timeOfPrevCollection};
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 ());
1279 pid2InstanceMap.
Add (
static_cast<int> (i.fValue), i.fKey);
1283 SetPrivilegeInContext_ s{SE_DEBUG_NAME, SetPrivilegeInContext_::eIgnoreError};
1284 ProcessMapType results;
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_);
1298#if qUseWinInternalSupport_
1299 struct AllSysInfo_ {
1301 : fBuf_ (2 * 0x4000)
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);
1314 fActualNumElts_ = returnLength /
sizeof (SYSTEM_PROCESS_INFORMATION);
1317 const SYSTEM_PROCESS_INFORMATION* GetProcessInfo ()
const
1319 return reinterpret_cast<const SYSTEM_PROCESS_INFORMATION*
> (fBuf_.begin ());
1321 static bool IsValidPID_ (
pid_t p)
1323 return static_cast<make_signed_t<pid_t>
> (p) > 0;
1327 const SYSTEM_PROCESS_INFORMATION* start = GetProcessInfo ();
1328 const SYSTEM_PROCESS_INFORMATION* end = start + fActualNumElts_;
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)) {
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;
1352 ULONG threadCount =
reinterpret_cast<const PRIVATE_SYSTEM_PROCESS_INFORMATION_*
> (i)->NumberOfThreads;
1353 tmp.
Add (pid, threadCount);
1356 fThreadCntMap_ = tmp;
1358 return *fThreadCntMap_;
1360 unsigned int fActualNumElts_;
1361 mutable optional<Mapping<pid_t, unsigned int>> fThreadCntMap_;
1363 AllSysInfo_ allSysInfo;
1369#if qUseCreateToolhelp32SnapshotToCountThreads
1370 ThreadCounter_ threadCounter;
1373 for (
pid_t pid : allPids) {
1374 if (_fOptions.fRestrictToPIDs) {
1375 if (not _fOptions.fRestrictToPIDs->Contains (pid)) {
1379 if (_fOptions.fOmitPIDs) {
1380 if (_fOptions.fOmitPIDs->Contains (pid)) {
1385 bool grabStaticData = _fOptions.fCachePolicy == CachePolicy::eIncludeAllRequestedValues or
1386 not _fContext.cget ().cref ()->fStaticSuppressedAgain.Contains (pid);
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);
1403 Memory::CopyToIf (&processInfo.fEXEPath, processEXEPath);
1405 Memory::CopyToIf (&processInfo.fCommandLine, cmdLine);
1406 Memory::CopyToIf (&processInfo.fUserName, userName);
1409 PROCESS_MEMORY_COUNTERS_EX memInfo{};
1410 if (::GetProcessMemoryInfo (hProcess,
reinterpret_cast<PROCESS_MEMORY_COUNTERS*
> (&memInfo),
sizeof (memInfo))) {
1412 processInfo.fPrivateBytes = memInfo.PrivateUsage;
1426 tmp.LowPart = ft.dwLowDateTime;
1427 tmp.HighPart = ft.dwHighDateTime;
1430 FILETIME creationTime{};
1431 FILETIME exitTime{};
1432 FILETIME kernelTime{};
1433 FILETIME userTime{};
1434 if (::GetProcessTimes (hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {
1435 if (grabStaticData) {
1437 processInfo.fProcessStartedAt = DateTime{creationTime};
1440 convertFILETIME2DurationSeconds (kernelTime) + convertFILETIME2DurationSeconds (userTime);
1443 DbgTrace (L
"error calling GetProcessTimes: {}"_f, ::GetLastError ());
1447 IO_COUNTERS ioCounters{};
1448 if (::GetProcessIoCounters (hProcess, &ioCounters)) {
1453 DbgTrace (L
"error calling GetProcessIoCounters: {}"_f, ::GetLastError ());
1457#if qUseCreateToolhelp32SnapshotToCountThreads
1458 processInfo.fThreadCount = threadCounter.CountThreads (pid);
1461#if qUseWinInternalSupport_
1463 if (
auto i = allSysInfo.GetThreadCountMap ().Lookup (pid)) {
1464 processInfo.fThreadCount = *i;
1470#if qUseWMICollectionSupport_
1473 if (
auto o = threadCounts_ByPID.
Lookup (instanceVal)) {
1474 processInfo.fThreadCount =
static_cast<unsigned int> (*o);
1476 if (
auto o = ioReadBytesPerSecond_ByPID.
Lookup (instanceVal)) {
1479 if (
auto o = ioWriteBytesPerSecond_ByPID.
Lookup (instanceVal)) {
1480 processInfo.fCombinedIOWriteRate = *o;
1482 if (
auto o = pctProcessorTime_ByPID.
Lookup (instanceVal)) {
1485 if (grabStaticData) {
1486 if (
auto o = processStartAt_ByPID.
Lookup (instanceVal)) {
1487 processInfo.fProcessStartedAt = DateTime::Now ().AddSeconds (-
static_cast<time_t
> (*o));
1492 if (not processInfo.
fCombinedIOReadRate.has_value () or not processInfo.fCombinedIOWriteRate.has_value () or
1494 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
1495 auto diffTime = now - p->fCapturedAt;
1496 if (diffTime >= _fOptions.fMinimumAveragingInterval) {
1501 processInfo.fCombinedIOWriteRate =
1529 results.Add (pid, processInfo);
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 ()};
1542 DWORD aProcesses[10 * 1024];
1546 if (not::EnumProcesses (aProcesses,
sizeof (aProcesses), &cbNeeded)) {
1552 DWORD cProcesses = cbNeeded /
sizeof (DWORD);
1553 for (DWORD i = 0; i < cProcesses; ++i) {
1554 result.
Add (aProcesses[i]);
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)
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};
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);
1582 if (cmdLine !=
nullptr) {
1583 if (_fOptions.fCaptureCommandLine ==
nullptr or not _fOptions.fCaptureCommandLine (pid,
NullCoalesce (*processEXEPath))) {
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) {
1596 if (NtQueryInformationProcess (hProcess, ProcessBasicInformation, &pbi,
sizeof (pbi), &ulSize) >= 0 && ulSize ==
sizeof (pbi)) {
1597 *parentProcessID =
static_cast<pid_t> (pbi[5]);
1599 if (cmdLine !=
nullptr) {
1601 void* pebAddress = GetPebAddress_ (hProcess);
1602 if (pebAddress !=
nullptr) {
1604 constexpr int kUserProcParamsOffset_ = 0x20;
1605 constexpr int kCmdLineOffset_ = 112;
1607 constexpr int kUserProcParamsOffset_ = 0x10;
1608 constexpr int kCmdLineOffset_ = 0x40;
1611 void* rtlUserProcParamsAddress{};
1612 if (not::ReadProcessMemory (hProcess, (PCHAR)pebAddress + kUserProcParamsOffset_, &rtlUserProcParamsAddress,
1613 sizeof (PVOID), NULL)) {
1616 UNICODE_STRING commandLine;
1619 if (not::ReadProcessMemory (hProcess, (PCHAR)rtlUserProcParamsAddress + kCmdLineOffset_, &commandLine,
1620 sizeof (commandLine), NULL)) {
1624 size_t strLen = commandLine.Length /
sizeof (WCHAR);
1627 if (not::ReadProcessMemory (hProcess, commandLine.Buffer, commandLineContents.begin (), commandLine.Length, NULL)) {
1630 commandLineContents[strLen] = 0;
1631 *cmdLine = commandLineContents.begin ();
1644 HANDLE processToken = 0;
1645 if (::OpenProcessToken (hProcess, TOKEN_QUERY, &processToken) != 0) {
1646 [[maybe_unused]]
auto&& cleanup =
Finally ([processToken] ()
noexcept {
Verify (::CloseHandle (processToken)); });
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));
1657 *userName = Execution::Platform::Windows::SID22UserName (tokenUser->User.Sid);
1668 struct ProcessInstrumentRep_
1669#if qStroika_Foundation_Common_Platform_Linux
1670 : InstrumentRep_Linux_
1671#elif qStroika_Foundation_Common_Platform_Windows
1672 : InstrumentRep_Windows_
1674 : InstrumentRepBase_<ModuleCommonContext_>
1677#if qStroika_Foundation_Common_Platform_Linux
1678 using inherited = InstrumentRep_Linux_;
1679#elif qStroika_Foundation_Common_Platform_Windows
1680 using inherited = InstrumentRep_Windows_;
1682 using inherited = InstrumentRepBase_<ModuleCommonContext_>;
1684 ProcessInstrumentRep_ (
const Options& options,
const shared_ptr<_Context>& context = make_shared<_Context> ())
1685 : inherited{options, context}
1687 Require (_fOptions.fMinimumAveragingInterval > 0s);
1693 Measurement m{Instruments::Process::kProcessMapMeasurement,
1695 results.fMeasurements.Add (m);
1700 auto before = _GetCaptureContextTime ();
1701 Info rawMeasurement = _InternalCapture ();
1702 if (outMeasuredAt !=
nullptr) {
1704 *outMeasuredAt =
Range<TimePointSeconds> (before, _GetCaptureContextTime (), Openness::eClosed, Openness::eClosed);
1706 return rawMeasurement;
1708 virtual unique_ptr<IRep> Clone ()
const override
1710 return make_unique<ProcessInstrumentRep_> (_fOptions, _fContext.load ());
1712 ProcessMapType _InternalCapture ()
1714 AssertExternallySynchronizedMutex::WriteContext declareContext{*
this};
1715#if USE_NOISY_TRACE_IN_THIS_MODULE_
1718#if qStroika_Foundation_Common_Platform_Linux or qStroika_Foundation_Common_Platform_Windows
1719 return inherited::_InternalCapture ();
1721 return ProcessMapType{};
1740 mapper.
AddClass<ProcessType::TCPStats> ({
1741 {
"Established"sv, &ProcessType::TCPStats::fEstablished},
1742 {
"Listening"sv, &ProcessType::TCPStats::fListening},
1743 {
"Other"sv, &ProcessType::TCPStats::fOther},
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},
1756 {
"Process-Started-At"sv, &ProcessType::fProcessStartedAt},
1757 {
"Run-Status"sv, &ProcessType::fRunStatus},
1761 {
"Private-Bytes"sv, &ProcessType::fPrivateBytes},
1767 {
"Thread-Count"sv, &ProcessType::fThreadCount},
1769 {
"Combined-IO-Write-Rate"sv, &ProcessType::fCombinedIOWriteRate},
1772 {
"TCP-Stats"sv, &ProcessType::fTCPStats},
1778Instruments::Process::Instrument::Instrument (
const Options& options)
1780 make_unique<ProcessInstrumentRep_> (options),
1781 {kProcessMapMeasurement},
1783 kObjectVariantMapper}
1796 ProcessInstrumentRep_* myCap =
dynamic_cast<ProcessInstrumentRep_*
> (fCaptureRep_.get ());
1798 return myCap->Capture_Raw (measurementTimeOut);
#define RequireNotNull(p)
#define AssertNotReached()
wstring Capture(const Options &options={})
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-...
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual RESULT_T As() const
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)
nonvirtual String str() const
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual size_t length() const noexcept
nonvirtual size_t size() const noexcept
nonvirtual bool EndsWith(const Character &c, CompareOptions co=eWithCase) const
nonvirtual String SubString(SZ from) const
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
nonvirtual String RTrim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
nonvirtual mapped_type LookupValue(ArgByValueType< key_type > key, ArgByValueType< mapped_type > defaultValue=mapped_type{}) const
nonvirtual void Add(ArgByValueType< T > item)
nonvirtual CounterType OccurrencesOf(ArgByValueType< T > item) const
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual void Append(ArgByValueType< value_type > item)
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
An Atom is like a String, except that its much cheaper to copy/store/compare, and the semantics of co...
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
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
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...
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
nonvirtual size_t size() const
Returns the number of items contained.
nonvirtual bool empty() const
Returns true iff size() == 0.
basic_string< SDKChar > SDKString
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
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...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
int pid_t
TODO - maybe move this to configuraiotn module???
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 ...