Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
CPU.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <filesystem>
7#include <optional>
8
9#if qStroika_Foundation_Common_Platform_Windows
10#include <Windows.h>
11#endif
12
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"
26
27#include "CPU.h"
28
29using namespace Stroika::Foundation;
33using namespace Stroika::Foundation::Execution;
34using namespace Stroika::Foundation::Memory;
35
36using namespace Stroika::Frameworks;
37using namespace Stroika::Frameworks::SystemPerformance;
38
39using Instruments::CPU::Info;
41
42// Comment this in to turn on aggressive noisy DbgTrace in this module
43//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
44
45#ifndef qUseWMICollectionSupport_
46#define qUseWMICollectionSupport_ qStroika_Foundation_Common_Platform_Windows
47#endif
48
49#if qUseWMICollectionSupport_
51
53#endif
54
55#if qUseWMICollectionSupport_
56namespace {
57 const String kInstanceName_{""sv};
58
59 const String kProcessorQueueLength_{"Processor Queue Length"sv};
60}
61#endif
62
63/*
64 ********************************************************************************
65 **************************** Instruments::CPU::Info ****************************
66 ********************************************************************************
67 */
68String Instruments::CPU::Info::ToString () const
69{
70 return DataExchange::Variant::JSON::Writer{}.WriteAsString (CPU::Instrument::kObjectVariantMapper.FromObject (*this));
71}
72
73namespace {
74 template <typename CONTEXT>
76}
77
78#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
79namespace {
80 template <typename ELT>
81 double EstimateRunQFromLoadAveArray_ (Time::DurationSeconds::rep backNSeconds, ELT loadAveArray[3])
82 {
83 // NB: Currently this is TOO simple. We should probably fit a curve to 3 points and use that to extrapolate. Maybe just fit 4 line segments?
84 Require (backNSeconds >= 0);
85 double backNMinutes = backNSeconds / 60.0;
86 if (backNMinutes <= 1) {
87 return static_cast<double> (loadAveArray[0]);
88 }
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);
93 }
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);
98 }
99 else {
100 return static_cast<double> (loadAveArray[2]);
101 }
102 }
103}
104#endif
105
106#if qStroika_Foundation_Common_Platform_Linux
107namespace {
108 struct POSIXSysTimeCaptureContext_ {
109 double user;
110 double nice;
111 double system;
112 double idle;
113 double iowait;
114 double irq;
115 double softirq;
116 double steal;
117 double guest;
118 double guest_nice;
119 };
120
121 struct _Context : SystemPerformance::Support::Context {
122 optional<POSIXSysTimeCaptureContext_> fLastSysTimeCapture;
123 };
124
125 struct InstrumentRep_Linux_ : InstrumentRepBase_<_Context> {
126
127 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
128
129 /*
130 * /proc/stat
131 * EXAMPLE:
132 * lewis@LewisLinuxDevVM2:~$ cat /proc/stat
133 * cpu 361378 1170 50632 2812384 5609 3 2684 0 0 0
134 * cpu0 202907 894 27261 1382587 3014 3 2672 0 0 0
135 * cpu1 158471 276 23371 1429797 2595 0 12 0 0 0
136 *
137 * From http://man7.org/linux/man-pages/man5/proc.5.html
138 *
139 * kernel/system statistics. Varies with architecture. Common
140 * entries include:
141 *
142 * cpu 3357 0 4313 1362393
143 * The amount of time, measured in units of USER_HZ
144 * (1/100ths of a second on most architectures, use
145 * sysconf(_SC_CLK_TCK) to obtain the right value), that
146 * the system spent in various states:
147 *
148 * user (1) Time spent in user mode.
149 *
150 * nice (2) Time spent in user mode with low priority
151 * (nice).
152 *
153 * system (3) Time spent in system mode.
154 *
155 * idle (4) Time spent in the idle task. This value
156 * should be USER_HZ times the second entry in the
157 * /proc/uptime pseudo-file.
158 *
159 * iowait (since Linux 2.5.41)
160 * (5) Time waiting for I/O to complete.
161 *
162 * irq (since Linux 2.6.0-test4)
163 * (6) Time servicing interrupts.
164 *
165 * softirq (since Linux 2.6.0-test4)
166 * (7) Time servicing softirqs.
167 *
168 * steal (since Linux 2.6.11)
169 * (8) Stolen time, which is the time spent in
170 * other operating systems when running in a
171 * virtualized environment
172 *
173 * guest (since Linux 2.6.24)
174 * (9) Time spent running a virtual CPU for guest
175 * operating systems under the control of the Linux
176 * kernel.
177 *
178 * guest_nice (since Linux 2.6.33)
179 * (10) Time spent running a niced guest (virtual
180 * CPU for guest operating systems under the
181 * control of the Linux kernel).
182 *
183 */
184 static POSIXSysTimeCaptureContext_ GetSysTimes_ ()
185 {
186 POSIXSysTimeCaptureContext_ result{};
189 static const filesystem::path kFileName_{"/proc/stat"sv};
190 // Note - /procfs files always unseekable
191 for (const Sequence<String>& line :
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]);
195#endif
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]);
202 if (sz >= 6) {
203 result.iowait = ToFloat<double> (line[5]);
204 }
205 if (sz >= 7) {
206 result.irq = ToFloat<double> (line[6]);
207 }
208 if (sz >= 8) {
209 result.softirq = ToFloat<double> (line[7]);
210 }
211 if (sz >= 9) {
212 result.steal = ToFloat<double> (line[8]);
213 }
214 break; // once found no need to read the rest...
215 }
216 }
217 return result;
218 }
219 struct CPUUsageTimes_ {
220 double fProcessCPUUsage;
221 double fTotalCPUUsage;
222 };
223 nonvirtual Info _InternalCapture ()
224 {
225 Info result;
226#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
227 {
228 double loadAve[3];
229 int lr = ::getloadavg (loadAve, NEltsOf (loadAve));
230 if (lr == 3) {
231 result.fLoadAverage = Info::LoadAverage{loadAve[0], loadAve[1], loadAve[2]};
232 auto tcNow = Time::GetTickCount ();
233 result.fRunQLength = EstimateRunQFromLoadAveArray_ ((tcNow - _GetCaptureContextTime ()).count (), loadAve);
234 Memory::AccumulateIf<double> (&result.fRunQLength, Common::GetNumberOfLogicalCPUCores (),
235 std::divides{}); // fRunQLength counts length normalized 0..1 with 1 menaing ALL CPU CORES
236 }
237 else {
238 DbgTrace ("getloadave failed - with result = {}"_f, lr);
239 }
240 }
241#endif
242 auto getCPUTime = [&] (POSIXSysTimeCaptureContext_* referenceValue) -> optional<CPUUsageTimes_> {
243 POSIXSysTimeCaptureContext_ newVal = GetSysTimes_ ();
244 *referenceValue = newVal;
245 if (auto baseline = _fContext.load ()->fLastSysTimeCapture) {
246 // from http://stackoverflow.com/questions/23367857/accurate-calculation-of-cpu-usage-given-in-percentage-in-linux
247 // Idle=idle+iowait
248 // NonIdle=user+nice+system+irq+softirq+steal
249 // Total=Idle+NonIdle # first line of file for all cpus
250 double idleTime = 0;
251 idleTime += (newVal.idle - baseline->idle);
252 idleTime += (newVal.iowait - baseline->iowait);
253
254 double processNonIdleTime = 0;
255 processNonIdleTime += (newVal.user - baseline->user);
256 processNonIdleTime += (newVal.nice - baseline->nice);
257 processNonIdleTime += (newVal.system - baseline->system);
258
259 double nonIdleTime = processNonIdleTime;
260 nonIdleTime += (newVal.irq - baseline->irq);
261 nonIdleTime += (newVal.softirq - baseline->softirq);
262
263 double totalTime = idleTime + nonIdleTime;
264 if (totalTime <= this->_fOptions.fMinimumAveragingInterval.count ()) {
265 // can happen if called too quickly together. No good answer
266 DbgTrace ("Warning - times too close together for cputime"_f);
267 return nullopt;
268 }
269 Assert (totalTime > 0);
270 double totalProcessCPUUsage = processNonIdleTime / totalTime;
271 double totalCPUUsage = nonIdleTime / totalTime;
272 return CPUUsageTimes_{totalProcessCPUUsage, totalCPUUsage};
273 }
274 return nullopt;
275 };
276 POSIXSysTimeCaptureContext_ referenceValue{};
277 if (auto tmp = getCPUTime (&referenceValue)) {
278 unsigned int nLogicalCores = Common::GetNumberOfLogicalCPUCores ();
279 result.fTotalProcessCPUUsage = tmp->fProcessCPUUsage * nLogicalCores;
280 result.fTotalCPUUsage = tmp->fTotalCPUUsage * nLogicalCores;
281 result.fTotalLogicalCores = nLogicalCores;
282 }
283 _NoteCompletedCapture ();
284 _fContext.rwget ().rwref ()->fLastSysTimeCapture = referenceValue;
285 return result;
286 }
287 };
288}
289#endif
290
291#if qStroika_Foundation_Common_Platform_Windows
292namespace {
293 struct WinSysTimeCaptureContext_ {
294 double IdleTime;
295 double KernelTime;
296 double UserTime;
297 };
298 struct _Context : SystemPerformance::Support::Context {
299 optional<WinSysTimeCaptureContext_> fLastSysTimeCapture{};
300#if qUseWMICollectionSupport_
301 WMICollector fSystemWMICollector_{"System"sv, {kInstanceName_}, {kProcessorQueueLength_}};
302#endif
303 };
304
305 struct InstrumentRep_Windows_ : InstrumentRepBase_<_Context> {
306
307 using InstrumentRepBase_<_Context>::InstrumentRepBase_;
308
309 nonvirtual Info _InternalCapture ()
310 {
311 auto getCPUTime = [=] (WinSysTimeCaptureContext_* newRawValueToStoreAsNextbaseline) -> optional<double> {
312 RequireNotNull (newRawValueToStoreAsNextbaseline);
313 auto getSysTimes = [] () -> WinSysTimeCaptureContext_ {
314 auto getAsSeconds = [] (const ::FILETIME& ft) {
315 ::ULARGE_INTEGER ui;
316 ui.LowPart = ft.dwLowDateTime;
317 ui.HighPart = ft.dwHighDateTime;
318 return static_cast<double> (ui.QuadPart) / 10000000; // convert from 100-nanosecond units
319 };
320 ::FILETIME curIdleTime_{};
321 ::FILETIME curKernelTime_{};
322 ::FILETIME curUserTime_{};
323 Verify (::GetSystemTimes (&curIdleTime_, &curKernelTime_, &curUserTime_));
324 return WinSysTimeCaptureContext_{getAsSeconds (curIdleTime_), getAsSeconds (curKernelTime_), getAsSeconds (curUserTime_)};
325 };
326
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;
334 /*
335 * This logic seems queer (sys = kern + user, and why doesnt denominator include idletime?), but is cribbed from
336 * http://en.literateprograms.org/CPU_usage_%28C,_Windows_XP%29
337 * http://www.codeproject.com/Articles/9113/Get-CPU-Usage-with-GetSystemTimes
338 * Must be that idle time is somehow INCLUDED in either kernel or user time.
339 */
340 double sys = kernelTimeOverInterval + userTimeOverInterval;
341 if (Time::Duration{sys} > _fOptions.fMinimumAveragingInterval) {
342 double cpu = 1 - idleTimeOverInterval / sys;
344 }
345 }
346 return nullopt;
347 };
348
349 Info result;
350 WinSysTimeCaptureContext_ newRawValueToStoreAsNextbaseline;
351 result.fTotalCPUUsage = getCPUTime (&newRawValueToStoreAsNextbaseline);
352 result.fTotalProcessCPUUsage = result.fTotalCPUUsage; // @todo fix - WMI - remove irq time etc from above? Or add into above if missing (See counter PRocessor/% Interrupt time) - not from System - but Processor - so new collector object
353 result.fTotalLogicalCores = Common::GetNumberOfLogicalCPUCores ();
354#if qUseWMICollectionSupport_
355 _fContext.rwget ().rwref ()->fSystemWMICollector_.Collect ();
356 Memory::CopyToIf (&result.fRunQLength,
357 _fContext.rwget ().rwref ()->fSystemWMICollector_.PeekCurrentValue (kInstanceName_, kProcessorQueueLength_));
358 // "if a computer has multiple processors, you need to divide this value by the number of processors servicing the workload"
359 Memory::AccumulateIf<double> (&result.fRunQLength, Common::GetNumberOfLogicalCPUCores (), std::divides{}); // both normalized so '1' means all logical cores
360#endif
361 _NoteCompletedCapture ();
362 _fContext.rwget ().rwref ()->fLastSysTimeCapture = newRawValueToStoreAsNextbaseline;
363 return result;
364 }
365 };
366}
367#endif
368
369namespace {
370 const MeasurementType kCPUMeasurment_ = MeasurementType{"CPU-Usage"sv};
371}
372
373namespace {
374 struct CPUInstrumentRep_
375#if qStroika_Foundation_Common_Platform_Linux
376 : InstrumentRep_Linux_
377#elif qStroika_Foundation_Common_Platform_Windows
378 : InstrumentRep_Windows_
379#else
380 : InstrumentRepBase_<SystemPerformance::Support::Context>
381#endif
382 {
383#if qStroika_Foundation_Common_Platform_Linux
384 using inherited = InstrumentRep_Linux_;
385#elif qStroika_Foundation_Common_Platform_Windows
386 using inherited = InstrumentRep_Windows_;
387#else
388 using inherited = InstrumentRepBase_<SystemPerformance::Support::Context>;
389#endif
390 CPUInstrumentRep_ (const Options& options, const shared_ptr<_Context>& context = make_shared<_Context> ())
391 : inherited{options, context}
392 {
393 Require (_fOptions.fMinimumAveragingInterval > 0s);
394 }
395 virtual MeasurementSet Capture () override
396 {
397 Debug::TraceContextBumper ctx{"SystemPerformance::Instrument...CPU...CPUInstrumentRep_::Capture ()"};
398 MeasurementSet results;
399 results.fMeasurements.Add (Measurement{
400 kCPUMeasurment_, Instruments::CPU::Instrument::kObjectVariantMapper.FromObject (Capture_Raw (&results.fMeasuredAt))});
401 return results;
402 }
403 nonvirtual Info Capture_Raw (Range<TimePointSeconds>* outMeasuredAt)
404 {
405 return Do_Capture_Raw<Info> ([this] () { return _InternalCapture (); }, outMeasuredAt);
406 }
407 virtual unique_ptr<IRep> Clone () const override
408 {
409 return make_unique<CPUInstrumentRep_> (_fOptions, _fContext.load ());
410 }
411 nonvirtual Info _InternalCapture ()
412 {
413 AssertExternallySynchronizedMutex::WriteContext declareContext{*this};
414#if USE_NOISY_TRACE_IN_THIS_MODULE_
415 Debug::TraceContextBumper ctx{"Instruments::CPU::{}CPUInstrumentRep_::_InternalCapture"};
416#endif
417#if qStroika_Foundation_Common_Platform_Linux or qStroika_Foundation_Common_Platform_Windows
418 Info result = inherited::_InternalCapture ();
419#else
420 Info result;
421#endif
422 return result;
423 }
424 };
425}
426
427/*
428 ********************************************************************************
429 ************************ Instruments::CPU::Instrument **************************
430 ********************************************************************************
431 */
433 ObjectVariantMapper mapper;
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},
439 });
440 mapper.AddCommonType<optional<Info::LoadAverage>> ();
441#endif
442 mapper.AddClass<Info> ({
443#if qSupport_SystemPerformance_Instruments_CPU_LoadAverage
444 {"Load-Average"_k, &Info::fLoadAverage},
445#endif
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},
450 });
451 return mapper;
452}();
453
454Instruments::CPU::Instrument::Instrument (const Options& options)
455 : SystemPerformance::Instrument{InstrumentNameType{"CPU"_k},
456 make_unique<CPUInstrumentRep_> (options),
457 {kCPUMeasurment_},
458 {KeyValuePair<type_index, MeasurementType>{typeid (Info), kCPUMeasurment_}},
459 kObjectVariantMapper}
460{
461}
462
463/*
464 ********************************************************************************
465 ********* SystemPerformance::Instrument::CaptureOneMeasurement *****************
466 ********************************************************************************
467 */
468template <>
469Instruments::CPU::Info SystemPerformance::Instrument::CaptureOneMeasurement (Range<TimePointSeconds>* measurementTimeOut)
470{
471 Debug::TraceContextBumper ctx{"SystemPerformance::Instrument::CaptureOneMeasurement<CPU::Info>"};
472 CPUInstrumentRep_* myCap = dynamic_cast<CPUInstrumentRep_*> (fCaptureRep_.get ());
473 AssertNotNull (myCap);
474 return myCap->Capture_Raw (measurementTimeOut);
475}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireNotNull(p)
Definition Assertions.h:347
#define Verify(c)
Definition Assertions.h:419
wstring Capture(const Options &options={})
Definition BackTrace.cpp:46
#define DbgTrace
Definition Trace.h:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
An Atom is like a String, except that its much cheaper to copy/store/compare, and the semantics of co...
Definition Atom.h:133
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
nonvirtual void AddClass(const Traversal::Iterable< StructFieldInfo > &fieldDescriptions, const ClassMapperOptions< CLASS > &mapperOptions={})
nonvirtual void AddCommonType(ARGS &&... args)
nonvirtual VariantValue FromObject(const T &from) const
This COULD be easily used to read CSV files, or tab-delimited files, for example.
nonvirtual String WriteAsString(const VariantValue &v) const
Definition Writer.cpp:53
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
An Instrument is a stateful object from which you can Capture () a series of measurements about a sys...
Definition Instrument.h:69
nonvirtual T CaptureOneMeasurement(Range< TimePointSeconds > *measurementTimeOut=nullptr)
unsigned int GetNumberOfLogicalCPUCores(const chrono::duration< double > &allowedStaleness=1min)
return the number of currently available CPU cores on this (virtual) machine