Overte C++ Documentation
WindowsSystemInfo.h
1 //
2 // WindowSystemInfo.h
3 // interface/src
4 //
5 // Split from Application.cpp by HifiExperiments on 3/30/24
6 // Created by Andrzej Kapolka on 5/10/13.
7 // Copyright 2013 High Fidelity, Inc.
8 // Copyright 2020 Vircadia contributors.
9 // Copyright 2022-2023 Overte e.V.
10 //
11 // Distributed under the Apache License, Version 2.0.
12 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
13 // SPDX-License-Identifier: Apache-2.0
14 //
15 
16 #ifndef hifi_WindowSystemInfo_h
17 #define hifi_WindowSystemInfo_h
18 
19 #include <qsystemdetection.h>
20 
21 #ifdef Q_OS_WIN
22 #include <Windows.h>
23 #include <TCHAR.h>
24 #include <pdh.h>
25 #pragma comment(lib, "pdh.lib")
26 #pragma comment(lib, "ntdll.lib")
27 
28 #include <mutex>
29 #include <string>
30 #include <vector>
31 
32 #include <glm/glm.hpp>
33 
34 #include <QThread>
35 #include <QTimer>
36 
37 #include "Profile.h"
38 #include "ThreadHelpers.h"
39 
40 extern "C" {
41  enum SYSTEM_INFORMATION_CLASS {
42  SystemBasicInformation = 0,
43  SystemProcessorPerformanceInformation = 8,
44  };
45 
46  struct SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION {
47  LARGE_INTEGER IdleTime;
48  LARGE_INTEGER KernelTime;
49  LARGE_INTEGER UserTime;
50  LARGE_INTEGER DpcTime;
51  LARGE_INTEGER InterruptTime;
52  ULONG InterruptCount;
53  };
54 
55  struct SYSTEM_BASIC_INFORMATION {
56  ULONG Reserved;
57  ULONG TimerResolution;
58  ULONG PageSize;
59  ULONG NumberOfPhysicalPages;
60  ULONG LowestPhysicalPageNumber;
61  ULONG HighestPhysicalPageNumber;
62  ULONG AllocationGranularity;
63  ULONG_PTR MinimumUserModeAddress;
64  ULONG_PTR MaximumUserModeAddress;
65  ULONG_PTR ActiveProcessorsAffinityMask;
66  CCHAR NumberOfProcessors;
67  };
68 
69  NTSYSCALLAPI LONG NTAPI NtQuerySystemInformation(
70  _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
71  _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation,
72  _In_ ULONG SystemInformationLength,
73  _Out_opt_ PULONG ReturnLength
74  );
75 
76 }
77 template <typename T>
78 LONG NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, T& t) {
79  return NtQuerySystemInformation(SystemInformationClass, &t, (ULONG)sizeof(T), nullptr);
80 }
81 
82 template <typename T>
83 LONG NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, std::vector<T>& t) {
84  return NtQuerySystemInformation(SystemInformationClass, t.data(), (ULONG)(sizeof(T) * t.size()), nullptr);
85 }
86 
87 
88 template <typename T>
89 void updateValueAndDelta(std::pair<T, T>& pair, T newValue) {
90  auto& value = pair.first;
91  auto& delta = pair.second;
92  delta = (value != 0) ? newValue - value : 0;
93  value = newValue;
94 }
95 
96 struct MyCpuInfo {
97  using ValueAndDelta = std::pair<LONGLONG, LONGLONG>;
98  std::string name;
99  ValueAndDelta kernel { 0, 0 };
100  ValueAndDelta user { 0, 0 };
101  ValueAndDelta idle { 0, 0 };
102  float kernelUsage { 0.0f };
103  float userUsage { 0.0f };
104 
105  void update(const SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION& cpuInfo) {
106  updateValueAndDelta(kernel, cpuInfo.KernelTime.QuadPart);
107  updateValueAndDelta(user, cpuInfo.UserTime.QuadPart);
108  updateValueAndDelta(idle, cpuInfo.IdleTime.QuadPart);
109  auto totalTime = kernel.second + user.second + idle.second;
110  if (totalTime != 0) {
111  kernelUsage = (FLOAT)kernel.second / totalTime;
112  userUsage = (FLOAT)user.second / totalTime;
113  } else {
114  kernelUsage = userUsage = 0.0f;
115  }
116  }
117 };
118 
119 void updateCpuInformation() {
120  static std::once_flag once;
121  static SYSTEM_BASIC_INFORMATION systemInfo {};
122  static SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION cpuTotals;
123  static std::vector<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION> cpuInfos;
124  static std::vector<MyCpuInfo> myCpuInfos;
125  static MyCpuInfo myCpuTotals;
126  std::call_once(once, [&] {
127  NtQuerySystemInformation( SystemBasicInformation, systemInfo);
128  cpuInfos.resize(systemInfo.NumberOfProcessors);
129  myCpuInfos.resize(systemInfo.NumberOfProcessors);
130  for (size_t i = 0; i < systemInfo.NumberOfProcessors; ++i) {
131  myCpuInfos[i].name = "cpu." + std::to_string(i);
132  }
133  myCpuTotals.name = "cpu.total";
134  });
135  NtQuerySystemInformation(SystemProcessorPerformanceInformation, cpuInfos);
136 
137  // Zero the CPU totals.
138  memset(&cpuTotals, 0, sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION));
139  for (size_t i = 0; i < systemInfo.NumberOfProcessors; ++i) {
140  auto& cpuInfo = cpuInfos[i];
141  // KernelTime includes IdleTime.
142  cpuInfo.KernelTime.QuadPart -= cpuInfo.IdleTime.QuadPart;
143 
144  // Update totals
145  cpuTotals.IdleTime.QuadPart += cpuInfo.IdleTime.QuadPart;
146  cpuTotals.KernelTime.QuadPart += cpuInfo.KernelTime.QuadPart;
147  cpuTotals.UserTime.QuadPart += cpuInfo.UserTime.QuadPart;
148 
149  // Update friendly structure
150  auto& myCpuInfo = myCpuInfos[i];
151  myCpuInfo.update(cpuInfo);
152  PROFILE_COUNTER(app, myCpuInfo.name.c_str(), {
153  { "kernel", myCpuInfo.kernelUsage },
154  { "user", myCpuInfo.userUsage }
155  });
156  }
157 
158  myCpuTotals.update(cpuTotals);
159  PROFILE_COUNTER(app, myCpuTotals.name.c_str(), {
160  { "kernel", myCpuTotals.kernelUsage },
161  { "user", myCpuTotals.userUsage }
162  });
163 }
164 
165 
166 static ULARGE_INTEGER lastCPU, lastSysCPU, lastUserCPU;
167 static int numProcessors;
168 static HANDLE self;
169 static PDH_HQUERY cpuQuery;
170 static PDH_HCOUNTER cpuTotal;
171 
172 void initCpuUsage() {
173  SYSTEM_INFO sysInfo;
174  FILETIME ftime, fsys, fuser;
175 
176  GetSystemInfo(&sysInfo);
177  numProcessors = sysInfo.dwNumberOfProcessors;
178 
179  GetSystemTimeAsFileTime(&ftime);
180  memcpy(&lastCPU, &ftime, sizeof(FILETIME));
181 
182  self = GetCurrentProcess();
183  GetProcessTimes(self, &ftime, &ftime, &fsys, &fuser);
184  memcpy(&lastSysCPU, &fsys, sizeof(FILETIME));
185  memcpy(&lastUserCPU, &fuser, sizeof(FILETIME));
186 
187  PdhOpenQuery(NULL, NULL, &cpuQuery);
188  PdhAddCounter(cpuQuery, "\\Processor(_Total)\\% Processor Time", NULL, &cpuTotal);
189  PdhCollectQueryData(cpuQuery);
190 }
191 
192 void getCpuUsage(glm::vec3& systemAndUser) {
193  FILETIME ftime, fsys, fuser;
194  ULARGE_INTEGER now, sys, user;
195 
196  GetSystemTimeAsFileTime(&ftime);
197  memcpy(&now, &ftime, sizeof(FILETIME));
198 
199  GetProcessTimes(self, &ftime, &ftime, &fsys, &fuser);
200  memcpy(&sys, &fsys, sizeof(FILETIME));
201  memcpy(&user, &fuser, sizeof(FILETIME));
202  systemAndUser.x = (sys.QuadPart - lastSysCPU.QuadPart);
203  systemAndUser.y = (user.QuadPart - lastUserCPU.QuadPart);
204  systemAndUser /= (float)(now.QuadPart - lastCPU.QuadPart);
205  systemAndUser /= (float)numProcessors;
206  systemAndUser *= 100.0f;
207  lastCPU = now;
208  lastUserCPU = user;
209  lastSysCPU = sys;
210 
211  PDH_FMT_COUNTERVALUE counterVal;
212  PdhCollectQueryData(cpuQuery);
213  PdhGetFormattedCounterValue(cpuTotal, PDH_FMT_DOUBLE, NULL, &counterVal);
214  systemAndUser.z = (float)counterVal.doubleValue;
215 }
216 
217 void setupCpuMonitorThread() {
218  initCpuUsage();
219  auto cpuMonitorThread = QThread::currentThread();
220  setThreadName("CPU Monitor Thread");
221 
222  QTimer* timer = new QTimer();
223  timer->setInterval(50);
224  QObject::connect(timer, &QTimer::timeout, [] {
225  updateCpuInformation();
226  glm::vec3 kernelUserAndSystem;
227  getCpuUsage(kernelUserAndSystem);
228  PROFILE_COUNTER(app, "cpuProcess", { { "system", kernelUserAndSystem.x }, { "user", kernelUserAndSystem.y } });
229  PROFILE_COUNTER(app, "cpuSystem", { { "system", kernelUserAndSystem.z } });
230  });
231  QObject::connect(cpuMonitorThread, &QThread::finished, [=] {
232  timer->deleteLater();
233  cpuMonitorThread->deleteLater();
234  });
235  timer->start();
236 }
237 
238 #endif
239 
240 #endif // hifi_WindowSystemInfo_h