VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/win/PerformanceWin.cpp

Last change on this file was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 10.6 KB
Line 
1/* $Id: PerformanceWin.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * VBox Windows-specific Performance Classes implementation.
4 */
5
6/*
7 * Copyright (C) 2008-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
29#ifndef _WIN32_WINNT
30# define _WIN32_WINNT 0x0500
31#else /* !_WIN32_WINNT */
32# if (_WIN32_WINNT < 0x0500)
33# error Win XP or later required!
34# endif /* _WIN32_WINNT < 0x0500 */
35#endif /* !_WIN32_WINNT */
36
37#include <iprt/win/windows.h>
38#include <winternl.h>
39#include <psapi.h>
40extern "C" {
41#include <powrprof.h>
42}
43
44#include <iprt/errcore.h>
45#include <iprt/ldr.h>
46#include <iprt/mp.h>
47#include <iprt/mem.h>
48#include <iprt/system.h>
49
50#include <map>
51
52#include "LoggingNew.h"
53#include "Performance.h"
54
55#ifndef NT_ERROR
56#define NT_ERROR(Status) ((ULONG)(Status) >> 30 == 3)
57#endif
58
59namespace pm {
60
61class CollectorWin : public CollectorHAL
62{
63public:
64 CollectorWin();
65 virtual ~CollectorWin();
66 virtual int preCollect(const CollectorHints& hints, uint64_t /* iTick */);
67 virtual int getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle);
68 virtual int getHostCpuMHz(ULONG *mhz);
69 virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
70 virtual int getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel);
71 virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
72
73 virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
74 virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
75
76private:
77 struct VMProcessStats
78 {
79 uint64_t cpuUser;
80 uint64_t cpuKernel;
81 uint64_t cpuTotal;
82 uint64_t ramUsed;
83 };
84
85 typedef std::map<RTPROCESS, VMProcessStats> VMProcessMap;
86
87 VMProcessMap mProcessStats;
88
89 typedef BOOL (WINAPI *PFNGST)(LPFILETIME lpIdleTime,
90 LPFILETIME lpKernelTime,
91 LPFILETIME lpUserTime);
92 typedef NTSTATUS (WINAPI *PFNNQSI)(SYSTEM_INFORMATION_CLASS SystemInformationClass,
93 PVOID SystemInformation,
94 ULONG SystemInformationLength,
95 PULONG ReturnLength);
96
97 PFNGST mpfnGetSystemTimes;
98 PFNNQSI mpfnNtQuerySystemInformation;
99
100 ULONG totalRAM;
101};
102
103CollectorHAL *createHAL()
104{
105 return new CollectorWin();
106}
107
108CollectorWin::CollectorWin() : CollectorHAL(), mpfnNtQuerySystemInformation(NULL)
109{
110 /* Note! Both kernel32.dll and ntdll.dll can be assumed to always be present. */
111 mpfnGetSystemTimes = (PFNGST)RTLdrGetSystemSymbol("kernel32.dll", "GetSystemTimes");
112 if (!mpfnGetSystemTimes)
113 {
114 /* Fall back to deprecated NtQuerySystemInformation */
115 mpfnNtQuerySystemInformation = (PFNNQSI)RTLdrGetSystemSymbol("ntdll.dll", "NtQuerySystemInformation");
116 if (!mpfnNtQuerySystemInformation)
117 LogRel(("Warning! Neither GetSystemTimes() nor NtQuerySystemInformation() is not available.\n"
118 " CPU and VM metrics will not be collected! (lasterr %u)\n", GetLastError()));
119 }
120
121 uint64_t cb;
122 int vrc = RTSystemQueryTotalRam(&cb);
123 if (RT_FAILURE(vrc))
124 totalRAM = 0;
125 else
126 totalRAM = (ULONG)(cb / 1024);
127}
128
129CollectorWin::~CollectorWin()
130{
131}
132
133#define FILETTIME_TO_100NS(ft) (((uint64_t)ft.dwHighDateTime << 32) + ft.dwLowDateTime)
134
135int CollectorWin::preCollect(const CollectorHints& hints, uint64_t /* iTick */)
136{
137 LogFlowThisFuncEnter();
138
139 uint64_t user, kernel, idle, total;
140 int vrc = getRawHostCpuLoad(&user, &kernel, &idle);
141 if (RT_FAILURE(vrc))
142 return vrc;
143 total = user + kernel + idle;
144
145 DWORD dwError;
146 const CollectorHints::ProcessList& processes = hints.getProcessFlags();
147 CollectorHints::ProcessList::const_iterator it;
148
149 mProcessStats.clear();
150
151 for (it = processes.begin(); it != processes.end() && RT_SUCCESS(vrc); ++it)
152 {
153 RTPROCESS process = it->first;
154 HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process);
155
156 if (!h)
157 {
158 dwError = GetLastError();
159 Log (("OpenProcess() -> 0x%x\n", dwError));
160 vrc = RTErrConvertFromWin32(dwError);
161 break;
162 }
163
164 VMProcessStats vmStats;
165 RT_ZERO(vmStats);
166 if ((it->second & COLLECT_CPU_LOAD) != 0)
167 {
168 FILETIME ftCreate, ftExit, ftKernel, ftUser;
169 if (!GetProcessTimes(h, &ftCreate, &ftExit, &ftKernel, &ftUser))
170 {
171 dwError = GetLastError();
172 Log (("GetProcessTimes() -> 0x%x\n", dwError));
173 vrc = RTErrConvertFromWin32(dwError);
174 }
175 else
176 {
177 vmStats.cpuKernel = FILETTIME_TO_100NS(ftKernel);
178 vmStats.cpuUser = FILETTIME_TO_100NS(ftUser);
179 vmStats.cpuTotal = total;
180 }
181 }
182 if (RT_SUCCESS(vrc) && (it->second & COLLECT_RAM_USAGE) != 0)
183 {
184 PROCESS_MEMORY_COUNTERS pmc;
185 if (!GetProcessMemoryInfo(h, &pmc, sizeof(pmc)))
186 {
187 dwError = GetLastError();
188 Log (("GetProcessMemoryInfo() -> 0x%x\n", dwError));
189 vrc = RTErrConvertFromWin32(dwError);
190 }
191 else
192 vmStats.ramUsed = pmc.WorkingSetSize;
193 }
194 CloseHandle(h);
195 mProcessStats[process] = vmStats;
196 }
197
198 LogFlowThisFuncLeave();
199
200 return vrc;
201}
202
203int CollectorWin::getHostCpuLoad(ULONG *user, ULONG *kernel, ULONG *idle)
204{
205 RT_NOREF(user, kernel, idle);
206 return VERR_NOT_IMPLEMENTED;
207}
208
209typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION
210{
211 LARGE_INTEGER IdleTime;
212 LARGE_INTEGER KernelTime;
213 LARGE_INTEGER UserTime;
214 LARGE_INTEGER Reserved1[2];
215 ULONG Reserved2;
216} SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;
217
218int CollectorWin::getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle)
219{
220 LogFlowThisFuncEnter();
221
222 FILETIME ftIdle, ftKernel, ftUser;
223
224 if (mpfnGetSystemTimes)
225 {
226 if (!mpfnGetSystemTimes(&ftIdle, &ftKernel, &ftUser))
227 {
228 DWORD dwError = GetLastError();
229 Log (("GetSystemTimes() -> 0x%x\n", dwError));
230 return RTErrConvertFromWin32(dwError);
231 }
232
233 *user = FILETTIME_TO_100NS(ftUser);
234 *idle = FILETTIME_TO_100NS(ftIdle);
235 *kernel = FILETTIME_TO_100NS(ftKernel) - *idle;
236 }
237 else
238 {
239 /* GetSystemTimes is not available, fall back to NtQuerySystemInformation */
240 if (!mpfnNtQuerySystemInformation)
241 return VERR_NOT_IMPLEMENTED;
242
243 SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION sppi[MAXIMUM_PROCESSORS];
244 ULONG ulReturned;
245 NTSTATUS status = mpfnNtQuerySystemInformation(
246 SystemProcessorPerformanceInformation, &sppi, sizeof(sppi), &ulReturned);
247 if (NT_ERROR(status))
248 {
249 Log(("NtQuerySystemInformation() -> 0x%x\n", status));
250 return RTErrConvertFromNtStatus(status);
251 }
252 /* Sum up values across all processors */
253 *user = *kernel = *idle = 0;
254 for (unsigned i = 0; i < ulReturned / sizeof(sppi[0]); ++i)
255 {
256 *idle += sppi[i].IdleTime.QuadPart;
257 *kernel += sppi[i].KernelTime.QuadPart - sppi[i].IdleTime.QuadPart;
258 *user += sppi[i].UserTime.QuadPart;
259 }
260 }
261
262 LogFlowThisFunc(("user=%lu kernel=%lu idle=%lu\n", *user, *kernel, *idle));
263 LogFlowThisFuncLeave();
264
265 return VINF_SUCCESS;
266}
267
268typedef struct _PROCESSOR_POWER_INFORMATION {
269 ULONG Number;
270 ULONG MaxMhz;
271 ULONG CurrentMhz;
272 ULONG MhzLimit;
273 ULONG MaxIdleState;
274 ULONG CurrentIdleState;
275} PROCESSOR_POWER_INFORMATION , *PPROCESSOR_POWER_INFORMATION;
276
277int CollectorWin::getHostCpuMHz(ULONG *mhz)
278{
279 uint64_t uTotalMhz = 0;
280 RTCPUID nProcessors = RTMpGetCount();
281 PPROCESSOR_POWER_INFORMATION ppi = (PPROCESSOR_POWER_INFORMATION)
282 RTMemAllocZ(nProcessors * sizeof(PROCESSOR_POWER_INFORMATION));
283
284 if (!ppi)
285 return VERR_NO_MEMORY;
286
287 LONG ns = CallNtPowerInformation(ProcessorInformation, NULL, 0, ppi,
288 nProcessors * sizeof(PROCESSOR_POWER_INFORMATION));
289 if (ns)
290 {
291 Log(("CallNtPowerInformation() -> %x\n", ns));
292 RTMemFree(ppi);
293 return VERR_INTERNAL_ERROR;
294 }
295
296 /* Compute an average over all CPUs */
297 for (unsigned i = 0; i < nProcessors; i++)
298 uTotalMhz += ppi[i].CurrentMhz;
299 *mhz = (ULONG)(uTotalMhz / nProcessors);
300
301 RTMemFree(ppi);
302 LogFlowThisFunc(("mhz=%u\n", *mhz));
303 LogFlowThisFuncLeave();
304
305 return VINF_SUCCESS;
306}
307
308int CollectorWin::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
309{
310 AssertReturn(totalRAM, VERR_INTERNAL_ERROR);
311 uint64_t cb;
312 int vrc = RTSystemQueryAvailableRam(&cb);
313 if (RT_SUCCESS(vrc))
314 {
315 *total = totalRAM;
316 *available = (ULONG)(cb / 1024);
317 *used = *total - *available;
318 }
319 return vrc;
320}
321
322int CollectorWin::getProcessCpuLoad(RTPROCESS process, ULONG *user, ULONG *kernel)
323{
324 RT_NOREF(process, user, kernel);
325 return VERR_NOT_IMPLEMENTED;
326}
327
328int CollectorWin::getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total)
329{
330 VMProcessMap::const_iterator it = mProcessStats.find(process);
331
332 if (it == mProcessStats.end())
333 {
334 Log (("No stats pre-collected for process %x\n", process));
335 return VERR_INTERNAL_ERROR;
336 }
337 *user = it->second.cpuUser;
338 *kernel = it->second.cpuKernel;
339 *total = it->second.cpuTotal;
340 return VINF_SUCCESS;
341}
342
343int CollectorWin::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
344{
345 VMProcessMap::const_iterator it = mProcessStats.find(process);
346
347 if (it == mProcessStats.end())
348 {
349 Log (("No stats pre-collected for process %x\n", process));
350 return VERR_INTERNAL_ERROR;
351 }
352 *used = (ULONG)(it->second.ramUsed / 1024);
353 return VINF_SUCCESS;
354}
355
356}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette