VirtualBox

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

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

Main/PerformanceLinux: switch to the runtime function for determining total/free RAM

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.5 KB
Line 
1/* $Id: PerformanceLinux.cpp 46328 2013-05-30 12:37:09Z 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
35#include <map>
36#include <vector>
37
38#include "Logging.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 char *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 totalRAM;
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 = hz;
103 LogFlowThisFunc(("mHZ=%u\n", mHZ));
104
105 uint64_t cb;
106 int rc = RTSystemQueryTotalRam(&cb);
107 if (RT_FAILURE(rc))
108 totalRAM = 0;
109 else
110 totalRAM = (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.size())
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(totalRAM, VERR_INTERNAL_ERROR);
214 uint64_t cb;
215 int rc = RTSystemQueryAvailableRam(&cb);
216 if (RT_SUCCESS(rc))
217 {
218 *total = totalRAM;
219 *available = 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 const unsigned _MB = 1024 * 1024;
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 / _MB);
237 *used = (ULONG)(cbBlock * (stats.f_blocks - stats.f_bfree) / _MB);
238 *available = (ULONG)(cbBlock * stats.f_bavail / _MB);
239
240 return VINF_SUCCESS;
241}
242
243int CollectorLinux::getHostDiskSize(const char *name, uint64_t *size)
244{
245 int rc = VINF_SUCCESS;
246 char *pszName = NULL;
247 long long unsigned int u64Size;
248
249 RTStrAPrintf(&pszName, "/sys/block/%s/size", name);
250 Assert(pszName);
251 FILE *f = fopen(pszName, "r");
252 RTMemFree(pszName);
253
254 if (f)
255 {
256 if (fscanf(f, "%llu", &u64Size) == 1)
257 *size = u64Size * 512;
258 else
259 rc = VERR_FILE_IO_ERROR;
260 fclose(f);
261 }
262 else
263 rc = VERR_ACCESS_DENIED;
264
265 return rc;
266}
267
268int CollectorLinux::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
269{
270 VMProcessMap::const_iterator it = mProcessStats.find(process);
271
272 if (it == mProcessStats.end())
273 {
274 Log (("No stats pre-collected for process %x\n", process));
275 return VERR_INTERNAL_ERROR;
276 }
277 *used = it->second.pagesUsed * (PAGE_SIZE / 1024);
278 return VINF_SUCCESS;
279}
280
281int CollectorLinux::getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed)
282{
283 int rc = VINF_SUCCESS;
284 char *pszName;
285 pid_t pid2;
286 char c;
287 int iTmp;
288 long long unsigned int u64Tmp;
289 unsigned uTmp;
290 unsigned long ulTmp;
291 signed long ilTmp;
292 ULONG u32user, u32kernel;
293 char buf[80]; /* @todo: this should be tied to max allowed proc name. */
294
295 RTStrAPrintf(&pszName, "/proc/%d/stat", process);
296 //printf("Opening %s...\n", pszName);
297 FILE *f = fopen(pszName, "r");
298 RTMemFree(pszName);
299
300 if (f)
301 {
302 if (fscanf(f, "%d %79s %c %d %d %d %d %d %u %lu %lu %lu %lu %u %u "
303 "%ld %ld %ld %ld %ld %ld %llu %lu %u",
304 &pid2, buf, &c, &iTmp, &iTmp, &iTmp, &iTmp, &iTmp, &uTmp,
305 &ulTmp, &ulTmp, &ulTmp, &ulTmp, &u32user, &u32kernel,
306 &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &u64Tmp,
307 &ulTmp, memPagesUsed) == 24)
308 {
309 Assert((pid_t)process == pid2);
310 *cpuUser = u32user;
311 *cpuKernel = u32kernel;
312 }
313 else
314 rc = VERR_FILE_IO_ERROR;
315 fclose(f);
316 }
317 else
318 rc = VERR_ACCESS_DENIED;
319
320 return rc;
321}
322
323int CollectorLinux::getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx)
324{
325 int rc = VINF_SUCCESS;
326 char szIfName[/*IFNAMSIZ*/ 16 + 36];
327 long long unsigned int u64Rx, u64Tx;
328
329 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/rx_bytes", name);
330 FILE *f = fopen(szIfName, "r");
331 if (f)
332 {
333 if (fscanf(f, "%llu", &u64Rx) == 1)
334 *rx = u64Rx;
335 else
336 rc = VERR_FILE_IO_ERROR;
337 fclose(f);
338 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/tx_bytes", name);
339 f = fopen(szIfName, "r");
340 if (f)
341 {
342 if (fscanf(f, "%llu", &u64Tx) == 1)
343 *tx = u64Tx;
344 else
345 rc = VERR_FILE_IO_ERROR;
346 fclose(f);
347 }
348 else
349 rc = VERR_ACCESS_DENIED;
350 }
351 else
352 rc = VERR_ACCESS_DENIED;
353
354 return rc;
355}
356
357int CollectorLinux::getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms)
358{
359#if 0
360 int rc = VINF_SUCCESS;
361 char szIfName[/*IFNAMSIZ*/ 16 + 36];
362 long long unsigned int u64Busy, tmp;
363
364 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/block/%s/stat", name);
365 FILE *f = fopen(szIfName, "r");
366 if (f)
367 {
368 if (fscanf(f, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
369 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
370 {
371 *disk_ms = u64Busy;
372 *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
373 }
374 else
375 rc = VERR_FILE_IO_ERROR;
376 fclose(f);
377 }
378 else
379 rc = VERR_ACCESS_DENIED;
380#else
381 int rc = VERR_MISSING;
382 FILE *f = fopen("/proc/diskstats", "r");
383 if (f)
384 {
385 char szBuf[128];
386 while (fgets(szBuf, sizeof(szBuf), f))
387 {
388 char *pszBufName = szBuf;
389 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
390 while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip major */
391 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
392 while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip minor */
393 while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
394
395 char *pszBufData = strchr(pszBufName, ' ');
396 if (!pszBufData)
397 {
398 LogRel(("CollectorLinux::getRawHostDiskLoad() failed to parse disk stats: %s\n", szBuf));
399 continue;
400 }
401 *pszBufData++ = '\0';
402 if (!strcmp(name, pszBufName))
403 {
404 long long unsigned int u64Busy, tmp;
405
406 if (sscanf(pszBufData, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
407 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
408 {
409 *disk_ms = u64Busy;
410 *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
411 rc = VINF_SUCCESS;
412 }
413 else
414 rc = VERR_FILE_IO_ERROR;
415 break;
416 }
417 }
418 fclose(f);
419 }
420#endif
421
422 return rc;
423}
424
425char *CollectorLinux::trimNewline(char *pszName)
426{
427 unsigned cbName = strlen(pszName);
428 if (cbName == 0)
429 return pszName;
430
431 char *pszEnd = pszName + cbName - 1;
432 while (pszEnd > pszName && *pszEnd == '\n')
433 pszEnd--;
434 pszEnd[1] = '\0';
435
436 return pszName;
437}
438
439char *CollectorLinux::trimTrailingDigits(char *pszName)
440{
441 unsigned cbName = strlen(pszName);
442 if (cbName == 0)
443 return pszName;
444
445 char *pszEnd = pszName + cbName - 1;
446 while (pszEnd > pszName && (RT_C_IS_DIGIT(*pszEnd) || *pszEnd == '\n'))
447 pszEnd--;
448 pszEnd[1] = '\0';
449
450 return pszName;
451}
452
453char *CollectorLinux::getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits)
454{
455 unsigned cbName = 0;
456 unsigned cbDevName = strlen(pszDevName);
457 const char *pszEnd = pszDevName + cbDevName - 1;
458 if (fTrimDigits)
459 while (pszEnd > pszDevName && RT_C_IS_DIGIT(*pszEnd))
460 pszEnd--;
461 while (pszEnd > pszDevName && *pszEnd != '/')
462 {
463 cbName++;
464 pszEnd--;
465 }
466 RTStrCopy(pszDiskName, RT_MIN(cbName + 1, cbDiskName), pszEnd + 1);
467 return pszDiskName;
468}
469
470void CollectorLinux::addRaidDisks(const char *pcszDevice, DiskList& listDisks)
471{
472 FILE *f = fopen("/proc/mdstat", "r");
473 if (f)
474 {
475 char szBuf[128];
476 while (fgets(szBuf, sizeof(szBuf), f))
477 {
478 char *pszBufName = szBuf;
479
480 char *pszBufData = strchr(pszBufName, ' ');
481 if (!pszBufData)
482 {
483 LogRel(("CollectorLinux::addRaidDisks() failed to parse disk stats: %s\n", szBuf));
484 continue;
485 }
486 *pszBufData++ = '\0';
487 if (!strcmp(pcszDevice, pszBufName))
488 {
489 while (*pszBufData == ':') ++pszBufData; /* Skip delimiter */
490 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
491 while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip status */
492 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
493 while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip type */
494
495 while (*pszBufData != '\0')
496 {
497 while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
498 char *pszDisk = pszBufData;
499 while (RT_C_IS_ALPHA(*pszBufData))
500 ++pszBufData;
501 if (*pszBufData)
502 {
503 *pszBufData++ = '\0';
504 listDisks.push_back(RTCString(pszDisk));
505 while (*pszBufData != '\0' && *pszBufData != ' ')
506 ++pszBufData;
507 }
508 else
509 listDisks.push_back(RTCString(pszDisk));
510 }
511 break;
512 }
513 }
514 fclose(f);
515 }
516}
517
518void CollectorLinux::addVolumeDependencies(const char *pcszVolume, DiskList& listDisks)
519{
520 char szVolInfo[RTPATH_MAX];
521 int rc = RTPathExecDir(szVolInfo, sizeof(szVolInfo) - sizeof("/" VBOXVOLINFO_NAME " ") - strlen(pcszVolume));
522 if (RT_FAILURE(rc))
523 {
524 LogRel(("VolInfo: Failed to get program path, rc=%Rrc\n", rc));
525 return;
526 }
527 strcat(szVolInfo, "/" VBOXVOLINFO_NAME " ");
528 strcat(szVolInfo, pcszVolume);
529
530 FILE *fp = popen(szVolInfo, "r");
531 if (fp)
532 {
533 char szBuf[128];
534
535 while (fgets(szBuf, sizeof(szBuf), fp))
536 if (strncmp(szBuf, "dm-", 3))
537 listDisks.push_back(RTCString(trimTrailingDigits(szBuf)));
538 else
539 listDisks.push_back(RTCString(trimNewline(szBuf)));
540
541 pclose(fp);
542 }
543 else
544 listDisks.push_back(RTCString(pcszVolume));
545}
546
547int CollectorLinux::getDiskListByFs(const char *pszPath, DiskList& listUsage, DiskList& listLoad)
548{
549 FILE *mtab = setmntent("/etc/mtab", "r");
550 if (mtab)
551 {
552 struct mntent *mntent;
553 while ((mntent = getmntent(mtab)))
554 {
555 /* Skip rootfs entry, there must be another root mount. */
556 if (strcmp(mntent->mnt_fsname, "rootfs") == 0)
557 continue;
558 if (strcmp(pszPath, mntent->mnt_dir) == 0)
559 {
560 char szDevName[128];
561 char szFsName[1024];
562 /* Try to resolve symbolic link if necessary */
563 ssize_t cbFsName = readlink(mntent->mnt_fsname, szFsName, sizeof(szFsName) - 1);
564 if (cbFsName != -1)
565 szFsName[cbFsName] = '\0';
566 else
567 strcpy(szFsName, mntent->mnt_fsname);
568 if (!strncmp(szFsName, "/dev/mapper", 11))
569 {
570 /* LVM */
571 getDiskName(szDevName, sizeof(szDevName), szFsName, false);
572 addVolumeDependencies(szDevName, listUsage);
573 listLoad = listUsage;
574 }
575 else if (!strncmp(szFsName, "/dev/md", 7))
576 {
577 /* Software RAID */
578 getDiskName(szDevName, sizeof(szDevName), szFsName, false);
579 listUsage.push_back(RTCString(szDevName));
580 addRaidDisks(szDevName, listLoad);
581 }
582 else
583 {
584 /* Plain disk partition */
585 getDiskName(szDevName, sizeof(szDevName), mntent->mnt_fsname, true);
586 listUsage.push_back(RTCString(szDevName));
587 listLoad.push_back(RTCString(szDevName));
588 }
589 if (listUsage.empty() || listLoad.empty())
590 {
591 LogRel(("Failed to retrive disk info: getDiskName(%s) --> %s\n", mntent->mnt_fsname, szDevName));
592 }
593 break;
594 }
595 }
596 endmntent(mtab);
597 }
598 return VINF_SUCCESS;
599}
600
601}
602
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