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"
25#include "Stroika/Foundation/Streams/MemoryStream.h"
35using namespace Stroika::Foundation::Memory;
37using namespace Stroika::Frameworks;
38using namespace Stroika::Frameworks::SystemPerformance;
40using Instruments::CPU::Info;
46#ifndef qUseWMICollectionSupport_
47#define qUseWMICollectionSupport_ qStroika_Foundation_Common_Platform_Windows
50#if qUseWMICollectionSupport_
56#if qUseWMICollectionSupport_
58 const String kInstanceName_{
""sv};
60 const String kProcessorQueueLength_{
"Processor Queue Length"sv};
69String Instruments::CPU::Info::ToString ()
const
75 template <
typename CONTEXT>
79#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
81 template <
typename ELT>
82 double EstimateRunQFromLoadAveArray_ (Time::DurationSeconds::rep backNSeconds, ELT loadAveArray[3])
85 Require (backNSeconds >= 0);
86 double backNMinutes = backNSeconds / 60.0;
87 if (backNMinutes <= 1) {
88 return static_cast<double> (loadAveArray[0]);
90 else if (backNMinutes <= 5) {
91 double distFrom1 = (backNMinutes - 1);
92 double distFrom5 = (5.0 - backNMinutes);
93 return static_cast<double> (loadAveArray[0]) * (1.0 - distFrom1 / 4) +
static_cast<double> (loadAveArray[1]) * (1.0 - distFrom5 / 4);
95 else if (backNMinutes <= 15) {
96 double distFrom5 = (backNMinutes - 5);
97 double distFrom15 = (15.0 - backNMinutes);
98 return static_cast<double> (loadAveArray[1]) * (1.0 - distFrom5 / 10) +
static_cast<double> (loadAveArray[2]) * (1.0 - distFrom15 / 10);
101 return static_cast<double> (loadAveArray[2]);
107#if qStroika_Foundation_Common_Platform_Linux
109 struct POSIXSysTimeCaptureContext_ {
123 optional<POSIXSysTimeCaptureContext_> fLastSysTimeCapture;
126 struct InstrumentRep_Linux_ : InstrumentRepBase_<_Context> {
128 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
185 static POSIXSysTimeCaptureContext_ GetSysTimes_ ()
187 POSIXSysTimeCaptureContext_ result{};
190 static const filesystem::path kFileName_{
"/proc/stat"sv};
193 reader.ReadMatrix (IO::FileSystem::FileInputStream::New (kFileName_, IO::FileSystem::FileInputStream::eNotSeekable))) {
194#if USE_NOISY_TRACE_IN_THIS_MODULE_
195 DbgTrace (L
"in Instruments::CPU::capture_GetSysTimes_ linesize={}, line[0]={}", line.size (), line.empty () ?
""_k : line[0]);
197 size_t sz = line.size ();
198 if (sz >= 5 and line[0] ==
"cpu"sv) {
199 result.user = ToFloat<double> (line[1]);
200 result.nice = ToFloat<double> (line[2]);
201 result.system = ToFloat<double> (line[3]);
202 result.idle = ToFloat<double> (line[4]);
204 result.iowait = ToFloat<double> (line[5]);
207 result.irq = ToFloat<double> (line[6]);
210 result.softirq = ToFloat<double> (line[7]);
213 result.steal = ToFloat<double> (line[8]);
220 struct CPUUsageTimes_ {
221 double fProcessCPUUsage;
222 double fTotalCPUUsage;
224 nonvirtual Info _InternalCapture ()
227#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
230 int lr = ::getloadavg (loadAve, std::size (loadAve));
232 result.fLoadAverage = Info::LoadAverage{loadAve[0], loadAve[1], loadAve[2]};
233 auto tcNow = Time::GetTickCount ();
234 result.fRunQLength = EstimateRunQFromLoadAveArray_ ((tcNow - _GetCaptureContextTime ()).count (), loadAve);
239 DbgTrace (
"getloadave failed - with result = {}"_f, lr);
243 auto getCPUTime = [&] (POSIXSysTimeCaptureContext_* referenceValue) -> optional<CPUUsageTimes_> {
244 POSIXSysTimeCaptureContext_ newVal = GetSysTimes_ ();
245 *referenceValue = newVal;
246 if (
auto baseline = _fContext.load ()->fLastSysTimeCapture) {
252 idleTime += (newVal.idle - baseline->idle);
253 idleTime += (newVal.iowait - baseline->iowait);
255 double processNonIdleTime = 0;
256 processNonIdleTime += (newVal.user - baseline->user);
257 processNonIdleTime += (newVal.nice - baseline->nice);
258 processNonIdleTime += (newVal.system - baseline->system);
260 double nonIdleTime = processNonIdleTime;
261 nonIdleTime += (newVal.irq - baseline->irq);
262 nonIdleTime += (newVal.softirq - baseline->softirq);
264 double totalTime = idleTime + nonIdleTime;
265 if (totalTime <= this->_fOptions.fMinimumAveragingInterval.count ()) {
267 DbgTrace (
"Warning - times too close together for cputime"_f);
270 Assert (totalTime > 0);
271 double totalProcessCPUUsage = processNonIdleTime / totalTime;
272 double totalCPUUsage = nonIdleTime / totalTime;
273 return CPUUsageTimes_{totalProcessCPUUsage, totalCPUUsage};
277 POSIXSysTimeCaptureContext_ referenceValue{};
278 if (
auto tmp = getCPUTime (&referenceValue)) {
280 result.fTotalProcessCPUUsage = tmp->fProcessCPUUsage * nLogicalCores;
281 result.fTotalCPUUsage = tmp->fTotalCPUUsage * nLogicalCores;
282 result.fTotalLogicalCores = nLogicalCores;
284 _NoteCompletedCapture ();
285 _fContext.rwget ().rwref ()->fLastSysTimeCapture = referenceValue;
292#if qStroika_Foundation_Common_Platform_Windows
294 struct WinSysTimeCaptureContext_ {
300 optional<WinSysTimeCaptureContext_> fLastSysTimeCapture{};
301#if qUseWMICollectionSupport_
302 WMICollector fSystemWMICollector_{
"System"sv, {kInstanceName_}, {kProcessorQueueLength_}};
306 struct InstrumentRep_Windows_ : InstrumentRepBase_<_Context> {
308 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
310 nonvirtual Info _InternalCapture ()
312 auto getCPUTime = [=] (WinSysTimeCaptureContext_* newRawValueToStoreAsNextbaseline) -> optional<double> {
314 auto getSysTimes = [] () -> WinSysTimeCaptureContext_ {
315 auto getAsSeconds = [] (const ::FILETIME& ft) {
317 ui.LowPart = ft.dwLowDateTime;
318 ui.HighPart = ft.dwHighDateTime;
319 return static_cast<double> (ui.QuadPart) / 10000000;
321 ::FILETIME curIdleTime_{};
322 ::FILETIME curKernelTime_{};
323 ::FILETIME curUserTime_{};
324 Verify (::GetSystemTimes (&curIdleTime_, &curKernelTime_, &curUserTime_));
325 return WinSysTimeCaptureContext_{getAsSeconds (curIdleTime_), getAsSeconds (curKernelTime_), getAsSeconds (curUserTime_)};
328 WinSysTimeCaptureContext_ newVal = getSysTimes ();
329 *newRawValueToStoreAsNextbaseline = newVal;
330 if (_fContext.load ()->fLastSysTimeCapture) {
331 WinSysTimeCaptureContext_ baseline = *_fContext.load ()->fLastSysTimeCapture;
332 double idleTimeOverInterval = newVal.IdleTime - baseline.IdleTime;
333 double kernelTimeOverInterval = newVal.KernelTime - baseline.KernelTime;
334 double userTimeOverInterval = newVal.UserTime - baseline.UserTime;
341 double sys = kernelTimeOverInterval + userTimeOverInterval;
343 double cpu = 1 - idleTimeOverInterval / sys;
351 WinSysTimeCaptureContext_ newRawValueToStoreAsNextbaseline;
352 result.fTotalCPUUsage = getCPUTime (&newRawValueToStoreAsNextbaseline);
353 result.fTotalProcessCPUUsage = result.fTotalCPUUsage;
355#if qUseWMICollectionSupport_
356 _fContext.rwget ().rwref ()->fSystemWMICollector_.Collect ();
357 Memory::CopyToIf (&result.fRunQLength,
358 _fContext.rwget ().rwref ()->fSystemWMICollector_.PeekCurrentValue (kInstanceName_, kProcessorQueueLength_));
362 _NoteCompletedCapture ();
363 _fContext.rwget ().rwref ()->fLastSysTimeCapture = newRawValueToStoreAsNextbaseline;
375 struct CPUInstrumentRep_
376#if qStroika_Foundation_Common_Platform_Linux
377 : InstrumentRep_Linux_
378#elif qStroika_Foundation_Common_Platform_Windows
379 : InstrumentRep_Windows_
381 : InstrumentRepBase_<SystemPerformance::Support::Context>
384#if qStroika_Foundation_Common_Platform_Linux
385 using inherited = InstrumentRep_Linux_;
386#elif qStroika_Foundation_Common_Platform_Windows
387 using inherited = InstrumentRep_Windows_;
389 using inherited = InstrumentRepBase_<SystemPerformance::Support::Context>;
391 CPUInstrumentRep_ (
const Options& options,
const shared_ptr<_Context>& context = Memory::MakeSharedPtr<_Context> ())
392 : inherited{options, context}
394 Require (_fOptions.fMinimumAveragingInterval > 0s);
406 return Do_Capture_Raw<Info> ([
this] () {
return _InternalCapture (); }, outMeasuredAt);
408 virtual unique_ptr<IRep> Clone ()
const override
410 return make_unique<CPUInstrumentRep_> (_fOptions, _fContext.load ());
412 nonvirtual Info _InternalCapture ()
414 AssertExternallySynchronizedMutex::WriteContext declareContext{*
this};
415#if USE_NOISY_TRACE_IN_THIS_MODULE_
418#if qStroika_Foundation_Common_Platform_Linux or qStroika_Foundation_Common_Platform_Windows
419 Info result = inherited::_InternalCapture ();
435#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
436 mapper.
AddClass<Info::LoadAverage> ({
437 {
"1-minute"_k, &Info::LoadAverage::f1MinuteAve},
438 {
"5-minute"_k, &Info::LoadAverage::f5MinuteAve},
439 {
"15-minute"_k, &Info::LoadAverage::f15MinuteAve},
444#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
445 {
"Load-Average"_k, &Info::fLoadAverage},
447 {
"Total-Logical-Cores"_k, &Info::fTotalLogicalCores},
448 {
"Total-Process-CPU-Usage"_k, &Info::fTotalProcessCPUUsage},
449 {
"Total-CPU-Usage"_k, &Info::fTotalCPUUsage},
450 {
"Run-Q-Length"_k, &Info::fRunQLength},
455Instruments::CPU::Instrument::Instrument (
const Options& options)
457 make_unique<CPUInstrumentRep_> (options),
460 kObjectVariantMapper}
473 CPUInstrumentRep_* myCap =
dynamic_cast<CPUInstrumentRep_*
> (fCaptureRep_.get ());
475 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