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