[gjs/halfline/debug-metrics: 2/4] context: Track heap over time
- From: Ray Strode <halfline src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/halfline/debug-metrics: 2/4] context: Track heap over time
- Date: Tue, 21 Dec 2021 17:47:21 +0000 (UTC)
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]