VirtualBox

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

Last change on this file since 45442 was 45051, checked in by vboxsync, 12 years ago

Main/Metrics: handle less common cases for FS to disk resolution (#6345)

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

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