Line data Source code
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/LinuxUtils.h"
11 : #include "mozilla/PodOperations.h"
12 : #include "mozilla/Preferences.h"
13 : #include "mozilla/TaggedAnonymousMemory.h"
14 : #include "mozilla/Unused.h"
15 :
16 : #include "nsDataHashtable.h"
17 : #include "nsIMemoryReporter.h"
18 : #include "nsPrintfCString.h"
19 : #include "nsString.h"
20 : #include "nsTHashtable.h"
21 : #include "nsHashKeys.h"
22 :
23 : #include <dirent.h>
24 : #include <inttypes.h>
25 : #include <stdio.h>
26 : #include <sys/stat.h>
27 : #include <sys/types.h>
28 : #include <unistd.h>
29 : #include <errno.h>
30 :
31 : // This file implements a Linux-specific, system-wide memory reporter. It
32 : // gathers all the useful memory measurements obtainable from the OS in a
33 : // single place, giving a high-level view of memory consumption for the entire
34 : // machine/device.
35 : //
36 : // Other memory reporters measure part of a single process's memory consumption.
37 : // This reporter is different in that it measures memory consumption of many
38 : // processes, and they end up in a single reports tree. This is a slight abuse
39 : // of the memory reporting infrastructure, and therefore the results are given
40 : // their own "process" called "System", which means they show up in about:memory
41 : // in their own section, distinct from the per-process sections.
42 :
43 : namespace mozilla {
44 : namespace SystemMemoryReporter {
45 :
46 : #if !defined(XP_LINUX)
47 : #error "This won't work if we're not on Linux."
48 : #endif
49 :
50 : /**
51 : * RAII helper that will close an open DIR handle.
52 : */
53 : struct MOZ_STACK_CLASS AutoDir
54 : {
55 0 : explicit AutoDir(DIR* aDir) : mDir(aDir) {}
56 0 : ~AutoDir() { if (mDir) closedir(mDir); };
57 : DIR* mDir;
58 : };
59 :
60 : /**
61 : * RAII helper that will close an open FILE handle.
62 : */
63 : struct MOZ_STACK_CLASS AutoFile
64 : {
65 0 : explicit AutoFile(FILE* aFile) : mFile(aFile) {}
66 0 : ~AutoFile() { if (mFile) fclose(mFile); }
67 : FILE* mFile;
68 : };
69 :
70 : static bool
71 0 : EndsWithLiteral(const nsCString& aHaystack, const char* aNeedle)
72 : {
73 0 : int32_t idx = aHaystack.RFind(aNeedle);
74 0 : return idx != -1 && idx + strlen(aNeedle) == aHaystack.Length();
75 : }
76 :
77 : static void
78 0 : GetDirname(const nsCString& aPath, nsACString& aOut)
79 : {
80 0 : int32_t idx = aPath.RFind("/");
81 0 : if (idx == -1) {
82 0 : aOut.Truncate();
83 : } else {
84 0 : aOut.Assign(Substring(aPath, 0, idx));
85 : }
86 0 : }
87 :
88 : static void
89 0 : GetBasename(const nsCString& aPath, nsACString& aOut)
90 : {
91 0 : nsCString out;
92 0 : int32_t idx = aPath.RFind("/");
93 0 : if (idx == -1) {
94 0 : out.Assign(aPath);
95 : } else {
96 0 : out.Assign(Substring(aPath, idx + 1));
97 : }
98 :
99 : // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g.
100 : // "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so
101 : // cut it off when getting the entry's basename.
102 0 : if (EndsWithLiteral(out, "(deleted)")) {
103 0 : out.Assign(Substring(out, 0, out.RFind("(deleted)")));
104 : }
105 0 : out.StripChars(" ");
106 :
107 0 : aOut.Assign(out);
108 0 : }
109 :
110 : static bool
111 0 : IsNumeric(const char* aStr)
112 : {
113 0 : MOZ_ASSERT(*aStr); // shouldn't see empty strings
114 0 : while (*aStr) {
115 0 : if (!isdigit(*aStr)) {
116 0 : return false;
117 : }
118 0 : ++aStr;
119 : }
120 0 : return true;
121 : }
122 :
123 : static bool
124 0 : IsAnonymous(const nsACString& aName)
125 : {
126 : // Recent kernels have multiple [stack:nnnn] entries, where |nnnn| is a
127 : // thread ID. However, the entire virtual memory area containing a thread's
128 : // stack pointer is considered the stack for that thread, even if it was
129 : // merged with an adjacent area containing non-stack data. So we treat them
130 : // as regular anonymous memory. However, see below about tagged anonymous
131 : // memory.
132 0 : return aName.IsEmpty() ||
133 0 : StringBeginsWith(aName, NS_LITERAL_CSTRING("[stack:"));
134 : }
135 :
136 1 : class SystemReporter final : public nsIMemoryReporter
137 : {
138 0 : ~SystemReporter() {}
139 :
140 : public:
141 : NS_DECL_THREADSAFE_ISUPPORTS
142 :
143 : #define REPORT(_path, _units, _amount, _desc) \
144 : do { \
145 : size_t __amount = _amount; /* evaluate _amount only once */ \
146 : if (__amount > 0) { \
147 : aHandleReport->Callback(NS_LITERAL_CSTRING("System"), _path, \
148 : KIND_OTHER, _units, __amount, _desc, aData); \
149 : } \
150 : } while (0)
151 :
152 0 : NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
153 : nsISupports* aData, bool aAnonymize) override
154 : {
155 : // There is lots of privacy-sensitive data in /proc. Just skip this
156 : // reporter entirely when anonymization is required.
157 0 : if (aAnonymize) {
158 0 : return NS_OK;
159 : }
160 :
161 0 : if (!Preferences::GetBool("memory.system_memory_reporter")) {
162 0 : return NS_OK;
163 : }
164 :
165 : // Read relevant fields from /proc/meminfo.
166 0 : int64_t memTotal = 0, memFree = 0;
167 0 : nsresult rv1 = ReadMemInfo(&memTotal, &memFree);
168 :
169 : // Collect per-process reports from /proc/<pid>/smaps.
170 0 : int64_t totalPss = 0;
171 0 : nsresult rv2 = CollectProcessReports(aHandleReport, aData, &totalPss);
172 :
173 : // Report the non-process numbers.
174 0 : if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) {
175 0 : int64_t other = memTotal - memFree - totalPss;
176 0 : REPORT(NS_LITERAL_CSTRING("mem/other"), UNITS_BYTES, other,
177 : NS_LITERAL_CSTRING(
178 : "Memory which is neither owned by any user-space process nor free. Note that "
179 : "this includes memory holding cached files from the disk which can be "
180 : "reclaimed by the OS at any time."));
181 :
182 0 : REPORT(NS_LITERAL_CSTRING("mem/free"), UNITS_BYTES, memFree,
183 : NS_LITERAL_CSTRING(
184 : "Memory which is free and not being used for any purpose."));
185 : }
186 :
187 : // Report reserved memory not included in memTotal.
188 0 : CollectPmemReports(aHandleReport, aData);
189 :
190 : // Report zram usage statistics.
191 0 : CollectZramReports(aHandleReport, aData);
192 :
193 : // Report kgsl graphics memory usage.
194 0 : CollectKgslReports(aHandleReport, aData);
195 :
196 : // Report ION memory usage.
197 0 : CollectIonReports(aHandleReport, aData);
198 :
199 0 : return NS_OK;
200 : }
201 :
202 : private:
203 : // These are the cross-cutting measurements across all processes.
204 0 : class ProcessSizes
205 : {
206 : public:
207 0 : void Add(const nsACString& aKey, size_t aSize)
208 : {
209 0 : mTagged.Put(aKey, mTagged.Get(aKey) + aSize);
210 0 : }
211 :
212 0 : void Report(nsIHandleReportCallback* aHandleReport, nsISupports* aData)
213 : {
214 0 : for (auto iter = mTagged.Iter(); !iter.Done(); iter.Next()) {
215 0 : nsCStringHashKey::KeyType key = iter.Key();
216 0 : size_t amount = iter.UserData();
217 :
218 0 : nsAutoCString path("processes/");
219 0 : path.Append(key);
220 :
221 0 : nsAutoCString desc("This is the sum of all processes' '");
222 0 : desc.Append(key);
223 0 : desc.AppendLiteral("' numbers.");
224 :
225 0 : REPORT(path, UNITS_BYTES, amount, desc);
226 : }
227 0 : }
228 :
229 : private:
230 : nsDataHashtable<nsCStringHashKey, size_t> mTagged;
231 : };
232 :
233 0 : nsresult ReadMemInfo(int64_t* aMemTotal, int64_t* aMemFree)
234 : {
235 0 : FILE* f = fopen("/proc/meminfo", "r");
236 0 : if (!f) {
237 0 : return NS_ERROR_FAILURE;
238 : }
239 :
240 0 : int n1 = fscanf(f, "MemTotal: %" SCNd64 " kB\n", aMemTotal);
241 0 : int n2 = fscanf(f, "MemFree: %" SCNd64 " kB\n", aMemFree);
242 :
243 0 : fclose(f);
244 :
245 0 : if (n1 != 1 || n2 != 1) {
246 0 : return NS_ERROR_FAILURE;
247 : }
248 :
249 : // Convert from KB to B.
250 0 : *aMemTotal *= 1024;
251 0 : *aMemFree *= 1024;
252 :
253 0 : return NS_OK;
254 : }
255 :
256 0 : nsresult CollectProcessReports(nsIHandleReportCallback* aHandleReport,
257 : nsISupports* aData,
258 : int64_t* aTotalPss)
259 : {
260 0 : *aTotalPss = 0;
261 0 : ProcessSizes processSizes;
262 :
263 0 : DIR* d = opendir("/proc");
264 0 : if (NS_WARN_IF(!d)) {
265 0 : return NS_ERROR_FAILURE;
266 : }
267 : struct dirent* ent;
268 0 : while ((ent = readdir(d))) {
269 : struct stat statbuf;
270 0 : const char* pidStr = ent->d_name;
271 : // Don't check the return value of stat() -- it can return -1 for these
272 : // directories even when it has succeeded, apparently.
273 0 : stat(pidStr, &statbuf);
274 0 : if (S_ISDIR(statbuf.st_mode) && IsNumeric(pidStr)) {
275 0 : nsCString processName("process(");
276 :
277 : // Get the command name from cmdline. If that fails, the pid is still
278 : // shown.
279 0 : nsPrintfCString cmdlinePath("/proc/%s/cmdline", pidStr);
280 0 : FILE* f = fopen(cmdlinePath.get(), "r");
281 0 : if (f) {
282 : static const size_t len = 256;
283 : char buf[len];
284 0 : if (fgets(buf, len, f)) {
285 0 : processName.Append(buf);
286 : // A hack: replace forward slashes with '\\' so they aren't treated
287 : // as path separators. Consumers of this reporter (such as
288 : // about:memory) have to undo this change.
289 0 : processName.ReplaceChar('/', '\\');
290 0 : processName.AppendLiteral(", ");
291 : }
292 0 : fclose(f);
293 : }
294 0 : processName.AppendLiteral("pid=");
295 0 : processName.Append(pidStr);
296 0 : processName.Append(')');
297 :
298 : // Read the PSS values from the smaps file.
299 0 : nsPrintfCString smapsPath("/proc/%s/smaps", pidStr);
300 0 : f = fopen(smapsPath.get(), "r");
301 0 : if (!f) {
302 : // Processes can terminate between the readdir() call above and now,
303 : // so just skip if we can't open the file.
304 0 : continue;
305 : }
306 : ParseMappings(f, processName, aHandleReport, aData, &processSizes,
307 0 : aTotalPss);
308 0 : fclose(f);
309 :
310 : // Report the open file descriptors for this process.
311 0 : nsPrintfCString procFdPath("/proc/%s/fd", pidStr);
312 0 : CollectOpenFileReports(aHandleReport, aData, procFdPath, processName);
313 : }
314 : }
315 0 : closedir(d);
316 :
317 : // Report the "processes/" tree.
318 0 : processSizes.Report(aHandleReport, aData);
319 :
320 0 : return NS_OK;
321 : }
322 :
323 0 : void ParseMappings(FILE* aFile,
324 : const nsACString& aProcessName,
325 : nsIHandleReportCallback* aHandleReport,
326 : nsISupports* aData,
327 : ProcessSizes* aProcessSizes,
328 : int64_t* aTotalPss)
329 : {
330 : // The first line of an entry in /proc/<pid>/smaps looks just like an entry
331 : // in /proc/<pid>/maps:
332 : //
333 : // address perms offset dev inode pathname
334 : // 02366000-025d8000 rw-p 00000000 00:00 0 [heap]
335 : //
336 : // Each of the following lines contains a key and a value, separated
337 : // by ": ", where the key does not contain either of those characters.
338 : // Assuming more than this about the structure of those lines has
339 : // failed to be future-proof in the past, so we avoid doing so.
340 : //
341 : // This makes it difficult to detect the start of a new entry
342 : // until it's been removed from the stdio buffer, so we just loop
343 : // over all lines in the file in this routine.
344 :
345 0 : const int argCount = 8;
346 :
347 : unsigned long long addrStart, addrEnd;
348 : char perms[5];
349 : unsigned long long offset;
350 : // The 2.6 and 3.0 kernels allocate 12 bits for the major device number and
351 : // 20 bits for the minor device number. Future kernels might allocate more.
352 : // 64 bits ought to be enough for anybody.
353 : char devMajor[17];
354 : char devMinor[17];
355 : unsigned int inode;
356 : char line[1025];
357 :
358 : // This variable holds the path of the current entry, or is void
359 : // if we're scanning for the start of a new entry.
360 0 : nsAutoCString currentPath;
361 : int pathOffset;
362 :
363 0 : currentPath.SetIsVoid(true);
364 0 : while (fgets(line, sizeof(line), aFile)) {
365 0 : if (currentPath.IsVoid()) {
366 : int n = sscanf(line,
367 : "%llx-%llx %4s %llx "
368 : "%16[0-9a-fA-F]:%16[0-9a-fA-F] %u %n",
369 : &addrStart, &addrEnd, perms, &offset, devMajor,
370 0 : devMinor, &inode, &pathOffset);
371 :
372 0 : if (n >= argCount - 1) {
373 0 : currentPath.Assign(line + pathOffset);
374 0 : currentPath.StripChars("\n");
375 : }
376 0 : continue;
377 : }
378 :
379 : // Now that we have a name and other metadata, scan for the PSS.
380 : size_t pss_kb;
381 0 : int n = sscanf(line, "Pss: %zu", &pss_kb);
382 0 : if (n < 1) {
383 0 : continue;
384 : }
385 :
386 0 : size_t pss = pss_kb * 1024;
387 0 : if (pss > 0) {
388 0 : nsAutoCString name, description, tag;
389 0 : GetReporterNameAndDescription(currentPath.get(), perms, name, description, tag);
390 :
391 0 : nsAutoCString processMemPath("mem/processes/");
392 0 : processMemPath.Append(aProcessName);
393 0 : processMemPath.Append('/');
394 0 : processMemPath.Append(name);
395 :
396 0 : REPORT(processMemPath, UNITS_BYTES, pss, description);
397 :
398 : // Increment the appropriate aProcessSizes values, and the total.
399 0 : aProcessSizes->Add(tag, pss);
400 0 : *aTotalPss += pss;
401 : }
402 :
403 : // Now that we've seen the PSS, we're done with this entry.
404 0 : currentPath.SetIsVoid(true);
405 : }
406 0 : }
407 :
408 0 : void GetReporterNameAndDescription(const char* aPath,
409 : const char* aPerms,
410 : nsACString& aName,
411 : nsACString& aDesc,
412 : nsACString& aTag)
413 : {
414 0 : aName.Truncate();
415 0 : aDesc.Truncate();
416 0 : aTag.Truncate();
417 :
418 : // If aPath points to a file, we have its absolute path; it might
419 : // also be a bracketed pseudo-name (see below). In either case
420 : // there is also some whitespace to trim.
421 0 : nsAutoCString absPath;
422 0 : absPath.Append(aPath);
423 0 : absPath.StripChars(" ");
424 :
425 0 : if (absPath.EqualsLiteral("[heap]")) {
426 0 : aName.AppendLiteral("anonymous/brk-heap");
427 : aDesc.AppendLiteral(
428 : "Memory in anonymous mappings within the boundaries defined by "
429 : "brk() / sbrk(). This is likely to be just a portion of the "
430 : "application's heap; the remainder lives in other anonymous mappings. "
431 0 : "This corresponds to '[heap]' in /proc/<pid>/smaps.");
432 0 : aTag = aName;
433 0 : } else if (absPath.EqualsLiteral("[stack]")) {
434 0 : aName.AppendLiteral("stack/main-thread");
435 : aDesc.AppendPrintf(
436 : "The stack size of the process's main thread. This corresponds to "
437 0 : "'[stack]' in /proc/<pid>/smaps.");
438 0 : aTag = aName;
439 0 : } else if (MozTaggedMemoryIsSupported() &&
440 0 : StringBeginsWith(absPath, NS_LITERAL_CSTRING("[stack:"))) {
441 : // If tagged memory is supported, we can be reasonably sure that
442 : // the virtual memory area containing the stack hasn't been
443 : // merged with unrelated heap memory. (This prevents the
444 : // "[stack:" entries from reaching the IsAnonymous case below.)
445 0 : pid_t tid = atoi(absPath.get() + 7);
446 0 : nsAutoCString threadName, escapedThreadName;
447 0 : LinuxUtils::GetThreadName(tid, threadName);
448 0 : if (threadName.IsEmpty()) {
449 0 : threadName.AssignLiteral("<unknown>");
450 : }
451 0 : escapedThreadName.Assign(threadName);
452 0 : escapedThreadName.StripChars("()");
453 0 : escapedThreadName.ReplaceChar('/', '\\');
454 :
455 0 : aName.AppendLiteral("stack/non-main-thread");
456 0 : aName.AppendLiteral("/name(");
457 0 : aName.Append(escapedThreadName);
458 0 : aName.Append(')');
459 0 : aTag = aName;
460 0 : aName.AppendPrintf("/thread(%d)", tid);
461 :
462 0 : aDesc.AppendPrintf("The stack size of a non-main thread named '%s' with "
463 : "thread ID %d. This corresponds to '[stack:%d]' "
464 0 : "in /proc/%d/smaps.", threadName.get(), tid, tid, tid);
465 0 : } else if (absPath.EqualsLiteral("[vdso]")) {
466 0 : aName.AppendLiteral("vdso");
467 : aDesc.AppendLiteral(
468 : "The virtual dynamically-linked shared object, also known as the "
469 : "'vsyscall page'. This is a memory region mapped by the operating "
470 : "system for the purpose of allowing processes to perform some "
471 0 : "privileged actions without the overhead of a syscall.");
472 0 : aTag = aName;
473 0 : } else if (StringBeginsWith(absPath, NS_LITERAL_CSTRING("[anon:")) &&
474 0 : EndsWithLiteral(absPath, "]")) {
475 : // It's tagged memory; see also "mfbt/TaggedAnonymousMemory.h".
476 0 : nsAutoCString tag(Substring(absPath, 6, absPath.Length() - 7));
477 :
478 0 : aName.AppendLiteral("anonymous/");
479 0 : aName.Append(tag);
480 0 : aTag = aName;
481 0 : aDesc.AppendLiteral("Memory in anonymous mappings tagged with '");
482 0 : aDesc.Append(tag);
483 0 : aDesc.Append('\'');
484 0 : } else if (!IsAnonymous(absPath)) {
485 : // We now know it's an actual file. Truncate this to its
486 : // basename, and put the absolute path in the description.
487 0 : nsAutoCString basename, dirname;
488 0 : GetBasename(absPath, basename);
489 0 : GetDirname(absPath, dirname);
490 :
491 : // Hack: A file is a shared library if the basename contains ".so" and
492 : // its dirname contains "/lib", or if the basename ends with ".so".
493 0 : if (EndsWithLiteral(basename, ".so") ||
494 0 : (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) {
495 0 : aName.AppendLiteral("shared-libraries/");
496 0 : aTag = aName;
497 :
498 0 : if (strncmp(aPerms, "r-x", 3) == 0) {
499 0 : aTag.AppendLiteral("read-executable");
500 0 : } else if (strncmp(aPerms, "rw-", 3) == 0) {
501 0 : aTag.AppendLiteral("read-write");
502 0 : } else if (strncmp(aPerms, "r--", 3) == 0) {
503 0 : aTag.AppendLiteral("read-only");
504 : } else {
505 0 : aTag.AppendLiteral("other");
506 : }
507 :
508 : } else {
509 0 : aName.AppendLiteral("other-files");
510 0 : if (EndsWithLiteral(basename, ".xpi")) {
511 0 : aName.AppendLiteral("/extensions");
512 0 : } else if (dirname.Find("/fontconfig") != -1) {
513 0 : aName.AppendLiteral("/fontconfig");
514 : } else {
515 0 : aName.AppendLiteral("/misc");
516 : }
517 0 : aTag = aName;
518 0 : aName.Append('/');
519 : }
520 :
521 0 : aName.Append(basename);
522 0 : aDesc.Append(absPath);
523 : } else {
524 0 : if (MozTaggedMemoryIsSupported()) {
525 0 : aName.AppendLiteral("anonymous/untagged");
526 0 : aDesc.AppendLiteral("Memory in untagged anonymous mappings.");
527 0 : aTag = aName;
528 : } else {
529 0 : aName.AppendLiteral("anonymous/outside-brk");
530 : aDesc.AppendLiteral("Memory in anonymous mappings outside the "
531 0 : "boundaries defined by brk() / sbrk().");
532 0 : aTag = aName;
533 : }
534 : }
535 :
536 0 : aName.AppendLiteral("/[");
537 0 : aName.Append(aPerms);
538 0 : aName.Append(']');
539 :
540 : // Append the permissions. This is useful for non-verbose mode in
541 : // about:memory when the filename is long and goes of the right side of the
542 : // window.
543 0 : aDesc.AppendLiteral(" [");
544 0 : aDesc.Append(aPerms);
545 0 : aDesc.Append(']');
546 0 : }
547 :
548 0 : void CollectPmemReports(nsIHandleReportCallback* aHandleReport,
549 : nsISupports* aData)
550 : {
551 : // The pmem subsystem allocates physically contiguous memory for
552 : // interfacing with hardware. In order to ensure availability,
553 : // this memory is reserved during boot, and allocations are made
554 : // within these regions at runtime.
555 : //
556 : // There are typically several of these pools allocated at boot.
557 : // The /sys/kernel/pmem_regions directory contains a subdirectory
558 : // for each one. Within each subdirectory, the files we care
559 : // about are "size" (the total amount of physical memory) and
560 : // "mapped_regions" (a list of the current allocations within that
561 : // area).
562 0 : DIR* d = opendir("/sys/kernel/pmem_regions");
563 0 : if (!d) {
564 0 : return;
565 : }
566 :
567 : struct dirent* ent;
568 0 : while ((ent = readdir(d))) {
569 0 : const char* name = ent->d_name;
570 : uint64_t size;
571 : int scanned;
572 :
573 : // Skip "." and ".." (and any other dotfiles).
574 0 : if (name[0] == '.') {
575 0 : continue;
576 : }
577 :
578 : // Read the total size. The file gives the size in decimal and
579 : // hex, in the form "13631488(0xd00000)"; we parse the former.
580 0 : nsPrintfCString sizePath("/sys/kernel/pmem_regions/%s/size", name);
581 0 : FILE* sizeFile = fopen(sizePath.get(), "r");
582 0 : if (NS_WARN_IF(!sizeFile)) {
583 0 : continue;
584 : }
585 0 : scanned = fscanf(sizeFile, "%" SCNu64, &size);
586 0 : fclose(sizeFile);
587 0 : if (NS_WARN_IF(scanned != 1)) {
588 0 : continue;
589 : }
590 :
591 : // Read mapped regions; format described below.
592 0 : uint64_t freeSize = size;
593 : nsPrintfCString regionsPath("/sys/kernel/pmem_regions/%s/mapped_regions",
594 0 : name);
595 0 : FILE* regionsFile = fopen(regionsPath.get(), "r");
596 0 : if (regionsFile) {
597 : static const size_t bufLen = 4096;
598 : char buf[bufLen];
599 0 : while (fgets(buf, bufLen, regionsFile)) {
600 : int pid;
601 :
602 : // Skip header line.
603 0 : if (strncmp(buf, "pid #", 5) == 0) {
604 0 : continue;
605 : }
606 : // Line format: "pid N:" + zero or more "(Start,Len) ".
607 : // N is decimal; Start and Len are in hex.
608 0 : scanned = sscanf(buf, "pid %d", &pid);
609 0 : if (NS_WARN_IF(scanned != 1)) {
610 0 : continue;
611 : }
612 0 : for (const char* nextParen = strchr(buf, '(');
613 0 : nextParen != nullptr;
614 0 : nextParen = strchr(nextParen + 1, '(')) {
615 : uint64_t mapStart, mapLen;
616 :
617 0 : scanned = sscanf(nextParen + 1, "%" SCNx64 ",%" SCNx64,
618 0 : &mapStart, &mapLen);
619 0 : if (NS_WARN_IF(scanned != 2)) {
620 0 : break;
621 : }
622 :
623 : nsPrintfCString path("mem/pmem/used/%s/segment(pid=%d, "
624 0 : "offset=0x%" PRIx64 ")", name, pid, mapStart);
625 : nsPrintfCString desc("Physical memory reserved for the \"%s\" pool "
626 0 : "and allocated to a buffer.", name);
627 0 : REPORT(path, UNITS_BYTES, mapLen, desc);
628 0 : freeSize -= mapLen;
629 : }
630 : }
631 0 : fclose(regionsFile);
632 : }
633 :
634 0 : nsPrintfCString path("mem/pmem/free/%s", name);
635 : nsPrintfCString desc("Physical memory reserved for the \"%s\" pool and "
636 : "unavailable to the rest of the system, but not "
637 0 : "currently allocated.", name);
638 0 : REPORT(path, UNITS_BYTES, freeSize, desc);
639 : }
640 0 : closedir(d);
641 : }
642 :
643 : void
644 0 : CollectIonReports(nsIHandleReportCallback* aHandleReport,
645 : nsISupports* aData)
646 : {
647 : // ION is a replacement for PMEM (and other similar allocators).
648 : //
649 : // More details from http://lwn.net/Articles/480055/
650 : // "Like its PMEM-like predecessors, ION manages one or more memory pools,
651 : // some of which are set aside at boot time to combat fragmentation or to
652 : // serve special hardware needs. GPUs, display controllers, and cameras
653 : // are some of the hardware blocks that may have special memory
654 : // requirements."
655 : //
656 : // The file format starts as follows:
657 : // client pid size
658 : // ----------------------------------------------------
659 : // adsprpc-smd 1 4096
660 : // fd900000.qcom,mdss_mdp 1 1658880
661 : // ----------------------------------------------------
662 : // orphaned allocations (info is from last known client):
663 : // Homescreen 24100 294912 0 1
664 : // b2g 23987 1658880 0 1
665 : // mdss_fb0 401 1658880 0 1
666 : // b2g 23987 4096 0 1
667 : // Built-in Keyboa 24205 61440 0 1
668 : // ----------------------------------------------------
669 : // <other stuff>
670 : //
671 : // For our purposes we only care about the first portion of the file noted
672 : // above which contains memory alloations (both sections). The term
673 : // "orphaned" is misleading, it appears that every allocation not by the
674 : // first process is considered orphaned on FxOS devices.
675 :
676 : // The first three fields of each entry interest us:
677 : // 1) client - Essentially the process name. We limit client names to 63
678 : // characters, in theory they should never be greater than 15
679 : // due to thread name length limitations.
680 : // 2) pid - The ID of the allocating process, read as a uint32_t.
681 : // 3) size - The size of the allocation in bytes, read as as a uint64_t.
682 0 : const char* const kFormatString = "%63s %" SCNu32 " %" SCNu64;
683 0 : const int kNumFields = 3;
684 0 : const size_t kStringSize = 64;
685 0 : const char* const kIonIommuPath = "/sys/kernel/debug/ion/iommu";
686 :
687 0 : FILE* iommu = fopen(kIonIommuPath, "r");
688 0 : if (!iommu) {
689 0 : return;
690 : }
691 :
692 0 : AutoFile iommuGuard(iommu);
693 :
694 0 : const size_t kBufferLen = 256;
695 : char buffer[kBufferLen];
696 : char client[kStringSize];
697 : uint32_t pid;
698 : uint64_t size;
699 :
700 : // Ignore the header line.
701 0 : Unused << fgets(buffer, kBufferLen, iommu);
702 :
703 : // Ignore the separator line.
704 0 : Unused << fgets(buffer, kBufferLen, iommu);
705 :
706 0 : const char* const kSep = "----";
707 0 : const size_t kSepLen = 4;
708 :
709 : // Read non-orphaned entries.
710 0 : while (fgets(buffer, kBufferLen, iommu) &&
711 0 : strncmp(kSep, buffer, kSepLen) != 0) {
712 0 : if (sscanf(buffer, kFormatString, client, &pid, &size) == kNumFields) {
713 0 : nsPrintfCString entryPath("ion-memory/%s (pid=%d)", client, pid);
714 0 : REPORT(entryPath, UNITS_BYTES, size,
715 : NS_LITERAL_CSTRING("An ION kernel memory allocation."));
716 : }
717 : }
718 :
719 : // Ignore the orphaned header.
720 0 : Unused << fgets(buffer, kBufferLen, iommu);
721 :
722 : // Read orphaned entries.
723 0 : while (fgets(buffer, kBufferLen, iommu) &&
724 0 : strncmp(kSep, buffer, kSepLen) != 0) {
725 0 : if (sscanf(buffer, kFormatString, client, &pid, &size) == kNumFields) {
726 0 : nsPrintfCString entryPath("ion-memory/%s (pid=%d)", client, pid);
727 0 : REPORT(entryPath, UNITS_BYTES, size,
728 : NS_LITERAL_CSTRING("An ION kernel memory allocation."));
729 : }
730 : }
731 :
732 : // Ignore the rest of the file.
733 : }
734 :
735 : uint64_t
736 0 : ReadSizeFromFile(const char* aFilename)
737 : {
738 0 : FILE* sizeFile = fopen(aFilename, "r");
739 0 : if (NS_WARN_IF(!sizeFile)) {
740 0 : return 0;
741 : }
742 :
743 0 : uint64_t size = 0;
744 0 : Unused << fscanf(sizeFile, "%" SCNu64, &size);
745 0 : fclose(sizeFile);
746 :
747 0 : return size;
748 : }
749 :
750 : void
751 0 : CollectZramReports(nsIHandleReportCallback* aHandleReport,
752 : nsISupports* aData)
753 : {
754 : // zram usage stats files can be found under:
755 : // /sys/block/zram<id>
756 : // |--> disksize - Maximum amount of uncompressed data that can be
757 : // stored on the disk (bytes)
758 : // |--> orig_data_size - Uncompressed size of data in the disk (bytes)
759 : // |--> compr_data_size - Compressed size of the data in the disk (bytes)
760 : // |--> num_reads - Number of attempted reads to the disk (count)
761 : // |--> num_writes - Number of attempted writes to the disk (count)
762 : //
763 : // Each file contains a single integer value in decimal form.
764 :
765 0 : DIR* d = opendir("/sys/block");
766 0 : if (!d) {
767 0 : return;
768 : }
769 :
770 : struct dirent* ent;
771 0 : while ((ent = readdir(d))) {
772 0 : const char* name = ent->d_name;
773 :
774 : // Skip non-zram entries.
775 0 : if (strncmp("zram", name, 4) != 0) {
776 0 : continue;
777 : }
778 :
779 : // Report disk size statistics.
780 0 : nsPrintfCString diskSizeFile("/sys/block/%s/disksize", name);
781 0 : nsPrintfCString origSizeFile("/sys/block/%s/orig_data_size", name);
782 :
783 0 : uint64_t diskSize = ReadSizeFromFile(diskSizeFile.get());
784 0 : uint64_t origSize = ReadSizeFromFile(origSizeFile.get());
785 0 : uint64_t unusedSize = diskSize - origSize;
786 :
787 0 : nsPrintfCString diskUsedPath("zram-disksize/%s/used", name);
788 : nsPrintfCString diskUsedDesc(
789 : "The uncompressed size of data stored in \"%s.\" "
790 : "This excludes zero-filled pages since "
791 0 : "no memory is allocated for them.", name);
792 0 : REPORT(diskUsedPath, UNITS_BYTES, origSize, diskUsedDesc);
793 :
794 0 : nsPrintfCString diskUnusedPath("zram-disksize/%s/unused", name);
795 : nsPrintfCString diskUnusedDesc(
796 : "The amount of uncompressed data that can still be "
797 0 : "be stored in \"%s\"", name);
798 0 : REPORT(diskUnusedPath, UNITS_BYTES, unusedSize, diskUnusedDesc);
799 :
800 : // Report disk accesses.
801 0 : nsPrintfCString readsFile("/sys/block/%s/num_reads", name);
802 0 : nsPrintfCString writesFile("/sys/block/%s/num_writes", name);
803 :
804 0 : uint64_t reads = ReadSizeFromFile(readsFile.get());
805 0 : uint64_t writes = ReadSizeFromFile(writesFile.get());
806 :
807 : nsPrintfCString readsDesc(
808 : "The number of reads (failed or successful) done on "
809 0 : "\"%s\"", name);
810 0 : nsPrintfCString readsPath("zram-accesses/%s/reads", name);
811 0 : REPORT(readsPath, UNITS_COUNT_CUMULATIVE, reads, readsDesc);
812 :
813 : nsPrintfCString writesDesc(
814 : "The number of writes (failed or successful) done "
815 0 : "on \"%s\"", name);
816 0 : nsPrintfCString writesPath("zram-accesses/%s/writes", name);
817 0 : REPORT(writesPath, UNITS_COUNT_CUMULATIVE, writes, writesDesc);
818 :
819 : // Report compressed data size.
820 0 : nsPrintfCString comprSizeFile("/sys/block/%s/compr_data_size", name);
821 0 : uint64_t comprSize = ReadSizeFromFile(comprSizeFile.get());
822 :
823 : nsPrintfCString comprSizeDesc(
824 : "The compressed size of data stored in \"%s\"",
825 0 : name);
826 0 : nsPrintfCString comprSizePath("zram-compr-data-size/%s", name);
827 0 : REPORT(comprSizePath, UNITS_BYTES, comprSize, comprSizeDesc);
828 : }
829 :
830 0 : closedir(d);
831 : }
832 :
833 : void
834 0 : CollectOpenFileReports(nsIHandleReportCallback* aHandleReport,
835 : nsISupports* aData,
836 : const nsACString& aProcPath,
837 : const nsACString& aProcessName)
838 : {
839 : // All file descriptors opened by a process are listed under
840 : // /proc/<pid>/fd/<numerical_fd>. Each entry is a symlink that points to the
841 : // path that was opened. This can be an actual file, a socket, a pipe, an
842 : // anon_inode, or possibly an uncategorized device.
843 0 : const char kFilePrefix[] = "/";
844 0 : const char kSocketPrefix[] = "socket:";
845 0 : const char kPipePrefix[] = "pipe:";
846 0 : const char kAnonInodePrefix[] = "anon_inode:";
847 :
848 0 : const nsCString procPath(aProcPath);
849 0 : DIR* d = opendir(procPath.get());
850 0 : if (!d) {
851 0 : return;
852 : }
853 :
854 : char linkPath[PATH_MAX + 1];
855 : struct dirent* ent;
856 0 : while ((ent = readdir(d))) {
857 0 : const char* fd = ent->d_name;
858 :
859 : // Skip "." and ".." (and any other dotfiles).
860 0 : if (fd[0] == '.') {
861 0 : continue;
862 : }
863 :
864 0 : nsPrintfCString fullPath("%s/%s", procPath.get(), fd);
865 0 : ssize_t linkPathSize = readlink(fullPath.get(), linkPath, PATH_MAX);
866 0 : if (linkPathSize > 0) {
867 0 : linkPath[linkPathSize] = '\0';
868 :
869 : #define CHECK_PREFIX(prefix) \
870 : (strncmp(linkPath, prefix, sizeof(prefix) - 1) == 0)
871 :
872 0 : const char* category = nullptr;
873 0 : const char* descriptionPrefix = nullptr;
874 :
875 0 : if (CHECK_PREFIX(kFilePrefix)) {
876 0 : category = "files"; // No trailing slash, the file path will have one
877 0 : descriptionPrefix = "An open";
878 0 : } else if (CHECK_PREFIX(kSocketPrefix)) {
879 0 : category = "sockets/";
880 0 : descriptionPrefix = "A socket";
881 0 : } else if (CHECK_PREFIX(kPipePrefix)) {
882 0 : category = "pipes/";
883 0 : descriptionPrefix = "A pipe";
884 0 : } else if (CHECK_PREFIX(kAnonInodePrefix)) {
885 0 : category = "anon_inodes/";
886 0 : descriptionPrefix = "An anon_inode";
887 : } else {
888 0 : category = "";
889 0 : descriptionPrefix = "An uncategorized";
890 : }
891 :
892 : #undef CHECK_PREFIX
893 :
894 0 : const nsCString processName(aProcessName);
895 : nsPrintfCString entryPath("open-fds/%s/%s%s/%s",
896 0 : processName.get(), category, linkPath, fd);
897 : nsPrintfCString entryDescription("%s file descriptor opened by the process",
898 0 : descriptionPrefix);
899 0 : REPORT(entryPath, UNITS_COUNT, 1, entryDescription);
900 : }
901 : }
902 :
903 0 : closedir(d);
904 : }
905 :
906 : void
907 0 : CollectKgslReports(nsIHandleReportCallback* aHandleReport,
908 : nsISupports* aData)
909 : {
910 : // Each process that uses kgsl memory will have an entry under
911 : // /sys/kernel/debug/kgsl/proc/<pid>/mem. This file format includes a
912 : // header and then entries with types as follows:
913 : // gpuaddr useraddr size id flags type usage sglen
914 : // hexaddr hexaddr int int str str str int
915 : // We care primarily about the usage and size.
916 :
917 : // For simplicity numbers will be uint64_t, strings 63 chars max.
918 : const char* const kScanFormat =
919 : "%" SCNx64 " %" SCNx64 " %" SCNu64 " %" SCNu64
920 0 : " %63s %63s %63s %" SCNu64;
921 0 : const int kNumFields = 8;
922 0 : const size_t kStringSize = 64;
923 :
924 0 : DIR* d = opendir("/sys/kernel/debug/kgsl/proc/");
925 0 : if (!d) {
926 0 : return;
927 : }
928 :
929 0 : AutoDir dirGuard(d);
930 :
931 : struct dirent* ent;
932 0 : while ((ent = readdir(d))) {
933 0 : const char* pid = ent->d_name;
934 :
935 : // Skip "." and ".." (and any other dotfiles).
936 0 : if (pid[0] == '.') {
937 0 : continue;
938 : }
939 :
940 0 : nsPrintfCString memPath("/sys/kernel/debug/kgsl/proc/%s/mem", pid);
941 0 : FILE* memFile = fopen(memPath.get(), "r");
942 0 : if (NS_WARN_IF(!memFile)) {
943 0 : continue;
944 : }
945 :
946 0 : AutoFile fileGuard(memFile);
947 :
948 : // Attempt to map the pid to a more useful name.
949 0 : nsAutoCString procName;
950 0 : LinuxUtils::GetThreadName(atoi(pid), procName);
951 :
952 0 : if (procName.IsEmpty()) {
953 0 : procName.Append("pid=");
954 0 : procName.Append(pid);
955 : } else {
956 0 : procName.Append(" (pid=");
957 0 : procName.Append(pid);
958 0 : procName.Append(")");
959 : }
960 :
961 : uint64_t gpuaddr, useraddr, size, id, sglen;
962 : char flags[kStringSize];
963 : char type[kStringSize];
964 : char usage[kStringSize];
965 :
966 : // Bypass the header line.
967 : char buff[1024];
968 0 : Unused << fgets(buff, 1024, memFile);
969 :
970 0 : while (fscanf(memFile, kScanFormat, &gpuaddr, &useraddr, &size, &id,
971 : flags, type, usage, &sglen) == kNumFields) {
972 0 : nsPrintfCString entryPath("kgsl-memory/%s/%s", procName.get(), usage);
973 0 : REPORT(entryPath, UNITS_BYTES, size,
974 : NS_LITERAL_CSTRING("A kgsl graphics memory allocation."));
975 : }
976 : }
977 : }
978 : };
979 :
980 13 : NS_IMPL_ISUPPORTS(SystemReporter, nsIMemoryReporter)
981 :
982 : void
983 1 : Init()
984 : {
985 1 : RegisterStrongMemoryReporter(new SystemReporter());
986 1 : }
987 :
988 : } // namespace SystemMemoryReporter
989 : } // namespace mozilla
|