VirtualBox

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

Last change on this file since 68033 was 63563, checked in by vboxsync, 8 years ago

scm: cleaning up todos

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