perf header: Store clock references for -k/--clockid option
authorJiri Olsa <jolsa@kernel.org>
Wed, 5 Aug 2020 09:34:40 +0000 (11:34 +0200)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Thu, 6 Aug 2020 12:35:06 +0000 (09:35 -0300)
Add a new CLOCK_DATA feature that stores reference times when
-k/--clockid option is specified.

It contains the clock id and its reference time together with wall clock
time taken at the 'same time', both values are in nanoseconds.

The format of data is as below:

  struct {
       u32 version;  /* version = 1 */
       u32 clockid;
       u64 wall_clock_ns;
       u64 clockid_time_ns;
  };

This clock reference times will be used in following changes to display
wall clock for perf events.

It's available only for recording with clockid specified, because it's
the only case where we can get reference time to wallclock time. It's
can't do that with perf clock yet.

Committer testing:

  $ perf record -h -k

   Usage: perf record [<options>] [<command>]
      or: perf record [<options>] -- <command> [<options>]

      -k, --clockid <clockid>
                            clockid to use for events, see clock_gettime()

  $ perf record -k monotonic sleep 1
  [ perf record: Woken up 1 times to write data ]
  [ perf record: Captured and wrote 0.017 MB perf.data (8 samples) ]
  $ perf report --header-only | grep clockid -A1
  # event : name = cycles:u, , id = { 88815, 88816, 88817, 88818, 88819, 88820, 88821, 88822 }, size = 120, { sample_period, sample_freq } = 4000, sample_type = IP|TID|TIME|PERIOD, read_format = ID, disabled = 1, inherit = 1, exclude_kernel = 1, mmap = 1, comm = 1, freq = 1, enable_on_exec = 1, task = 1, precise_ip = 3, sample_id_all = 1, exclude_guest = 1, mmap2 = 1, comm_exec = 1, use_clockid = 1, ksymbol = 1, bpf_event = 1, clockid = 1
  # CPU_TOPOLOGY info available, use -I to display
  --
  # clockid frequency: 1000 MHz
  # cpu pmu capabilities: branches=32, max_precise=3, pmu_name=skylake
  # clockid: monotonic (1)
  # reference time: 2020-08-06 09:40:21.619290 = 1596717621.619290 (TOD) = 21931.077673635 (monotonic)
  $

Original-patch-by: David Ahern <dsahern@gmail.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Geneviève Bastien <gbastien@versatic.net>
Cc: Ian Rogers <irogers@google.com>
Cc: Jeremie Galarneau <jgalar@efficios.com>
Cc: Michael Petlan <mpetlan@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Wang Nan <wangnan0@huawei.com>
Link: http://lore.kernel.org/lkml/20200805093444.314999-4-jolsa@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/Documentation/perf.data-file-format.txt
tools/perf/builtin-record.c
tools/perf/util/env.h
tools/perf/util/header.c
tools/perf/util/header.h

index b6472e4..9ee9664 100644 (file)
@@ -389,6 +389,19 @@ struct {
 Example:
  cpu pmu capabilities: branches=32, max_precise=3, pmu_name=icelake
 
+       HEADER_CLOCK_DATA = 29,
+
+       Contains clock id and its reference time together with wall clock
+       time taken at the 'same time', both values are in nanoseconds.
+       The format of data is as below.
+
+struct {
+       u32 version;  /* version = 1 */
+       u32 clockid;
+       u64 wall_clock_ns;
+       u64 clockid_time_ns;
+};
+
        other bits are reserved and should ignored for now
        HEADER_FEAT_BITS        = 256,
 
index c9a07a1..b21a876 100644 (file)
@@ -71,6 +71,7 @@
 #include <linux/time64.h>
 #include <linux/zalloc.h>
 #include <linux/bitmap.h>
+#include <sys/time.h>
 
 struct switch_output {
        bool             enabled;
@@ -1204,6 +1205,9 @@ static void record__init_features(struct record *rec)
        if (!(rec->opts.use_clockid && rec->opts.clockid_res_ns))
                perf_header__clear_feat(&session->header, HEADER_CLOCKID);
 
+       if (!rec->opts.use_clockid)
+               perf_header__clear_feat(&session->header, HEADER_CLOCK_DATA);
+
        perf_header__clear_feat(&session->header, HEADER_DIR_FORMAT);
        if (!record__comp_enabled(rec))
                perf_header__clear_feat(&session->header, HEADER_COMPRESSED);
@@ -1552,6 +1556,40 @@ static int record__setup_sb_evlist(struct record *rec)
        return 0;
 }
 
+static int record__init_clock(struct record *rec)
+{
+       struct perf_session *session = rec->session;
+       struct timespec ref_clockid;
+       struct timeval ref_tod;
+       u64 ref;
+
+       if (!rec->opts.use_clockid)
+               return 0;
+
+       session->header.env.clock.clockid = rec->opts.clockid;
+
+       if (gettimeofday(&ref_tod, NULL) != 0) {
+               pr_err("gettimeofday failed, cannot set reference time.\n");
+               return -1;
+       }
+
+       if (clock_gettime(rec->opts.clockid, &ref_clockid)) {
+               pr_err("clock_gettime failed, cannot set reference time.\n");
+               return -1;
+       }
+
+       ref = (u64) ref_tod.tv_sec * NSEC_PER_SEC +
+             (u64) ref_tod.tv_usec * NSEC_PER_USEC;
+
+       session->header.env.clock.tod_ns = ref;
+
+       ref = (u64) ref_clockid.tv_sec * NSEC_PER_SEC +
+             (u64) ref_clockid.tv_nsec;
+
+       session->header.env.clock.clockid_ns = ref;
+       return 0;
+}
+
 static int __cmd_record(struct record *rec, int argc, const char **argv)
 {
        int err;
@@ -1632,6 +1670,9 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
                return -1;
        }
 
+       if (record__init_clock(rec))
+               return -1;
+
        record__init_features(rec);
 
        if (rec->opts.use_clockid && rec->opts.clockid_res_ns)
index 1ab2682..7218f7d 100644 (file)
@@ -100,6 +100,18 @@ struct perf_env {
        /* For fast cpu to numa node lookup via perf_env__numa_node */
        int                     *numa_map;
        int                      nr_numa_map;
+
+       /* For real clock time reference. */
+       struct {
+               u64     tod_ns;
+               u64     clockid_ns;
+               int     clockid;
+               /*
+                * enabled is valid for report mode, and is true if above
+                * values are set, it's set in process_clock_data
+                */
+               bool    enabled;
+       } clock;
 };
 
 enum perf_compress_type {
index 7a67d01..eece94c 100644 (file)
@@ -46,6 +46,7 @@
 #include "util/util.h" // perf_exe()
 #include "cputopo.h"
 #include "bpf-event.h"
+#include "clockid.h"
 
 #include <linux/ctype.h>
 #include <internal/lib.h>
@@ -895,6 +896,40 @@ static int write_clockid(struct feat_fd *ff,
                        sizeof(ff->ph->env.clockid_res_ns));
 }
 
+static int write_clock_data(struct feat_fd *ff,
+                           struct evlist *evlist __maybe_unused)
+{
+       u64 *data64;
+       u32 data32;
+       int ret;
+
+       /* version */
+       data32 = 1;
+
+       ret = do_write(ff, &data32, sizeof(data32));
+       if (ret < 0)
+               return ret;
+
+       /* clockid */
+       data32 = ff->ph->env.clock.clockid;
+
+       ret = do_write(ff, &data32, sizeof(data32));
+       if (ret < 0)
+               return ret;
+
+       /* TOD ref time */
+       data64 = &ff->ph->env.clock.tod_ns;
+
+       ret = do_write(ff, data64, sizeof(*data64));
+       if (ret < 0)
+               return ret;
+
+       /* clockid ref time */
+       data64 = &ff->ph->env.clock.clockid_ns;
+
+       return do_write(ff, data64, sizeof(*data64));
+}
+
 static int write_dir_format(struct feat_fd *ff,
                            struct evlist *evlist __maybe_unused)
 {
@@ -1549,6 +1584,49 @@ static void print_clockid(struct feat_fd *ff, FILE *fp)
                ff->ph->env.clockid_res_ns * 1000);
 }
 
+static void print_clock_data(struct feat_fd *ff, FILE *fp)
+{
+       struct timespec clockid_ns;
+       char tstr[64], date[64];
+       struct timeval tod_ns;
+       clockid_t clockid;
+       struct tm ltime;
+       u64 ref;
+
+       if (!ff->ph->env.clock.enabled) {
+               fprintf(fp, "# reference time disabled\n");
+               return;
+       }
+
+       /* Compute TOD time. */
+       ref = ff->ph->env.clock.tod_ns;
+       tod_ns.tv_sec = ref / NSEC_PER_SEC;
+       ref -= tod_ns.tv_sec * NSEC_PER_SEC;
+       tod_ns.tv_usec = ref / NSEC_PER_USEC;
+
+       /* Compute clockid time. */
+       ref = ff->ph->env.clock.clockid_ns;
+       clockid_ns.tv_sec = ref / NSEC_PER_SEC;
+       ref -= clockid_ns.tv_sec * NSEC_PER_SEC;
+       clockid_ns.tv_nsec = ref;
+
+       clockid = ff->ph->env.clock.clockid;
+
+       if (localtime_r(&tod_ns.tv_sec, &ltime) == NULL)
+               snprintf(tstr, sizeof(tstr), "<error>");
+       else {
+               strftime(date, sizeof(date), "%F %T", &ltime);
+               scnprintf(tstr, sizeof(tstr), "%s.%06d",
+                         date, (int) tod_ns.tv_usec);
+       }
+
+       fprintf(fp, "# clockid: %s (%u)\n", clockid_name(clockid), clockid);
+       fprintf(fp, "# reference time: %s = %ld.%06d (TOD) = %ld.%09ld (%s)\n",
+                   tstr, tod_ns.tv_sec, (int) tod_ns.tv_usec,
+                   clockid_ns.tv_sec, clockid_ns.tv_nsec,
+                   clockid_name(clockid));
+}
+
 static void print_dir_format(struct feat_fd *ff, FILE *fp)
 {
        struct perf_session *session;
@@ -2738,6 +2816,40 @@ static int process_clockid(struct feat_fd *ff,
        return 0;
 }
 
+static int process_clock_data(struct feat_fd *ff,
+                             void *_data __maybe_unused)
+{
+       u32 data32;
+       u64 data64;
+
+       /* version */
+       if (do_read_u32(ff, &data32))
+               return -1;
+
+       if (data32 != 1)
+               return -1;
+
+       /* clockid */
+       if (do_read_u32(ff, &data32))
+               return -1;
+
+       ff->ph->env.clock.clockid = data32;
+
+       /* TOD ref time */
+       if (do_read_u64(ff, &data64))
+               return -1;
+
+       ff->ph->env.clock.tod_ns = data64;
+
+       /* clockid ref time */
+       if (do_read_u64(ff, &data64))
+               return -1;
+
+       ff->ph->env.clock.clockid_ns = data64;
+       ff->ph->env.clock.enabled = true;
+       return 0;
+}
+
 static int process_dir_format(struct feat_fd *ff,
                              void *_data __maybe_unused)
 {
@@ -3008,6 +3120,7 @@ const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE] = {
        FEAT_OPR(BPF_BTF,       bpf_btf,        false),
        FEAT_OPR(COMPRESSED,    compressed,     false),
        FEAT_OPR(CPU_PMU_CAPS,  cpu_pmu_caps,   false),
+       FEAT_OPR(CLOCK_DATA,    clock_data,     false),
 };
 
 struct header_print_data {
index 650bd1c..2aca717 100644 (file)
@@ -44,6 +44,7 @@ enum {
        HEADER_BPF_BTF,
        HEADER_COMPRESSED,
        HEADER_CPU_PMU_CAPS,
+       HEADER_CLOCK_DATA,
        HEADER_LAST_FEATURE,
        HEADER_FEAT_BITS        = 256,
 };