1 : /* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 ci et: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Foundation.
19 : *
20 : * Portions created by the Initial Developer are Copyright (C) 2011
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Justin Lebar <justin.lebar@gmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "mozilla/Util.h"
41 :
42 : #include "mozilla/MapsMemoryReporter.h"
43 : #include "nsIMemoryReporter.h"
44 : #include "nsString.h"
45 : #include "nsCOMPtr.h"
46 : #include "nsTHashtable.h"
47 : #include "nsHashKeys.h"
48 : #include <stdio.h>
49 :
50 : namespace mozilla {
51 : namespace MapsMemoryReporter {
52 :
53 : #if !defined(XP_LINUX)
54 : #error "This doesn't have a prayer of working if we're not on Linux."
55 : #endif
56 :
57 : // mozillaLibraries is a list of all the shared libraries we build. This list
58 : // is used for determining whether a library is a "Mozilla library" or a
59 : // "third-party library". But even if this list is missing items, about:memory
60 : // will identify a library in the same directory as libxul.so as a "Mozilla
61 : // library".
62 : const char* mozillaLibraries[] =
63 : {
64 : "libfreebl3.so",
65 : "libmozalloc.so",
66 : "libmozsqlite3.so",
67 : "libnspr4.so",
68 : "libnss3.so",
69 : "libnssckbi.so",
70 : "libnssdbm3.so",
71 : "libnssutil3.so",
72 : "libplc4.so",
73 : "libplds4.so",
74 : "libsmime3.so",
75 : "libsoftokn3.so",
76 : "libssl3.so",
77 : "libxpcom.so",
78 : "libxul.so"
79 : };
80 :
81 : namespace {
82 :
83 1467 : bool EndsWithLiteral(const nsCString &aHaystack, const char *aNeedle)
84 : {
85 1467 : PRInt32 idx = aHaystack.RFind(aNeedle);
86 1467 : if (idx == -1) {
87 912 : return false;
88 : }
89 :
90 555 : return idx + strlen(aNeedle) == aHaystack.Length();
91 : }
92 :
93 568 : void GetDirname(const nsCString &aPath, nsACString &aOut)
94 : {
95 568 : PRInt32 idx = aPath.RFind("/");
96 568 : if (idx == -1) {
97 0 : aOut.Truncate();
98 : }
99 : else {
100 568 : aOut.Assign(Substring(aPath, 0, idx));
101 : }
102 568 : }
103 :
104 888 : void GetBasename(const nsCString &aPath, nsACString &aOut)
105 : {
106 1776 : nsCString out;
107 888 : PRInt32 idx = aPath.RFind("/");
108 888 : if (idx == -1) {
109 185 : out.Assign(aPath);
110 : }
111 : else {
112 703 : out.Assign(Substring(aPath, idx + 1));
113 : }
114 :
115 : // On Android, some entries in /dev/ashmem end with "(deleted)" (e.g.
116 : // "/dev/ashmem/libxul.so(deleted)"). We don't care about this modifier, so
117 : // cut it off when getting the entry's basename.
118 888 : if (EndsWithLiteral(out, "(deleted)")) {
119 0 : out.Assign(Substring(out, 0, out.RFind("(deleted)")));
120 : }
121 888 : out.StripChars(" ");
122 :
123 888 : aOut.Assign(out);
124 888 : }
125 :
126 : // MapsReporter::CollectReports uses this stuct to keep track of whether it's
127 : // seen a mapping under 'smaps/resident', 'smaps/pss', 'smaps/vsize', and
128 : // 'smaps/swap'.
129 : struct CategoriesSeen {
130 3 : CategoriesSeen() :
131 : mSeenResident(false),
132 : mSeenPss(false),
133 : mSeenVsize(false),
134 3 : mSeenSwap(false)
135 : {
136 3 : }
137 :
138 : bool mSeenResident;
139 : bool mSeenPss;
140 : bool mSeenVsize;
141 : bool mSeenSwap;
142 : };
143 :
144 : } // anonymous namespace
145 :
146 : class MapsReporter : public nsIMemoryMultiReporter
147 1419 : {
148 : public:
149 : MapsReporter();
150 :
151 : NS_DECL_ISUPPORTS
152 :
153 0 : NS_IMETHOD GetName(nsACString &aName)
154 : {
155 0 : aName.AssignLiteral("smaps");
156 0 : return NS_OK;
157 : }
158 :
159 : NS_IMETHOD
160 : CollectReports(nsIMemoryMultiReporterCallback *aCb,
161 : nsISupports *aClosure);
162 :
163 : NS_IMETHOD
164 3 : GetExplicitNonHeap(PRInt64 *aAmount) {
165 : // This reporter doesn't do any "explicit" measurements.
166 3 : *aAmount = 0;
167 3 : return NS_OK;
168 : }
169 :
170 : private:
171 : // Search through /proc/self/maps for libxul.so, and set mLibxulDir to the
172 : // the directory containing libxul.
173 : nsresult FindLibxul();
174 :
175 : nsresult
176 : ParseMapping(FILE *aFile,
177 : nsIMemoryMultiReporterCallback *aCb,
178 : nsISupports *aClosure,
179 : CategoriesSeen *aCategoriesSeen);
180 :
181 : void
182 : GetReporterNameAndDescription(const char *aPath,
183 : const char *aPermissions,
184 : nsACString &aName,
185 : nsACString &aDesc);
186 :
187 : nsresult
188 : ParseMapBody(FILE *aFile,
189 : const nsACString &aName,
190 : const nsACString &aDescription,
191 : nsIMemoryMultiReporterCallback *aCb,
192 : nsISupports *aClosure,
193 : CategoriesSeen *aCategoriesSeen);
194 :
195 : bool mSearchedForLibxul;
196 : nsCString mLibxulDir;
197 : nsTHashtable<nsCStringHashKey> mMozillaLibraries;
198 : };
199 :
200 11382 : NS_IMPL_THREADSAFE_ISUPPORTS1(MapsReporter, nsIMemoryMultiReporter)
201 :
202 1419 : MapsReporter::MapsReporter()
203 1419 : : mSearchedForLibxul(false)
204 : {
205 1419 : const PRUint32 len = ArrayLength(mozillaLibraries);
206 1419 : mMozillaLibraries.Init(len);
207 22704 : for (PRUint32 i = 0; i < len; i++) {
208 42570 : nsCAutoString str;
209 21285 : str.Assign(mozillaLibraries[i]);
210 21285 : mMozillaLibraries.PutEntry(str);
211 : }
212 1419 : }
213 :
214 : NS_IMETHODIMP
215 3 : MapsReporter::CollectReports(nsIMemoryMultiReporterCallback *aCb,
216 : nsISupports *aClosure)
217 : {
218 3 : CategoriesSeen categoriesSeen;
219 :
220 3 : FILE *f = fopen("/proc/self/smaps", "r");
221 3 : if (!f)
222 0 : return NS_ERROR_FAILURE;
223 :
224 752 : while (true) {
225 755 : nsresult rv = ParseMapping(f, aCb, aClosure, &categoriesSeen);
226 755 : if (NS_FAILED(rv))
227 : break;
228 : }
229 :
230 3 : fclose(f);
231 :
232 : // For sure we should have created some node under 'smaps/resident' and
233 : // 'smaps/vsize'; otherwise we're probably not reading smaps correctly. If we
234 : // didn't create a node under 'smaps/swap', create one here so about:memory
235 : // knows to create an empty 'smaps/swap' tree. See also bug 682735.
236 :
237 3 : NS_ASSERTION(categoriesSeen.mSeenVsize, "Didn't create a vsize node?");
238 3 : NS_ASSERTION(categoriesSeen.mSeenVsize, "Didn't create a resident node?");
239 3 : if (!categoriesSeen.mSeenSwap) {
240 : nsresult rv;
241 3 : rv = aCb->Callback(NS_LITERAL_CSTRING(""),
242 3 : NS_LITERAL_CSTRING("smaps/swap/total"),
243 : nsIMemoryReporter::KIND_NONHEAP,
244 : nsIMemoryReporter::UNITS_BYTES,
245 : 0,
246 3 : NS_LITERAL_CSTRING("This process uses no swap space."),
247 3 : aClosure);
248 3 : NS_ENSURE_SUCCESS(rv, rv);
249 : }
250 :
251 3 : return NS_OK;
252 : }
253 :
254 : nsresult
255 755 : MapsReporter::FindLibxul()
256 : {
257 755 : if (mSearchedForLibxul)
258 754 : return NS_OK;
259 :
260 1 : mSearchedForLibxul = true;
261 :
262 1 : mLibxulDir.Truncate();
263 :
264 : // Note that we're scanning /proc/self/*maps*, not smaps, here.
265 1 : FILE *f = fopen("/proc/self/maps", "r");
266 1 : if (!f) {
267 0 : return NS_ERROR_FAILURE;
268 : }
269 :
270 135 : while (true) {
271 : // Skip any number of non-slash characters, then capture starting with the
272 : // slash to the newline. This is the path part of /proc/self/maps.
273 : char path[1025];
274 136 : int numRead = fscanf(f, "%*[^/]%1024[^\n]", path);
275 136 : if (numRead != 1) {
276 0 : break;
277 : }
278 :
279 272 : nsCAutoString pathStr;
280 136 : pathStr.Append(path);
281 :
282 272 : nsCAutoString basename;
283 136 : GetBasename(pathStr, basename);
284 :
285 136 : if (basename.EqualsLiteral("libxul.so")) {
286 1 : GetDirname(pathStr, mLibxulDir);
287 : break;
288 : }
289 : }
290 :
291 1 : fclose(f);
292 1 : return mLibxulDir.IsEmpty() ? NS_ERROR_FAILURE : NS_OK;
293 : }
294 :
295 : nsresult
296 755 : MapsReporter::ParseMapping(
297 : FILE *aFile,
298 : nsIMemoryMultiReporterCallback *aCb,
299 : nsISupports *aClosure,
300 : CategoriesSeen *aCategoriesSeen)
301 : {
302 : // We need to use native types in order to get good warnings from fscanf, so
303 : // let's make sure that the native types have the sizes we expect.
304 : PR_STATIC_ASSERT(sizeof(long long) == sizeof(PRInt64));
305 : PR_STATIC_ASSERT(sizeof(int) == sizeof(PRInt32));
306 :
307 : // Don't bail if FindLibxul fails. We can still gather meaningful stats
308 : // here.
309 755 : FindLibxul();
310 :
311 : // The first line of an entry in /proc/self/smaps looks just like an entry
312 : // in /proc/maps:
313 : //
314 : // address perms offset dev inode pathname
315 : // 02366000-025d8000 rw-p 00000000 00:00 0 [heap]
316 :
317 755 : const int argCount = 8;
318 :
319 : unsigned long long addrStart, addrEnd;
320 : char perms[5];
321 : unsigned long long offset;
322 : char devMajor[3];
323 : char devMinor[3];
324 : unsigned int inode;
325 : char path[1025];
326 :
327 : // A path might not be present on this line; set it to the empty string.
328 755 : path[0] = '\0';
329 :
330 : // This is a bit tricky. Whitespace in a scanf pattern matches *any*
331 : // whitespace, including newlines. We want this pattern to match a line
332 : // with or without a path, but we don't want to look to a new line for the
333 : // path. Thus we have %u%1024[^\n] at the end of the pattern. This will
334 : // capture into the path some leading whitespace, which we'll later trim off.
335 : int numRead = fscanf(aFile, "%llx-%llx %4s %llx %2s:%2s %u%1024[^\n]",
336 : &addrStart, &addrEnd, perms, &offset, devMajor,
337 755 : devMinor, &inode, path);
338 :
339 : // Eat up any whitespace at the end of this line, including the newline.
340 755 : fscanf(aFile, " ");
341 :
342 : // We might or might not have a path, but the rest of the arguments should be
343 : // there.
344 755 : if (numRead != argCount && numRead != argCount - 1) {
345 3 : return NS_ERROR_FAILURE;
346 : }
347 :
348 1504 : nsCAutoString name, description;
349 752 : GetReporterNameAndDescription(path, perms, name, description);
350 :
351 4512 : while (true) {
352 : nsresult rv = ParseMapBody(aFile, name, description, aCb,
353 5264 : aClosure, aCategoriesSeen);
354 5264 : if (NS_FAILED(rv))
355 : break;
356 : }
357 :
358 752 : return NS_OK;
359 : }
360 :
361 : void
362 752 : MapsReporter::GetReporterNameAndDescription(
363 : const char *aPath,
364 : const char *aPerms,
365 : nsACString &aName,
366 : nsACString &aDesc)
367 : {
368 752 : aName.Truncate();
369 752 : aDesc.Truncate();
370 :
371 : // If aPath points to a file, we have its absolute path, plus some
372 : // whitespace. Truncate this to its basename, and put the absolute path in
373 : // the description.
374 1504 : nsCAutoString absPath;
375 752 : absPath.Append(aPath);
376 752 : absPath.StripChars(" ");
377 :
378 1504 : nsCAutoString basename;
379 752 : GetBasename(absPath, basename);
380 :
381 752 : if (basename.EqualsLiteral("[heap]")) {
382 0 : aName.Append("anonymous/anonymous, within brk()");
383 : aDesc.Append("Memory in anonymous mappings within the boundaries "
384 : "defined by brk() / sbrk(). This is likely to be just "
385 : "a portion of the application's heap; the remainder "
386 : "lives in other anonymous mappings. This node corresponds to "
387 0 : "'[heap]' in /proc/self/smaps.");
388 : }
389 752 : else if (basename.EqualsLiteral("[stack]")) {
390 3 : aName.Append("main thread's stack");
391 : aDesc.Append("The stack size of the process's main thread. This node "
392 3 : "corresponds to '[stack]' in /proc/self/smaps.");
393 : }
394 749 : else if (basename.EqualsLiteral("[vdso]")) {
395 3 : aName.Append("vdso");
396 : aDesc.Append("The virtual dynamically-linked shared object, also known as "
397 : "the 'vsyscall page'. This is a memory region mapped by the "
398 : "operating system for the purpose of allowing processes to "
399 : "perform some privileged actions without the overhead of a "
400 3 : "syscall.");
401 : }
402 746 : else if (!basename.IsEmpty()) {
403 1134 : nsCAutoString dirname;
404 567 : GetDirname(absPath, dirname);
405 :
406 : // Hack: A file is a shared library if the basename contains ".so" and its
407 : // dirname contains "/lib", or if the basename ends with ".so".
408 1221 : if (EndsWithLiteral(basename, ".so") ||
409 654 : (basename.Find(".so") != -1 && dirname.Find("/lib") != -1)) {
410 555 : aName.Append("shared-libraries/");
411 1005 : if ((!mLibxulDir.IsEmpty() && dirname.Equals(mLibxulDir)) ||
412 450 : mMozillaLibraries.Contains(basename)) {
413 105 : aName.Append("shared-libraries-mozilla/");
414 : }
415 : else {
416 450 : aName.Append("shared-libraries-other/");
417 : }
418 : }
419 : else {
420 12 : aName.Append("other-files/");
421 12 : if (EndsWithLiteral(basename, ".xpi")) {
422 0 : aName.Append("extensions/");
423 : }
424 12 : else if (dirname.Find("/fontconfig") != -1) {
425 0 : aName.Append("fontconfig/");
426 : }
427 : }
428 :
429 567 : aName.Append(basename);
430 567 : aDesc.Append(absPath);
431 : }
432 : else {
433 179 : aName.Append("anonymous/anonymous, outside brk()");
434 : aDesc.Append("Memory in anonymous mappings outside the boundaries defined "
435 179 : "by brk() / sbrk().");
436 : }
437 :
438 752 : aName.Append(" [");
439 752 : aName.Append(aPerms);
440 752 : aName.Append("]");
441 :
442 : // Modify the description to include an explanation of the permissions.
443 752 : aDesc.Append(" (");
444 752 : if (strstr(aPerms, "rw")) {
445 405 : aDesc.Append("read/write, ");
446 : }
447 347 : else if (strchr(aPerms, 'r')) {
448 309 : aDesc.Append("read-only, ");
449 : }
450 38 : else if (strchr(aPerms, 'w')) {
451 0 : aDesc.Append("write-only, ");
452 : }
453 : else {
454 38 : aDesc.Append("not readable, not writable, ");
455 : }
456 :
457 752 : if (strchr(aPerms, 'x')) {
458 707 : aDesc.Append("executable, ");
459 : }
460 : else {
461 45 : aDesc.Append("not executable, ");
462 : }
463 :
464 752 : if (strchr(aPerms, 's')) {
465 3 : aDesc.Append("shared");
466 : }
467 749 : else if (strchr(aPerms, 'p')) {
468 749 : aDesc.Append("private");
469 : }
470 : else {
471 0 : aDesc.Append("not shared or private??");
472 : }
473 752 : aDesc.Append(")");
474 752 : }
475 :
476 : nsresult
477 5264 : MapsReporter::ParseMapBody(
478 : FILE *aFile,
479 : const nsACString &aName,
480 : const nsACString &aDescription,
481 : nsIMemoryMultiReporterCallback *aCb,
482 : nsISupports *aClosure,
483 : CategoriesSeen *aCategoriesSeen)
484 : {
485 : PR_STATIC_ASSERT(sizeof(long long) == sizeof(PRInt64));
486 :
487 5264 : const int argCount = 2;
488 :
489 : char desc[1025];
490 : unsigned long long size;
491 5264 : if (fscanf(aFile, "%1024[a-zA-Z_]: %llu kB\n",
492 5264 : desc, &size) != argCount) {
493 752 : return NS_ERROR_FAILURE;
494 : }
495 :
496 : // Don't report nodes with size 0.
497 4512 : if (size == 0)
498 2326 : return NS_OK;
499 :
500 : const char* category;
501 2186 : if (strcmp(desc, "Size") == 0) {
502 752 : category = "vsize";
503 752 : aCategoriesSeen->mSeenVsize = true;
504 : }
505 1434 : else if (strcmp(desc, "Rss") == 0) {
506 684 : category = "resident";
507 684 : aCategoriesSeen->mSeenResident = true;
508 : }
509 750 : else if (strcmp(desc, "Pss") == 0) {
510 0 : category = "pss";
511 0 : aCategoriesSeen->mSeenPss = true;
512 : }
513 750 : else if (strcmp(desc, "Swap") == 0) {
514 0 : category = "swap";
515 0 : aCategoriesSeen->mSeenSwap = true;
516 : }
517 : else {
518 : // Don't report this category.
519 750 : return NS_OK;
520 : }
521 :
522 2872 : nsCAutoString path;
523 1436 : path.Append("smaps/");
524 1436 : path.Append(category);
525 1436 : path.Append("/");
526 1436 : path.Append(aName);
527 :
528 : nsresult rv;
529 1436 : rv = aCb->Callback(NS_LITERAL_CSTRING(""),
530 : path,
531 : nsIMemoryReporter::KIND_NONHEAP,
532 : nsIMemoryReporter::UNITS_BYTES,
533 : PRInt64(size) * 1024, // convert from kB to bytes
534 1436 : aDescription, aClosure);
535 1436 : NS_ENSURE_SUCCESS(rv, rv);
536 :
537 1436 : return NS_OK;
538 : }
539 :
540 1419 : void Init()
541 : {
542 2838 : nsCOMPtr<nsIMemoryMultiReporter> reporter = new MapsReporter();
543 1419 : NS_RegisterMemoryMultiReporter(reporter);
544 1419 : }
545 :
546 : } // namespace MapsMemoryReporter
547 : } // namespace mozilla
|