4#include "Stroika/Frameworks/StroikaPreComp.h"
9#if qStroika_Foundation_Common_Platform_Windows
13#include "Stroika/Foundation/Characters/FloatConversion.h"
19#include "Stroika/Foundation/Execution/Exceptions.h"
20#include "Stroika/Foundation/Execution/ProcessRunner.h"
23#include "Stroika/Foundation/Math/Common.h"
24#include "Stroika/Foundation/Streams/MemoryStream.h"
34using namespace Stroika::Foundation::Memory;
36using namespace Stroika::Frameworks;
37using namespace Stroika::Frameworks::SystemPerformance;
39using Instruments::CPU::Info;
45#ifndef qUseWMICollectionSupport_
46#define qUseWMICollectionSupport_ qStroika_Foundation_Common_Platform_Windows
49#if qUseWMICollectionSupport_
55#if qUseWMICollectionSupport_
57 const String kInstanceName_{
""sv};
59 const String kProcessorQueueLength_{
"Processor Queue Length"sv};
68String Instruments::CPU::Info::ToString ()
const
74 template <
typename CONTEXT>
78#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
80 template <
typename ELT>
81 double EstimateRunQFromLoadAveArray_ (Time::DurationSeconds::rep backNSeconds, ELT loadAveArray[3])
84 Require (backNSeconds >= 0);
85 double backNMinutes = backNSeconds / 60.0;
86 if (backNMinutes <= 1) {
87 return static_cast<double> (loadAveArray[0]);
89 else if (backNMinutes <= 5) {
90 double distFrom1 = (backNMinutes - 1);
91 double distFrom5 = (5.0 - backNMinutes);
92 return static_cast<double> (loadAveArray[0]) * (1.0 - distFrom1 / 4) +
static_cast<double> (loadAveArray[1]) * (1.0 - distFrom5 / 4);
94 else if (backNMinutes <= 15) {
95 double distFrom5 = (backNMinutes - 5);
96 double distFrom15 = (15.0 - backNMinutes);
97 return static_cast<double> (loadAveArray[1]) * (1.0 - distFrom5 / 10) +
static_cast<double> (loadAveArray[2]) * (1.0 - distFrom15 / 10);
100 return static_cast<double> (loadAveArray[2]);
106#if qStroika_Foundation_Common_Platform_Linux
108 struct POSIXSysTimeCaptureContext_ {
122 optional<POSIXSysTimeCaptureContext_> fLastSysTimeCapture;
125 struct InstrumentRep_Linux_ : InstrumentRepBase_<_Context> {
127 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
184 static POSIXSysTimeCaptureContext_ GetSysTimes_ ()
186 POSIXSysTimeCaptureContext_ result{};
189 static const filesystem::path kFileName_{
"/proc/stat"sv};
192 reader.ReadMatrix (IO::FileSystem::FileInputStream::New (kFileName_, IO::FileSystem::FileInputStream::eNotSeekable))) {
193#if USE_NOISY_TRACE_IN_THIS_MODULE_
194 DbgTrace (L
"in Instruments::CPU::capture_GetSysTimes_ linesize={}, line[0]={}", line.size (), line.empty () ?
""_k : line[0]);
196 size_t sz = line.size ();
197 if (sz >= 5 and line[0] ==
"cpu"sv) {
198 result.user = ToFloat<double> (line[1]);
199 result.nice = ToFloat<double> (line[2]);
200 result.system = ToFloat<double> (line[3]);
201 result.idle = ToFloat<double> (line[4]);
203 result.iowait = ToFloat<double> (line[5]);
206 result.irq = ToFloat<double> (line[6]);
209 result.softirq = ToFloat<double> (line[7]);
212 result.steal = ToFloat<double> (line[8]);
219 struct CPUUsageTimes_ {
220 double fProcessCPUUsage;
221 double fTotalCPUUsage;
223 nonvirtual Info _InternalCapture ()
226#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
229 int lr = ::getloadavg (loadAve, NEltsOf (loadAve));
231 result.fLoadAverage = Info::LoadAverage{loadAve[0], loadAve[1], loadAve[2]};
232 auto tcNow = Time::GetTickCount ();
233 result.fRunQLength = EstimateRunQFromLoadAveArray_ ((tcNow - _GetCaptureContextTime ()).count (), loadAve);
238 DbgTrace (
"getloadave failed - with result = {}"_f, lr);
242 auto getCPUTime = [&] (POSIXSysTimeCaptureContext_* referenceValue) -> optional<CPUUsageTimes_> {
243 POSIXSysTimeCaptureContext_ newVal = GetSysTimes_ ();
244 *referenceValue = newVal;
245 if (
auto baseline = _fContext.load ()->fLastSysTimeCapture) {
251 idleTime += (newVal.idle - baseline->idle);
252 idleTime += (newVal.iowait - baseline->iowait);
254 double processNonIdleTime = 0;
255 processNonIdleTime += (newVal.user - baseline->user);
256 processNonIdleTime += (newVal.nice - baseline->nice);
257 processNonIdleTime += (newVal.system - baseline->system);
259 double nonIdleTime = processNonIdleTime;
260 nonIdleTime += (newVal.irq - baseline->irq);
261 nonIdleTime += (newVal.softirq - baseline->softirq);
263 double totalTime = idleTime + nonIdleTime;
264 if (totalTime <= this->_fOptions.fMinimumAveragingInterval.count ()) {
266 DbgTrace (
"Warning - times too close together for cputime"_f);
269 Assert (totalTime > 0);
270 double totalProcessCPUUsage = processNonIdleTime / totalTime;
271 double totalCPUUsage = nonIdleTime / totalTime;
272 return CPUUsageTimes_{totalProcessCPUUsage, totalCPUUsage};
276 POSIXSysTimeCaptureContext_ referenceValue{};
277 if (
auto tmp = getCPUTime (&referenceValue)) {
279 result.fTotalProcessCPUUsage = tmp->fProcessCPUUsage * nLogicalCores;
280 result.fTotalCPUUsage = tmp->fTotalCPUUsage * nLogicalCores;
281 result.fTotalLogicalCores = nLogicalCores;
283 _NoteCompletedCapture ();
284 _fContext.rwget ().rwref ()->fLastSysTimeCapture = referenceValue;
291#if qStroika_Foundation_Common_Platform_Windows
293 struct WinSysTimeCaptureContext_ {
299 optional<WinSysTimeCaptureContext_> fLastSysTimeCapture{};
300#if qUseWMICollectionSupport_
301 WMICollector fSystemWMICollector_{
"System"sv, {kInstanceName_}, {kProcessorQueueLength_}};
305 struct InstrumentRep_Windows_ : InstrumentRepBase_<_Context> {
307 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
309 nonvirtual Info _InternalCapture ()
311 auto getCPUTime = [=] (WinSysTimeCaptureContext_* newRawValueToStoreAsNextbaseline) -> optional<double> {
313 auto getSysTimes = [] () -> WinSysTimeCaptureContext_ {
314 auto getAsSeconds = [] (const ::FILETIME& ft) {
316 ui.LowPart = ft.dwLowDateTime;
317 ui.HighPart = ft.dwHighDateTime;
318 return static_cast<double> (ui.QuadPart) / 10000000;
320 ::FILETIME curIdleTime_{};
321 ::FILETIME curKernelTime_{};
322 ::FILETIME curUserTime_{};
323 Verify (::GetSystemTimes (&curIdleTime_, &curKernelTime_, &curUserTime_));
324 return WinSysTimeCaptureContext_{getAsSeconds (curIdleTime_), getAsSeconds (curKernelTime_), getAsSeconds (curUserTime_)};
327 WinSysTimeCaptureContext_ newVal = getSysTimes ();
328 *newRawValueToStoreAsNextbaseline = newVal;
329 if (_fContext.load ()->fLastSysTimeCapture) {
330 WinSysTimeCaptureContext_ baseline = *_fContext.load ()->fLastSysTimeCapture;
331 double idleTimeOverInterval = newVal.IdleTime - baseline.IdleTime;
332 double kernelTimeOverInterval = newVal.KernelTime - baseline.KernelTime;
333 double userTimeOverInterval = newVal.UserTime - baseline.UserTime;
340 double sys = kernelTimeOverInterval + userTimeOverInterval;
342 double cpu = 1 - idleTimeOverInterval / sys;
350 WinSysTimeCaptureContext_ newRawValueToStoreAsNextbaseline;
351 result.fTotalCPUUsage = getCPUTime (&newRawValueToStoreAsNextbaseline);
352 result.fTotalProcessCPUUsage = result.fTotalCPUUsage;
354#if qUseWMICollectionSupport_
355 _fContext.rwget ().rwref ()->fSystemWMICollector_.Collect ();
356 Memory::CopyToIf (&result.fRunQLength,
357 _fContext.rwget ().rwref ()->fSystemWMICollector_.PeekCurrentValue (kInstanceName_, kProcessorQueueLength_));
361 _NoteCompletedCapture ();
362 _fContext.rwget ().rwref ()->fLastSysTimeCapture = newRawValueToStoreAsNextbaseline;
374 struct CPUInstrumentRep_
375#if qStroika_Foundation_Common_Platform_Linux
376 : InstrumentRep_Linux_
377#elif qStroika_Foundation_Common_Platform_Windows
378 : InstrumentRep_Windows_
380 : InstrumentRepBase_<SystemPerformance::Support::Context>
383#if qStroika_Foundation_Common_Platform_Linux
384 using inherited = InstrumentRep_Linux_;
385#elif qStroika_Foundation_Common_Platform_Windows
386 using inherited = InstrumentRep_Windows_;
388 using inherited = InstrumentRepBase_<SystemPerformance::Support::Context>;
390 CPUInstrumentRep_ (
const Options& options,
const shared_ptr<_Context>& context = make_shared<_Context> ())
391 : inherited{options, context}
393 Require (_fOptions.fMinimumAveragingInterval > 0s);
405 return Do_Capture_Raw<Info> ([
this] () {
return _InternalCapture (); }, outMeasuredAt);
407 virtual unique_ptr<IRep> Clone ()
const override
409 return make_unique<CPUInstrumentRep_> (_fOptions, _fContext.load ());
411 nonvirtual Info _InternalCapture ()
413 AssertExternallySynchronizedMutex::WriteContext declareContext{*
this};
414#if USE_NOISY_TRACE_IN_THIS_MODULE_
417#if qStroika_Foundation_Common_Platform_Linux or qStroika_Foundation_Common_Platform_Windows
418 Info result = inherited::_InternalCapture ();
434#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
435 mapper.
AddClass<Info::LoadAverage> ({
436 {
"1-minute"_k, &Info::LoadAverage::f1MinuteAve},
437 {
"5-minute"_k, &Info::LoadAverage::f5MinuteAve},
438 {
"15-minute"_k, &Info::LoadAverage::f15MinuteAve},
443#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
444 {
"Load-Average"_k, &Info::fLoadAverage},
446 {
"Total-Logical-Cores"_k, &Info::fTotalLogicalCores},
447 {
"Total-Process-CPU-Usage"_k, &Info::fTotalProcessCPUUsage},
448 {
"Total-CPU-Usage"_k, &Info::fTotalCPUUsage},
449 {
"Run-Q-Length"_k, &Info::fRunQLength},
454Instruments::CPU::Instrument::Instrument (
const Options& options)
456 make_unique<CPUInstrumentRep_> (options),
459 kObjectVariantMapper}
472 CPUInstrumentRep_* myCap =
dynamic_cast<CPUInstrumentRep_*
> (fCaptureRep_.get ());
474 return myCap->Capture_Raw (measurementTimeOut);
#define RequireNotNull(p)
wstring Capture(const Options &options={})
String is like std::u32string, except it is much easier to use, often much more space efficient,...
A generalization of a vector: a container whose elements are keyed by the natural numbers.
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
This COULD be easily used to read CSV files, or tab-delimited files, for example.
nonvirtual String WriteAsString(const VariantValue &v) const
Duration is a chrono::duration<double> (=.
T ToFloat(span< const CHAR_T > s)
unsigned int GetNumberOfLogicalCPUCores(const chrono::duration< double > &allowedStaleness=1min)
return the number of currently available CPU cores on this (virtual) machine