VirtualBox

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

Last change on this file since 48015 was 48015, checked in by vboxsync, 11 years ago

Main/PerformanceLinux: use RTLinuxSysFs* functions to access files in /sys; use RT_STR_TUPLE; use RTPathReal to deal with symbolic links (the latter fixes cases like /dev/root => sda5)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.9 KB
Line 
1/* $Id: PerformanceLinux.cpp 48015 2013-08-23 09:02:51Z vboxsync $ */
2
3/** @file
4 *
5 * VBox Linux-specific Performance Classes implementation.
6 */
7
8/*
9 * Copyright (C) 2008-2012 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 totalRAM;
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 = hz;
104 LogFlowThisFunc(("mHZ=%u\n", mHZ));
105
106 uint64_t cb;
107 int rc = RTSystemQueryTotalRam(&cb);
108 if (RT_FAILURE(rc))
109 totalRAM = 0;
110 else
111 totalRAM = (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.size())
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(totalRAM, VERR_INTERNAL_ERROR);
215 uint64_t cb;
216 int rc = RTSystemQueryAvailableRam(&cb);
217 if (RT_SUCCESS(rc))
218 {
219 *total = totalRAM;
220 *available = 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 = RTLinuxSysFsReadIntFile(0, pszPath);
256 if (cSize < 0)
257 rc = VERR_ACCESS_DENIED;
258 else
259 *size = cSize * 512;
260 }
261 RTStrFree(pszPath);
262 return rc;
263}
264
265int CollectorLinux::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
266{
267 VMProcessMap::const_iterator it = mProcessStats.find(process);
268
269 if (it == mProcessStats.end())
270 {
271 Log (("No stats pre-collected for process %x\n", process));
272 return VERR_INTERNAL_ERROR;
273 }
274 *used = it->second.pagesUsed * (PAGE_SIZE / 1024);
275 return VINF_SUCCESS;
276}
277
278int CollectorLinux::getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed)
279{
280 int rc = VINF_SUCCESS;
281 char *pszName;
282 pid_t pid2;
283 char c;
284 int iTmp;
285 long long unsigned int u64Tmp;
286 unsigned uTmp;
287 unsigned long ulTmp;
288 signed long ilTmp;
289 ULONG u32user, u32kernel;
290 char buf[80]; /* @todo: this should be tied to max allowed proc name. */
291
292 RTStrAPrintf(&pszName, "/proc/%d/stat", process);
293 FILE *f = fopen(pszName, "r");
294 RTStrFree(pszName);
295
296 if (f)
297 {
298 if (fscanf(f, "%d %79s %c %d %d %d %d %d %u %lu %lu %lu %lu %u %u "
299 "%ld %ld %ld %ld %ld %ld %llu %lu %u",
300 &pid2, buf, &c, &iTmp, &iTmp, &iTmp, &iTmp, &iTmp, &uTmp,
301 &ulTmp, &ulTmp, &ulTmp, &ulTmp, &u32user, &u32kernel,
302 &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &u64Tmp,
303 &ulTmp, memPagesUsed) == 24)
304 {
305 Assert((pid_t)process == pid2);
306 *cpuUser = u32user;
307 *cpuKernel = u32kernel;
308 }
309 else
310 rc = VERR_FILE_IO_ERROR;
311 fclose(f);
312 }
313 else
314 rc = VERR_ACCESS_DENIED;
315
316 return rc;
317}
318
319int CollectorLinux::getRawHostNetworkLoad(const char *pszFile, uint64_t *rx, uint64_t *tx)
320{
321 int rc = VINF_SUCCESS;
322 char szIfName[/*IFNAMSIZ*/ 16 + 36];
323
324 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/rx_bytes", pszFile);
325 if (!RTLinuxSysFsExists(szIfName))
326 return VERR_FILE_NOT_FOUND;
327
328 int64_t cSize = RTLinuxSysFsReadIntFile(0, szIfName);
329 if (cSize < 0)
330 return VERR_ACCESS_DENIED;
331
332 *rx = cSize;
333
334 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/tx_bytes", pszFile);
335 if (!RTLinuxSysFsExists(szIfName))
336 return VERR_FILE_NOT_FOUND;
337
338 cSize = RTLinuxSysFsReadIntFile(0, szIfName);
339 if (cSize < 0)
340 return VERR_ACCESS_DENIED;
341
342 *tx = cSize;
343 return VINF_SUCCESS;
344}
345
346int CollectorLinux::getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms)
347{
348#if 0
349 int rc = VINF_SUCCESS;
350 char szIfName[/*IFNAMSIZ*/ 16 + 36];
351 long long unsigned int u64Busy, tmp;
352
353 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/block/%s/stat", name);
354 FILE *f = fopen(szIfName, "r");
355 if (f)
356 {
357 if (fscanf(f, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
358 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
359 {
360 *disk_ms = u64Busy;
361 *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
362 }
363 else
364 rc = VERR_FILE_IO_ERROR;
365 fclose(f);
366 }
367 else
368 rc = VERR_ACCESS_DENIED;
369#else
370 int rc = VERR_MISSING;
371 FILE *f = fopen("/proc/diskstats", "r");
372 if (f)
373 {
374 char szBuf[128];
375 while (fgets(szBuf, sizeof(szBuf), f))
376 {
377 char *pszBufName = szBuf;
378 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
379 while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip major */
380 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
381 while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip minor */
382 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
383
384 char *pszBufData = strchr(pszBufName, ' ');
385 if (!pszBufData)
386 {
387 LogRel(("CollectorLinux::getRawHostDiskLoad() failed to parse disk stats: %s\n", szBuf));
388 continue;
389 }
390 *pszBufData++ = '\0';
391 if (!strcmp(name, pszBufName))
392 {
393 long long unsigned int u64Busy, tmp;
394
395 if (sscanf(pszBufData, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
396 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
397 {
398 *disk_ms = u64Busy;
399 *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
400 rc = VINF_SUCCESS;
401 }
402 else
403 rc = VERR_FILE_IO_ERROR;
404 break;
405 }
406 }
407 fclose(f);
408 }
409#endif
410
411 return rc;
412}
413
414char *CollectorLinux::trimNewline(char *pszName)
415{
416 unsigned cbName = strlen(pszName);
417 if (cbName == 0)
418 return pszName;
419
420 char *pszEnd = pszName + cbName - 1;
421 while (pszEnd > pszName && *pszEnd == '\n')
422 pszEnd--;
423 pszEnd[1] = '\0';
424
425 return pszName;
426}
427
428char *CollectorLinux::trimTrailingDigits(char *pszName)
429{
430 unsigned cbName = strlen(pszName);
431 if (cbName == 0)
432 return pszName;
433
434 char *pszEnd = pszName + cbName - 1;
435 while (pszEnd > pszName && (RT_C_IS_DIGIT(*pszEnd) || *pszEnd == '\n'))
436 pszEnd--;
437 pszEnd[1] = '\0';
438
439 return pszName;
440}
441
442/**
443 * Use the partition name to get the name of the disk. Any path component is stripped.
444 * if fTrimDigits is true, trailing digits are stripped as well, for example '/dev/sda5'
445 * is converted to 'sda'.
446 *
447 * @param pszDiskName Where to store the name of the disk.
448 * @param cbDiskName The size of the buffer pszDiskName points to.
449 * @param pszDevName The device name used to get the disk name.
450 * @param fTrimDigits Trim trailing digits (e.g. /dev/sda5)
451 */
452void CollectorLinux::getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits)
453{
454 unsigned cbName = 0;
455 unsigned cbDevName = strlen(pszDevName);
456 const char *pszEnd = pszDevName + cbDevName - 1;
457 if (fTrimDigits)
458 while (pszEnd > pszDevName && RT_C_IS_DIGIT(*pszEnd))
459 pszEnd--;
460 while (pszEnd > pszDevName && *pszEnd != '/')
461 {
462 cbName++;
463 pszEnd--;
464 }
465 RTStrCopy(pszDiskName, RT_MIN(cbName + 1, cbDiskName), pszEnd + 1);
466}
467
468void CollectorLinux::addRaidDisks(const char *pcszDevice, DiskList& listDisks)
469{
470 FILE *f = fopen("/proc/mdstat", "r");
471 if (f)
472 {
473 char szBuf[128];
474 while (fgets(szBuf, sizeof(szBuf), f))
475 {
476 char *pszBufName = szBuf;
477
478 char *pszBufData = strchr(pszBufName, ' ');
479 if (!pszBufData)
480 {
481 LogRel(("CollectorLinux::addRaidDisks() failed to parse disk stats: %s\n", szBuf));
482 continue;
483 }
484 *pszBufData++ = '\0';
485 if (!strcmp(pcszDevice, pszBufName))
486 {
487 while (*pszBufData == ':') ++pszBufData; /* Skip delimiter */
488 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
489 while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip status */
490 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
491 while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip type */
492
493 while (*pszBufData != '\0')
494 {
495 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
496 char *pszDisk = pszBufData;
497 while (RT_C_IS_ALPHA(*pszBufData))
498 ++pszBufData;
499 if (*pszBufData)
500 {
501 *pszBufData++ = '\0';
502 listDisks.push_back(RTCString(pszDisk));
503 while (*pszBufData != '\0' && *pszBufData != ' ')
504 ++pszBufData;
505 }
506 else
507 listDisks.push_back(RTCString(pszDisk));
508 }
509 break;
510 }
511 }
512 fclose(f);
513 }
514}
515
516void CollectorLinux::addVolumeDependencies(const char *pcszVolume, DiskList& listDisks)
517{
518 char szVolInfo[RTPATH_MAX];
519 int rc = RTPathExecDir(szVolInfo, 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 if (!strncmp(szFsName, RT_STR_TUPLE("/dev/mapper")))
565 {
566 /* LVM */
567 getDiskName(szDevName, sizeof(szDevName), szFsName, false /*=fTrimDigits*/);
568 addVolumeDependencies(szDevName, listUsage);
569 listLoad = listUsage;
570 }
571 else if (!strncmp(szFsName, RT_STR_TUPLE("/dev/md")))
572 {
573 /* Software RAID */
574 getDiskName(szDevName, sizeof(szDevName), szFsName, false /*=fTrimDigits*/);
575 listUsage.push_back(RTCString(szDevName));
576 addRaidDisks(szDevName, listLoad);
577 }
578 else
579 {
580 /* Plain disk partition. Trim the trailing digits to get the drive name */
581 getDiskName(szDevName, sizeof(szDevName), szFsName, true /*=fTrimDigits*/);
582 listUsage.push_back(RTCString(szDevName));
583 listLoad.push_back(RTCString(szDevName));
584 }
585 if (listUsage.empty() || listLoad.empty())
586 {
587 LogRel(("Failed to retrive disk info: getDiskName(%s) --> %s\n",
588 mntent->mnt_fsname, szDevName));
589 }
590 break;
591 }
592 }
593 endmntent(mtab);
594 }
595 return VINF_SUCCESS;
596}
597
598}
599
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