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