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"
39#include "Stroika/Foundation/Streams/MemoryStream.h"
40#include "Stroika/Foundation/Streams/iostream/FStreamSupport.h"
43#if qStroika_Foundation_Common_Platform_POSIX
44#include "Stroika/Foundation/Execution/Platform/POSIX/Users.h"
45#elif qStroika_Foundation_Common_Platform_Windows
46#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
47#include "Stroika/Foundation/Execution/Platform/Windows/Users.h"
62using namespace Stroika::Foundation::Memory;
63using namespace Stroika::Foundation::Streams;
65using namespace Stroika::Frameworks;
66using namespace Stroika::Frameworks::SystemPerformance;
67using namespace Stroika::Frameworks::SystemPerformance::Instruments;
74using Instruments::Process::CachePolicy;
76using Instruments::Process::MemorySizeType;
77using Instruments::Process::Options;
87#define qUseWinInternalSupport_ 0
88#ifndef qUseWinInternalSupport_
89#define qUseWinInternalSupport_ qStroika_Foundation_Common_Platform_Windows
94#ifndef qUseCreateToolhelp32SnapshotToCountThreads
95#define qUseCreateToolhelp32SnapshotToCountThreads qStroika_Foundation_Common_Platform_Windows
99#define qUseWMICollectionSupport_ 0
100#ifndef qUseWMICollectionSupport_
101#define qUseWMICollectionSupport_ \
102 qStroika_Foundation_Common_Platform_Windows && (!qUseCreateToolhelp32SnapshotToCountThreads and !qUseWinInternalSupport_)
105#if qUseWinInternalSupport_
108#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
109#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
115#pragma comment(lib, "Ntdll.lib")
119#if qUseCreateToolhelp32SnapshotToCountThreads
122#pragma comment(lib, "Ntdll.lib")
126#if qUseWMICollectionSupport_
133#pragma comment(lib, "psapi.lib")
136#if qStroika_Foundation_Common_Platform_Windows
138 struct SetPrivilegeInContext_ {
142 HANDLE fToken_{INVALID_HANDLE_VALUE};
144 SetPrivilegeInContext_ (LPCTSTR privilege)
145 : fPrivilege_{privilege}
152 if (fToken_ != INVALID_HANDLE_VALUE) {
153 ::CloseHandle (fToken_);
158 SetPrivilegeInContext_ (LPCTSTR privilege, IgnoreError)
159 : fPrivilege_{privilege}
164 if (not SetPrivilege_ (fToken_, fPrivilege_.c_str (),
true)) {
165 DbgTrace (
"Failed to set privilege: error#: {}"_f, ::GetLastError ());
173 if (fToken_ != INVALID_HANDLE_VALUE) {
174 ::CloseHandle (fToken_);
175 fToken_ = INVALID_HANDLE_VALUE;
179 SetPrivilegeInContext_ (
const SetPrivilegeInContext_&) =
delete;
180 SetPrivilegeInContext_& operator= (
const SetPrivilegeInContext_&) =
delete;
181 ~SetPrivilegeInContext_ ()
183 if (fToken_ != INVALID_HANDLE_VALUE) {
184 SetPrivilege_ (fToken_, fPrivilege_.c_str (),
false);
185 Verify (::CloseHandle (fToken_));
192 if (not::OpenThreadToken (::GetCurrentThread (), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
false, &fToken_)) {
193 if (::GetLastError () == ERROR_NO_TOKEN) {
196 ::OpenThreadToken (::GetCurrentThread (), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &fToken_));
203 bool SetPrivilege_ (HANDLE hToken, LPCTSTR privilege,
bool bEnablePrivilege)
noexcept
206 if (::LookupPrivilegeValue (
nullptr, privilege, &luid) == 0) {
214 tp.PrivilegeCount = 1;
215 tp.Privileges[0].Luid = luid;
216 tp.Privileges[0].Attributes = 0;
218 TOKEN_PRIVILEGES tpPrevious;
219 DWORD cbPrevious =
sizeof (tpPrevious);
220 ::AdjustTokenPrivileges (hToken,
false, &tp,
sizeof (tp), &tpPrevious, &cbPrevious);
221 if (::GetLastError () != ERROR_SUCCESS) {
230 tpPrevious.PrivilegeCount = 1;
231 tpPrevious.Privileges[0].Luid = luid;
233 if (bEnablePrivilege) {
234 tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
237 tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED & tpPrevious.Privileges[0].Attributes);
239 ::AdjustTokenPrivileges (hToken,
false, &tpPrevious, cbPrevious,
nullptr,
nullptr);
240 if (::GetLastError () != ERROR_SUCCESS) {
251#if qUseCreateToolhelp32SnapshotToCountThreads
253 class ThreadCounter_ {
260 HANDLE hThreadSnap = ::CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0);
261 if (hThreadSnap == INVALID_HANDLE_VALUE) {
262 DbgTrace (L
"CreateToolhelp32Snapshot failed: {}"_f, ::GetLastError ());
265 [[maybe_unused]]
auto&& cleanup =
Finally ([hThreadSnap] ()
noexcept { ::CloseHandle (hThreadSnap); });
268 THREADENTRY32 te32{};
269 te32.dwSize =
sizeof (THREADENTRY32);
272 if (not::Thread32First (hThreadSnap, &te32)) {
273 DbgTrace (L
"CreateToolhelp32Snapshot failed: {}"_f, ::GetLastError ());
278 fThreads_.
Add (te32.th32OwnerProcessID);
279 }
while (::Thread32Next (hThreadSnap, &te32));
283 optional<unsigned int> CountThreads (
pid_t pid)
const
307 template <
typename CONTEXT>
311#if qStroika_Foundation_Common_Platform_Linux
321 TimePointSeconds fCapturedAt;
322 optional<DurationSeconds> fTotalCPUTimeEverUsed;
323 optional<double> fCombinedIOReadBytes;
324 optional<double> fCombinedIOWriteBytes;
326 struct _Context : ModuleCommonContext_ {
330 struct InstrumentRep_Linux_ : InstrumentRepBase_<_Context> {
332 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
334 ProcessMapType _InternalCapture ()
336 ProcessMapType result{};
337 if (_fOptions.fAllowUse_ProcFS) {
338 result = ExtractFromProcFS_ ();
340 else if (_fOptions.fAllowUse_PS) {
341 result = capture_using_ps_ ();
350 static optional<ProcessType::RunStatus> cvtStatusCharToStatus_ (
char state)
354 return ProcessType::RunStatus::eRunning;
356 return ProcessType::RunStatus::eSleeping;
358 return ProcessType::RunStatus::eWaitingOnDisk;
360 return ProcessType::RunStatus::eZombie;
362 return ProcessType::RunStatus::eSuspended;
364 return ProcessType::RunStatus::eWaitingOnPaging;
369 ProcessMapType ExtractFromProcFS_ ()
376 static const filesystem::path kCWDFilename_{
"cwd"sv};
377 static const filesystem::path kEXEFilename_{
"exe"sv};
378 static const filesystem::path kEnvironFilename_{
"environ"sv};
379 static const filesystem::path kRootFilename_{
"root"sv};
380 static const filesystem::path kCmdLineFilename_{
"cmdline"sv};
381 static const filesystem::path kStatFilename_{
"stat"sv};
382 static const filesystem::path kStatusFilename_{
"status"sv};
383 static const filesystem::path kIOFilename_{
"io"sv};
384 static const filesystem::path kNetTCPFilename_{
"net/tcp"sv};
386 ProcessMapType results;
401 filesystem::directory_iterator{
"/proc", filesystem::directory_options{filesystem::directory_options::skip_permission_denied}}) {
402 const filesystem::path& dir = p.path ();
403 String dirFileNameString{dir.filename ()};
404 bool isAllNumeric = not dirFileNameString.
Find ([] (
Character c) ->
bool {
return not c.IsDigit (); });
405#if USE_NOISY_TRACE_IN_THIS_MODULE_
406 Debug::TraceContextBumper ctx{
"...SystemPerformance::Instruments::Process::{}::ExtractFromProcFS_::reading /proc files"};
407 DbgTrace (L
"isAllNumeric=%d, dir= %s, is_dir=%d", isAllNumeric,
ToString (dir).c_str (), p.is_directory ());
409 if (isAllNumeric and p.is_directory ()) {
410 pid_t pid{String2Int<pid_t> (dirFileNameString)};
412#if USE_NOISY_TRACE_IN_THIS_MODULE_
413 DbgTrace (
"reading for pid = %d", pid);
415 if (_fOptions.fRestrictToPIDs) {
416 if (not _fOptions.fRestrictToPIDs->Contains (pid)) {
420 if (_fOptions.fOmitPIDs) {
421 if (_fOptions.fOmitPIDs->Contains (pid)) {
426 bool grabStaticData = _fOptions.fCachePolicy == CachePolicy::eIncludeAllRequestedValues or
427 not _fContext.cget ().cref ()->fStaticSuppressedAgain.Contains (pid);
431 if (grabStaticData) {
432 processDetails.fEXEPath = OptionallyResolveShortcut_ (dir / kEXEFilename_);
433 if (processDetails.fEXEPath and
String{*processDetails.fEXEPath}.
EndsWith (
" (deleted)"sv)) {
434 processDetails.fEXEPath =
String{*processDetails.fEXEPath}.SubString (0, -10).As<filesystem::path> ();
437 if (_fOptions.fProcessNameReadPolicy == Options::eAlways or
438 (_fOptions.fProcessNameReadPolicy == Options::eOnlyIfEXENotRead and not processDetails.fEXEPath.has_value ())) {
439 processDetails.fProcessName =
453 processDetails.
fKernelProcess = not processDetails.fEXEPath.has_value ();
455 if (_fOptions.fCaptureCommandLine and _fOptions.fCaptureCommandLine (pid,
NullCoalesce (processDetails.fEXEPath))) {
456 processDetails.fCommandLine = ReadCmdLineString_ (dir / kCmdLineFilename_);
459 if (_fOptions.fCaptureRoot and processDetails.
fKernelProcess ==
false) {
460 processDetails.
fRoot = OptionallyResolveShortcut_ (dir / kRootFilename_);
463 if (_fOptions.fCaptureEnvironmentVariables and processDetails.
fKernelProcess ==
false) {
464 processDetails.fEnvironmentVariables = OptionallyReadFileStringsMap_ (dir / kEnvironFilename_);
469 if (_fOptions.fCaptureCurrentWorkingDirectory and processDetails.
fKernelProcess ==
false) {
470 processDetails.fCurrentWorkingDirectory = OptionallyResolveShortcut_ (dir / kCWDFilename_);
473 static const double kClockTick_ = ::sysconf (_SC_CLK_TCK);
476 StatFileInfo_ stats = ReadStatFile_ (dir / kStatFilename_);
478 processDetails.fRunStatus = cvtStatusCharToStatus_ (stats.state);
480 static const size_t kPageSizeInBytes_ = ::sysconf (_SC_PAGESIZE);
482 if (grabStaticData) {
483 static const time_t kUNIXEpochTimeOfBoot_ = [] () {
486 return ::time (NULL) - info.uptime;
492 processDetails.fProcessStartedAt = DateTime{
static_cast<time_t
> (stats.start_time / kClockTick_ + kUNIXEpochTimeOfBoot_)};
496 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
497 auto diffTime = now - p->fCapturedAt;
498 if (p->fTotalCPUTimeEverUsed and (diffTime >= _fOptions.fMinimumAveragingInterval)) {
503 if (stats.nlwp != 0) {
504 processDetails.fThreadCount = stats.nlwp;
506 if (grabStaticData) {
525 processDetails.fPrivateBytes = ReadPrivateBytes_ (dir /
"smaps");
527#if USE_NOISY_TRACE_IN_THIS_MODULE_
528 DbgTrace (
"loaded processDetails.fProcessStartedAt={} wuit stats.start_time = {}"_f,
529 processDetails.fProcessStartedAt, stats.start_time);
530 DbgTrace (
"loaded processDetails.fTotalCPUTimeEverUsed={} wuit stats.utime = {}, stats.stime = {}"_f,
537 if (_fOptions.fCaptureTCPStatistics) {
538 IgnoreExceptionsForCall (processDetails.fTCPStats = ReadTCPStats_ (dir / kNetTCPFilename_));
541 if (grabStaticData) {
545 processDetails.fUserName =
"root"sv;
548 proc_status_data_ stats = Readproc_proc_status_data_ (dir / kStatusFilename_);
549 processDetails.fUserName = Execution::Platform::POSIX::uid_t2UserName (stats.ruid);
558 optional<proc_io_data_> stats = Readproc_io_data_ (dir / kIOFilename_);
559 if (stats.has_value ()) {
562 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
564 if (diffTime >= _fOptions.fMinimumAveragingInterval) {
565 if (p->fCombinedIOReadBytes) {
569 if (p->fCombinedIOWriteBytes) {
570 processDetails.fCombinedIOWriteRate =
578 DbgTrace (
"ignored: {}"_f, current_exception ());
585 results.Add (pid, processDetails);
588 _NoteCompletedCapture ();
589 _fContext.rwget ().rwref ()->fMap = newContextStats;
590 if (_fOptions.fCachePolicy == CachePolicy::eOmitUnchangedValues) {
591 _fContext.rwget ().rwref ()->fStaticSuppressedAgain =
Set<pid_t>{results.Keys ()};
595 template <
typename T>
596 static optional<T> OptionallyReadIfFileExists_ (
const filesystem::path& fullPath,
600 IgnoreExceptionsExceptThreadAbortForCall (
601 return reader (IO::FileSystem::FileInputStream::New (fullPath, IO::FileSystem::FileInputStream::eNotSeekable)));
610 for (optional<byte> b; (b = in.
ReadBlocking ()).has_value ();) {
611 if ((*b) ==
byte{0}) {
624 for (
const String& i : ReadFileStrings_ (fullPath)) {
625 auto tokens = i.Tokenize ({
'='});
626 if (tokens.size () == 2) {
627 results.
Add (tokens[0], tokens[1]);
633 static optional<String> ReadCmdLineString_ (
const filesystem::path& fullPath2CmdLineFile)
637#if USE_NOISY_TRACE_IN_THIS_MODULE_
641 bool lastCharNullRemappedToSpace =
false;
642 for (optional<byte> b; (b = in.
ReadBlocking ()).has_value ();) {
645 lastCharNullRemappedToSpace =
true;
649 lastCharNullRemappedToSpace =
false;
652 if (lastCharNullRemappedToSpace) {
661 IgnoreExceptionsExceptThreadAbortForCall (
return ReadFileString_ (
662 IO::FileSystem::FileInputStream::New (fullPath2CmdLineFile, IO::FileSystem::FileInputStream::eNotSeekable)));
667 static optional<filesystem::path> OptionallyResolveShortcut_ (
const filesystem::path& shortcutPath)
669 std::error_code ec{};
670 if (filesystem::exists (shortcutPath, ec) and filesystem::is_symlink (shortcutPath, ec)) {
671 auto r = filesystem::read_symlink (shortcutPath, ec);
678 static optional<Mapping<String, String>> OptionallyReadFileStringsMap_ (
const filesystem::path& fullPath)
681 IgnoreExceptionsExceptThreadAbortForCall (
return ReadFileStringsMap_ (fullPath));
685 struct StatFileInfo_ {
828 unsigned long long utime;
829 unsigned long long stime;
831 unsigned long long start_time;
832 unsigned long long vsize;
833 unsigned long long rss;
834 unsigned long minflt;
835 unsigned long majflt;
837 static StatFileInfo_ ReadStatFile_ (
const filesystem::path& fullPath)
839#if USE_NOISY_TRACE_IN_THIS_MODULE_
840 Debug::TraceContextBumper ctx{L
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadStatFile_",
"fullPath={}"_f, fullPath};
842 StatFileInfo_ result{};
844 byte data[10 * 1024];
845 size_t nBytes = in.
ReadAll (span{data}).size ();
846 Assert (nBytes <= std::size (data));
847#if USE_NOISY_TRACE_IN_THIS_MODULE_
848 DbgTrace (
"nBytes read = {}"_f, nBytes);
850 if (nBytes == std::size (data)) {
853 data[nBytes] =
byte{0};
855 const char* S =
reinterpret_cast<const char*
> (data);
858 S = ::strchr (S,
'(') + 1;
859 Assert (S <
reinterpret_cast<const char*
> (end (data)));
860#if USE_NOISY_TRACE_IN_THIS_MODULE_
863 const char* tmp = ::strrchr (S,
')');
864#if USE_NOISY_TRACE_IN_THIS_MODULE_
868 Assert (S <
reinterpret_cast<const char*
> (end (data)));
874 DISABLE_COMPILER_MSC_WARNING_START (4996)
875 [[maybe_unused]]
int ignoredInt{};
876 [[maybe_unused]]
long ignoredLong{};
877 [[maybe_unused]]
unsigned long ignoredUnsignedLong{};
878 [[maybe_unused]]
unsigned long long ignoredUnsignedLongLong{};
879 [[maybe_unused]]
unsigned long int ignored_unsigned_long{};
880 [[maybe_unused]]
int num =
890 "%lu %lu %lu %lu %lu "
917 &result.ppid, &ignoredInt, &ignoredInt, &ignoredInt, &ignoredInt,
920 &ignoredUnsignedLong, &result.minflt, &ignoredUnsignedLong, &result.majflt, &ignoredUnsignedLong,
923 &result.utime, &result.stime,
926 &ignoredUnsignedLongLong, &ignoredUnsignedLongLong,
929 &ignoredLong, &ignoredLong,
941 &result.vsize, &result.rss);
942 DISABLE_COMPILER_MSC_WARNING_END (4996)
946#if USE_NOISY_TRACE_IN_THIS_MODULE_
947 DbgTrace (
"result.start_time=%lld", result.start_time);
948 DbgTrace (
"result.vsize=%ld", result.vsize);
949 DbgTrace (
"result.rss=%ld", result.rss);
950 DbgTrace (
"result.utime=%lld", result.utime);
957 struct proc_io_data_ {
959 uint64_t write_bytes;
961 static optional<proc_io_data_> Readproc_io_data_ (
const filesystem::path& fullPath)
963#if USE_NOISY_TRACE_IN_THIS_MODULE_
964 Debug::TraceContextBumper ctx{
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::Readproc_io_data_",
"fullPath={}"_f, fullPath};
967#if USE_NOISY_TRACE_IN_THIS_MODULE_
968 DbgTrace (
"Skipping read cuz no access");
972 proc_io_data_ result{};
973 ifstream r{fullPath, ios_base::binary | ios_base::in};
977 if (r.getline (buf, sizeof (buf))) {
978 constexpr char kReadLbl_[] =
"read_bytes:";
979 constexpr char kWriteLbl_[] =
"write_bytes:";
980 if (::strncmp (buf, kReadLbl_, ::strlen (kReadLbl_)) == 0) {
981 result.read_bytes = CString::String2Int<
decltype (result.read_bytes)> (buf + ::strlen (kReadLbl_));
983 else if (::strncmp (buf, kWriteLbl_, ::strlen (kWriteLbl_)) == 0) {
984 result.write_bytes = CString::String2Int<
decltype (result.write_bytes)> (buf + ::strlen (kWriteLbl_));
990 static optional<ProcessType::TCPStats> ReadTCPStats_ (
const filesystem::path& fullPath)
998#if USE_NOISY_TRACE_IN_THIS_MODULE_
999 Debug::TraceContextBumper ctx{
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadTCPStats_",
"fullPath={}"_f, fullPath};
1003#if USE_NOISY_TRACE_IN_THIS_MODULE_
1004 DbgTrace (L
"Skipping read cuz no access");
1008 ProcessType::TCPStats stats;
1009 bool didSkip =
false;
1011 BinaryToText::Reader::New (IO::FileSystem::FileInputStream::New (fullPath, IO::FileSystem::FileInputStream::eNotSeekable)).ReadLines ()) {
1017 if (splits.
size () >= 4) {
1022 if (st == TCP_ESTABLISHED) {
1023 ++stats.fEstablished;
1025 else if (st == TCP_LISTEN) {
1035 static optional<MemorySizeType> ReadPrivateBytes_ (
const filesystem::path& fullPath)
1037#if USE_NOISY_TRACE_IN_THIS_MODULE_
1038 Debug::TraceContextBumper ctx{
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::ReadPrivateBytes_",
"fullPath={}"_f, fullPath};
1042#if USE_NOISY_TRACE_IN_THIS_MODULE_
1043 DbgTrace (L
"Skipping read cuz no access");
1047 MemorySizeType result{};
1049 r.open (fullPath, ios_base::in);
1053 if (r.getline (buf, sizeof (buf))) {
1055 constexpr char kPrivate1Lbl_[] =
"Private_Clean:";
1056 constexpr char kPrivate2Lbl_[] =
"Private_Dirty:";
1058 if (::strncmp (buf, kPrivate1Lbl_, ::strlen (kPrivate1Lbl_)) == 0) {
1059 result += CString::String2Int<MemorySizeType> (buf + strlen (kPrivate1Lbl_)) * 1024;
1061 else if (::strncmp (buf, kPrivate2Lbl_, ::strlen (kPrivate2Lbl_)) == 0) {
1062 result += CString::String2Int<MemorySizeType> (buf + ::strlen (kPrivate2Lbl_)) * 1024;
1070 struct proc_status_data_ {
1073 static proc_status_data_ Readproc_proc_status_data_ (
const filesystem::path& fullPath)
1075#if USE_NOISY_TRACE_IN_THIS_MODULE_
1076 Debug::TraceContextBumper ctx{
"Stroika::Frameworks::SystemPerformance::Instruments::Process::{}::Readproc_proc_status_data_",
1077 "fullPath={}"_f, fullPath};
1079 proc_status_data_ result{};
1081 r.open (fullPath, ios_base::in);
1085 if (r.getline (buf, sizeof (buf))) {
1086 constexpr char kUidLbl[] =
"Uid:";
1087 if (::strncmp (buf, kUidLbl, ::strlen (kUidLbl)) == 0) {
1088 Assert (::strlen (buf) >= strlen (kUidLbl));
1089 char* S = buf + ::strlen (kUidLbl);
1090 Assert (S < std::end (buf));
1091 int ruid = ::strtol (S, &S, 10);
1092 Assert (S < std::end (buf));
1093 [[maybe_unused]]
int euid = ::strtol (S, &S, 10);
1094 Assert (S < std::end (buf));
1095 [[maybe_unused]]
int suid = ::strtol (S, &S, 10);
1096 Assert (S < std::end (buf));
1097 [[maybe_unused]]
int fuid = ::strtol (S, &S, 10);
1098 Assert (S < std::end (buf));
1106 nonvirtual ProcessMapType capture_using_ps_ ()
1109 ProcessMapType result;
1126 constexpr size_t kVSZ_Idx_{5};
1127 constexpr size_t kUser_Idx_{6};
1128 constexpr size_t kThreadCnt_Idx_{7};
1129 constexpr size_t kColCountIncludingCmd_{9};
1130 ProcessRunner pr{
"ps -A -o \"pid,ppid,s,time,rss,vsz,user,nlwp,cmd\""sv};
1132 pr.Run (
nullptr, useStdOut);
1135 bool skippedHeader =
false;
1136 size_t headerLen = 0;
1138 if (not skippedHeader) {
1139 skippedHeader =
true;
1140 headerLen = i.RTrim ().length ();
1144 if (l.
size () < kColCountIncludingCmd_) {
1149 pid_t pid = String2Int<int> (l[0].Trim ());
1154 processDetails.fRunStatus = cvtStatusCharToStatus_ (
static_cast<char> (s[0].As<wchar_t> ()));
1158 string tmp = l[3].AsUTF8<
string> ();
1162 sscanf (tmp.c_str (),
"%d:%d:%d", &hours, &minutes, &seconds);
1167 processDetails.fUserName = l[kUser_Idx_].Trim ();
1168 processDetails.fThreadCount = String2Int<unsigned int> (l[kThreadCnt_Idx_].Trim ());
1173 const size_t kCmdNameStartsAt_ = headerLen - 3;
1177 processDetails.
fKernelProcess = not cmdLine.empty () and cmdLine[0] ==
'[';
1180 if (not t.
empty () and not t[0].empty () and t[0][0] ==
'/') {
1181 processDetails.fEXEPath = t[0].As<filesystem::path> ();
1184 if (_fOptions.fCaptureCommandLine and _fOptions.fCaptureCommandLine (pid,
NullCoalesce (processDetails.fEXEPath))) {
1185 processDetails.fCommandLine = cmdLine;
1187 result.Add (pid, processDetails);
1195#if qStroika_Foundation_Common_Platform_Windows
1197 struct UNICODE_STRING {
1199 USHORT MaximumLength;
1202 struct PROCESS_BASIC_INFORMATION {
1204 PVOID PebBaseAddress;
1206 ULONG_PTR UniqueProcessId;
1209 PVOID GetPebAddress_ (HANDLE ProcessHandle)
1211 static LONG (WINAPI * NtQueryInformationProcess) (HANDLE ProcessHandle, ULONG ProcessInformationClass, PVOID ProcessInformation,
1212 ULONG ProcessInformationLength, PULONG ReturnLength) =
1213 (LONG (WINAPI*) (HANDLE, ULONG, PVOID, ULONG, PULONG))::GetProcAddress (::LoadLibraryA (
"NTDLL.DLL"),
1214 "NtQueryInformationProcess");
1215 PROCESS_BASIC_INFORMATION pbi{};
1216 NtQueryInformationProcess (ProcessHandle, 0, &pbi,
sizeof (pbi), NULL);
1217 return pbi.PebBaseAddress;
1222#if qUseWMICollectionSupport_
1224 const String kProcessID_{
"ID Process"sv};
1225 const String kThreadCount_{
"Thread Count"sv};
1226 const String kIOReadBytesPerSecond_{
"IO Read Bytes/sec"sv};
1227 const String kIOWriteBytesPerSecond_{
"IO Write Bytes/sec"sv};
1228 const String kPercentProcessorTime_{
"% Processor Time"sv};
1233 const String kElapsedTime_{
"Elapsed Time"sv};
1237#if qStroika_Foundation_Common_Platform_Windows
1241 optional<DurationSeconds> fTotalCPUTimeEverUsed;
1242 optional<double> fCombinedIOReadBytes;
1243 optional<double> fCombinedIOWriteBytes;
1245 struct _Context : ModuleCommonContext_ {
1246#if qUseWMICollectionSupport_
1247 WMICollector fProcessWMICollector_{
"Process"sv,
1248 {WMICollector::kWildcardInstance},
1251 kIOReadBytesPerSecond_,
1252 kIOWriteBytesPerSecond_,
1253 kPercentProcessorTime_,
1259 struct InstrumentRep_Windows_ : InstrumentRepBase_<_Context> {
1261 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
1263 ProcessMapType _InternalCapture ()
1265#if qUseWMICollectionSupport_
1266 processWMICollectorLock = fProcessWMICollector_.rwget ();
1267 TimePointSeconds timeOfPrevCollection = processWMICollectorLock.rwref ().GetTimeOfLastCollection ();
1268 IgnoreExceptionsForCall (processWMICollectorLock.rwref ().Collect ());
1269 DurationSeconds timeCollecting{processWMICollectorLock.rwref ().GetTimeOfLastCollection () - timeOfPrevCollection};
1271#if USE_NOISY_TRACE_IN_THIS_MODULE_
1272 for (
const String& i : processWMICollectorLock.rwref ().GetAvailableInstances ()) {
1273 DbgTrace (L
"WMI instance name %s", i.c_str ());
1280 pid2InstanceMap.
Add (
static_cast<int> (i.fValue), i.fKey);
1284 SetPrivilegeInContext_ s{SE_DEBUG_NAME, SetPrivilegeInContext_::eIgnoreError};
1285 ProcessMapType results;
1287#if qUseWMICollectionSupport_
1288 Mapping<String, double> threadCounts_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kThreadCount_);
1289 Mapping<String, double> ioReadBytesPerSecond_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kIOReadBytesPerSecond_);
1290 Mapping<String, double> ioWriteBytesPerSecond_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kIOWriteBytesPerSecond_);
1291 Mapping<String, double> pctProcessorTime_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kPercentProcessorTime_);
1292 Mapping<String, double> processStartAt_ByPID = processWMICollectorLock.rwref ().GetCurrentValues (kElapsedTime_);
1299#if qUseWinInternalSupport_
1300 struct AllSysInfo_ {
1302 : fBuf_ (2 * 0x4000)
1305 ULONG returnLength{};
1306 NTSTATUS status = ::NtQuerySystemInformation (mSystemProcessInformation, fBuf_.begin (),
1307 static_cast<ULONG
> (fBuf_.GetSize ()), &returnLength);
1308 if (status == STATUS_BUFFER_TOO_SMALL or status == STATUS_INFO_LENGTH_MISMATCH) {
1309 fBuf_.GrowToSize (returnLength);
1315 fActualNumElts_ = returnLength /
sizeof (SYSTEM_PROCESS_INFORMATION);
1318 const SYSTEM_PROCESS_INFORMATION* GetProcessInfo ()
const
1320 return reinterpret_cast<const SYSTEM_PROCESS_INFORMATION*
> (fBuf_.begin ());
1322 static bool IsValidPID_ (
pid_t p)
1324 return static_cast<make_signed_t<pid_t>
> (p) > 0;
1328 const SYSTEM_PROCESS_INFORMATION* start = GetProcessInfo ();
1329 const SYSTEM_PROCESS_INFORMATION* end = start + fActualNumElts_;
1331 for (
const SYSTEM_PROCESS_INFORMATION* i = start; i < end; ++i) {
1332 pid_t pid =
reinterpret_cast<pid_t> (i->UniqueProcessId);
1333 if (IsValidPID_ (pid)) {
1341 if (not fThreadCntMap_.has_value ()) {
1342 const SYSTEM_PROCESS_INFORMATION* start = GetProcessInfo ();
1343 const SYSTEM_PROCESS_INFORMATION* end = start + fActualNumElts_;
1345 for (
const SYSTEM_PROCESS_INFORMATION* i = start; i < end; ++i) {
1346 pid_t pid =
reinterpret_cast<pid_t> (i->UniqueProcessId);
1347 if (IsValidPID_ (pid)) {
1348 struct PRIVATE_SYSTEM_PROCESS_INFORMATION_ {
1349 ULONG NextEntryOffset;
1350 ULONG NumberOfThreads;
1353 ULONG threadCount =
reinterpret_cast<const PRIVATE_SYSTEM_PROCESS_INFORMATION_*
> (i)->NumberOfThreads;
1354 tmp.
Add (pid, threadCount);
1357 fThreadCntMap_ = tmp;
1359 return *fThreadCntMap_;
1361 unsigned int fActualNumElts_;
1362 mutable optional<Mapping<pid_t, unsigned int>> fThreadCntMap_;
1364 AllSysInfo_ allSysInfo;
1370#if qUseCreateToolhelp32SnapshotToCountThreads
1371 ThreadCounter_ threadCounter;
1374 for (
pid_t pid : allPids) {
1375 if (_fOptions.fRestrictToPIDs) {
1376 if (not _fOptions.fRestrictToPIDs->Contains (pid)) {
1380 if (_fOptions.fOmitPIDs) {
1381 if (_fOptions.fOmitPIDs->Contains (pid)) {
1386 bool grabStaticData = _fOptions.fCachePolicy == CachePolicy::eIncludeAllRequestedValues or
1387 not _fContext.cget ().cref ()->fStaticSuppressedAgain.Contains (pid);
1389 HANDLE hProcess = ::OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
1390 if (hProcess !=
nullptr) {
1391 [[maybe_unused]]
auto&& cleanup =
Finally ([hProcess] ()
noexcept {
Verify (::CloseHandle (hProcess)); });
1392 if (grabStaticData) {
1393 optional<String> processName;
1394 optional<filesystem::path> processEXEPath;
1395 optional<pid_t> parentProcessID;
1396 optional<String> cmdLine;
1397 optional<String> userName;
1398 LookupProcessPath_ (pid, hProcess, &processName, &processEXEPath, &parentProcessID,
1399 _fOptions.fCaptureCommandLine ? &cmdLine : nullptr, &userName);
1400 if (_fOptions.fProcessNameReadPolicy == Options::eAlways or
1401 (_fOptions.fProcessNameReadPolicy == Options::eOnlyIfEXENotRead and not processEXEPath.has_value ())) {
1402 Memory::CopyToIf (&processInfo.fProcessName, processName);
1404 Memory::CopyToIf (&processInfo.fEXEPath, processEXEPath);
1406 Memory::CopyToIf (&processInfo.fCommandLine, cmdLine);
1407 Memory::CopyToIf (&processInfo.fUserName, userName);
1410 PROCESS_MEMORY_COUNTERS_EX memInfo{};
1411 if (::GetProcessMemoryInfo (hProcess,
reinterpret_cast<PROCESS_MEMORY_COUNTERS*
> (&memInfo),
sizeof (memInfo))) {
1413 processInfo.fPrivateBytes = memInfo.PrivateUsage;
1427 tmp.LowPart = ft.dwLowDateTime;
1428 tmp.HighPart = ft.dwHighDateTime;
1431 FILETIME creationTime{};
1432 FILETIME exitTime{};
1433 FILETIME kernelTime{};
1434 FILETIME userTime{};
1435 if (::GetProcessTimes (hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {
1436 if (grabStaticData) {
1438 processInfo.fProcessStartedAt = DateTime{creationTime};
1441 convertFILETIME2DurationSeconds (kernelTime) + convertFILETIME2DurationSeconds (userTime);
1444 DbgTrace (L
"error calling GetProcessTimes: {}"_f, ::GetLastError ());
1448 IO_COUNTERS ioCounters{};
1449 if (::GetProcessIoCounters (hProcess, &ioCounters)) {
1454 DbgTrace (L
"error calling GetProcessIoCounters: {}"_f, ::GetLastError ());
1458#if qUseCreateToolhelp32SnapshotToCountThreads
1459 processInfo.fThreadCount = threadCounter.CountThreads (pid);
1462#if qUseWinInternalSupport_
1464 if (
auto i = allSysInfo.GetThreadCountMap ().Lookup (pid)) {
1465 processInfo.fThreadCount = *i;
1471#if qUseWMICollectionSupport_
1474 if (
auto o = threadCounts_ByPID.
Lookup (instanceVal)) {
1475 processInfo.fThreadCount =
static_cast<unsigned int> (*o);
1477 if (
auto o = ioReadBytesPerSecond_ByPID.
Lookup (instanceVal)) {
1480 if (
auto o = ioWriteBytesPerSecond_ByPID.
Lookup (instanceVal)) {
1481 processInfo.fCombinedIOWriteRate = *o;
1483 if (
auto o = pctProcessorTime_ByPID.
Lookup (instanceVal)) {
1486 if (grabStaticData) {
1487 if (
auto o = processStartAt_ByPID.
Lookup (instanceVal)) {
1488 processInfo.fProcessStartedAt = DateTime::Now ().AddSeconds (-
static_cast<time_t
> (*o));
1493 if (not processInfo.
fCombinedIOReadRate.has_value () or not processInfo.fCombinedIOWriteRate.has_value () or
1495 if (optional<PerfStats_> p = _fContext.load ()->fMap.Lookup (pid)) {
1496 auto diffTime = now - p->fCapturedAt;
1497 if (diffTime >= _fOptions.fMinimumAveragingInterval) {
1502 processInfo.fCombinedIOWriteRate =
1530 results.Add (pid, processInfo);
1532 _NoteCompletedCapture (now);
1533 _fContext.rwget ().rwref ()->fMap = newContextStats;
1534 if (_fOptions.fCachePolicy == CachePolicy::eOmitUnchangedValues) {
1535 _fContext.rwget ().rwref ()->fStaticSuppressedAgain =
Set<pid_t>{results.Keys ()};
1543 DWORD aProcesses[10 * 1024];
1547 if (not::EnumProcesses (aProcesses,
sizeof (aProcesses), &cbNeeded)) {
1553 DWORD cProcesses = cbNeeded /
sizeof (DWORD);
1554 for (DWORD i = 0; i < cProcesses; ++i) {
1555 result.
Add (aProcesses[i]);
1559 nonvirtual
void LookupProcessPath_ (
pid_t pid, HANDLE hProcess, optional<String>* processName, optional<filesystem::path>* processEXEPath,
1560 optional<pid_t>* parentProcessID, optional<String>* cmdLine, optional<String>* userName)
1569 if (::EnumProcessModules (hProcess, &hMod,
sizeof (hMod), &cbNeeded)) {
1570 TCHAR moduleFullPath[MAX_PATH];
1571 moduleFullPath[0] =
'\0';
1572 if (::GetModuleFileNameEx (hProcess, hMod, moduleFullPath,
static_cast<DWORD
> (std::size (moduleFullPath))) != 0) {
1573 *processEXEPath = filesystem::path{moduleFullPath};
1575 if (processName !=
nullptr) {
1576 TCHAR moduleBaseName[MAX_PATH];
1577 moduleBaseName[0] =
'\0';
1578 if (::GetModuleBaseName (hProcess, hMod, moduleBaseName,
static_cast<DWORD
> (std::size (moduleBaseName))) != 0) {
1579 *processName = String::FromSDKString (moduleBaseName);
1583 if (cmdLine !=
nullptr) {
1584 if (_fOptions.fCaptureCommandLine ==
nullptr or not _fOptions.fCaptureCommandLine (pid,
NullCoalesce (*processEXEPath))) {
1589 const ULONG ProcessBasicInformation = 0;
1590 static LONG (WINAPI * NtQueryInformationProcess) (HANDLE ProcessHandle, ULONG ProcessInformationClass, PVOID ProcessInformation,
1591 ULONG ProcessInformationLength, PULONG ReturnLength) =
1592 (LONG (WINAPI*) (HANDLE, ULONG, PVOID, ULONG, PULONG))::GetProcAddress (::LoadLibraryA (
"NTDLL.DLL"),
1593 "NtQueryInformationProcess");
1594 if (NtQueryInformationProcess) {
1597 if (NtQueryInformationProcess (hProcess, ProcessBasicInformation, &pbi,
sizeof (pbi), &ulSize) >= 0 && ulSize ==
sizeof (pbi)) {
1598 *parentProcessID =
static_cast<pid_t> (pbi[5]);
1600 if (cmdLine !=
nullptr) {
1602 void* pebAddress = GetPebAddress_ (hProcess);
1603 if (pebAddress !=
nullptr) {
1605 constexpr int kUserProcParamsOffset_ = 0x20;
1606 constexpr int kCmdLineOffset_ = 112;
1608 constexpr int kUserProcParamsOffset_ = 0x10;
1609 constexpr int kCmdLineOffset_ = 0x40;
1612 void* rtlUserProcParamsAddress{};
1613 if (not::ReadProcessMemory (hProcess, (PCHAR)pebAddress + kUserProcParamsOffset_, &rtlUserProcParamsAddress,
1614 sizeof (PVOID), NULL)) {
1617 UNICODE_STRING commandLine;
1620 if (not::ReadProcessMemory (hProcess, (PCHAR)rtlUserProcParamsAddress + kCmdLineOffset_, &commandLine,
1621 sizeof (commandLine), NULL)) {
1625 size_t strLen = commandLine.Length /
sizeof (WCHAR);
1628 if (not::ReadProcessMemory (hProcess, commandLine.Buffer, commandLineContents.begin (), commandLine.Length, NULL)) {
1631 commandLineContents[strLen] = 0;
1632 *cmdLine = commandLineContents.begin ();
1645 HANDLE processToken = 0;
1646 if (::OpenProcessToken (hProcess, TOKEN_QUERY, &processToken) != 0) {
1647 [[maybe_unused]]
auto&& cleanup =
Finally ([processToken] ()
noexcept {
Verify (::CloseHandle (processToken)); });
1653 byte tokenUserBuf[1024];
1654 TOKEN_USER* tokenUser =
reinterpret_cast<TOKEN_USER*
> (begin (tokenUserBuf));
1655 if (::GetTokenInformation (processToken, TokenUser, tokenUser,
sizeof (tokenUserBuf), &nlen) != 0) {
1656 Assert (nlen >=
sizeof (TOKEN_USER));
1658 *userName = Execution::Platform::Windows::SID22UserName (tokenUser->User.Sid);
1669 struct ProcessInstrumentRep_
1670#if qStroika_Foundation_Common_Platform_Linux
1671 : InstrumentRep_Linux_
1672#elif qStroika_Foundation_Common_Platform_Windows
1673 : InstrumentRep_Windows_
1675 : InstrumentRepBase_<ModuleCommonContext_>
1678#if qStroika_Foundation_Common_Platform_Linux
1679 using inherited = InstrumentRep_Linux_;
1680#elif qStroika_Foundation_Common_Platform_Windows
1681 using inherited = InstrumentRep_Windows_;
1683 using inherited = InstrumentRepBase_<ModuleCommonContext_>;
1685 ProcessInstrumentRep_ (
const Options& options,
const shared_ptr<_Context>& context = Memory::MakeSharedPtr<_Context> ())
1686 : inherited{options, context}
1688 Require (_fOptions.fMinimumAveragingInterval > 0s);
1694 Measurement m{Instruments::Process::kProcessMapMeasurement,
1696 results.fMeasurements.Add (m);
1701 auto before = _GetCaptureContextTime ();
1702 Info rawMeasurement = _InternalCapture ();
1703 if (outMeasuredAt !=
nullptr) {
1705 *outMeasuredAt =
Range<TimePointSeconds> (before, _GetCaptureContextTime (), Openness::eClosed, Openness::eClosed);
1707 return rawMeasurement;
1709 virtual unique_ptr<IRep> Clone ()
const override
1711 return make_unique<ProcessInstrumentRep_> (_fOptions, _fContext.load ());
1713 ProcessMapType _InternalCapture ()
1715 AssertExternallySynchronizedMutex::WriteContext declareContext{*
this};
1716#if USE_NOISY_TRACE_IN_THIS_MODULE_
1719#if qStroika_Foundation_Common_Platform_Linux or qStroika_Foundation_Common_Platform_Windows
1720 return inherited::_InternalCapture ();
1722 return ProcessMapType{};
1741 mapper.
AddClass<ProcessType::TCPStats> ({
1742 {
"Established"sv, &ProcessType::TCPStats::fEstablished},
1743 {
"Listening"sv, &ProcessType::TCPStats::fListening},
1744 {
"Other"sv, &ProcessType::TCPStats::fOther},
1750 {
"Process-Name"sv, &ProcessType::fProcessName},
1751 {
"User-Name"sv, &ProcessType::fUserName},
1752 {
"Command-Line"sv, &ProcessType::fCommandLine},
1753 {
"Current-Working-Directory"sv, &ProcessType::fCurrentWorkingDirectory},
1754 {
"Environment-Variables"sv, &ProcessType::fEnvironmentVariables},
1755 {
"EXE-Path"sv, &ProcessType::fEXEPath},
1757 {
"Process-Started-At"sv, &ProcessType::fProcessStartedAt},
1758 {
"Run-Status"sv, &ProcessType::fRunStatus},
1762 {
"Private-Bytes"sv, &ProcessType::fPrivateBytes},
1768 {
"Thread-Count"sv, &ProcessType::fThreadCount},
1770 {
"Combined-IO-Write-Rate"sv, &ProcessType::fCombinedIOWriteRate},
1773 {
"TCP-Stats"sv, &ProcessType::fTCPStats},
1779Instruments::Process::Instrument::Instrument (
const Options& options)
1781 make_unique<ProcessInstrumentRep_> (options),
1782 {kProcessMapMeasurement},
1784 kObjectVariantMapper}
1797 ProcessInstrumentRep_* myCap =
dynamic_cast<ProcessInstrumentRep_*
> (fCaptureRep_.get ());
1799 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 ...