VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/linux/PerformanceLinux.cpp@ 94078

Last change on this file since 94078 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.1 KB
Line 
1/* $Id: PerformanceLinux.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * VBox Linux-specific Performance Classes implementation.
4 */
5
6/*
7 * Copyright (C) 2008-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
19#include <stdio.h>
20#include <unistd.h>
21#include <sys/statvfs.h>
22#include <errno.h>
23#include <mntent.h>
24#include <iprt/alloc.h>
25#include <iprt/cdefs.h>
26#include <iprt/ctype.h>
27#include <iprt/err.h>
28#include <iprt/param.h>
29#include <iprt/path.h>
30#include <iprt/string.h>
31#include <iprt/system.h>
32#include <iprt/mp.h>
33#include <iprt/linux/sysfs.h>
34
35#include <map>
36#include <vector>
37
38#include "LoggingNew.h"
39#include "Performance.h"
40
41#define VBOXVOLINFO_NAME "VBoxVolInfo"
42
43namespace pm {
44
45class CollectorLinux : public CollectorHAL
46{
47public:
48 CollectorLinux();
49 virtual int preCollect(const CollectorHints& hints, uint64_t /* iTick */);
50 virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
51 virtual int getHostFilesystemUsage(const char *name, ULONG *total, ULONG *used, ULONG *available);
52 virtual int getHostDiskSize(const char *name, uint64_t *size);
53 virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
54
55 virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
56 virtual int getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx);
57 virtual int getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms);
58 virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
59
60 virtual int getDiskListByFs(const char *name, DiskList& listUsage, DiskList& listLoad);
61private:
62 virtual int _getRawHostCpuLoad();
63 int getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed);
64 void getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits);
65 void addVolumeDependencies(const char *pcszVolume, DiskList& listDisks);
66 void addRaidDisks(const char *pcszDevice, DiskList& listDisks);
67 char *trimTrailingDigits(char *pszName);
68 char *trimNewline(char *pszName);
69
70 struct VMProcessStats
71 {
72 uint64_t cpuUser;
73 uint64_t cpuKernel;
74 ULONG pagesUsed;
75 };
76
77 typedef std::map<RTPROCESS, VMProcessStats> VMProcessMap;
78
79 VMProcessMap mProcessStats;
80 uint64_t mUser, mKernel, mIdle;
81 uint64_t mSingleUser, mSingleKernel, mSingleIdle;
82 uint32_t mHZ;
83 ULONG mTotalRAM;
84};
85
86CollectorHAL *createHAL()
87{
88 return new CollectorLinux();
89}
90
91// Collector HAL for Linux
92
93CollectorLinux::CollectorLinux()
94{
95 long hz = sysconf(_SC_CLK_TCK);
96 if (hz == -1)
97 {
98 LogRel(("CollectorLinux failed to obtain HZ from kernel, assuming 100.\n"));
99 mHZ = 100;
100 }
101 else
102 mHZ = (uint32_t)hz;
103 LogFlowThisFunc(("mHZ=%u\n", mHZ));
104
105 uint64_t cb;
106 int rc = RTSystemQueryTotalRam(&cb);
107 if (RT_FAILURE(rc))
108 mTotalRAM = 0;
109 else
110 mTotalRAM = (ULONG)(cb / 1024);
111}
112
113int CollectorLinux::preCollect(const CollectorHints& hints, uint64_t /* iTick */)
114{
115 std::vector<RTPROCESS> processes;
116 hints.getProcesses(processes);
117
118 std::vector<RTPROCESS>::iterator it;
119 for (it = processes.begin(); it != processes.end(); ++it)
120 {
121 VMProcessStats vmStats;
122 int rc = getRawProcessStats(*it, &vmStats.cpuUser, &vmStats.cpuKernel, &vmStats.pagesUsed);
123 /* On failure, do NOT stop. Just skip the entry. Having the stats for
124 * one (probably broken) process frozen/zero is a minor issue compared
125 * to not updating many process stats and the host cpu stats. */
126 if (RT_SUCCESS(rc))
127 mProcessStats[*it] = vmStats;
128 }
129 if (hints.isHostCpuLoadCollected() || !mProcessStats.empty())
130 {
131 _getRawHostCpuLoad();
132 }
133 return VINF_SUCCESS;
134}
135
136int CollectorLinux::_getRawHostCpuLoad()
137{
138 int rc = VINF_SUCCESS;
139 long long unsigned uUser, uNice, uKernel, uIdle, uIowait, uIrq, uSoftirq;
140 FILE *f = fopen("/proc/stat", "r");
141
142 if (f)
143 {
144 char szBuf[128];
145 if (fgets(szBuf, sizeof(szBuf), f))
146 {
147 if (sscanf(szBuf, "cpu %llu %llu %llu %llu %llu %llu %llu",
148 &uUser, &uNice, &uKernel, &uIdle, &uIowait,
149 &uIrq, &uSoftirq) == 7)
150 {
151 mUser = uUser + uNice;
152 mKernel = uKernel + uIrq + uSoftirq;
153 mIdle = uIdle + uIowait;
154 }
155 /* Try to get single CPU stats. */
156 if (fgets(szBuf, sizeof(szBuf), f))
157 {
158 if (sscanf(szBuf, "cpu0 %llu %llu %llu %llu %llu %llu %llu",
159 &uUser, &uNice, &uKernel, &uIdle, &uIowait,
160 &uIrq, &uSoftirq) == 7)
161 {
162 mSingleUser = uUser + uNice;
163 mSingleKernel = uKernel + uIrq + uSoftirq;
164 mSingleIdle = uIdle + uIowait;
165 }
166 else
167 {
168 /* Assume that this is not an SMP system. */
169 Assert(RTMpGetCount() == 1);
170 mSingleUser = mUser;
171 mSingleKernel = mKernel;
172 mSingleIdle = mIdle;
173 }
174 }
175 else
176 rc = VERR_FILE_IO_ERROR;
177 }
178 else
179 rc = VERR_FILE_IO_ERROR;
180 fclose(f);
181 }
182 else
183 rc = VERR_ACCESS_DENIED;
184
185 return rc;
186}
187
188int CollectorLinux::getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle)
189{
190 *user = mUser;
191 *kernel = mKernel;
192 *idle = mIdle;
193 return VINF_SUCCESS;
194}
195
196int CollectorLinux::getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total)
197{
198 VMProcessMap::const_iterator it = mProcessStats.find(process);
199
200 if (it == mProcessStats.end())
201 {
202 Log (("No stats pre-collected for process %x\n", process));
203 return VERR_INTERNAL_ERROR;
204 }
205 *user = it->second.cpuUser;
206 *kernel = it->second.cpuKernel;
207 *total = mUser + mKernel + mIdle;
208 return VINF_SUCCESS;
209}
210
211int CollectorLinux::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
212{
213 AssertReturn(mTotalRAM, VERR_INTERNAL_ERROR);
214 uint64_t cb;
215 int rc = RTSystemQueryAvailableRam(&cb);
216 if (RT_SUCCESS(rc))
217 {
218 *total = mTotalRAM;
219 *available = (ULONG)(cb / 1024);
220 *used = *total - *available;
221 }
222 return rc;
223}
224
225int CollectorLinux::getHostFilesystemUsage(const char *path, ULONG *total, ULONG *used, ULONG *available)
226{
227 struct statvfs stats;
228
229 if (statvfs(path, &stats) == -1)
230 {
231 LogRel(("Failed to collect %s filesystem usage: errno=%d.\n", path, errno));
232 return VERR_ACCESS_DENIED;
233 }
234 uint64_t cbBlock = stats.f_frsize ? stats.f_frsize : stats.f_bsize;
235 *total = (ULONG)(cbBlock * stats.f_blocks / _1M);
236 *used = (ULONG)(cbBlock * (stats.f_blocks - stats.f_bfree) / _1M);
237 *available = (ULONG)(cbBlock * stats.f_bavail / _1M);
238
239 return VINF_SUCCESS;
240}
241
242int CollectorLinux::getHostDiskSize(const char *pszFile, uint64_t *size)
243{
244 char *pszPath = NULL;
245
246 RTStrAPrintf(&pszPath, "/sys/block/%s/size", pszFile);
247 Assert(pszPath);
248
249 int rc = VINF_SUCCESS;
250 if (!RTLinuxSysFsExists(pszPath))
251 rc = VERR_FILE_NOT_FOUND;
252 else
253 {
254 int64_t cSize = 0;
255 rc = RTLinuxSysFsReadIntFile(0, &cSize, pszPath);
256 if (RT_SUCCESS(rc))
257 *size = cSize * 512;
258 }
259 RTStrFree(pszPath);
260 return rc;
261}
262
263int CollectorLinux::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
264{
265 VMProcessMap::const_iterator it = mProcessStats.find(process);
266
267 if (it == mProcessStats.end())
268 {
269 Log (("No stats pre-collected for process %x\n", process));
270 return VERR_INTERNAL_ERROR;
271 }
272 *used = it->second.pagesUsed * (PAGE_SIZE / 1024);
273 return VINF_SUCCESS;
274}
275
276int CollectorLinux::getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed)
277{
278 int rc = VINF_SUCCESS;
279 char *pszName;
280 pid_t pid2;
281 char c;
282 int iTmp;
283 long long unsigned int u64Tmp;
284 unsigned uTmp;
285 unsigned long ulTmp;
286 signed long ilTmp;
287 ULONG u32user, u32kernel;
288 char buf[80]; /** @todo this should be tied to max allowed proc name. */
289
290 RTStrAPrintf(&pszName, "/proc/%d/stat", process);
291 FILE *f = fopen(pszName, "r");
292 RTStrFree(pszName);
293
294 if (f)
295 {
296 if (fscanf(f, "%d %79s %c %d %d %d %d %d %u %lu %lu %lu %lu %u %u "
297 "%ld %ld %ld %ld %ld %ld %llu %lu %u",
298 &pid2, buf, &c, &iTmp, &iTmp, &iTmp, &iTmp, &iTmp, &uTmp,
299 &ulTmp, &ulTmp, &ulTmp, &ulTmp, &u32user, &u32kernel,
300 &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &u64Tmp,
301 &ulTmp, memPagesUsed) == 24)
302 {
303 Assert((pid_t)process == pid2);
304 *cpuUser = u32user;
305 *cpuKernel = u32kernel;
306 }
307 else
308 rc = VERR_FILE_IO_ERROR;
309 fclose(f);
310 }
311 else
312 rc = VERR_ACCESS_DENIED;
313
314 return rc;
315}
316
317int CollectorLinux::getRawHostNetworkLoad(const char *pszFile, uint64_t *rx, uint64_t *tx)
318{
319 char szIfName[/*IFNAMSIZ*/ 16 + 36];
320
321 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/rx_bytes", pszFile);
322 if (!RTLinuxSysFsExists(szIfName))
323 return VERR_FILE_NOT_FOUND;
324
325 int64_t cSize = 0;
326 int rc = RTLinuxSysFsReadIntFile(0, &cSize, szIfName);
327 if (RT_FAILURE(rc))
328 return rc;
329
330 *rx = cSize;
331
332 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/tx_bytes", pszFile);
333 if (!RTLinuxSysFsExists(szIfName))
334 return VERR_FILE_NOT_FOUND;
335
336 rc = RTLinuxSysFsReadIntFile(0, &cSize, szIfName);
337 if (RT_FAILURE(rc))
338 return rc;
339
340 *tx = cSize;
341 return VINF_SUCCESS;
342}
343
344int CollectorLinux::getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms)
345{
346#if 0
347 int rc = VINF_SUCCESS;
348 char szIfName[/*IFNAMSIZ*/ 16 + 36];
349 long long unsigned int u64Busy, tmp;
350
351 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/block/%s/stat", name);
352 FILE *f = fopen(szIfName, "r");
353 if (f)
354 {
355 if (fscanf(f, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
356 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
357 {
358 *disk_ms = u64Busy;
359 *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
360 }
361 else
362 rc = VERR_FILE_IO_ERROR;
363 fclose(f);
364 }
365 else
366 rc = VERR_ACCESS_DENIED;
367#else
368 int rc = VERR_MISSING;
369 FILE *f = fopen("/proc/diskstats", "r");
370 if (f)
371 {
372 char szBuf[128];
373 while (fgets(szBuf, sizeof(szBuf), f))
374 {
375 char *pszBufName = szBuf;
376 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
377 while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip major */
378 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
379 while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip minor */
380 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
381
382 char *pszBufData = strchr(pszBufName, ' ');
383 if (!pszBufData)
384 {
385 LogRel(("CollectorLinux::getRawHostDiskLoad() failed to parse disk stats: %s\n", szBuf));
386 continue;
387 }
388 *pszBufData++ = '\0';
389 if (!strcmp(name, pszBufName))
390 {
391 long long unsigned int u64Busy, tmp;
392
393 if (sscanf(pszBufData, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
394 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
395 {
396 *disk_ms = u64Busy;
397 *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
398 rc = VINF_SUCCESS;
399 }
400 else
401 rc = VERR_FILE_IO_ERROR;
402 break;
403 }
404 }
405 fclose(f);
406 }
407#endif
408
409 return rc;
410}
411
412char *CollectorLinux::trimNewline(char *pszName)
413{
414 size_t cbName = strlen(pszName);
415 if (cbName == 0)
416 return pszName;
417
418 char *pszEnd = pszName + cbName - 1;
419 while (pszEnd > pszName && *pszEnd == '\n')
420 pszEnd--;
421 pszEnd[1] = '\0';
422
423 return pszName;
424}
425
426char *CollectorLinux::trimTrailingDigits(char *pszName)
427{
428 size_t cbName = strlen(pszName);
429 if (cbName == 0)
430 return pszName;
431
432 char *pszEnd = pszName + cbName - 1;
433 while (pszEnd > pszName && (RT_C_IS_DIGIT(*pszEnd) || *pszEnd == '\n'))
434 pszEnd--;
435 pszEnd[1] = '\0';
436
437 return pszName;
438}
439
440/**
441 * Use the partition name to get the name of the disk. Any path component is stripped.
442 * if fTrimDigits is true, trailing digits are stripped as well, for example '/dev/sda5'
443 * is converted to 'sda'.
444 *
445 * @param pszDiskName Where to store the name of the disk.
446 * @param cbDiskName The size of the buffer pszDiskName points to.
447 * @param pszDevName The device name used to get the disk name.
448 * @param fTrimDigits Trim trailing digits (e.g. /dev/sda5)
449 */
450void CollectorLinux::getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits)
451{
452 unsigned cbName = 0;
453 size_t cbDevName = strlen(pszDevName);
454 const char *pszEnd = pszDevName + cbDevName - 1;
455 if (fTrimDigits)
456 while (pszEnd > pszDevName && RT_C_IS_DIGIT(*pszEnd))
457 pszEnd--;
458 while (pszEnd > pszDevName && *pszEnd != '/')
459 {
460 cbName++;
461 pszEnd--;
462 }
463 RTStrCopy(pszDiskName, RT_MIN(cbName + 1, cbDiskName), pszEnd + 1);
464}
465
466void CollectorLinux::addRaidDisks(const char *pcszDevice, DiskList& listDisks)
467{
468 FILE *f = fopen("/proc/mdstat", "r");
469 if (f)
470 {
471 char szBuf[128];
472 while (fgets(szBuf, sizeof(szBuf), f))
473 {
474 char *pszBufName = szBuf;
475
476 char *pszBufData = strchr(pszBufName, ' ');
477 if (!pszBufData)
478 {
479 LogRel(("CollectorLinux::addRaidDisks() failed to parse disk stats: %s\n", szBuf));
480 continue;
481 }
482 *pszBufData++ = '\0';
483 if (!strcmp(pcszDevice, pszBufName))
484 {
485 while (*pszBufData == ':') ++pszBufData; /* Skip delimiter */
486 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
487 while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip status */
488 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
489 while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip type */
490
491 while (*pszBufData != '\0')
492 {
493 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
494 char *pszDisk = pszBufData;
495 while (RT_C_IS_ALPHA(*pszBufData))
496 ++pszBufData;
497 if (*pszBufData)
498 {
499 *pszBufData++ = '\0';
500 listDisks.push_back(RTCString(pszDisk));
501 while (*pszBufData != '\0' && *pszBufData != ' ')
502 ++pszBufData;
503 }
504 else
505 listDisks.push_back(RTCString(pszDisk));
506 }
507 break;
508 }
509 }
510 fclose(f);
511 }
512}
513
514void CollectorLinux::addVolumeDependencies(const char *pcszVolume, DiskList& listDisks)
515{
516 char szVolInfo[RTPATH_MAX];
517 int rc = RTPathAppPrivateArch(szVolInfo,
518 sizeof(szVolInfo) - sizeof("/" VBOXVOLINFO_NAME " ") - strlen(pcszVolume));
519 if (RT_FAILURE(rc))
520 {
521 LogRel(("VolInfo: Failed to get program path, rc=%Rrc\n", rc));
522 return;
523 }
524 strcat(szVolInfo, "/" VBOXVOLINFO_NAME " ");
525 strcat(szVolInfo, pcszVolume);
526
527 FILE *fp = popen(szVolInfo, "r");
528 if (fp)
529 {
530 char szBuf[128];
531
532 while (fgets(szBuf, sizeof(szBuf), fp))
533 if (strncmp(szBuf, RT_STR_TUPLE("dm-")))
534 listDisks.push_back(RTCString(trimTrailingDigits(szBuf)));
535 else
536 listDisks.push_back(RTCString(trimNewline(szBuf)));
537
538 pclose(fp);
539 }
540 else
541 listDisks.push_back(RTCString(pcszVolume));
542}
543
544int CollectorLinux::getDiskListByFs(const char *pszPath, DiskList& listUsage, DiskList& listLoad)
545{
546 FILE *mtab = setmntent("/etc/mtab", "r");
547 if (mtab)
548 {
549 struct mntent *mntent;
550 while ((mntent = getmntent(mtab)))
551 {
552 /* Skip rootfs entry, there must be another root mount. */
553 if (strcmp(mntent->mnt_fsname, "rootfs") == 0)
554 continue;
555 if (strcmp(pszPath, mntent->mnt_dir) == 0)
556 {
557 char szDevName[128];
558 char szFsName[1024];
559 /* Try to resolve symbolic link if necessary. Yes, we access the file system here! */
560 int rc = RTPathReal(mntent->mnt_fsname, szFsName, sizeof(szFsName));
561 if (RT_FAILURE(rc))
562 continue; /* something got wrong, just ignore this path */
563 /* check against the actual mtab entry, NOT the real path as /dev/mapper/xyz is
564 * often a symlink to something else */
565 if (!strncmp(mntent->mnt_fsname, RT_STR_TUPLE("/dev/mapper")))
566 {
567 /* LVM */
568 getDiskName(szDevName, sizeof(szDevName), mntent->mnt_fsname, false /*=fTrimDigits*/);
569 addVolumeDependencies(szDevName, listUsage);
570 listLoad = listUsage;
571 }
572 else if (!strncmp(szFsName, RT_STR_TUPLE("/dev/md")))
573 {
574 /* Software RAID */
575 getDiskName(szDevName, sizeof(szDevName), szFsName, false /*=fTrimDigits*/);
576 listUsage.push_back(RTCString(szDevName));
577 addRaidDisks(szDevName, listLoad);
578 }
579 else
580 {
581 /* Plain disk partition. Trim the trailing digits to get the drive name */
582 getDiskName(szDevName, sizeof(szDevName), szFsName, true /*=fTrimDigits*/);
583 listUsage.push_back(RTCString(szDevName));
584 listLoad.push_back(RTCString(szDevName));
585 }
586 if (listUsage.empty() || listLoad.empty())
587 {
588 LogRel(("Failed to retrive disk info: getDiskName(%s) --> %s\n",
589 mntent->mnt_fsname, szDevName));
590 }
591 break;
592 }
593 }
594 endmntent(mtab);
595 }
596 return VINF_SUCCESS;
597}
598
599}
600
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