1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Copyright © 2011 Igalia S.L.
4 *
5 * This file is part of Epiphany.
6 *
7 * Epiphany is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Epiphany is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22 #include "ephy-smaps.h"
23
24 #include <errno.h>
25 #include <gio/gio.h>
26 #include <stdio.h>
27 #include <string.h>
28
29 struct _EphySMaps {
30 GObject parent_instance;
31 GRegex *header;
32 GRegex *detail;
33 };
34
35 G_DEFINE_TYPE (EphySMaps, ephy_smaps, G_TYPE_OBJECT)
36
37 typedef struct {
38 char *start;
39 char *end;
40 char *perms;
41 char *offset;
42 char *major;
43 char *minor;
44 char *inode;
45 char *filename;
46 char *size;
47 char *rss;
48 char *pss;
49 char *shared_clean;
50 char *shared_dirty;
51 char *private_clean;
52 char *private_dirty;
53 } VMA_t;
54
55 typedef struct {
56 guint shared_clean;
57 guint shared_dirty;
58 guint private_clean;
59 guint private_dirty;
60 } PermEntry;
61
62 typedef enum {
63 EPHY_PROCESS_EPIPHANY,
64 EPHY_PROCESS_WEB,
65 EPHY_PROCESS_PLUGIN,
66
67 EPHY_PROCESS_OTHER
68 } EphyProcess;
69
70 static const char *
get_ephy_process_name(EphyProcess process)71 get_ephy_process_name (EphyProcess process)
72 {
73 switch (process) {
74 case EPHY_PROCESS_EPIPHANY:
75 return "Browser";
76 case EPHY_PROCESS_WEB:
77 return "Web Process";
78 case EPHY_PROCESS_PLUGIN:
79 return "Plugin Process";
80 case EPHY_PROCESS_OTHER:
81 default:
82 g_assert_not_reached ();
83 }
84
85 return NULL;
86 }
87
88 static void
vma_free(VMA_t * vma)89 vma_free (VMA_t *vma)
90 {
91 g_free (vma->start);
92 g_free (vma->end);
93 g_free (vma->perms);
94 g_free (vma->offset);
95 g_free (vma->major);
96 g_free (vma->minor);
97 g_free (vma->inode);
98 g_free (vma->filename);
99 g_free (vma->size);
100 g_free (vma->rss);
101 g_free (vma->pss);
102 g_free (vma->shared_clean);
103 g_free (vma->shared_dirty);
104 g_free (vma->private_clean);
105 g_free (vma->private_dirty);
106
107 g_free (vma);
108 }
109
110 static void
perm_entry_free(PermEntry * entry)111 perm_entry_free (PermEntry *entry)
112 {
113 g_free (entry);
114 }
115
116 static void
add_to_perm_entry(GHashTable * hash,VMA_t * entry)117 add_to_perm_entry (GHashTable *hash,
118 VMA_t *entry)
119 {
120 const char *perms = entry->perms;
121 PermEntry *value;
122 guint number;
123 gboolean insert = FALSE;
124
125 value = g_hash_table_lookup (hash, perms);
126
127 if (!value) {
128 value = g_new0 (PermEntry, 1);
129 insert = TRUE;
130 }
131
132 sscanf (entry->shared_clean, "%u", &number);
133 value->shared_clean += number;
134 sscanf (entry->shared_dirty, "%u", &number);
135 value->shared_dirty += number;
136 sscanf (entry->private_clean, "%u", &number);
137 value->private_clean += number;
138 sscanf (entry->private_dirty, "%u", &number);
139 value->private_dirty += number;
140
141 if (insert)
142 g_hash_table_insert (hash, g_strdup (perms), value);
143 }
144
145 static void
add_to_totals(PermEntry * entry,PermEntry * totals)146 add_to_totals (PermEntry *entry,
147 PermEntry *totals)
148 {
149 totals->shared_clean += entry->shared_clean;
150 totals->shared_dirty += entry->shared_dirty;
151 totals->private_dirty += entry->private_dirty;
152 totals->private_dirty += entry->private_dirty;
153 }
154
155 static void
print_vma_table(GString * str,GHashTable * hash,const char * caption)156 print_vma_table (GString *str,
157 GHashTable *hash,
158 const char *caption)
159 {
160 PermEntry *pentry, totals;
161
162 memset (&totals, 0, sizeof (PermEntry));
163
164 g_string_append_printf (str, "<table class=\"memory-table\"><caption>%s</caption><colgroup><colgroup span=\"2\" align=\"center\"><colgroup span=\"2\" align=\"center\"><colgroup><thead><tr><th><th colspan=\"2\">Shared</th><th colspan=\"2\">Private</th><th></tr></thead>", caption);
165 g_string_append (str, "<tbody><tr><td></td><td>Clean</td><td>Dirty</td><td>Clean</td><td>Dirty</td><td></td></tr>");
166 pentry = g_hash_table_lookup (hash, "r-xp");
167 if (pentry) {
168 g_string_append_printf (str, "<tbody><tr><td>r-xp</td><td>%u</td><td>%u</td><td>%u</td><td>%u</td><td>Code</td></tr>",
169 pentry->shared_clean, pentry->shared_dirty, pentry->private_clean, pentry->private_dirty);
170 add_to_totals (pentry, &totals);
171 }
172 pentry = g_hash_table_lookup (hash, "rw-p");
173 if (pentry) {
174 g_string_append_printf (str, "<tbody><tr><td>rw-p</td><td>%u</td><td>%u</td><td>%u</td><td>%u</td><td>Data</td></tr>",
175 pentry->shared_clean, pentry->shared_dirty, pentry->private_clean, pentry->private_dirty);
176 add_to_totals (pentry, &totals);
177 }
178 pentry = g_hash_table_lookup (hash, "r--p");
179 if (pentry) {
180 g_string_append_printf (str, "<tbody><tr><td>r--p</td><td>%u</td><td>%u</td><td>%u</td><td>%u</td><td>Read-only Data</td></tr>",
181 pentry->shared_clean, pentry->shared_dirty, pentry->private_clean, pentry->private_dirty);
182 add_to_totals (pentry, &totals);
183 }
184 pentry = g_hash_table_lookup (hash, "---p");
185 if (pentry) {
186 g_string_append_printf (str, "<tbody><tr><td>---p</td><td>%u</td><td>%u</td><td>%u</td><td>%u</td><td></td></tr>",
187 pentry->shared_clean, pentry->shared_dirty, pentry->private_clean, pentry->private_dirty);
188 add_to_totals (pentry, &totals);
189 }
190 pentry = g_hash_table_lookup (hash, "r--s");
191 if (pentry) {
192 g_string_append_printf (str, "<tbody><tr><td>r--s</td><td>%u</td><td>%u</td><td>%u</td><td>%u</td><td></td></tr>",
193 pentry->shared_clean, pentry->shared_dirty, pentry->private_clean, pentry->private_dirty);
194 add_to_totals (pentry, &totals);
195 }
196 g_string_append_printf (str, "<tbody><tr><td>Total:</td><td>%u kB</td><td>%u kB</td><td>%u kB</td><td>%u kB</td><td></td></tr>",
197 totals.shared_clean, totals.shared_dirty, totals.private_clean, totals.private_dirty);
198 g_string_append (str, "</table>");
199 }
200
201 static void
ephy_smaps_pid_to_html(EphySMaps * smaps,GString * str,pid_t pid,EphyProcess process)202 ephy_smaps_pid_to_html (EphySMaps *smaps,
203 GString *str,
204 pid_t pid,
205 EphyProcess process)
206 {
207 GFileInputStream *stream;
208 GDataInputStream *data_stream;
209 char *path;
210 GFile *file;
211 char *line;
212 GError *error = NULL;
213 VMA_t *vma = NULL;
214 GHashTable *anon_hash, *mapped_hash;
215 GSList *vma_entries = NULL, *p;
216
217 path = g_strdup_printf ("/proc/%u/smaps", pid);
218 file = g_file_new_for_path (path);
219 g_free (path);
220
221 stream = g_file_read (file, NULL, &error);
222 g_object_unref (file);
223
224 if (error && error->code == G_IO_ERROR_NOT_FOUND) {
225 /* This is not GNU/Linux, do nothing. */
226 g_error_free (error);
227 return;
228 }
229
230 data_stream = g_data_input_stream_new (G_INPUT_STREAM (stream));
231 g_object_unref (stream);
232
233 while ((line = g_data_input_stream_read_line (data_stream, NULL, NULL, NULL))) {
234 GMatchInfo *match_info = NULL;
235 gboolean matched = FALSE;
236
237 g_regex_match (smaps->header, line, 0, &match_info);
238 if (g_match_info_matches (match_info)) {
239 matched = TRUE;
240
241 if (vma)
242 vma_entries = g_slist_append (vma_entries, vma);
243
244 vma = g_new0 (VMA_t, 1);
245
246 vma->start = g_match_info_fetch (match_info, 1);
247 vma->end = g_match_info_fetch (match_info, 2);
248 vma->perms = g_match_info_fetch (match_info, 3);
249 vma->offset = g_match_info_fetch (match_info, 4);
250 vma->major = g_match_info_fetch (match_info, 5);
251 vma->minor = g_match_info_fetch (match_info, 6);
252 vma->inode = g_match_info_fetch (match_info, 7);
253 vma->filename = g_match_info_fetch (match_info, 8);
254 }
255
256 g_match_info_free (match_info);
257
258 if (matched)
259 goto out;
260
261 g_regex_match (smaps->detail, line, 0, &match_info);
262 if (vma && g_match_info_matches (match_info)) {
263 char *name = g_match_info_fetch (match_info, 1);
264 char **size = NULL;
265
266 if (!strcmp (name, "Size"))
267 size = &vma->size;
268 else if (!strcmp (name, "Rss"))
269 size = &vma->rss;
270 else if (!strcmp (name, "Pss"))
271 size = &vma->pss;
272 else if (!strcmp (name, "Shared_Clean"))
273 size = &vma->shared_clean;
274 else if (!strcmp (name, "Shared_Dirty"))
275 size = &vma->shared_dirty;
276 else if (!strcmp (name, "Private_Clean"))
277 size = &vma->private_clean;
278 else if (!strcmp (name, "Private_Dirty"))
279 size = &vma->private_dirty;
280
281 if (size)
282 *size = g_match_info_fetch (match_info, 2);
283
284 g_free (name);
285 }
286
287 g_match_info_free (match_info);
288 out:
289 g_free (line);
290 }
291
292 if (vma)
293 vma_entries = g_slist_append (vma_entries, vma);
294
295 g_object_unref (data_stream);
296
297 /* All the file is parsed now. We have parsed more stuff than what
298 * we are going to use, but it might be useful in the future. */
299 anon_hash = g_hash_table_new_full (g_str_hash,
300 g_str_equal,
301 (GDestroyNotify)g_free,
302 (GDestroyNotify)perm_entry_free);
303 mapped_hash = g_hash_table_new_full (g_str_hash,
304 g_str_equal,
305 (GDestroyNotify)g_free,
306 (GDestroyNotify)perm_entry_free);
307
308 for (p = vma_entries; p; p = p->next) {
309 VMA_t *entry = (VMA_t *)p->data;
310
311 if (g_strcmp0 (entry->major, "00") && g_strcmp0 (entry->minor, "00"))
312 add_to_perm_entry (anon_hash, entry);
313 else
314 add_to_perm_entry (mapped_hash, entry);
315
316 vma_free (entry);
317 }
318
319 g_slist_free (vma_entries);
320
321 g_string_append_printf (str, "<h2>%s</h2>", get_ephy_process_name (process));
322
323 /* Anon table. */
324 print_vma_table (str, anon_hash, "Anonymous memory");
325
326 /* Mapped table. */
327 print_vma_table (str, mapped_hash, "Mapped memory");
328
329 /* Done. */
330 g_hash_table_unref (anon_hash);
331 g_hash_table_unref (mapped_hash);
332 }
333
334 static pid_t
get_pid_from_proc_name(const char * name)335 get_pid_from_proc_name (const char *name)
336 {
337 guint i;
338 char *end_ptr = NULL;
339 guint64 pid;
340
341 for (i = 0; name[i]; i++) {
342 if (!g_ascii_isdigit (name[i]))
343 return 0;
344 }
345
346 errno = 0;
347 pid = g_ascii_strtoll (name, &end_ptr, 10);
348 if (errno || end_ptr == name)
349 return 0;
350
351 return pid;
352 }
353
354 static pid_t
get_parent_pid(pid_t pid)355 get_parent_pid (pid_t pid)
356 {
357 char *path;
358 char *data;
359 gsize data_length = 0;
360 char *p;
361 char *end_ptr = NULL;
362 pid_t ppid;
363
364 path = g_strdup_printf ("/proc/%u/stat", pid);
365 if (!g_file_get_contents (path, &data, &data_length, NULL)) {
366 g_free (path);
367
368 return 0;
369 }
370 g_free (path);
371
372 p = strstr (data, ")");
373 if (!p) {
374 g_free (data);
375
376 return 0;
377 }
378
379 /* Skip whitespace + state + whitespace. */
380 p += 3;
381 errno = 0;
382 ppid = g_ascii_strtoll (p, &end_ptr, 10);
383 if (errno || end_ptr == p) {
384 g_free (data);
385
386 return 0;
387 }
388 g_free (data);
389
390 return ppid;
391 }
392
393 static EphyProcess
get_ephy_process(pid_t pid)394 get_ephy_process (pid_t pid)
395 {
396 char *path;
397 char *data;
398 gsize data_length = 0;
399 char *p;
400 char *name;
401 EphyProcess process = EPHY_PROCESS_OTHER;
402
403 path = g_strdup_printf ("/proc/%u/cmdline", pid);
404 if (!g_file_get_contents (path, &data, &data_length, NULL)) {
405 g_free (path);
406
407 return process;
408 }
409 g_free (path);
410
411 p = strstr (data, " ");
412 if (p)
413 *p = '\0';
414
415 name = g_path_get_basename (data);
416 if (g_strcmp0 (name, "WebKitWebProcess") == 0)
417 process = EPHY_PROCESS_WEB;
418 else if (g_strcmp0 (name, "WebKitPluginProcess") == 0)
419 process = EPHY_PROCESS_PLUGIN;
420
421 g_free (data);
422 g_free (name);
423
424 return process;
425 }
426
427 static void
ephy_smaps_pid_children_to_html(EphySMaps * smaps,GString * str,pid_t parent_pid)428 ephy_smaps_pid_children_to_html (EphySMaps *smaps,
429 GString *str,
430 pid_t parent_pid)
431 {
432 GDir *proc;
433 const char *name;
434
435 proc = g_dir_open ("/proc/", 0, NULL);
436 if (!proc)
437 return;
438
439 while ((name = g_dir_read_name (proc))) {
440 pid_t pid, ppid;
441 EphyProcess process;
442
443 if (!strcmp (name, "self"))
444 continue;
445
446 pid = get_pid_from_proc_name (name);
447 if (pid == 0 || pid == parent_pid)
448 continue;
449
450 ppid = get_parent_pid (pid);
451 if (ppid != parent_pid)
452 continue;
453
454 process = get_ephy_process (pid);
455 if (process != EPHY_PROCESS_OTHER)
456 ephy_smaps_pid_to_html (smaps, str, pid, process);
457 }
458 g_dir_close (proc);
459 }
460
461 char *
ephy_smaps_to_html(EphySMaps * smaps)462 ephy_smaps_to_html (EphySMaps *smaps)
463 {
464 GString *str = g_string_new ("");
465 pid_t pid = getpid ();
466
467 g_string_append (str, "<body>");
468
469 ephy_smaps_pid_to_html (smaps, str, pid, EPHY_PROCESS_EPIPHANY);
470 ephy_smaps_pid_children_to_html (smaps, str, pid);
471
472 g_string_append (str, "</body>");
473
474 return g_string_free (str, FALSE);
475 }
476
477 static void
ephy_smaps_init(EphySMaps * smaps)478 ephy_smaps_init (EphySMaps *smaps)
479 {
480 /* Prepare the regexps for the smaps file. */
481 smaps->header = g_regex_new ("^([0-9a-f]+)-([0-9a-f]+) (....) ([0-9a-f]+) (..):(..) (\\d+) *(.*)$",
482 G_REGEX_OPTIMIZE,
483 0,
484 NULL);
485 smaps->detail = g_regex_new ("^(.*): +(\\d+) kB", G_REGEX_OPTIMIZE, 0, NULL);
486 }
487
488 static void
ephy_smaps_finalize(GObject * obj)489 ephy_smaps_finalize (GObject *obj)
490 {
491 EphySMaps *smaps = EPHY_SMAPS (obj);
492
493 g_regex_unref (smaps->header);
494 g_regex_unref (smaps->detail);
495
496 G_OBJECT_CLASS (ephy_smaps_parent_class)->finalize (obj);
497 }
498
499 static void
ephy_smaps_class_init(EphySMapsClass * smaps_class)500 ephy_smaps_class_init (EphySMapsClass *smaps_class)
501 {
502 GObjectClass *gobject_class = G_OBJECT_CLASS (smaps_class);
503
504 gobject_class->finalize = ephy_smaps_finalize;
505 }
506
507 EphySMaps *
ephy_smaps_new(void)508 ephy_smaps_new (void)
509 {
510 return EPHY_SMAPS (g_object_new (EPHY_TYPE_SMAPS, NULL));
511 }
512