[gjs/halfline/debug-metrics: 2/4] context: Track heap over time




commit 2f972b824d86788871df47d9ca275fcd707a1a40
Author: Ray Strode <rstrode redhat com>
Date:   Wed Dec 15 10:51:21 2021 -0500

    context: Track heap over time

 gjs/context.cpp | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gjs/context.h   |   5 +++
 gjs/engine.cpp  |  15 +++++++
 gjs/stack.cpp   |  66 +++++++++++++++++++++++++++++
 4 files changed, 215 insertions(+)
---
diff --git a/gjs/context.cpp b/gjs/context.cpp
index fc887413..97bad8c5 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -146,6 +146,10 @@ static GList *all_contexts = NULL;
 static GjsAutoChar dump_heap_output;
 static unsigned dump_heap_idle_id = 0;
 
+static GMetricsFile *metrics_file;
+static GMetricsInstanceCounter *instance_counter = NULL;
+static FILE *heap_fp;
+
 static void
 gjs_context_dump_heaps(void)
 {
@@ -434,6 +438,115 @@ gjs_context_finalize(GObject *object)
     G_OBJECT_CLASS(gjs_context_parent_class)->finalize(object);
 }
 
+static gboolean
+on_javascript_dump_timeout (void)
+{
+    FILE *fp;
+    g_autofree char *filename = NULL;
+
+    if (heap_fp != NULL) {
+        return G_SOURCE_CONTINUE;
+    }
+
+    filename = g_build_filename (g_metrics_get_log_dir(),
+                                 "javascript-heap",
+                                 NULL);
+    fp = fopen(filename, "w+");
+    if (!fp) {
+        return G_SOURCE_CONTINUE;
+    }
+
+    g_mutex_lock(&contexts_lock);
+    for (GList *l = all_contexts; l; l = g_list_next(l)) {
+        auto js_context = static_cast<GjsContext *>(l->data);
+
+        JS::GCForReason(js_context->context, GC_SHRINK, JS::gcreason::Reason::API);
+        JS_GC(js_context->context);
+        js::DumpHeap(js_context->context, fp, js::IgnoreNurseryObjects);
+    }
+    g_mutex_unlock(&contexts_lock);
+
+    heap_fp = fp;
+    return G_SOURCE_CONTINUE;
+}
+
+static void
+on_javascript_metrics_timeout (void)
+{
+    char *line = NULL;
+    size_t line_size;
+
+    if (heap_fp == NULL) {
+        g_metrics_file_start_record(metrics_file);
+        g_metrics_file_end_record(metrics_file);
+        return;
+    }
+
+    if (instance_counter == NULL)
+        instance_counter = g_metrics_instance_counter_new();
+
+    g_metrics_instance_counter_start_record(instance_counter);
+    rewind(heap_fp);
+    while (getline(&line, &line_size, heap_fp) >= 0) {
+        char *save_state = NULL, *ignored_field, *name;
+        char *address;
+
+        if (line_size <= 1)
+            continue;
+
+        if (line[0] == '#')
+            continue;
+
+        if (line[0] == '>')
+            continue;
+
+        ignored_field = strtok_r(line, " \n", &save_state);
+
+        if (ignored_field == NULL)
+            continue;
+
+        ignored_field = strtok_r(NULL, " \n", &save_state);
+
+        if (ignored_field == NULL)
+            continue;
+
+        name = strtok_r(NULL, "\n", &save_state);
+
+        if (name == NULL)
+            continue;
+
+        address = strstr (name, " 0x");
+
+        if (address != NULL)
+          *address = '\0';
+
+        g_metrics_instance_counter_add_instance (instance_counter, name, 1);
+    }
+    free (line);
+    g_metrics_instance_counter_end_record(instance_counter);
+
+    GMetricsInstanceCounterIter iter;
+    GMetricsInstanceCounterMetrics *metrics;
+    const char *name = NULL;
+
+    g_metrics_file_start_record(metrics_file);
+    g_metrics_instance_counter_iter_init (&iter, instance_counter);
+    while (g_metrics_instance_counter_iter_next (&iter, &name, &metrics)) {
+        g_metrics_file_add_row (metrics_file,
+                                name,
+                                metrics->instance_count,
+                                metrics->instance_change > 0? "+" : "",
+                                metrics->instance_change,
+                                metrics->instance_watermark,
+                                metrics->average_instance_change > 0? "+" : "",
+                                metrics->average_instance_change);
+    }
+    g_metrics_file_end_record(metrics_file);
+
+    fclose(heap_fp);
+    heap_fp = NULL;
+}
+
 static void
 gjs_context_constructed(GObject *object)
 {
@@ -513,6 +626,21 @@ gjs_context_constructed(GObject *object)
 
     g_mutex_lock (&contexts_lock);
     all_contexts = g_list_prepend(all_contexts, object);
+
+    if (metrics_file == NULL) {
+        if (g_metrics_requested ("javascript")) {
+            metrics_file = g_metrics_file_new ("javascript",
+                                               "name", "%s",
+                                               "count", "%ld",
+                                               "change", "%s%ld",
+                                               "max seen", "%lu",
+                                               "average change", "%s%ld",
+                                               NULL);
+
+            g_metrics_start_timeout (on_javascript_metrics_timeout);
+            g_timeout_add_seconds (60, (GSourceFunc) on_javascript_dump_timeout, NULL);
+        }
+    }
     g_mutex_unlock (&contexts_lock);
 
     setup_dump_heap();
@@ -1080,3 +1208,4 @@ gjs_context_get_profiler(GjsContext *self)
 {
     return self->profiler;
 }
+
diff --git a/gjs/context.h b/gjs/context.h
index 8a2df292..df3518b8 100644
--- a/gjs/context.h
+++ b/gjs/context.h
@@ -86,6 +86,11 @@ void*           gjs_context_get_native_context   (GjsContext *js_context);
 GJS_EXPORT
 void            gjs_context_print_stack_stderr    (GjsContext *js_context);
 
+GJS_EXPORT
+void            gjs_context_get_stack_trace       (GjsContext *context,
+                                                   char       *buffer,
+                                                   gsize       buffer_size);
+
 GJS_EXPORT
 void            gjs_context_maybe_gc              (GjsContext  *context);
 
diff --git a/gjs/engine.cpp b/gjs/engine.cpp
index 720267d9..758cad67 100644
--- a/gjs/engine.cpp
+++ b/gjs/engine.cpp
@@ -262,6 +262,18 @@ public:
 static GjsInit gjs_is_inited;
 #endif
 
+static gboolean
+annotate_stack_trace (char annotation[],
+                      gsize annotation_size,
+                      gpointer user_data)
+{
+  GjsContext *context = (GjsContext *) user_data;
+
+  gjs_context_get_stack_trace (context, annotation, annotation_size);
+
+  return annotation[0] != '\0';
+}
+
 JSContext *
 gjs_create_js_context(GjsContext *js_context)
 {
@@ -293,6 +305,9 @@ gjs_create_js_context(GjsContext *js_context)
     /* set ourselves as the private data */
     JS_SetContextPrivate(cx, js_context);
 
+    g_metrics_set_stack_trace_annotation_handler (annotate_stack_trace,
+                                                  js_context);
+
     JS_AddFinalizeCallback(cx, gjs_finalize_callback, js_context);
     JS_SetGCCallback(cx, on_garbage_collect, js_context);
     JS_SetLocaleCallbacks(cx, &gjs_locale_callbacks);
diff --git a/gjs/stack.cpp b/gjs/stack.cpp
index c2c6c0f3..0320627a 100644
--- a/gjs/stack.cpp
+++ b/gjs/stack.cpp
@@ -56,6 +56,72 @@ gjs_context_print_stack_stderr(GjsContext *context)
     js::DumpBacktrace(cx, stderr);
 }
 
+void
+gjs_context_get_stack_trace (GjsContext *context,
+                             char *buffer,
+                             gsize buffer_size)
+{
+    FILE *memory_file;
+    char *line = NULL;
+    gsize line_size;
+    char *end = NULL;
+
+    JSContext *cx = (JSContext*) gjs_context_get_native_context(context);
+
+    memory_file = fmemopen(NULL, buffer_size, "w+");
+    js::DumpBacktrace(cx, memory_file);
+
+    rewind(memory_file);
+    while (getline(&line, &line_size, memory_file) >= 0) {
+        char *save_state = NULL;
+        char *frame_number, *ignored_field, *source_line;
+
+        if (line_size <= 1)
+            continue;
+
+        if (line[0] != '#')
+            continue;
+
+        if (end == NULL) {
+            end = buffer;
+        } else {
+            strcpy(end, " -> ");
+            end += strlen (" -> ");
+        }
+
+        frame_number = strtok_r(line, " \t", &save_state);
+
+        if (frame_number == NULL)
+            continue;
+
+        strcpy(end, frame_number);
+        end += strlen(frame_number);
+
+        strcpy(end, " ");
+        end += strlen(" ");
+
+        ignored_field = strtok_r(NULL, " \t", &save_state);
+
+        if (ignored_field == NULL)
+            continue;
+
+        ignored_field = strtok_r(NULL, " \t", &save_state);
+
+        if (ignored_field == NULL)
+            continue;
+
+        source_line = strtok_r(NULL, " \t", &save_state);
+
+        if (source_line == NULL)
+            continue;
+
+        strcpy(end, source_line);
+        end += strlen(source_line);
+    }
+    free (line);
+    fclose (memory_file);
+}
+
 void
 gjs_dumpstack(void)
 {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]