|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "mozilla/SystemMemoryReporter.h" |
|
8 |
|
9 #include "mozilla/Attributes.h" |
|
10 #include "mozilla/Preferences.h" |
|
11 #include "mozilla/unused.h" |
|
12 |
|
13 #include "nsIMemoryReporter.h" |
|
14 #include "nsPrintfCString.h" |
|
15 #include "nsString.h" |
|
16 #include "nsTHashtable.h" |
|
17 #include "nsHashKeys.h" |
|
18 |
|
19 #include <dirent.h> |
|
20 #include <inttypes.h> |
|
21 #include <stdio.h> |
|
22 #include <sys/stat.h> |
|
23 #include <sys/types.h> |
|
24 #include <unistd.h> |
|
25 #include <errno.h> |
|
26 |
|
27 // This file implements a Linux-specific, system-wide memory reporter. It |
|
28 // gathers all the useful memory measurements obtainable from the OS in a |
|
29 // single place, giving a high-level view of memory consumption for the entire |
|
30 // machine/device. |
|
31 // |
|
32 // Other memory reporters measure part of a single process's memory consumption. |
|
33 // This reporter is different in that it measures memory consumption of many |
|
34 // processes, and they end up in a single reports tree. This is a slight abuse |
|
35 // of the memory reporting infrastructure, and therefore the results are given |
|
36 // their own "process" called "System", which means they show up in about:memory |
|
37 // in their own section, distinct from the per-process sections. |
|
38 |
|
39 namespace mozilla { |
|
40 namespace SystemMemoryReporter { |
|
41 |
|
42 #if !defined(XP_LINUX) |
|
43 #error "This won't work if we're not on Linux." |
|
44 #endif |
|
45 |
|
46 static bool |
|
47 EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle) |
|
48 { |
|
49 int32_t idx = aHaystack.RFind(aNeedle); |
|
50 return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length(); |
|
51 } |
|
52 |
|
53 static void |
|
54 GetDirname(const nsCString& aPath, nsACString& aOut) |
|
55 { |
|
56 int32_t idx = aPath.RFind("/"); |
|
57 if (idx == -1) { |
|
58 aOut.Truncate(); |
|
59 } else { |
|
60 aOut.Assign(Substring(aPath, 0, idx)); |
|
61 } |
|
62 } |
|
63 |
|
64 static void |
|
65 GetBasename(const nsCString& aPath, nsACString& aOut) |
|
66 { |
|
67 nsCString out; |
|
68 int32_t idx = aPath.RFind("/"); |
|
69 if (idx == -1) { |
|
70 out.Assign(aPath); |
|
71 } else { |
|
72 out.Assign(Substring(aPath, idx + 1)); |
|
73 } |
|
74 |
|
75 // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g. |
|
76 // "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so |
|
77 // cut it off when getting the entry's basename. |
|
78 if (EndsWithLiteral(out, "(deleted)")) { |
|
79 out.Assign(Substring(out, 0, out.RFind("(deleted)"))); |
|
80 } |
|
81 out.StripChars(" "); |
|
82 |
|
83 aOut.Assign(out); |
|
84 } |
|
85 |
|
86 static bool |
|
87 IsNumeric(const char* s) |
|
88 { |
|
89 MOZ_ASSERT(*s); // shouldn't see empty strings |
|
90 while (*s) { |
|
91 if (!isdigit(*s)) { |
|
92 return false; |
|
93 } |
|
94 s++; |
|
95 } |
|
96 return true; |
|
97 } |
|
98 |
|
99 static bool |
|
100 IsAnonymous(const nsACString& aName) |
|
101 { |
|
102 // Recent kernels (e.g. 3.5) have multiple [stack:nnnn] entries, where |nnnn| |
|
103 // is a thread ID. However, [stack:nnnn] entries count both stack memory |
|
104 // *and* anonymous memory because the kernel only knows about the start of |
|
105 // each thread stack, not its end. So we treat such entries as anonymous |
|
106 // memory instead of stack. This is consistent with older kernels that don't |
|
107 // even show [stack:nnnn] entries. |
|
108 return aName.IsEmpty() || |
|
109 StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:")); |
|
110 } |
|
111 |
|
112 class SystemReporter MOZ_FINAL : public nsIMemoryReporter |
|
113 { |
|
114 public: |
|
115 NS_DECL_THREADSAFE_ISUPPORTS |
|
116 |
|
117 #define REPORT_WITH_CLEANUP(_path, _units, _amount, _desc, _cleanup) \ |
|
118 do { \ |
|
119 size_t amount = _amount; /* evaluate _amount only once */ \ |
|
120 if (amount > 0) { \ |
|
121 nsresult rv; \ |
|
122 rv = aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path, \ |
|
123 KIND_NONHEAP, _units, amount, _desc, \ |
|
124 aData); \ |
|
125 if (NS_WARN_IF(NS_FAILED(rv))) { \ |
|
126 _cleanup; \ |
|
127 return rv; \ |
|
128 } \ |
|
129 } \ |
|
130 } while (0) |
|
131 |
|
132 #define REPORT(_path, _amount, _desc) \ |
|
133 REPORT_WITH_CLEANUP(_path, UNITS_BYTES, _amount, _desc, (void)0) |
|
134 |
|
135 NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, |
|
136 nsISupports* aData) |
|
137 { |
|
138 if (!Preferences::GetBool("memory.system_memory_reporter")) { |
|
139 return NS_OK; |
|
140 } |
|
141 |
|
142 // Read relevant fields from /proc/meminfo. |
|
143 int64_t memTotal = 0, memFree = 0; |
|
144 nsresult rv = ReadMemInfo(&memTotal, &memFree); |
|
145 |
|
146 // Collect per-process reports from /proc/<pid>/smaps. |
|
147 int64_t totalPss = 0; |
|
148 rv = CollectProcessReports(aHandleReport, aData, &totalPss); |
|
149 NS_ENSURE_SUCCESS(rv, rv); |
|
150 |
|
151 // Report the non-process numbers. |
|
152 int64_t other = memTotal - memFree - totalPss; |
|
153 REPORT(NS_LITERAL_CSTRING("mem/other"), other, NS_LITERAL_CSTRING( |
|
154 "Memory which is neither owned by any user-space process nor free. Note that " |
|
155 "this includes memory holding cached files from the disk which can be " |
|
156 "reclaimed by the OS at any time.")); |
|
157 |
|
158 REPORT(NS_LITERAL_CSTRING("mem/free"), memFree, NS_LITERAL_CSTRING( |
|
159 "Memory which is free and not being used for any purpose.")); |
|
160 |
|
161 // Report reserved memory not included in memTotal. |
|
162 rv = CollectPmemReports(aHandleReport, aData); |
|
163 NS_ENSURE_SUCCESS(rv, rv); |
|
164 |
|
165 // Report zram usage statistics. |
|
166 rv = CollectZramReports(aHandleReport, aData); |
|
167 NS_ENSURE_SUCCESS(rv, rv); |
|
168 |
|
169 return rv; |
|
170 } |
|
171 |
|
172 private: |
|
173 // Keep this in sync with SystemReporter::kindPathSuffixes! |
|
174 enum ProcessSizeKind { |
|
175 AnonymousOutsideBrk = 0, |
|
176 AnonymousBrkHeap = 1, |
|
177 SharedLibrariesRX = 2, |
|
178 SharedLibrariesRW = 3, |
|
179 SharedLibrariesR = 4, |
|
180 SharedLibrariesOther = 5, |
|
181 OtherFiles = 6, |
|
182 MainThreadStack = 7, |
|
183 Vdso = 8, |
|
184 |
|
185 ProcessSizeKindLimit = 9 // must be last |
|
186 }; |
|
187 |
|
188 static const char* kindPathSuffixes[ProcessSizeKindLimit]; |
|
189 |
|
190 // These are the cross-cutting measurements across all processes. |
|
191 struct ProcessSizes |
|
192 { |
|
193 ProcessSizes() { memset(this, 0, sizeof(*this)); } |
|
194 |
|
195 size_t mSizes[ProcessSizeKindLimit]; |
|
196 }; |
|
197 |
|
198 nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree) |
|
199 { |
|
200 FILE* f = fopen("/proc/meminfo", "r"); |
|
201 if (!f) { |
|
202 return NS_ERROR_FAILURE; |
|
203 } |
|
204 |
|
205 int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal); |
|
206 int n2 = fscanf(f, "MemFree: %" SCNd64 " kB\n", aMemFree); |
|
207 |
|
208 fclose(f); |
|
209 |
|
210 if (n1 != 1 || n2 != 1) { |
|
211 return NS_ERROR_FAILURE; |
|
212 } |
|
213 |
|
214 // Convert from KB to B. |
|
215 *aMemTotal *= 1024; |
|
216 *aMemFree *= 1024; |
|
217 |
|
218 return NS_OK; |
|
219 } |
|
220 |
|
221 nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport, |
|
222 nsISupports* aData, |
|
223 int64_t* aTotalPss) |
|
224 { |
|
225 *aTotalPss = 0; |
|
226 ProcessSizes processSizes; |
|
227 |
|
228 DIR* d = opendir("/proc"); |
|
229 if (NS_WARN_IF(!d)) { |
|
230 return NS_ERROR_FAILURE; |
|
231 } |
|
232 struct dirent* ent; |
|
233 while ((ent = readdir(d))) { |
|
234 struct stat statbuf; |
|
235 const char* pidStr = ent->d_name; |
|
236 // Don't check the return value of stat() -- it can return -1 for these |
|
237 // directories even when it has succeeded, apparently. |
|
238 stat(pidStr, &statbuf); |
|
239 if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) { |
|
240 nsCString processName("process("); |
|
241 |
|
242 // Get the command name from cmdline. If that fails, the pid is still |
|
243 // shown. |
|
244 nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr); |
|
245 FILE* f = fopen(cmdlinePath.get(), "r"); |
|
246 if (f) { |
|
247 static const size_t len = 256; |
|
248 char buf[len]; |
|
249 if (fgets(buf, len, f)) { |
|
250 processName.Append(buf); |
|
251 // A hack: replace forward slashes with '\\' so they aren't treated |
|
252 // as path separators. Consumers of this reporter (such as |
|
253 // about:memory) have to undo this change. |
|
254 processName.ReplaceChar('/', '\\'); |
|
255 processName.Append(", "); |
|
256 } |
|
257 fclose(f); |
|
258 } |
|
259 processName.Append("pid="); |
|
260 processName.Append(pidStr); |
|
261 processName.Append(")"); |
|
262 |
|
263 // Read the PSS values from the smaps file. |
|
264 nsPrintfCString smapsPath("/proc/%s/smaps", pidStr); |
|
265 f = fopen(smapsPath.get(), "r"); |
|
266 if (!f) { |
|
267 // Processes can terminate between the readdir() call above and now, |
|
268 // so just skip if we can't open the file. |
|
269 continue; |
|
270 } |
|
271 while (true) { |
|
272 nsresult rv = ParseMapping(f, processName, aHandleReport, aData, |
|
273 &processSizes, aTotalPss); |
|
274 if (NS_FAILED(rv)) |
|
275 break; |
|
276 } |
|
277 fclose(f); |
|
278 } |
|
279 } |
|
280 closedir(d); |
|
281 |
|
282 // Report the "processes/" tree. |
|
283 |
|
284 for (size_t i = 0; i < ProcessSizeKindLimit; i++) { |
|
285 nsAutoCString path("processes/"); |
|
286 path.Append(kindPathSuffixes[i]); |
|
287 |
|
288 nsAutoCString desc("This is the sum of all processes' '"); |
|
289 desc.Append(kindPathSuffixes[i]); |
|
290 desc.Append("' numbers."); |
|
291 |
|
292 REPORT(path, processSizes.mSizes[i], desc); |
|
293 } |
|
294 |
|
295 return NS_OK; |
|
296 } |
|
297 |
|
298 nsresult ParseMapping(FILE* aFile, |
|
299 const nsACString& aProcessName, |
|
300 nsIHandleReportCallback* aHandleReport, |
|
301 nsISupports* aData, |
|
302 ProcessSizes* aProcessSizes, |
|
303 int64_t* aTotalPss) |
|
304 { |
|
305 // The first line of an entry in /proc/<pid>/smaps looks just like an entry |
|
306 // in /proc/<pid>/maps: |
|
307 // |
|
308 // address perms offset dev inode pathname |
|
309 // 02366000-025d8000 rw-p 00000000 00:00 0 [heap] |
|
310 |
|
311 const int argCount = 8; |
|
312 |
|
313 unsigned long long addrStart, addrEnd; |
|
314 char perms[5]; |
|
315 unsigned long long offset; |
|
316 // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and |
|
317 // 20 bits for the minor device number. Future kernels might allocate more. |
|
318 // 64 bits ought to be enough for anybody. |
|
319 char devMajor[17]; |
|
320 char devMinor[17]; |
|
321 unsigned int inode; |
|
322 char path[1025]; |
|
323 |
|
324 // A path might not be present on this line; set it to the empty string. |
|
325 path[0] = '\0'; |
|
326 |
|
327 // This is a bit tricky. Whitespace in a scanf pattern matches *any* |
|
328 // whitespace, including newlines. We want this pattern to match a line |
|
329 // with or without a path, but we don't want to look to a new line for the |
|
330 // path. Thus we have %u%1024[^\n] at the end of the pattern. This will |
|
331 // capture into the path some leading whitespace, which we'll later trim |
|
332 // off. |
|
333 int n = fscanf(aFile, |
|
334 "%llx-%llx %4s %llx " |
|
335 "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u%1024[^\n]", |
|
336 &addrStart, &addrEnd, perms, &offset, devMajor, |
|
337 devMinor, &inode, path); |
|
338 |
|
339 // Eat up any whitespace at the end of this line, including the newline. |
|
340 unused << fscanf(aFile, " "); |
|
341 |
|
342 // We might or might not have a path, but the rest of the arguments should |
|
343 // be there. |
|
344 if (n != argCount && n != argCount - 1) { |
|
345 return NS_ERROR_FAILURE; |
|
346 } |
|
347 |
|
348 nsAutoCString name, description; |
|
349 ProcessSizeKind kind; |
|
350 GetReporterNameAndDescription(path, perms, name, description, &kind); |
|
351 |
|
352 while (true) { |
|
353 size_t pss = 0; |
|
354 nsresult rv = ParseMapBody(aFile, aProcessName, name, description, |
|
355 aHandleReport, aData, &pss); |
|
356 if (NS_FAILED(rv)) |
|
357 break; |
|
358 |
|
359 // Increment the appropriate aProcessSizes values, and the total. |
|
360 aProcessSizes->mSizes[kind] += pss; |
|
361 *aTotalPss += pss; |
|
362 } |
|
363 |
|
364 return NS_OK; |
|
365 } |
|
366 |
|
367 void GetReporterNameAndDescription(const char* aPath, |
|
368 const char* aPerms, |
|
369 nsACString& aName, |
|
370 nsACString& aDesc, |
|
371 ProcessSizeKind* aProcessSizeKind) |
|
372 { |
|
373 aName.Truncate(); |
|
374 aDesc.Truncate(); |
|
375 |
|
376 // If aPath points to a file, we have its absolute path, plus some |
|
377 // whitespace. Truncate this to its basename, and put the absolute path in |
|
378 // the description. |
|
379 nsAutoCString absPath; |
|
380 absPath.Append(aPath); |
|
381 absPath.StripChars(" "); |
|
382 |
|
383 nsAutoCString basename; |
|
384 GetBasename(absPath, basename); |
|
385 |
|
386 if (basename.EqualsLiteral("[heap]")) { |
|
387 aName.Append("anonymous/brk-heap"); |
|
388 aDesc.Append("Memory in anonymous mappings within the boundaries " |
|
389 "defined by brk() / sbrk(). This is likely to be just " |
|
390 "a portion of the application's heap; the remainder " |
|
391 "lives in other anonymous mappings. This corresponds to " |
|
392 "'[heap]' in /proc/<pid>/smaps."); |
|
393 *aProcessSizeKind = AnonymousBrkHeap; |
|
394 |
|
395 } else if (basename.EqualsLiteral("[stack]")) { |
|
396 aName.Append("main-thread-stack"); |
|
397 aDesc.Append("The stack size of the process's main thread. This " |
|
398 "corresponds to '[stack]' in /proc/<pid>/smaps."); |
|
399 *aProcessSizeKind = MainThreadStack; |
|
400 |
|
401 } else if (basename.EqualsLiteral("[vdso]")) { |
|
402 aName.Append("vdso"); |
|
403 aDesc.Append("The virtual dynamically-linked shared object, also known " |
|
404 "as the 'vsyscall page'. This is a memory region mapped by " |
|
405 "the operating system for the purpose of allowing processes " |
|
406 "to perform some privileged actions without the overhead of " |
|
407 "a syscall."); |
|
408 *aProcessSizeKind = Vdso; |
|
409 |
|
410 } else if (!IsAnonymous(basename)) { |
|
411 nsAutoCString dirname; |
|
412 GetDirname(absPath, dirname); |
|
413 |
|
414 // Hack: A file is a shared library if the basename contains ".so" and |
|
415 // its dirname contains "/lib", or if the basename ends with ".so". |
|
416 if (EndsWithLiteral(basename, ".so") || |
|
417 (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) { |
|
418 aName.Append("shared-libraries/"); |
|
419 |
|
420 if (strncmp(aPerms, "r-x", 3) == 0) { |
|
421 *aProcessSizeKind = SharedLibrariesRX; |
|
422 } else if (strncmp(aPerms, "rw-", 3) == 0) { |
|
423 *aProcessSizeKind = SharedLibrariesRW; |
|
424 } else if (strncmp(aPerms, "r--", 3) == 0) { |
|
425 *aProcessSizeKind = SharedLibrariesR; |
|
426 } else { |
|
427 *aProcessSizeKind = SharedLibrariesOther; |
|
428 } |
|
429 |
|
430 } else { |
|
431 aName.Append("other-files/"); |
|
432 if (EndsWithLiteral(basename, ".xpi")) { |
|
433 aName.Append("extensions/"); |
|
434 } else if (dirname.Find("/fontconfig") != -1) { |
|
435 aName.Append("fontconfig/"); |
|
436 } |
|
437 *aProcessSizeKind = OtherFiles; |
|
438 } |
|
439 |
|
440 aName.Append(basename); |
|
441 aDesc.Append(absPath); |
|
442 |
|
443 } else { |
|
444 aName.Append("anonymous/outside-brk"); |
|
445 aDesc.Append("Memory in anonymous mappings outside the boundaries " |
|
446 "defined by brk() / sbrk()."); |
|
447 *aProcessSizeKind = AnonymousOutsideBrk; |
|
448 } |
|
449 |
|
450 aName.Append("/["); |
|
451 aName.Append(aPerms); |
|
452 aName.Append("]"); |
|
453 |
|
454 // Append the permissions. This is useful for non-verbose mode in |
|
455 // about:memory when the filename is long and goes of the right side of the |
|
456 // window. |
|
457 aDesc.Append(" ["); |
|
458 aDesc.Append(aPerms); |
|
459 aDesc.Append("]"); |
|
460 } |
|
461 |
|
462 nsresult ParseMapBody( |
|
463 FILE* aFile, |
|
464 const nsACString& aProcessName, |
|
465 const nsACString& aName, |
|
466 const nsACString& aDescription, |
|
467 nsIHandleReportCallback* aHandleReport, |
|
468 nsISupports* aData, |
|
469 size_t* aPss) |
|
470 { |
|
471 // Most of the lines in the body look like this: |
|
472 // |
|
473 // Size: 132 kB |
|
474 // Rss: 20 kB |
|
475 // Pss: 20 kB |
|
476 // |
|
477 // We're only interested in Pss. In newer kernels, the last line in the |
|
478 // body has a different form: |
|
479 // |
|
480 // VmFlags: rd wr mr mw me dw ac |
|
481 // |
|
482 // The strings after "VmFlags: " vary. |
|
483 |
|
484 char desc[1025]; |
|
485 int64_t sizeKB; |
|
486 int n = fscanf(aFile, "%1024[a-zA-Z_]: %" SCNd64 " kB\n", desc, &sizeKB); |
|
487 if (n == EOF || n == 0) { |
|
488 return NS_ERROR_FAILURE; |
|
489 } else if (n == 1 && strcmp(desc, "VmFlags") == 0) { |
|
490 // This is the "VmFlags:" line. Chew up the rest of it. |
|
491 fscanf(aFile, "%*1024[a-z ]\n"); |
|
492 return NS_ERROR_FAILURE; |
|
493 } |
|
494 |
|
495 // Only report "Pss" values. |
|
496 if (strcmp(desc, "Pss") == 0) { |
|
497 *aPss = sizeKB * 1024; |
|
498 |
|
499 // Don't report zero values. |
|
500 if (*aPss == 0) { |
|
501 return NS_OK; |
|
502 } |
|
503 |
|
504 nsAutoCString path("mem/processes/"); |
|
505 path.Append(aProcessName); |
|
506 path.Append("/"); |
|
507 path.Append(aName); |
|
508 |
|
509 REPORT(path, *aPss, aDescription); |
|
510 } else { |
|
511 *aPss = 0; |
|
512 } |
|
513 |
|
514 return NS_OK; |
|
515 } |
|
516 |
|
517 nsresult CollectPmemReports(nsIHandleReportCallback* aHandleReport, |
|
518 nsISupports* aData) |
|
519 { |
|
520 // The pmem subsystem allocates physically contiguous memory for |
|
521 // interfacing with hardware. In order to ensure availability, |
|
522 // this memory is reserved during boot, and allocations are made |
|
523 // within these regions at runtime. |
|
524 // |
|
525 // There are typically several of these pools allocated at boot. |
|
526 // The /sys/kernel/pmem_regions directory contains a subdirectory |
|
527 // for each one. Within each subdirectory, the files we care |
|
528 // about are "size" (the total amount of physical memory) and |
|
529 // "mapped_regions" (a list of the current allocations within that |
|
530 // area). |
|
531 DIR* d = opendir("/sys/kernel/pmem_regions"); |
|
532 if (!d) { |
|
533 if (NS_WARN_IF(errno != ENOENT)) { |
|
534 return NS_ERROR_FAILURE; |
|
535 } |
|
536 // If ENOENT, system doesn't use pmem. |
|
537 return NS_OK; |
|
538 } |
|
539 |
|
540 struct dirent* ent; |
|
541 while ((ent = readdir(d))) { |
|
542 const char* name = ent->d_name; |
|
543 uint64_t size; |
|
544 int scanned; |
|
545 |
|
546 // Skip "." and ".." (and any other dotfiles). |
|
547 if (name[0] == '.') { |
|
548 continue; |
|
549 } |
|
550 |
|
551 // Read the total size. The file gives the size in decimal and |
|
552 // hex, in the form "13631488(0xd00000)"; we parse the former. |
|
553 nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name); |
|
554 FILE* sizeFile = fopen(sizePath.get(), "r"); |
|
555 if (NS_WARN_IF(!sizeFile)) { |
|
556 continue; |
|
557 } |
|
558 scanned = fscanf(sizeFile, "%" SCNu64, &size); |
|
559 if (NS_WARN_IF(scanned != 1)) { |
|
560 continue; |
|
561 } |
|
562 fclose(sizeFile); |
|
563 |
|
564 // Read mapped regions; format described below. |
|
565 uint64_t freeSize = size; |
|
566 nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions", |
|
567 name); |
|
568 FILE* regionsFile = fopen(regionsPath.get(), "r"); |
|
569 if (regionsFile) { |
|
570 static const size_t bufLen = 4096; |
|
571 char buf[bufLen]; |
|
572 while (fgets(buf, bufLen, regionsFile)) { |
|
573 int pid; |
|
574 |
|
575 // Skip header line. |
|
576 if (strncmp(buf, "pid #", 5) == 0) { |
|
577 continue; |
|
578 } |
|
579 // Line format: "pid N:" + zero or more "(Start,Len) ". |
|
580 // N is decimal; Start and Len are in hex. |
|
581 scanned = sscanf(buf, "pid %d", &pid); |
|
582 if (NS_WARN_IF(scanned != 1)) { |
|
583 continue; |
|
584 } |
|
585 for (const char* nextParen = strchr(buf, '('); |
|
586 nextParen != nullptr; |
|
587 nextParen = strchr(nextParen + 1, '(')) { |
|
588 uint64_t mapStart, mapLen; |
|
589 |
|
590 scanned = sscanf(nextParen + 1, "%" SCNx64 ",%" SCNx64, |
|
591 &mapStart, &mapLen); |
|
592 if (NS_WARN_IF(scanned != 2)) { |
|
593 break; |
|
594 } |
|
595 |
|
596 nsPrintfCString path("mem/pmem/used/%s/segment(pid=%d, " |
|
597 "offset=0x%" PRIx64 ")", name, pid, mapStart); |
|
598 nsPrintfCString desc("Physical memory reserved for the \"%s\" pool " |
|
599 "and allocated to a buffer.", name); |
|
600 REPORT_WITH_CLEANUP(path, UNITS_BYTES, mapLen, desc, |
|
601 (fclose(regionsFile), closedir(d))); |
|
602 freeSize -= mapLen; |
|
603 } |
|
604 } |
|
605 fclose(regionsFile); |
|
606 } |
|
607 |
|
608 nsPrintfCString path("mem/pmem/free/%s", name); |
|
609 nsPrintfCString desc("Physical memory reserved for the \"%s\" pool and " |
|
610 "unavailable to the rest of the system, but not " |
|
611 "currently allocated.", name); |
|
612 REPORT_WITH_CLEANUP(path, UNITS_BYTES, freeSize, desc, closedir(d)); |
|
613 } |
|
614 closedir(d); |
|
615 return NS_OK; |
|
616 } |
|
617 |
|
618 uint64_t |
|
619 ReadSizeFromFile(const char* aFilename) |
|
620 { |
|
621 FILE* sizeFile = fopen(aFilename, "r"); |
|
622 if (NS_WARN_IF(!sizeFile)) { |
|
623 return 0; |
|
624 } |
|
625 |
|
626 uint64_t size = 0; |
|
627 fscanf(sizeFile, "%" SCNu64, &size); |
|
628 fclose(sizeFile); |
|
629 |
|
630 return size; |
|
631 } |
|
632 |
|
633 nsresult |
|
634 CollectZramReports(nsIHandleReportCallback* aHandleReport, |
|
635 nsISupports* aData) |
|
636 { |
|
637 // zram usage stats files can be found under: |
|
638 // /sys/block/zram<id> |
|
639 // |--> disksize - Maximum amount of uncompressed data that can be |
|
640 // stored on the disk (bytes) |
|
641 // |--> orig_data_size - Uncompressed size of data in the disk (bytes) |
|
642 // |--> compr_data_size - Compressed size of the data in the disk (bytes) |
|
643 // |--> num_reads - Number of attempted reads to the disk (count) |
|
644 // |--> num_writes - Number of attempted writes to the disk (count) |
|
645 // |
|
646 // Each file contains a single integer value in decimal form. |
|
647 |
|
648 DIR* d = opendir("/sys/block"); |
|
649 if (!d) { |
|
650 if (NS_WARN_IF(errno != ENOENT)) { |
|
651 return NS_ERROR_FAILURE; |
|
652 } |
|
653 |
|
654 return NS_OK; |
|
655 } |
|
656 |
|
657 struct dirent* ent; |
|
658 while ((ent = readdir(d))) { |
|
659 const char* name = ent->d_name; |
|
660 |
|
661 // Skip non-zram entries. |
|
662 if (strncmp("zram", name, 4) != 0) { |
|
663 continue; |
|
664 } |
|
665 |
|
666 // Report disk size statistics. |
|
667 nsPrintfCString diskSizeFile("/sys/block/%s/disksize", name); |
|
668 nsPrintfCString origSizeFile("/sys/block/%s/orig_data_size", name); |
|
669 |
|
670 uint64_t diskSize = ReadSizeFromFile(diskSizeFile.get()); |
|
671 uint64_t origSize = ReadSizeFromFile(origSizeFile.get()); |
|
672 uint64_t unusedSize = diskSize - origSize; |
|
673 |
|
674 nsPrintfCString diskUsedPath("zram-disksize/%s/used", name); |
|
675 nsPrintfCString diskUsedDesc( |
|
676 "The uncompressed size of data stored in \"%s.\" " |
|
677 "This excludes zero-filled pages since " |
|
678 "no memory is allocated for them.", name); |
|
679 REPORT_WITH_CLEANUP(diskUsedPath, UNITS_BYTES, origSize, |
|
680 diskUsedDesc, closedir(d)); |
|
681 |
|
682 nsPrintfCString diskUnusedPath("zram-disksize/%s/unused", name); |
|
683 nsPrintfCString diskUnusedDesc( |
|
684 "The amount of uncompressed data that can still be " |
|
685 "be stored in \"%s\"", name); |
|
686 REPORT_WITH_CLEANUP(diskUnusedPath, UNITS_BYTES, unusedSize, |
|
687 diskUnusedDesc, closedir(d)); |
|
688 |
|
689 // Report disk accesses. |
|
690 nsPrintfCString readsFile("/sys/block/%s/num_reads", name); |
|
691 nsPrintfCString writesFile("/sys/block/%s/num_writes", name); |
|
692 |
|
693 uint64_t reads = ReadSizeFromFile(readsFile.get()); |
|
694 uint64_t writes = ReadSizeFromFile(writesFile.get()); |
|
695 |
|
696 nsPrintfCString readsDesc( |
|
697 "The number of reads (failed or successful) done on " |
|
698 "\"%s\"", name); |
|
699 nsPrintfCString readsPath("zram-accesses/%s/reads", name); |
|
700 REPORT_WITH_CLEANUP(readsPath, UNITS_COUNT_CUMULATIVE, reads, |
|
701 readsDesc, closedir(d)); |
|
702 |
|
703 nsPrintfCString writesDesc( |
|
704 "The number of writes (failed or successful) done " |
|
705 "on \"%s\"", name); |
|
706 nsPrintfCString writesPath("zram-accesses/%s/writes", name); |
|
707 REPORT_WITH_CLEANUP(writesPath, UNITS_COUNT_CUMULATIVE, writes, |
|
708 writesDesc, closedir(d)); |
|
709 |
|
710 // Report compressed data size. |
|
711 nsPrintfCString comprSizeFile("/sys/block/%s/compr_data_size", name); |
|
712 uint64_t comprSize = ReadSizeFromFile(comprSizeFile.get()); |
|
713 |
|
714 nsPrintfCString comprSizeDesc( |
|
715 "The compressed size of data stored in \"%s\"", |
|
716 name); |
|
717 nsPrintfCString comprSizePath("zram-compr-data-size/%s", name); |
|
718 REPORT_WITH_CLEANUP(comprSizePath, UNITS_BYTES, comprSize, |
|
719 comprSizeDesc, closedir(d)); |
|
720 } |
|
721 |
|
722 closedir(d); |
|
723 return NS_OK; |
|
724 } |
|
725 |
|
726 #undef REPORT |
|
727 }; |
|
728 |
|
729 NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter) |
|
730 |
|
731 // Keep this in sync with SystemReporter::ProcessSizeKind! |
|
732 const char* SystemReporter::kindPathSuffixes[] = { |
|
733 "anonymous/outside-brk", |
|
734 "anonymous/brk-heap", |
|
735 "shared-libraries/read-executable", |
|
736 "shared-libraries/read-write", |
|
737 "shared-libraries/read-only", |
|
738 "shared-libraries/other", |
|
739 "other-files", |
|
740 "main-thread-stack", |
|
741 "vdso" |
|
742 }; |
|
743 |
|
744 void Init() |
|
745 { |
|
746 RegisterStrongMemoryReporter(new SystemReporter()); |
|
747 } |
|
748 |
|
749 } // namespace SystemMemoryReporter |
|
750 } // namespace mozilla |