summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorEmmanuele Bassi <ebassi@gnome.org>2018-01-30 17:50:42 +0000
committerP. F. Chimento <philip.chimento@gmail.com>2018-03-21 12:14:59 -0700
commit1cec67ba646c13a037829147790dbc5570473909 (patch)
tree003ec904b3373c6cc4aa5ac15ec1737b8f70a41c /tools
parent80408632f1322a44b3094f3dfb6ee6afcdef13dc (diff)
Add eos-profile-convert command
If we want to perform additional analysis on a profile capture file it's generally going to be easier to have it in a different format than a GVDB binary blob, especially if we want to use tools that are written in high level languages that may not have access to the GVDB API. The simplest format we can convert to is JSON, which is structured and easy to parse with other languages.
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am.inc1
-rw-r--r--tools/eos-profile-tool/eos-profile-cmd-convert.c320
-rw-r--r--tools/eos-profile-tool/eos-profile-cmds.h11
-rw-r--r--tools/eos-profile-tool/eos-profile-main.c7
4 files changed, 335 insertions, 4 deletions
diff --git a/tools/Makefile.am.inc b/tools/Makefile.am.inc
index d8751bb..b4cc2f5 100644
--- a/tools/Makefile.am.inc
+++ b/tools/Makefile.am.inc
@@ -48,6 +48,7 @@ bin_PROGRAMS = \
eos_profile_SOURCES = \
tools/eos-profile-tool/eos-profile-cmds.h \
+ tools/eos-profile-tool/eos-profile-cmd-convert.c \
tools/eos-profile-tool/eos-profile-cmd-help.c \
tools/eos-profile-tool/eos-profile-cmd-show.c \
tools/eos-profile-tool/eos-profile-main.c \
diff --git a/tools/eos-profile-tool/eos-profile-cmd-convert.c b/tools/eos-profile-tool/eos-profile-cmd-convert.c
new file mode 100644
index 0000000..ef374b1
--- /dev/null
+++ b/tools/eos-profile-tool/eos-profile-cmd-convert.c
@@ -0,0 +1,320 @@
+#include "config.h"
+
+#include "eos-profile-cmds.h"
+#include "eos-profile-utils.h"
+
+#include "endless/eosprofile-private.h"
+#include "endless/gvdb/gvdb-reader.h"
+
+#include <math.h>
+#include <json-glib/json-glib.h>
+
+static char *opt_format;
+static char *opt_output;
+static char *opt_input;
+static gboolean opt_pretty;
+
+static GOptionEntry opts[] = {
+ {
+ .long_name = "format",
+ .short_name = 'f',
+ .flags = G_OPTION_FLAG_NONE,
+ .arg = G_OPTION_ARG_STRING,
+ .arg_data = &opt_format,
+ .description = "The output format (valid values: json)",
+ .arg_description = "FORMAT",
+ },
+ {
+ .long_name = "pretty",
+ .short_name = 0,
+ .flags = G_OPTION_FLAG_NONE,
+ .arg = G_OPTION_ARG_NONE,
+ .arg_data = &opt_pretty,
+ .description = "Enable pretty-printing the output",
+ .arg_description = NULL,
+ },
+ {
+ .long_name = "output",
+ .short_name = 0,
+ .flags = G_OPTION_FLAG_NONE,
+ .arg = G_OPTION_ARG_FILENAME,
+ .arg_data = &opt_output,
+ .description = "The output file",
+ .arg_description = "FILE",
+ },
+
+ { NULL, },
+};
+
+gboolean
+eos_profile_cmd_convert_parse_args (int argc,
+ char **argv)
+{
+ g_autoptr(GError) error = NULL;
+
+ g_autoptr(GOptionContext) context = g_option_context_new (NULL);
+
+ g_option_context_set_help_enabled (context, TRUE);
+ g_option_context_add_main_entries (context, opts, GETTEXT_PACKAGE);
+
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ eos_profile_util_print_error ("Invalid argument: %s", error->message);
+ return FALSE;
+ }
+
+ if (argc < 2)
+ return FALSE;
+
+ opt_input = argv[1];
+
+ return TRUE;
+}
+
+static JsonNode *
+collect_probe_samples (GVariant *array,
+ JsonObject *probe_obj)
+{
+ g_autoptr(GArray) samples = g_array_new (FALSE, FALSE, sizeof (ProfileSample));
+
+ GVariantIter iter;
+ g_variant_iter_init (&iter, array);
+
+ gint64 start, end;
+ while (g_variant_iter_next (&iter, "(xx)", &start, &end))
+ {
+ g_array_append_vals (samples,
+ &(ProfileSample) {
+ .start_time = start,
+ .end_time = end,
+ },
+ 1);
+ }
+
+ gint64 min_sample = G_MAXINT64, max_sample = 0;
+ gint64 total = 0;
+
+ g_autoptr(GArray) valid_samples = g_array_new (FALSE, FALSE, sizeof (guint));
+
+ for (int i = 0; i < samples->len; i++)
+ {
+ const ProfileSample *sample = &g_array_index (samples, ProfileSample, i);
+
+ gint64 delta = sample->end_time - sample->start_time;
+
+ /* If the probe never got stopped we need to skip this sample */
+ if (delta < 0)
+ continue;
+
+ g_array_append_val (valid_samples, i);
+
+ if (delta < min_sample)
+ min_sample = delta;
+ if (delta > max_sample)
+ max_sample = delta;
+
+ total += delta;
+ }
+
+ g_autofree char *msg = NULL;
+
+ if (valid_samples->len > 1)
+ {
+ JsonObject *obj = json_object_new ();
+
+ double avg = total / (double) valid_samples->len;
+ double s = 0;
+ double s_part = 0;
+
+ JsonArray *raw_array = json_array_sized_new (valid_samples->len);
+
+ for (int i = 1; i < valid_samples->len - 1; i++)
+ {
+ guint idx = g_array_index (valid_samples, guint, i);
+ const ProfileSample *sample = &g_array_index (samples, ProfileSample, idx);
+
+ gint64 delta = sample->end_time - sample->start_time;
+ g_assert (delta >= 0);
+
+ double deviation = delta - avg;
+ s_part += (deviation * deviation);
+
+ json_array_add_int_element (raw_array, delta);
+ }
+
+ json_object_set_int_member (probe_obj, "numSamples", valid_samples->len);
+ json_object_set_array_member (probe_obj, "rawSamples", raw_array);
+
+ if (valid_samples->len > 1)
+ s = sqrt (s_part / (double) valid_samples->len - 1);
+ else
+ s = 0.0;
+
+ if (s != 0.0 && !isnan (s))
+ json_object_set_double_member (probe_obj, "sigma", s);
+
+ json_object_set_int_member (probe_obj, "totalTime", total);
+ json_object_set_double_member (probe_obj, "minSample", min_sample);
+ json_object_set_double_member (probe_obj, "maxSample", max_sample);
+ json_object_set_double_member (probe_obj, "average", avg);
+ }
+ else
+ {
+ json_object_set_int_member (probe_obj, "totalTime", total);
+ json_object_set_int_member (probe_obj, "numSamples", 0);
+ json_object_set_array_member (probe_obj, "rawSamples", NULL);
+ }
+}
+
+int
+eos_profile_cmd_convert_main (void)
+{
+ g_assert (opt_input != NULL);
+
+ if (opt_format == NULL)
+ opt_format = "json";
+
+ if (g_strcmp0 (opt_format, "json") != 0)
+ {
+ eos_profile_util_print_error ("Unknown format '%s'; please, use 'json'", opt_format);
+ return 1;
+ }
+
+ g_autoptr(GError) error = NULL;
+
+ GvdbTable *db = gvdb_table_new (opt_input, TRUE, &error);
+ if (error != NULL)
+ {
+ eos_profile_util_print_error ("Unable to load '%s': %s\n",
+ opt_input,
+ error->message);
+ return 1;
+ }
+
+ GVariant *v = gvdb_table_get_raw_value (db, PROBE_DB_META_VERSION_KEY);
+ gint32 version = v != NULL ? g_variant_get_int32 (v) : -1;
+ g_clear_pointer (&v, g_variant_unref);
+
+ if (version != PROBE_DB_VERSION)
+ {
+ eos_profile_util_print_error ("Unable to load '%s': invalid version\n", opt_input);
+ gvdb_table_free (db);
+ return 1;
+ }
+
+ v = gvdb_table_get_raw_value (db, PROBE_DB_META_APPID_KEY);
+ g_autofree char *appid = v != NULL ? g_variant_dup_string (v, NULL) : NULL;
+ g_clear_pointer (&v, g_variant_unref);
+
+ v = gvdb_table_get_raw_value (db, PROBE_DB_META_PROFILE_KEY);
+ gint64 profile_time = v != NULL ? g_variant_get_int64 (v) : -1;
+ g_clear_pointer (&v, g_variant_unref);
+
+ v = gvdb_table_get_raw_value (db, PROBE_DB_META_START_KEY);
+ g_autofree char *start_time = NULL;
+ if (v != NULL)
+ {
+ g_autoptr(GDateTime) dt =
+ g_date_time_new_from_unix_local (g_variant_get_int64 (v));
+
+ start_time = g_date_time_format (dt, "%Y-%m-%d %T");
+ }
+ g_clear_pointer (&v, g_variant_unref);
+
+ JsonNode *root = json_node_new (JSON_NODE_OBJECT);
+ JsonObject *obj = json_object_new ();
+ json_node_take_object (root, obj);
+
+ JsonObject *meta = json_object_new ();
+ json_object_set_int_member (meta, "version", version);
+ json_object_set_string_member (meta, "appId", appid);
+ json_object_set_int_member (meta, "profileTime", profile_time);
+ json_object_set_string_member (meta, "startTime", start_time);
+
+ json_object_set_object_member (obj, "meta", meta);
+
+ int names_len = 0;
+ g_auto(GStrv) names = gvdb_table_get_names (db, &names_len);
+
+ const char * const meta_keys[] = {
+ PROBE_DB_META_VERSION_KEY,
+ PROBE_DB_META_APPID_KEY,
+ PROBE_DB_META_PROFILE_KEY,
+ PROBE_DB_META_START_KEY,
+ NULL,
+ };
+
+ JsonArray *probes_arr = json_array_new ();
+
+ for (int i = 0; i < names_len; i++)
+ {
+ const char *key_name = names[i];
+
+ if (g_strv_contains (meta_keys, key_name))
+ continue;
+
+ if (!gvdb_table_has_value (db, key_name))
+ continue;
+
+ g_autoptr(GVariant) value = gvdb_table_get_raw_value (db, key_name);
+ if (value == NULL)
+ continue;
+
+ const char *file = NULL;
+ const char *function = NULL;
+ const char *probe_name = NULL;
+ g_autoptr(GVariant) samples = NULL;
+ gint32 line, n_samples;
+
+ g_variant_get (value, "(&s&s&suu@a(xx))",
+ &probe_name,
+ &function,
+ &file,
+ &line,
+ &n_samples,
+ &samples);
+
+ JsonObject *probe_obj = json_object_new ();
+
+ json_object_set_string_member (probe_obj, "name", probe_name);
+ json_object_set_string_member (probe_obj, "file", file);
+ json_object_set_int_member (probe_obj, "line", line);
+ json_object_set_string_member (probe_obj, "function", function);
+
+ JsonObject *samples_obj = json_object_new ();
+ collect_probe_samples (samples, samples_obj);
+ json_object_set_object_member (probe_obj, "samples", samples_obj);
+
+ json_array_add_object_element (probes_arr, probe_obj);
+ }
+
+ json_object_set_array_member (obj, "probes", probes_arr);
+
+ gvdb_table_free (db);
+
+ g_autoptr(JsonGenerator) gen = json_generator_new ();
+ json_generator_set_pretty (gen, opt_pretty);
+ json_generator_set_root (gen, root);
+
+ if (opt_output != NULL)
+ {
+ g_autoptr(GError) gen_error = NULL;
+
+ json_generator_to_file (gen, opt_output, &error);
+ if (error != NULL)
+ {
+ eos_profile_util_print_error ("Unable to write to '%s': %s",
+ opt_output,
+ error->message);
+ return 1;
+ }
+ }
+ else
+ {
+ g_autofree char *str = json_generator_to_data (gen, NULL);
+
+ g_print ("%s\n", str);
+ }
+
+ return 0;
+}
diff --git a/tools/eos-profile-tool/eos-profile-cmds.h b/tools/eos-profile-tool/eos-profile-cmds.h
index 1b06db5..c16eded 100644
--- a/tools/eos-profile-tool/eos-profile-cmds.h
+++ b/tools/eos-profile-tool/eos-profile-cmds.h
@@ -17,11 +17,14 @@ typedef struct {
typedef gboolean (* EosProfileCmdCallback) (const EosProfileCmd *cmd,
gpointer data);
-gboolean eos_profile_cmd_help_parse_args (int argc, char **argv);
-int eos_profile_cmd_help_main (void);
+gboolean eos_profile_cmd_help_parse_args (int argc, char **argv);
+int eos_profile_cmd_help_main (void);
-gboolean eos_profile_cmd_show_parse_args (int argc, char **argv);
-int eos_profile_cmd_show_main (void);
+gboolean eos_profile_cmd_show_parse_args (int argc, char **argv);
+int eos_profile_cmd_show_main (void);
+
+gboolean eos_profile_cmd_convert_parse_args (int argc, char **argv);
+int eos_profile_cmd_convert_main (void);
void eos_profile_foreach_cmd (EosProfileCmdCallback cb,
gpointer data);
diff --git a/tools/eos-profile-tool/eos-profile-main.c b/tools/eos-profile-tool/eos-profile-main.c
index 1691bff..79e7706 100644
--- a/tools/eos-profile-tool/eos-profile-main.c
+++ b/tools/eos-profile-tool/eos-profile-main.c
@@ -25,6 +25,13 @@ static const EosProfileCmd profile_commands[] = {
.parse_args = eos_profile_cmd_show_parse_args,
.main = eos_profile_cmd_show_main,
},
+ {
+ .name = "convert",
+ .description = "Converts a capture file to another format",
+ .usage = "convert [OPTIONS…] <FILE>",
+ .parse_args = eos_profile_cmd_convert_parse_args,
+ .main = eos_profile_cmd_convert_main,
+ },
};
void