diff options
author | Emmanuele Bassi <ebassi@gnome.org> | 2018-01-30 17:50:42 +0000 |
---|---|---|
committer | P. F. Chimento <philip.chimento@gmail.com> | 2018-03-21 12:14:59 -0700 |
commit | 1cec67ba646c13a037829147790dbc5570473909 (patch) | |
tree | 003ec904b3373c6cc4aa5ac15ec1737b8f70a41c | |
parent | 80408632f1322a44b3094f3dfb6ee6afcdef13dc (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.
-rw-r--r-- | tools/Makefile.am.inc | 1 | ||||
-rw-r--r-- | tools/eos-profile-tool/eos-profile-cmd-convert.c | 320 | ||||
-rw-r--r-- | tools/eos-profile-tool/eos-profile-cmds.h | 11 | ||||
-rw-r--r-- | tools/eos-profile-tool/eos-profile-main.c | 7 |
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 |