VirtualBox

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

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

Main/Metrics: Alternative way to collect disk stats on Linux (#6345)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.2 KB
Line 
1/* $Id: PerformanceLinux.cpp 43842 2012-11-08 16:51:46Z vboxsync $ */
2
3/** @file
4 *
5 * VBox Linux-specific Performance Classes implementation.
6 */
7
8/*
9 * Copyright (C) 2008-2011 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 getProcessMemoryUsage(RTPROCESS process, ULONG *used);
52
53 virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
54 virtual int getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx);
55 virtual int getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms);
56 virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
57
58 virtual int getDiskListByFs(const char *name, DiskList& list);
59private:
60 virtual int _getRawHostCpuLoad();
61 int getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed);
62 char *getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits);
63 void addVolumeDependencies(const char *pcszVolume, DiskList& listDisks);
64 char *trimTrailingDigits(char *pszName);
65
66 struct VMProcessStats
67 {
68 uint64_t cpuUser;
69 uint64_t cpuKernel;
70 ULONG pagesUsed;
71 };
72
73 typedef std::map<RTPROCESS, VMProcessStats> VMProcessMap;
74
75 VMProcessMap mProcessStats;
76 uint64_t mUser, mKernel, mIdle;
77 uint64_t mSingleUser, mSingleKernel, mSingleIdle;
78 uint32_t mHZ;
79};
80
81CollectorHAL *createHAL()
82{
83 return new CollectorLinux();
84}
85
86// Collector HAL for Linux
87
88CollectorLinux::CollectorLinux()
89{
90 long hz = sysconf(_SC_CLK_TCK);
91 if (hz == -1)
92 {
93 LogRel(("CollectorLinux failed to obtain HZ from kernel, assuming 100.\n"));
94 mHZ = 100;
95 }
96 else
97 mHZ = hz;
98 LogFlowThisFunc(("mHZ=%u\n", mHZ));
99}
100
101int CollectorLinux::preCollect(const CollectorHints& hints, uint64_t /* iTick */)
102{
103 std::vector<RTPROCESS> processes;
104 hints.getProcesses(processes);
105
106 std::vector<RTPROCESS>::iterator it;
107 for (it = processes.begin(); it != processes.end(); it++)
108 {
109 VMProcessStats vmStats;
110 int rc = getRawProcessStats(*it, &vmStats.cpuUser, &vmStats.cpuKernel, &vmStats.pagesUsed);
111 /* On failure, do NOT stop. Just skip the entry. Having the stats for
112 * one (probably broken) process frozen/zero is a minor issue compared
113 * to not updating many process stats and the host cpu stats. */
114 if (RT_SUCCESS(rc))
115 mProcessStats[*it] = vmStats;
116 }
117 if (hints.isHostCpuLoadCollected() || mProcessStats.size())
118 {
119 _getRawHostCpuLoad();
120 }
121 return VINF_SUCCESS;
122}
123
124int CollectorLinux::_getRawHostCpuLoad()
125{
126 int rc = VINF_SUCCESS;
127 long long unsigned uUser, uNice, uKernel, uIdle, uIowait, uIrq, uSoftirq;
128 FILE *f = fopen("/proc/stat", "r");
129
130 if (f)
131 {
132 char szBuf[128];
133 if (fgets(szBuf, sizeof(szBuf), f))
134 {
135 if (sscanf(szBuf, "cpu %llu %llu %llu %llu %llu %llu %llu",
136 &uUser, &uNice, &uKernel, &uIdle, &uIowait,
137 &uIrq, &uSoftirq) == 7)
138 {
139 mUser = uUser + uNice;
140 mKernel = uKernel + uIrq + uSoftirq;
141 mIdle = uIdle + uIowait;
142 }
143 /* Try to get single CPU stats. */
144 if (fgets(szBuf, sizeof(szBuf), f))
145 {
146 if (sscanf(szBuf, "cpu0 %llu %llu %llu %llu %llu %llu %llu",
147 &uUser, &uNice, &uKernel, &uIdle, &uIowait,
148 &uIrq, &uSoftirq) == 7)
149 {
150 mSingleUser = uUser + uNice;
151 mSingleKernel = uKernel + uIrq + uSoftirq;
152 mSingleIdle = uIdle + uIowait;
153 }
154 else
155 {
156 /* Assume that this is not an SMP system. */
157 Assert(RTMpGetCount() == 1);
158 mSingleUser = mUser;
159 mSingleKernel = mKernel;
160 mSingleIdle = mIdle;
161 }
162 }
163 else
164 rc = VERR_FILE_IO_ERROR;
165 }
166 else
167 rc = VERR_FILE_IO_ERROR;
168 fclose(f);
169 }
170 else
171 rc = VERR_ACCESS_DENIED;
172
173 return rc;
174}
175
176int CollectorLinux::getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle)
177{
178 *user = mUser;
179 *kernel = mKernel;
180 *idle = mIdle;
181 return VINF_SUCCESS;
182}
183
184int CollectorLinux::getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total)
185{
186 VMProcessMap::const_iterator it = mProcessStats.find(process);
187
188 if (it == mProcessStats.end())
189 {
190 Log (("No stats pre-collected for process %x\n", process));
191 return VERR_INTERNAL_ERROR;
192 }
193 *user = it->second.cpuUser;
194 *kernel = it->second.cpuKernel;
195 *total = mUser + mKernel + mIdle;
196 return VINF_SUCCESS;
197}
198
199int CollectorLinux::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
200{
201 int rc = VINF_SUCCESS;
202 ULONG buffers, cached;
203 FILE *f = fopen("/proc/meminfo", "r");
204
205 if (f)
206 {
207 int processed = fscanf(f, "MemTotal: %u kB\n", total);
208 processed += fscanf(f, "MemFree: %u kB\n", available);
209 processed += fscanf(f, "Buffers: %u kB\n", &buffers);
210 processed += fscanf(f, "Cached: %u kB\n", &cached);
211 if (processed == 4)
212 {
213 *available += buffers + cached;
214 *used = *total - *available;
215 }
216 else
217 rc = VERR_FILE_IO_ERROR;
218 fclose(f);
219 }
220 else
221 rc = VERR_ACCESS_DENIED;
222
223 return rc;
224}
225
226int CollectorLinux::getHostFilesystemUsage(const char *path, ULONG *total, ULONG *used, ULONG *available)
227{
228 struct statvfs stats;
229 const unsigned _MB = 1024 * 1024;
230
231 if (statvfs(path, &stats) == -1)
232 {
233 LogRel(("Failed to collect %s filesystem usage: errno=%d.\n", path, errno));
234 return VERR_ACCESS_DENIED;
235 }
236 uint64_t cbBlock = stats.f_frsize ? stats.f_frsize : stats.f_bsize;
237 *total = (ULONG)(cbBlock * stats.f_blocks / _MB);
238 *used = (ULONG)(cbBlock * (stats.f_blocks - stats.f_bfree) / _MB);
239 *available = (ULONG)(cbBlock * stats.f_bavail / _MB);
240
241 return VINF_SUCCESS;
242}
243
244int CollectorLinux::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
245{
246 VMProcessMap::const_iterator it = mProcessStats.find(process);
247
248 if (it == mProcessStats.end())
249 {
250 Log (("No stats pre-collected for process %x\n", process));
251 return VERR_INTERNAL_ERROR;
252 }
253 *used = it->second.pagesUsed * (PAGE_SIZE / 1024);
254 return VINF_SUCCESS;
255}
256
257int CollectorLinux::getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed)
258{
259 int rc = VINF_SUCCESS;
260 char *pszName;
261 pid_t pid2;
262 char c;
263 int iTmp;
264 long long unsigned int u64Tmp;
265 unsigned uTmp;
266 unsigned long ulTmp;
267 signed long ilTmp;
268 ULONG u32user, u32kernel;
269 char buf[80]; /* @todo: this should be tied to max allowed proc name. */
270
271 RTStrAPrintf(&pszName, "/proc/%d/stat", process);
272 //printf("Opening %s...\n", pszName);
273 FILE *f = fopen(pszName, "r");
274 RTMemFree(pszName);
275
276 if (f)
277 {
278 if (fscanf(f, "%d %79s %c %d %d %d %d %d %u %lu %lu %lu %lu %u %u "
279 "%ld %ld %ld %ld %ld %ld %llu %lu %u",
280 &pid2, buf, &c, &iTmp, &iTmp, &iTmp, &iTmp, &iTmp, &uTmp,
281 &ulTmp, &ulTmp, &ulTmp, &ulTmp, &u32user, &u32kernel,
282 &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &u64Tmp,
283 &ulTmp, memPagesUsed) == 24)
284 {
285 Assert((pid_t)process == pid2);
286 *cpuUser = u32user;
287 *cpuKernel = u32kernel;
288 }
289 else
290 rc = VERR_FILE_IO_ERROR;
291 fclose(f);
292 }
293 else
294 rc = VERR_ACCESS_DENIED;
295
296 return rc;
297}
298
299int CollectorLinux::getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx)
300{
301 int rc = VINF_SUCCESS;
302 char szIfName[/*IFNAMSIZ*/ 16 + 36];
303 long long unsigned int u64Rx, u64Tx;
304
305 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/rx_bytes", name);
306 FILE *f = fopen(szIfName, "r");
307 if (f)
308 {
309 if (fscanf(f, "%llu", &u64Rx) == 1)
310 *rx = u64Rx;
311 else
312 rc = VERR_FILE_IO_ERROR;
313 fclose(f);
314 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/tx_bytes", name);
315 f = fopen(szIfName, "r");
316 if (f)
317 {
318 if (fscanf(f, "%llu", &u64Tx) == 1)
319 *tx = u64Tx;
320 else
321 rc = VERR_FILE_IO_ERROR;
322 fclose(f);
323 }
324 else
325 rc = VERR_ACCESS_DENIED;
326 }
327 else
328 rc = VERR_ACCESS_DENIED;
329
330 return rc;
331}
332
333int CollectorLinux::getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms)
334{
335#if 0
336 int rc = VINF_SUCCESS;
337 char szIfName[/*IFNAMSIZ*/ 16 + 36];
338 long long unsigned int u64Busy, tmp;
339
340 RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/block/%s/stat", name);
341 FILE *f = fopen(szIfName, "r");
342 if (f)
343 {
344 if (fscanf(f, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
345 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
346 {
347 *disk_ms = u64Busy;
348 *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
349 }
350 else
351 rc = VERR_FILE_IO_ERROR;
352 fclose(f);
353 }
354 else
355 rc = VERR_ACCESS_DENIED;
356#else
357 int rc = VERR_MISSING;
358 FILE *f = fopen("/proc/diskstats", "r");
359 if (f)
360 {
361 char szBuf[128];
362 while (fgets(szBuf, sizeof(szBuf), f))
363 {
364 char *pszBufName = &szBuf[13];
365 char *pszBufData = strchr(pszBufName, ' ');
366 if (!pszBufData)
367 {
368 LogRel(("CollectorLinux::getRawHostDiskLoad() failed to parse disk stats: %s\n", szBuf));
369 continue;
370 }
371 *pszBufData++ = '\0';
372 if (!strcmp(name, pszBufName))
373 {
374 long long unsigned int u64Busy, tmp;
375
376 if (sscanf(pszBufData, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
377 &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
378 {
379 *disk_ms = u64Busy;
380 *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
381 rc = VINF_SUCCESS;
382 }
383 else
384 rc = VERR_FILE_IO_ERROR;
385 break;
386 }
387 }
388 fclose(f);
389 }
390#endif
391
392 return rc;
393}
394
395char *CollectorLinux::trimTrailingDigits(char *pszName)
396{
397 unsigned cbName = strlen(pszName);
398 if (cbName == 0)
399 return pszName;
400
401 char *pszEnd = pszName + cbName - 1;
402 while (pszEnd > pszName && RT_C_IS_DIGIT(*pszEnd))
403 pszEnd--;
404 pszEnd[1] = '\0';
405
406 return pszName;
407}
408
409char *CollectorLinux::getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits)
410{
411 unsigned cbName = 0;
412 unsigned cbDevName = strlen(pszDevName);
413 const char *pszEnd = pszDevName + cbDevName - 1;
414 if (fTrimDigits)
415 while (pszEnd > pszDevName && RT_C_IS_DIGIT(*pszEnd))
416 pszEnd--;
417 while (pszEnd > pszDevName && *pszEnd != '/')
418 {
419 cbName++;
420 pszEnd--;
421 }
422 RTStrCopy(pszDiskName, RT_MIN(cbName + 1, cbDiskName), pszEnd + 1);
423 return pszDiskName;
424}
425
426void CollectorLinux::addVolumeDependencies(const char *pcszVolume, DiskList& listDisks)
427{
428 char szVolInfo[RTPATH_MAX];
429 int rc = RTPathExecDir(szVolInfo, sizeof(szVolInfo) - sizeof("/" VBOXVOLINFO_NAME " ") - strlen(pcszVolume));
430 if (RT_FAILURE(rc))
431 {
432 LogRel(("VolInfo: Failed to get program path, rc=%Rrc\n", rc));
433 return;
434 }
435 strcat(szVolInfo, "/" VBOXVOLINFO_NAME " ");
436 strcat(szVolInfo, pcszVolume);
437
438 FILE *fp = popen(szVolInfo, "r");
439 if (fp)
440 {
441 char szBuf[128];
442
443 while (fgets(szBuf, sizeof(szBuf), fp))
444 listDisks.push_back(RTCString(trimTrailingDigits(szBuf)));
445
446 pclose(fp);
447 }
448 else
449 listDisks.push_back(RTCString(pcszVolume));
450}
451
452int CollectorLinux::getDiskListByFs(const char *pszPath, DiskList& listDisks)
453{
454 FILE *mtab = setmntent("/etc/mtab", "r");
455 if (mtab)
456 {
457 struct mntent *mntent;
458 while ((mntent = getmntent(mtab)))
459 {
460 if (strcmp(pszPath, mntent->mnt_dir) == 0)
461 {
462 char szDevName[128];
463 if (strncmp(mntent->mnt_fsname, "/dev/mapper", 11))
464 {
465 getDiskName(szDevName, sizeof(szDevName), mntent->mnt_fsname, true);
466 listDisks.push_back(RTCString(szDevName));
467 }
468 else
469 {
470 getDiskName(szDevName, sizeof(szDevName), mntent->mnt_fsname, false);
471 addVolumeDependencies(szDevName, listDisks);
472 }
473 break;
474 }
475 }
476 endmntent(mtab);
477 }
478 return VINF_SUCCESS;
479}
480
481}
482
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