From: Slavomir Kaslev <kaslevs@vmware.com>
To: Tzvetomir Stoyanov <tstoyanov@vmware.com>
Cc: rostedt@goodmis.org, linux-trace-devel@vger.kernel.org
Subject: Re: [PATCH v5 9/9] trace-cmd [POC]: Implemented timestamps synch PTP-like algorithm, using vsock events.
Date: Mon, 11 Mar 2019 13:46:05 +0200 [thread overview]
Message-ID: <20190311114601.GA11420@box> (raw)
In-Reply-To: <20190308134423.22008-10-tstoyanov@vmware.com>
Some minor comments inline since we discussed the more major issues with patches 8 and 9 offline already.
On Fri, Mar 08, 2019 at 03:44:23PM +0200, Tzvetomir Stoyanov wrote:
> This is a POC patch, implementing a PTP-like algorithm for syncing timestamps between
> host and guest machines, using vsock trace events to catch the host / guest time.
This patch needs a new commit message since it's not doing anything resembling PTP anymore.
>
> Signed-off-by: Tzvetomir Stoyanov <tstoyanov@vmware.com>
> ---
> include/trace-cmd/trace-cmd.h | 9 +
> tracecmd/Makefile | 1 +
> tracecmd/include/trace-local.h | 50 +++
> tracecmd/trace-msg.c | 284 ++++++++++++++-
> tracecmd/trace-record.c | 72 +++-
> tracecmd/trace-timesync.c | 622 +++++++++++++++++++++++++++++++++
> 6 files changed, 1024 insertions(+), 14 deletions(-)
> create mode 100644 tracecmd/trace-timesync.c
>
> diff --git a/include/trace-cmd/trace-cmd.h b/include/trace-cmd/trace-cmd.h
> index cea6bb9..dbfbe14 100644
> --- a/include/trace-cmd/trace-cmd.h
> +++ b/include/trace-cmd/trace-cmd.h
> @@ -352,6 +352,15 @@ int tracecmd_msg_recv_trace_resp(struct tracecmd_msg_handle *msg_handle,
> int *nr_cpus, int *page_size,
> unsigned int **ports, bool *use_fifos);
>
> +struct tracecmd_clock_sync;
> +
> +int tracecmd_msg_rcv_time_sync(struct tracecmd_msg_handle *msg_handle,
> + struct tracecmd_clock_sync *clock_sync,
> + long long *toffset, long long *ts_offset);
> +int tracecmd_msg_snd_time_sync(struct tracecmd_msg_handle *msg_handle,
> + struct tracecmd_clock_sync *clock_sync,
> + long long *toffset, long long *ts_offset);
> +
> /* --- Plugin handling --- */
> extern struct tep_plugin_option trace_ftrace_options[];
>
> diff --git a/tracecmd/Makefile b/tracecmd/Makefile
> index d3e3080..8a73bf7 100644
> --- a/tracecmd/Makefile
> +++ b/tracecmd/Makefile
> @@ -32,6 +32,7 @@ TRACE_CMD_OBJS += trace-list.o
> TRACE_CMD_OBJS += trace-output.o
> TRACE_CMD_OBJS += trace-usage.o
> TRACE_CMD_OBJS += trace-msg.o
> +TRACE_CMD_OBJS += trace-timesync.o
>
> ifeq ($(VSOCK_DEFINED), 1)
> TRACE_CMD_OBJS += trace-agent.o
> diff --git a/tracecmd/include/trace-local.h b/tracecmd/include/trace-local.h
> index 62f5e47..ba0b06d 100644
> --- a/tracecmd/include/trace-local.h
> +++ b/tracecmd/include/trace-local.h
> @@ -214,6 +214,11 @@ struct buffer_instance {
> int port;
> int *fds;
> bool use_fifos;
> +
> + struct tracecmd_clock_sync *clock_sync;
> + int time_sync_count;
> + long long *time_sync_ts;
> + long long *time_sync_ofssets;
> };
>
> extern struct buffer_instance top_instance;
> @@ -227,6 +232,37 @@ extern struct buffer_instance *first_instance;
> #define is_agent(instance) ((instance)->flags & BUFFER_FL_AGENT)
> #define is_guest(instance) ((instance)->flags & BUFFER_FL_GUEST)
>
> +struct tracecmd_ftrace_param {
> + char *file;
> + char *set;
> + char *reset;
> +};
> +
> +struct tracecmd_event_hit {
> + int id;
> + int cpu;
> + int pid;
> + unsigned long long ts;
> +};
> +
> +enum clock_sync_conext {
> + CLOCK_KVM_X86_VSOCK_HOST,
> + CLOCK_KVM_X86_VSOCK_GUEST,
> + CLOCK_CONTEXT_MAX,
> +};
I don't think those should be exposed since they are not generic and are not used outside of time synchronization code.
> +
> +struct tracecmd_clock_sync {
> + enum clock_sync_conext clock_context_id;
> + struct tracecmd_ftrace_param *ftrace_params;
> + struct tracecmd_event_hit *events;
> + struct tep_handle *tep;
> + struct buffer_instance *vinst;
> + unsigned int local_cid;
> + unsigned int local_port;
> + unsigned int remote_cid;
> + unsigned int remote_port;
> +};
> +
> struct buffer_instance *create_instance(const char *name);
> void add_instance(struct buffer_instance *instance, int cpu_count);
> char *get_instance_file(struct buffer_instance *instance, const char *file);
> @@ -235,6 +271,20 @@ void update_first_instance(struct buffer_instance *instance, int topt);
> void show_instance_file(struct buffer_instance *instance, const char *name);
>
> int count_cpus(void);
> +
> +struct tracecmd_clock_sync*
> +tracecmd_clock_context_init(struct tracecmd_msg_handle *msg_handle,
> + const char *clock_str,
> + enum clock_sync_conext id);
> +void tracecmd_clock_context_free(struct tracecmd_clock_sync *clock_context);
> +int tracecmd_clock_find_event(struct tracecmd_clock_sync *clock, int cpu,
> + struct tracecmd_event_hit *event);
> +
> +void tracecmd_clock_synch_enable(struct tracecmd_clock_sync *clock_context);
> +void tracecmd_clock_synch_disable(struct tracecmd_clock_sync *clock_context);
> +
> +void sync_time_with_host_v3(struct buffer_instance *instance);
> +void sync_time_with_guest_v3(struct buffer_instance *instance);
> void write_tracing_on(struct buffer_instance *instance, int on);
> char *get_instance_dir(struct buffer_instance *instance);
> int write_instance_file(struct buffer_instance *instance,
> diff --git a/tracecmd/trace-msg.c b/tracecmd/trace-msg.c
> index 065a01e..0f1f071 100644
> --- a/tracecmd/trace-msg.c
> +++ b/tracecmd/trace-msg.c
> @@ -18,16 +18,22 @@
> #include <stdarg.h>
> #include <string.h>
> #include <unistd.h>
> +#include <time.h>
> #include <arpa/inet.h>
> #include <sys/types.h>
> #include <linux/types.h>
> +#include <linux/vm_sockets.h>
This last #include is not necessary.
>
> #include "trace-cmd-local.h"
> #include "trace-local.h"
> #include "trace-msg.h"
>
> +typedef __u16 u16;
> +typedef __s16 s16;
> typedef __u32 u32;
> typedef __be32 be32;
> +typedef __u64 u64;
> +typedef __s64 s64;
>
> static inline void dprint(const char *fmt, ...)
> {
> @@ -50,6 +56,9 @@ static inline void dprint(const char *fmt, ...)
>
> unsigned int page_size;
>
> +/* Try a few times to get an accurate time sync */
> +#define TSYNC_TRIES 300
> +
> struct tracecmd_msg_opt {
> be32 size;
> be32 opt_cmd;
> @@ -76,6 +85,16 @@ struct tracecmd_msg_trace_resp {
> be32 page_size;
> } __attribute__((packed));
>
> +struct tracecmd_msg_tsync_stop {
> + long long offset;
> + long long ts_offset;
> +} __attribute__((packed));
> +
> +struct tracecmd_msg_tsync_pkt {
> + u64 tlocal;
> +} __attribute__((packed));
> +
> +
> struct tracecmd_msg_header {
> be32 size;
> be32 cmd;
> @@ -83,14 +102,18 @@ struct tracecmd_msg_header {
> } __attribute__((packed));
>
> #define MSG_MAP \
> - C(CLOSE, 0, 0), \
> - C(TINIT, 1, sizeof(struct tracecmd_msg_tinit)), \
> - C(RINIT, 2, sizeof(struct tracecmd_msg_rinit)), \
> - C(SEND_DATA, 3, 0), \
> - C(FIN_DATA, 4, 0), \
> - C(NOT_SUPP, 5, 0), \
> - C(TRACE_REQ, 6, sizeof(struct tracecmd_msg_trace_req)), \
> - C(TRACE_RESP, 7, sizeof(struct tracecmd_msg_trace_resp)),
> + C(CLOSE, 0, 0), \
> + C(TINIT, 1, sizeof(struct tracecmd_msg_tinit)), \
> + C(RINIT, 2, sizeof(struct tracecmd_msg_rinit)), \
> + C(SEND_DATA, 3, 0), \
> + C(FIN_DATA, 4, 0), \
> + C(NOT_SUPP, 5, 0), \
> + C(TRACE_REQ, 6, sizeof(struct tracecmd_msg_trace_req)), \
> + C(TRACE_RESP, 7, sizeof(struct tracecmd_msg_trace_resp)),\
> + C(TSYNC_START, 8, 0), \
> + C(TSYNC_STOP, 9, sizeof(struct tracecmd_msg_tsync_stop)),\
> + C(TSYNC_REQ, 10, sizeof(struct tracecmd_msg_tsync_pkt)), \
> + C(TSYNC_RESP, 11, sizeof(struct tracecmd_msg_tsync_pkt)),
>
> #undef C
> #define C(a,b,c) MSG_##a = b
> @@ -120,10 +143,13 @@ static const char *cmd_to_name(int cmd)
> struct tracecmd_msg {
> struct tracecmd_msg_header hdr;
> union {
> - struct tracecmd_msg_tinit tinit;
> - struct tracecmd_msg_rinit rinit;
> - struct tracecmd_msg_trace_req trace_req;
> - struct tracecmd_msg_trace_resp trace_resp;
> + struct tracecmd_msg_tinit tinit;
> + struct tracecmd_msg_rinit rinit;
> + struct tracecmd_msg_trace_req trace_req;
> + struct tracecmd_msg_trace_resp trace_resp;
> + struct tracecmd_msg_tsync_stop ts_stop;
> + struct tracecmd_msg_tsync_pkt ts_req;
> + struct tracecmd_msg_tsync_pkt ts_resp;
> };
> union {
> struct tracecmd_msg_opt *opt;
> @@ -859,6 +885,240 @@ out:
> return ret;
> }
>
> +int tracecmd_msg_rcv_time_sync(struct tracecmd_msg_handle *msg_handle,
> + struct tracecmd_clock_sync *clock_context,
> + long long *toffset, long long *ts_offset)
> +{
> + struct tracecmd_event_hit event;
> + struct tracecmd_msg msg;
> + int cpu_pid, ret;
> +
> + if (clock_context == NULL || msg_handle == NULL)
> + return 0;
> +
> + if (toffset)
> + *toffset = 0;
> +
> + ret = tracecmd_msg_recv(msg_handle->fd, &msg);
> + if (ret < 0 || ntohl(msg.hdr.cmd) == MSG_TSYNC_STOP)
> + return 0;
> + if (ntohl(msg.hdr.cmd) != MSG_TSYNC_START) {
> + tracecmd_msg_init(MSG_NOT_SUPP, &msg);
> + tracecmd_msg_send(msg_handle->fd, &msg);
Can you switch this below to the helper function?
tracecmd_msg_send_notsupp(msg_handle);
> + return 0;
> + }
> +
> + tracecmd_msg_init(MSG_TSYNC_START, &msg);
> + tracecmd_msg_send(msg_handle->fd, &msg);
> + tracecmd_clock_synch_enable(clock_context);
> +
> + do {
> + memset(&event, 0, sizeof(event));
> + ret = tracecmd_msg_recv(msg_handle->fd, &msg);
> + if (ret < 0 || ntohl(msg.hdr.cmd) == MSG_TSYNC_STOP)
> + break;
> + if (ntohl(msg.hdr.cmd) != MSG_TSYNC_REQ) {
> + tracecmd_msg_init(MSG_NOT_SUPP, &msg);
> + tracecmd_msg_send(msg_handle->fd, &msg);
> + break;
> + }
> + ret = tracecmd_msg_recv(msg_handle->fd, &msg);
> + if (ret < 0 || ntohl(msg.hdr.cmd) == MSG_TSYNC_STOP)
> + break;
> + if (ntohl(msg.hdr.cmd) != MSG_TSYNC_REQ) {
> + tracecmd_msg_init(MSG_NOT_SUPP, &msg);
> + tracecmd_msg_send(msg_handle->fd, &msg);
> + break;
> + }
> + /* Get kvm_exit events related to the corresponding VCPU */
> + cpu_pid = get_guest_vcpu_pid(clock_context->remote_cid,
> + ntohll(msg.ts_req.tlocal));
> + tracecmd_clock_find_event(clock_context, cpu_pid, &event);
> + tracecmd_msg_init(MSG_TSYNC_RESP, &msg);
> + msg.ts_resp.tlocal = htonll(event.ts);
> + tracecmd_msg_send(msg_handle->fd, &msg);
> + } while (true);
> + tracecmd_clock_synch_disable(clock_context);
> +
> + if (ret >= 0 && ntohl(msg.hdr.cmd) == MSG_TSYNC_STOP) {
> + if (toffset)
> + *toffset = ntohll(msg.ts_stop.offset);
> + if (ts_offset)
> + *ts_offset = ntohll(msg.ts_stop.ts_offset);
> + }
> +
> + msg_free(&msg);
> + return 0;
> +}
> +
> +//#define TSYNC_DEBUG
> +int tracecmd_msg_snd_time_sync(struct tracecmd_msg_handle *msg_handle,
> + struct tracecmd_clock_sync *clock_context,
> + long long *toffset, long long *ts_offset)
> +{
> + struct tracecmd_event_hit event;
> + struct tracecmd_msg msg_resp;
> + struct tracecmd_msg msg_req;
> + int sync_loop = TSYNC_TRIES;
> + long long min = 0, max = 0;
> + long long offset_av = 0;
> + long long ts_local = 0;
> + int k = 0, n, ret = 0;
> + long long tresch = 0;
> + long long offset = 0;
> + long long *ts_locals;
> + long long *offsets;
> + long long m_t1 = 0;
> + long long s_t2 = 0;
> + int probes = 0;
> +#ifdef TSYNC_DEBUG
> +/* Write all ts in a file, used to analyze the raw data */
> + struct timespec tsStart, tsEnd;
> + int zm = 0, zs = 0;
> + long long duration;
> + char buff[256];
> + int iFd;
> +#endif
> +
> + if (clock_context == NULL || msg_handle == NULL)
> + return 0;
> +
> + if (toffset == NULL) {
> + /* No time sync needed, inform the client */
> + msg_req.ts_stop.offset = 0;
> + msg_req.ts_stop.ts_offset = 0;
> + tracecmd_msg_init(MSG_TSYNC_STOP, &msg_req);
> + tracecmd_msg_send(msg_handle->fd, &msg_req);
> + return 0;
> + }
> +
> + *toffset = 0;
> + offsets = calloc(sizeof(long long), TSYNC_TRIES);
> + ts_locals = calloc(sizeof(long long), TSYNC_TRIES);
> + if (!offsets || !ts_locals) {
> + free(offsets);
> + free(ts_locals);
> + return 0;
> + }
> +
> + tracecmd_msg_init(MSG_TSYNC_START, &msg_req);
> + tracecmd_msg_send(msg_handle->fd, &msg_req);
> + ret = tracecmd_msg_recv(msg_handle->fd, &msg_resp);
> + if (ret < 0 || ntohl(msg_resp.hdr.cmd) != MSG_TSYNC_START) {
> + free(offsets);
> + free(ts_locals);
> + return 0;
> + }
> +
> +#ifdef TSYNC_DEBUG
> + sprintf(buff, "s-cid%d.txt", clock_context->remote_cid);
> + iFd = open(buff, O_CREAT|O_WRONLY|O_TRUNC, 0644);
> + clock_gettime(CLOCK_MONOTONIC, &tsStart);
> +#endif
> + tracecmd_clock_synch_enable(clock_context);
> + do {
> + memset(&msg_resp, 0, sizeof(msg_resp));
> + memset(&event, 0, sizeof(event));
> + tracecmd_msg_init(MSG_TSYNC_REQ, &msg_req);
> + tracecmd_msg_send(msg_handle->fd, &msg_req);
> + /* Get the ts and CPU of the sent event */
> + tracecmd_clock_find_event(clock_context, -1, &event);
> +
> + tracecmd_msg_init(MSG_TSYNC_REQ, &msg_req);
> + msg_req.ts_req.tlocal = event.cpu;
> + msg_req.ts_req.tlocal = htonll(msg_req.ts_req.tlocal);
> + tracecmd_msg_send(msg_handle->fd, &msg_req);
> + ret = tracecmd_msg_recv(msg_handle->fd, &msg_resp);
> + if (ret < 0)
> + break;
> + if (ntohl(msg_resp.hdr.cmd) != MSG_TSYNC_RESP) {
> + tracecmd_msg_init(MSG_NOT_SUPP, &msg_req);
> + tracecmd_msg_send(msg_handle->fd, &msg_req);
> + break;
> + }
> + m_t1 = event.ts;
> + s_t2 = htonll(msg_resp.ts_resp.tlocal); /* Client rcv ts */
> +#ifdef TSYNC_DEBUG
> + if (!s_t2)
> + zs++;
> + if (!m_t1)
> + zm++;
> +#endif
> + if (!s_t2 || !m_t1)
> + continue;
> + offsets[probes] = s_t2 - m_t1;
> + ts_locals[probes] = m_t1;
> + offset_av += offsets[probes];
> + if (!min || min > llabs(offsets[probes]))
> + min = llabs(offsets[probes]);
> + if (!max || max < llabs(offsets[probes]))
> + max = llabs(offsets[probes]);
> + probes++;
> +#ifdef TSYNC_DEBUG
> + sprintf(buff, "%lld %lld\n", m_t1, s_t2);
> + write(iFd, buff, strlen(buff));
> +#endif
> + } while (--sync_loop);
> +
> +#ifdef TSYNC_DEBUG
> + clock_gettime(CLOCK_MONOTONIC, &tsEnd);
> + close(iFd);
> +#endif
> + tracecmd_clock_synch_disable(clock_context);
> +
Can you split the part that processes the events from the one that collects them and make this function shorter and simpler?
> + if (probes)
> + offset_av /= (long long)probes;
> + tresch = (long long)((max - min)/10);
> + for (n = 0; n < TSYNC_TRIES; n++) {
> + /* filter the offsets with deviation up to 10% */
> + if (offsets[n] &&
> + llabs(offsets[n] - offset_av) < tresch) {
> + offset += offsets[n];
> + k++;
> + }
> + }
> + if (k)
> + offset /= (long long)k;
> + tresch = 0;
> + for (n = 0; n < TSYNC_TRIES; n++) {
> + if (offsets[n] &&
> + (!tresch || tresch > llabs(offset-offsets[n]))) {
> + tresch = llabs(offset-offsets[n]);
> + ts_local = ts_locals[n];
> + }
> + }
> +
> + tracecmd_msg_init(MSG_TSYNC_STOP, &msg_req);
> + msg_req.ts_stop.offset = htonll(offset);
> + msg_req.ts_stop.ts_offset = htonll(ts_local);
> + tracecmd_msg_send(msg_handle->fd, &msg_req);
> +
> + msg_free(&msg_req);
> + msg_free(&msg_resp);
> + free(offsets);
> + free(ts_locals);
> +
> + if (toffset)
> + *toffset = offset;
> + if (ts_offset)
> + *ts_offset = ts_local;
> +
> +#ifdef TSYNC_DEBUG
> + duration = tsEnd.tv_sec * 1000000000LL;
> + duration += tsEnd.tv_nsec;
> + duration -= (tsStart.tv_sec * 1000000000LL);
> + duration -= tsStart.tv_nsec;
> +
> + printf("\n selected: %lld (in %lld ns), used %s clock, %d probes\n\r",
> + offset, duration,
> + clock_context->vinst->clock?clock_context->vinst->clock:"default",
> + probes);
> + printf("\t good probes: %d / %d, threshold %lld ns, Zm %d, Zs %d\n\r",
> + k, TSYNC_TRIES, tresch, zm, zs);
> +#endif
> + return 0;
> +}
> +
> static int make_trace_resp(struct tracecmd_msg *msg, int page_size, int nr_cpus,
> unsigned int *ports, bool use_fifos)
> {
> diff --git a/tracecmd/trace-record.c b/tracecmd/trace-record.c
> index d9e9c69..f07eb4d 100644
> --- a/tracecmd/trace-record.c
> +++ b/tracecmd/trace-record.c
> @@ -210,6 +210,8 @@ struct common_record_context {
> char *date2ts;
> char *max_graph_depth;
> int data_flags;
> + int time_shift_count;
> + struct tracecmd_option *time_shift_option;
>
> int record_all;
> int total_disable;
> @@ -649,11 +651,20 @@ static void tell_guests_to_stop(void)
> for_all_instances(instance) {
> if (is_guest(instance)) {
> tracecmd_msg_send_close_msg(instance->msg_handle);
> - tracecmd_msg_handle_close(instance->msg_handle);
> }
> }
> }
>
> +static void close_guests_handle(void)
> +{
> + struct buffer_instance *instance;
> +
> + for_all_instances(instance) {
> + if (is_guest(instance))
> + tracecmd_msg_handle_close(instance->msg_handle);
> + }
> +}
> +
> static void stop_threads(enum trace_type type)
> {
> struct timeval tv = { 0, 0 };
> @@ -3417,6 +3428,8 @@ static void connect_to_agent(struct buffer_instance *instance)
>
> /* the msg_handle now points to the guest fd */
> instance->msg_handle = msg_handle;
> +
> + sync_time_with_guest_v3(instance);
> }
>
> static void setup_guest(struct buffer_instance *instance)
> @@ -3441,10 +3454,13 @@ static void setup_guest(struct buffer_instance *instance)
> close(fd);
> }
>
> -static void setup_agent(struct buffer_instance *instance, struct common_record_context *ctx)
> +static void setup_agent(struct buffer_instance *instance,
> + struct common_record_context *ctx)
> {
> struct tracecmd_output *network_handle;
>
> + sync_time_with_host_v3(instance);
> +
> network_handle = tracecmd_create_init_fd_msg(instance->msg_handle,
> listed_events);
> add_options(network_handle, ctx);
> @@ -5619,6 +5635,42 @@ static bool has_local_instances(void)
> return false;
> }
>
> +//#define TSYNC_DEBUG
> +static void write_guest_time_shift(struct buffer_instance *instance)
> +{
> + struct tracecmd_output *handle;
> + struct iovec vector[3];
> + char *file;
> + int fd;
> +
> + if (!instance->time_sync_count)
> + return;
> +
> + file = get_guest_file(output_file, instance->name);
> + fd = open(file, O_RDWR);
> + if (fd < 0)
> + die("error opening %s", file);
> + put_temp_file(file);
> + handle = tracecmd_get_output_handle_fd(fd);
> + vector[0].iov_len = sizeof(instance->time_sync_count);
> + vector[0].iov_base = &instance->time_sync_count;
> + vector[1].iov_len = sizeof(long long) * instance->time_sync_count;
> + vector[1].iov_base = instance->time_sync_ts;
> + vector[2].iov_len = sizeof(long long) * instance->time_sync_count;
> + vector[2].iov_base = instance->time_sync_ofssets;
> + tracecmd_add_option_v(handle, TRACECMD_OPTION_TIME_SHIFT, vector, 3);
> + tracecmd_append_options(handle);
> + tracecmd_output_close(handle);
> +#ifdef TSYNC_DEBUG
> + if (instance->time_sync_count > 1)
> + printf("\n\rDetected %lld ns ts offset drift in %lld ns for guest %s\n\r",
> + instance->time_sync_ofssets[instance->time_sync_count-1] -
> + instance->time_sync_ofssets[0],
> + instance->time_sync_ts[instance->time_sync_count-1]-
> + instance->time_sync_ts[0], instance->name);
> +#endif
> +}
> +
> /*
> * This function contains common code for the following commands:
> * record, start, stream, profile.
> @@ -5738,6 +5790,22 @@ static void record_trace(int argc, char **argv,
> if (!latency)
> wait_threads();
>
> + if (ctx->instance && is_agent(ctx->instance)) {
> + sync_time_with_host_v3(ctx->instance);
> + tracecmd_clock_context_free(ctx->instance->clock_sync);
> + ctx->instance->clock_sync = NULL;
> + } else {
> + for_all_instances(instance) {
> + if (is_guest(instance)) {
> + sync_time_with_guest_v3(instance);
> + write_guest_time_shift(instance);
> + tracecmd_clock_context_free(instance->clock_sync);
> + instance->clock_sync = NULL;
> + }
> + }
> + }
> + close_guests_handle();
> +
> if (IS_RECORD(ctx)) {
> record_data(ctx);
> delete_thread_data();
> diff --git a/tracecmd/trace-timesync.c b/tracecmd/trace-timesync.c
> new file mode 100644
> index 0000000..f8366ed
> --- /dev/null
> +++ b/tracecmd/trace-timesync.c
> @@ -0,0 +1,622 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tstoyanov@vmware.com>
> + *
> + */
> +
> +#include <fcntl.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <arpa/inet.h>
> +#include <linux/vm_sockets.h>
> +#include "trace-local.h"
> +
> +static int clock_sync_x86_host_init(struct tracecmd_clock_sync *clock_context);
> +static int clock_sync_x86_host_free(struct tracecmd_clock_sync *clock_context);
> +static int clock_sync_x86_host_find_events(struct tracecmd_clock_sync *clock,
> + int cpu, struct tracecmd_event_hit *event);
> +static int clock_sync_x86_guest_init(struct tracecmd_clock_sync *clock_context);
> +static int clock_sync_x86_guest_free(struct tracecmd_clock_sync *clock_context);
> +static int clock_sync_x86_guest_find_events(struct tracecmd_clock_sync *clock,
> + int pid,
> + struct tracecmd_event_hit *event);
> +
> +struct tracecmd_event_descr {
> + char *system;
> + char *name;
> +};
> +
> +struct {
> + char *systems[3];
> + struct tracecmd_ftrace_param ftrace_params[5];
> + struct tracecmd_event_descr events[3];
> + int (*clock_sync_init)(struct tracecmd_clock_sync *clock_context);
> + int (*clock_sync_free)(struct tracecmd_clock_sync *clock_context);
> + int (*clock_sync_find_events)(struct tracecmd_clock_sync *clock_context,
> + int pid,
> + struct tracecmd_event_hit *event);
> +} static clock_sync[CLOCK_CONTEXT_MAX] = {
> + { /* CLOCK_KVM_X86_VSOCK_HOST */
> + .systems = {"vsock", "ftrace", NULL},
> + .ftrace_params = {
> + {"set_ftrace_filter", "vmx_read_l1_tsc_offset\nsvm_read_l1_tsc_offset", "\0"},
> + {"current_tracer", "function", "nop"},
> + {"events/vsock/virtio_transport_recv_pkt/enable", "1", "0"},
> + {"events/vsock/virtio_transport_recv_pkt/filter", NULL, "\0"},
> + {NULL, NULL, NULL} },
> + .events = {
> + {.system = "ftrace", .name = "function"},
> + {.system = "vsock", .name = "virtio_transport_recv_pkt"},
> + {.system = NULL, .name = NULL} },
> + clock_sync_x86_host_init,
> + clock_sync_x86_host_free,
> + clock_sync_x86_host_find_events,
> + },
> +
> + { /* CLOCK_KVM_X86_VSOCK_GUEST */
> + .systems = { "vsock", "ftrace", NULL},
> + .ftrace_params = {
> + {"set_ftrace_filter", "vp_notify", "\0"},
> + {"current_tracer", "function", "nop"},
> + {"events/vsock/virtio_transport_alloc_pkt/enable", "1", "0"},
> + {"events/vsock/virtio_transport_alloc_pkt/filter", NULL, "\0"},
> + {NULL, NULL, NULL},
> + },
> + .events = {
> + {.system = "vsock", .name = "virtio_transport_alloc_pkt"},
> + {.system = "ftrace", .name = "function"},
> + {.system = NULL, .name = NULL}
> + },
> + clock_sync_x86_guest_init,
> + clock_sync_x86_guest_free,
> + clock_sync_x86_guest_find_events,
> + }
> +};
> +
> +static int clock_sync_x86_host_init(struct tracecmd_clock_sync *clock_context)
> +{
> + char vsock_filter[255];
> +
> + snprintf(vsock_filter, 255,
> + "src_cid==%u && src_port==%u && dst_cid==%u && dst_port==%u && len!=0",
> + clock_context->remote_cid, clock_context->remote_port,
> + clock_context->local_cid, clock_context->local_port);
> +
> + clock_context->ftrace_params[3].set = strdup(vsock_filter);
> + return 1;
> +}
> +
> +static int clock_sync_x86_host_free(struct tracecmd_clock_sync *clock_context)
> +{
> + free(clock_context->ftrace_params[3].set);
> + clock_context->ftrace_params[3].set = NULL;
> + return 1;
> +}
> +
> +static int clock_sync_x86_guest_init(struct tracecmd_clock_sync *clock_context)
> +{
> + char vsock_filter[255];
> +
> + snprintf(vsock_filter, 255,
> + "src_cid==%u && src_port==%u && dst_cid==%u && dst_port==%u && len!=0",
> + clock_context->local_cid, clock_context->local_port,
> + clock_context->remote_cid, clock_context->remote_port);
> +
> + clock_context->ftrace_params[3].set = strdup(vsock_filter);
> + return 1;
> +}
> +
> +static int clock_sync_x86_guest_free(struct tracecmd_clock_sync *clock_context)
> +{
> + free(clock_context->ftrace_params[3].set);
> + clock_context->ftrace_params[3].set = NULL;
> + return 1;
> +}
> +
> +#define EVENTS_CHUNK 10
> +static int
> +get_events_in_page(struct tep_handle *tep, void *page,
> + int size, int cpu, struct tracecmd_event_hit **events,
> + int *events_count, int *events_size)
> +{
> + struct tracecmd_event_hit *events_array = NULL;
> + struct tep_record *last_record = NULL;
> + struct tep_event *event = NULL;
> + struct tep_record *record;
> + int id, cnt = 0;
> +
> + if (size <= 0)
> + return 0;
> +
> + if (*events == NULL) {
> + *events = malloc(EVENTS_CHUNK*sizeof(struct tracecmd_event_hit));
> + *events_size = EVENTS_CHUNK;
> + *events_count = 0;
> + }
> +
> + while (true) {
> + event = NULL;
> + record = tracecmd_read_page_record(tep, page, size,
> + last_record);
> + if (!record)
> + break;
> + free_record(last_record);
> + id = tep_data_type(tep, record);
> + event = tep_data_event_from_type(tep, id);
> + if (event) {
> + if (*events_count >= *events_size) {
> + events_array = realloc(*events,
> + (*events_size + EVENTS_CHUNK)*
> + sizeof(struct tracecmd_event_hit));
> + if (events_array) {
> + *events = events_array;
> + (*events_size) += EVENTS_CHUNK;
> + }
Reallocating in chunks only reduces the number of realloc() calls by constant. If this is performance critical, let's do exponential buffer growth so that we call realloc() only log(#events) times. Otherwise, let's just not bother and call realloc() each time which is also (empirically) linear but with higher constant factor.
> + }
> +
> + if (*events_count < *events_size) {
> + (*events)[*events_count].ts = record->ts;
> + (*events)[*events_count].cpu = cpu;
> + (*events)[*events_count].id = id;
> + (*events)[*events_count].pid = tep_data_pid(tep, record);
> + (*events_count)++;
> + cnt++;
> + }
> + }
> + last_record = record;
> + }
> + free_record(last_record);
> +
> + return cnt;
> +}
> +
> +static int sync_events_cmp(const void *a, const void *b)
> +{
> + const struct tracecmd_event_hit *ea = (const struct tracecmd_event_hit *)a;
> + const struct tracecmd_event_hit *eb = (const struct tracecmd_event_hit *)b;
> +
> + if (ea->ts > eb->ts)
> + return 1;
> + if (ea->ts < eb->ts)
> + return -1;
> + return 0;
> +}
> +
> +static int find_sync_events(struct tep_handle *pevent,
> + struct tracecmd_event_hit *recorded,
> + int rsize, struct tracecmd_event_hit *events)
> +{
> + int i = 0, j = 0;
> +
> + while (i < rsize) {
> + if (!events[j].ts && events[j].id == recorded[i].id &&
> + (events[j].pid < 0 || events[j].pid == recorded[i].pid)) {
> + events[j].cpu = recorded[i].cpu;
> + events[j].ts = recorded[i].ts;
> + j++;
> + } else if (j > 0 && events[j-1].id == recorded[i].id &&
> + (events[j-1].pid < 0 || events[j-1].pid == recorded[i].pid)) {
> + events[j-1].cpu = recorded[i].cpu;
> + events[j-1].ts = recorded[i].ts;
> + }
> + i++;
> + }
> + return j;
> +}
> +
> +//#define TSYNC_RBUFFER_DEBUG
> +static int find_raw_events(struct tep_handle *tep,
> + struct buffer_instance *instance,
> + struct tracecmd_event_hit *events)
> +{
> + struct tracecmd_event_hit *events_array = NULL;
> + int events_count = 0;
> + int events_size = 0;
> + struct dirent *dent;
> + unsigned int p_size;
> + int ts = 0;
> + void *page;
> + char *path;
> + char *file;
> + DIR *dir;
> + int cpu;
> + int len;
> + int fd;
> + int r;
> +
> + p_size = getpagesize();
> +#ifdef TSYNC_RBUFFER_DEBUG
> + file = get_instance_file(instance, "trace");
> + if (!file)
> + return ts;
> + {
> + char *buf = NULL;
> + FILE *fp;
> + size_t n;
> + int r;
> +
> + printf("Events:\n\r");
> + fp = fopen(file, "r");
> + while ((r = getline(&buf, &n, fp)) >= 0) {
> +
> + if (buf[0] != '#')
> + printf("%s", buf);
> + free(buf);
> + buf = NULL;
> + }
> + fclose(fp);
> + }
> + tracecmd_put_tracing_file(file);
> +#endif /* TSYNC_RBUFFER_DEBUG */
> + path = get_instance_file(instance, "per_cpu");
> + if (!path)
> + return ts;
> +
> + dir = opendir(path);
> + if (!dir)
> + goto out;
> +
> + len = strlen(path);
> + file = malloc(len + strlen("trace_pipe_raw") + 32);
> + page = malloc(p_size);
> + if (!file || !page)
> + die("Failed to allocate time_stamp info");
> +
> + while ((dent = readdir(dir))) {
> +
> + const char *name = dent->d_name;
> +
> + if (strncmp(name, "cpu", 3) != 0)
> + continue;
> + cpu = atoi(&name[3]);
> + sprintf(file, "%s/%s/trace_pipe_raw", path, name);
> + fd = open(file, O_RDONLY | O_NONBLOCK);
> + if (fd < 0)
> + continue;
> + do {
> + r = read(fd, page, p_size);
> + if (r > 0) {
> + get_events_in_page(tep, page, r, cpu,
> + &events_array, &events_count,
> + &events_size);
> + }
> + } while (r > 0);
> + close(fd);
> + }
> + qsort(events_array, events_count, sizeof(*events_array), sync_events_cmp);
> + r = find_sync_events(tep, events_array, events_count, events);
> +#ifdef TSYNC_RBUFFER_DEBUG
> + len = 0;
> + while (events[len].id) {
> + printf("Found %d @ cpu %d: %lld pid %d\n\r",
> + events[len].id, events[len].cpu,
> + events[len].ts, events[len].pid);
> + len++;
> + }
> +#endif
> +
> + free(events_array);
> + free(file);
> + free(page);
> + closedir(dir);
> +
> + out:
> + tracecmd_put_tracing_file(path);
> + return r;
> +}
> +
> +static int clock_sync_x86_host_find_events(struct tracecmd_clock_sync *clock,
> + int pid,
> + struct tracecmd_event_hit *event)
> +{
> + int ret;
> +
> + clock->events[0].pid = pid;
> + ret = find_raw_events(clock->tep, clock->vinst, clock->events);
> + event->ts = clock->events[0].ts;
> + event->cpu = clock->events[0].cpu;
> + return ret;
> +
> +}
> +
> +static int clock_sync_x86_guest_find_events(struct tracecmd_clock_sync *clock,
> + int pid,
> + struct tracecmd_event_hit *event)
> +{
> + int ret;
> +
> + ret = find_raw_events(clock->tep, clock->vinst, clock->events);
> + event->ts = clock->events[1].ts;
> + event->cpu = clock->events[0].cpu;
> + return ret;
> +
> +}
> +
> +static void tracecmd_clock_sync_reset(struct tracecmd_clock_sync *clock_context)
> +{
> + int i = 0;
> +
> + while (clock_context->events[i].id) {
> + clock_context->events[i].cpu = 0;
> + clock_context->events[i].ts = 0;
> + clock_context->events[i].pid = -1;
> + i++;
> + }
> +}
> +
> +int tracecmd_clock_find_event(struct tracecmd_clock_sync *clock, int pid,
> + struct tracecmd_event_hit *event)
> +{
> + int ret = 0;
> + int id;
> +
> + if (clock == NULL ||
> + clock->clock_context_id >= CLOCK_CONTEXT_MAX)
> + return 0;
> +
> + id = clock->clock_context_id;
> + if (clock_sync[id].clock_sync_find_events)
> + ret = clock_sync[id].clock_sync_find_events(clock, pid, event);
> +
> + tracecmd_clock_sync_reset(clock);
> + return ret;
> +}
> +
> +static void clock_context_copy(struct tracecmd_clock_sync *clock_context,
> + struct tracecmd_ftrace_param *params,
> + struct tracecmd_event_descr *events)
> +{
> + int i;
> +
> + i = 0;
> + while (params[i].file)
> + i++;
> + clock_context->ftrace_params = calloc(i+1, sizeof(struct tracecmd_ftrace_param));
> + i = 0;
> + while (params[i].file) {
> + clock_context->ftrace_params[i].file = strdup(params[i].file);
> + if (params[i].set)
> + clock_context->ftrace_params[i].set = strdup(params[i].set);
> + if (params[i].reset)
> + clock_context->ftrace_params[i].reset = strdup(params[i].reset);
> + i++;
> + }
> +
> + i = 0;
> + while (events[i].name)
> + i++;
> + clock_context->events = calloc(i+1, sizeof(struct tracecmd_event_hit));
> +}
> +
> +void trace_instance_reset(struct buffer_instance *vinst)
> +{
> + write_instance_file(vinst, "trace", "\0", NULL);
> +}
> +
> +static struct buffer_instance *
> +clock_synch_create_instance(const char *clock, unsigned int cid)
> +{
> + struct buffer_instance *vinst;
> + char inst_name[256];
> +
> + snprintf(inst_name, 256, "clock_synch-%d", cid);
> +
> + vinst = create_instance(strdup(inst_name));
> + tracecmd_init_instance(vinst);
> + vinst->cpu_count = tracecmd_local_cpu_count();
> + tracecmd_make_instance(vinst);
> + trace_instance_reset(vinst);
> + if (clock)
> + vinst->clock = strdup(clock);
> + tracecmd_set_clock(vinst);
> + return vinst;
> +}
> +
> +static struct tep_handle *clock_synch_get_tep(struct buffer_instance *instance,
> + char **systems)
> +{
> + struct tep_handle *tep = NULL;
> + char *path;
> +
> + path = get_instance_dir(instance);
> + tep = tracecmd_local_events_system(path, systems);
> + tracecmd_put_tracing_file(path);
> +
> + tep_set_file_bigendian(tep, tracecmd_host_bigendian());
> + tep_set_host_bigendian(tep, tracecmd_host_bigendian());
> +
> + return tep;
> +}
> +
> +static void get_vsocket_params(int fd, unsigned int *lcid, unsigned int *lport,
> + unsigned int *rcid, unsigned int *rport)
> +{
> + struct sockaddr_vm addr;
> + socklen_t addr_len = sizeof(addr);
> +
> + if (lcid || lport) {
> + memset(&addr, 0, sizeof(addr));
> + if (getsockname(fd, (struct sockaddr *)&addr, &addr_len))
> + return;
> + if (addr.svm_family != AF_VSOCK)
> + return;
> + if (lport)
> + *lport = addr.svm_port;
> + if (lcid)
> + *lcid = addr.svm_cid;
> + }
> +
> + if (rcid || rport) {
> + memset(&addr, 0, sizeof(addr));
> + addr_len = sizeof(addr);
> + if (getpeername(fd, (struct sockaddr *)&addr, &addr_len))
> + return;
> + if (addr.svm_family != AF_VSOCK)
> + return;
> +
> + if (rport)
> + *rport = addr.svm_port;
> + if (rcid)
> + *rcid = addr.svm_cid;
> + }
> +}
> +
> +struct tracecmd_clock_sync*
> +tracecmd_clock_context_init(struct tracecmd_msg_handle *msg_handle,
> + const char *clock_str,
> + enum clock_sync_conext id)
> +{
> + struct tracecmd_clock_sync *clock_context;
> + struct tep_event *event;
> + unsigned int i = 0;
> +
> + switch (id) {
> +#ifdef VSOCK
> + case CLOCK_KVM_X86_VSOCK_HOST:
> + case CLOCK_KVM_X86_VSOCK_GUEST:
> + break;
> +#endif
> + default: /* not supported clock sync context */
> + return NULL;
> + }
> +
> + if (id >= CLOCK_CONTEXT_MAX || NULL == msg_handle)
> + return NULL;
> + clock_context = calloc(1, sizeof(struct tracecmd_clock_sync));
> + if (!clock_context)
> + return NULL;
> + clock_context->clock_context_id = id;
> + clock_context_copy(clock_context,
> + clock_sync[id].ftrace_params, clock_sync[id].events);
> +
> + get_vsocket_params(msg_handle->fd,
> + &(clock_context->local_cid),
> + &(clock_context->local_port),
> + &(clock_context->remote_cid),
> + &(clock_context->remote_port));
> +
> + if (clock_sync[id].clock_sync_init)
> + clock_sync[id].clock_sync_init(clock_context);
> +
> + clock_context->vinst = clock_synch_create_instance(clock_str, clock_context->remote_cid);
> + clock_context->tep = clock_synch_get_tep(clock_context->vinst,
> + clock_sync[id].systems);
> + i = 0;
> + while (clock_sync[id].events[i].name) {
> + event = tep_find_event_by_name(clock_context->tep,
> + clock_sync[id].events[i].system,
> + clock_sync[id].events[i].name);
> + if (!event)
> + break;
> + clock_context->events[i].id = event->id;
> + i++;
> + }
> + return clock_context;
> +
> +}
> +
> +void tracecmd_clock_context_free(struct tracecmd_clock_sync *clock_context)
> +{
> + int i;
> +
> + if (clock_context == NULL ||
> + clock_context->clock_context_id >= CLOCK_CONTEXT_MAX)
> + return;
> + if (clock_sync[clock_context->clock_context_id].clock_sync_free)
> + clock_sync[clock_context->clock_context_id].clock_sync_free(clock_context);
> +
> + i = 0;
> + while (clock_context->ftrace_params[i].file) {
> + free(clock_context->ftrace_params[i].file);
> + free(clock_context->ftrace_params[i].set);
> + free(clock_context->ftrace_params[i].reset);
> + i++;
> + }
> + free(clock_context->ftrace_params);
> + free(clock_context->events);
> + tracecmd_remove_instance(clock_context->vinst);
> + /* todo: clean up the instance */
> + tep_free(clock_context->tep);
> + free(clock_context);
> +}
> +
> +void sync_time_with_host_v3(struct buffer_instance *instance)
> +{
> + long long ts_local = 0;
> + long long toffset = 0;
> +
> + if (instance->clock_sync == NULL)
> + instance->clock_sync = tracecmd_clock_context_init(instance->msg_handle,
> + instance->clock, CLOCK_KVM_X86_VSOCK_GUEST);
> +
> + tracecmd_msg_snd_time_sync(instance->msg_handle, instance->clock_sync,
> + &toffset, &ts_local);
> +}
> +
> +void sync_time_with_guest_v3(struct buffer_instance *instance)
> +{
> + long long ts_local = 0;
> + long long toffset = 0;
How about switching throughout `ts_local` and `toffset` to `guest_timestamp` and `guest_offset` respectively? I think it will make the code easier to read.
> + long long *sync_array_ts;
> + long long *sync_array_ofs;
> +
> + if (instance->clock_sync == NULL)
> + instance->clock_sync = tracecmd_clock_context_init(instance->msg_handle,
> + top_instance.clock, CLOCK_KVM_X86_VSOCK_HOST);
> +
> + tracecmd_msg_rcv_time_sync(instance->msg_handle,
> + instance->clock_sync, &toffset, &ts_local);
> +
> + sync_array_ts = realloc(instance->time_sync_ts,
> + (instance->time_sync_count+1)*sizeof(long long));
> + sync_array_ofs = realloc(instance->time_sync_ofssets,
> + (instance->time_sync_count+1)*sizeof(long long));
> +
> + if (sync_array_ts && sync_array_ofs) {
> + sync_array_ts[instance->time_sync_count] = ts_local;
> + sync_array_ofs[instance->time_sync_count] = toffset;
> + instance->time_sync_count++;
> + instance->time_sync_ts = sync_array_ts;
> + instance->time_sync_ofssets = sync_array_ofs;
> +
> + } else {
> + free(sync_array_ts);
> + free(sync_array_ofs);
> + }
> +}
> +
> +static void set_clock_synch_events(struct buffer_instance *instance,
> + struct tracecmd_ftrace_param *params,
> + bool enable)
> +{
> + int i = 0;
> +
> + if (!enable)
> + write_tracing_on(instance, 0);
> +
> + while (params[i].file) {
> + if (enable && params[i].set) {
> + write_instance_file(instance, params[i].file,
> + params[i].set, NULL);
> + }
> + if (!enable && params[i].reset)
> + write_instance_file(instance, params[i].file,
> + params[i].reset, NULL);
> + i++;
> + }
> +
> + if (enable)
> + write_tracing_on(instance, 1);
> +}
> +
> +void tracecmd_clock_synch_enable(struct tracecmd_clock_sync *clock_context)
> +{
> + set_clock_synch_events(clock_context->vinst,
> + clock_context->ftrace_params, true);
> +}
> +
> +void tracecmd_clock_synch_disable(struct tracecmd_clock_sync *clock_context)
> +{
> + set_clock_synch_events(clock_context->vinst,
> + clock_context->ftrace_params, false);
> +}
> --
> 2.20.1
>
prev parent reply other threads:[~2019-03-11 11:46 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-03-08 13:44 [PATCH v5 0/9] [POC] trace-cmd: Timetamps sync between host and guest machines, relying on vsock events Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 1/9] trace-cmd: Implemented new lib API: tracecmd_local_events_system() Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 2/9] trace-cmd: Added support for negative time offsets in trace.dat file Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 3/9] trace-cmd: Fix tracecmd_read_page_record() to read more than one event Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 4/9] trace-cmd: Added implementation of htonll() and ntohll() Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 5/9] trace-cmd: Refactored few functions in trace-record.c Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 6/9] trace-cmd: Find and store pids of tasks, which run virtual CPUs of given VM Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 7/9] trace-cmd: Implemented new API tracecmd_add_option_v() Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 8/9] trace-cmd: Implemented new option in trace.dat file: TRACECMD_OPTION_TIME_SHIFT Tzvetomir Stoyanov
2019-03-08 13:44 ` [PATCH v5 9/9] trace-cmd [POC]: Implemented timestamps synch PTP-like algorithm, using vsock events Tzvetomir Stoyanov
2019-03-11 11:46 ` Slavomir Kaslev [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190311114601.GA11420@box \
--to=kaslevs@vmware.com \
--cc=linux-trace-devel@vger.kernel.org \
--cc=rostedt@goodmis.org \
--cc=tstoyanov@vmware.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).