diff options
Diffstat (limited to 'lib/trace-cmd/trace-timesync.c')
-rw-r--r-- | lib/trace-cmd/trace-timesync.c | 1079 |
1 files changed, 1079 insertions, 0 deletions
diff --git a/lib/trace-cmd/trace-timesync.c b/lib/trace-cmd/trace-timesync.c new file mode 100644 index 00000000..bbefda20 --- /dev/null +++ b/lib/trace-cmd/trace-timesync.c @@ -0,0 +1,1079 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> + * + */ + +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <linux/limits.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <dirent.h> +#include <errno.h> +#include <pthread.h> + +#include "trace-cmd-private.h" +#include "trace-cmd-local.h" +#include "tracefs.h" +#include "event-utils.h" +#include "trace-tsync-local.h" + +struct tsync_proto { + struct tsync_proto *next; + char proto_name[TRACECMD_TSYNC_PNAME_LENGTH]; + enum tracecmd_time_sync_role roles; + int accuracy; + int supported_clocks; + unsigned int flags; + + int (*clock_sync_init)(struct tracecmd_time_sync *clock_context); + int (*clock_sync_free)(struct tracecmd_time_sync *clock_context); + int (*clock_sync_calc)(struct tracecmd_time_sync *clock_context, + long long *offset, long long *scaling, long long *frac, + long long *timestamp, unsigned int cpu); +}; + +struct tsync_probe_request_msg { + unsigned short cpu; +} __packed; + +#ifdef __ANDROID__ +#define __NR_sched_setaffinity 122 +#define __NR_sched_getaffinity 123 + +static int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset) +{ + return -syscall(__NR_sched_setaffinity, thread, cpusetsize, cpuset); +} + +static int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset) +{ + long ret = syscall(__NR_sched_getaffinity, thread, cpusetsize, cpuset); + + if (ret < 0) + return ret; + if (ret < cpusetsize) + memset((char *)cpuset+ret, 0, cpusetsize-ret); + + return 0; +} +#endif /* __ANDROID__ */ + +static struct tsync_proto *tsync_proto_list; + +static struct tsync_proto *tsync_proto_find(const char *proto_name) +{ + struct tsync_proto *proto; + + if (!proto_name) + return NULL; + for (proto = tsync_proto_list; proto; proto = proto->next) { + if (strlen(proto->proto_name) == strlen(proto_name) && + !strncmp(proto->proto_name, proto_name, TRACECMD_TSYNC_PNAME_LENGTH)) + return proto; + } + return NULL; +} + +/** + * tracecmd_tsync_init - Initialize the global, per task, time sync data. + */ +void tracecmd_tsync_init(void) +{ + ptp_clock_sync_register(); + kvm_clock_sync_register(); +} + +int tracecmd_tsync_proto_register(const char *proto_name, int accuracy, int roles, + int supported_clocks, unsigned int flags, + int (*init)(struct tracecmd_time_sync *), + int (*free)(struct tracecmd_time_sync *), + int (*calc)(struct tracecmd_time_sync *, + long long *, long long *, long long *, + long long *, unsigned int)) +{ + struct tsync_proto *proto = NULL; + + if (tsync_proto_find(proto_name)) + return -1; + proto = calloc(1, sizeof(struct tsync_proto)); + if (!proto) + return -1; + strncpy(proto->proto_name, proto_name, TRACECMD_TSYNC_PNAME_LENGTH); + proto->accuracy = accuracy; + proto->roles = roles; + proto->flags = flags; + proto->supported_clocks = supported_clocks; + proto->clock_sync_init = init; + proto->clock_sync_free = free; + proto->clock_sync_calc = calc; + + proto->next = tsync_proto_list; + tsync_proto_list = proto; + return 0; +} + +int tracecmd_tsync_proto_unregister(char *proto_name) +{ + struct tsync_proto **last = &tsync_proto_list; + + if (!proto_name) + return -1; + + for (; *last; last = &(*last)->next) { + if (strlen((*last)->proto_name) == strlen(proto_name) && + !strncmp((*last)->proto_name, proto_name, TRACECMD_TSYNC_PNAME_LENGTH)) { + struct tsync_proto *proto = *last; + + *last = proto->next; + free(proto); + return 0; + } + } + + return -1; +} + +bool __hidden tsync_proto_is_supported(const char *proto_name) +{ + if (tsync_proto_find(proto_name)) + return true; + return false; +} + +/** + * tracecmd_tsync_get_offsets - Return the calculated time offsets + * + * @tsync: Pointer to time sync context + * @cpu: CPU for which to get the calculated offsets + * @count: Returns the number of calculated time offsets + * @ts: Array of size @count containing timestamps of callculated offsets + * @offsets: array of size @count, containing offsets for each timestamp + * @scalings: array of size @count, containing scaling ratios for each timestamp + * @frac: array of size @count, containing fraction bits for each timestamp + * + * Retuns -1 in case of an error, or 0 otherwise + */ +int tracecmd_tsync_get_offsets(struct tracecmd_time_sync *tsync, int cpu, + int *count, long long **ts, + long long **offsets, long long **scalings, long long **frac) +{ + struct clock_sync_context *tsync_context; + + if (!tsync || !tsync->context) + return -1; + tsync_context = (struct clock_sync_context *)tsync->context; + if (cpu >= tsync_context->cpu_count || !tsync_context->offsets) + return -1; + if (count) + *count = tsync_context->offsets[cpu].sync_count; + if (ts) + *ts = tsync_context->offsets[cpu].sync_ts; + if (offsets) + *offsets = tsync_context->offsets[cpu].sync_offsets; + if (scalings) + *scalings = tsync_context->offsets[cpu].sync_scalings; + if (frac) + *frac = tsync_context->offsets[cpu].sync_frac; + + return 0; +} + +/** + * tsync_get_proto_flags - Get protocol flags + * + * @tsync: Pointer to time sync context + * @flags: Returns the protocol flags, a combination of TRACECMD_TSYNC_FLAG_... + * + * Retuns -1 in case of an error, or 0 otherwise + */ +static int tsync_get_proto_flags(struct tracecmd_time_sync *tsync, + unsigned int *flags) +{ + struct tsync_proto *protocol; + + if (!tsync) + return -1; + protocol = tsync_proto_find(tsync->proto_name); + if (!protocol) + return -1; + + if (flags) + *flags = protocol->flags; + + return 0; +} + + +#define PROTO_MASK_SIZE (sizeof(char)) +#define PROTO_MASK_BITS (PROTO_MASK_SIZE * 8) +/** + * tsync_proto_select - Select time sync protocol, to be used for + * timestamp synchronization with a peer + * + * @protos: list of tsync protocol names + * @clock : trace clock + * @role : local time sync role + * + * Retuns pointer to a protocol name, that can be used with the peer, or NULL + * in case there is no match with supported protocols. + * The returned string MUST NOT be freed by the caller + */ +static const char * +tsync_proto_select(const struct tracecmd_tsync_protos *protos, + const char *clock, enum tracecmd_time_sync_role role) +{ + struct tsync_proto *selected = NULL; + struct tsync_proto *proto; + char **pname; + int clock_id = 0; + + if (!protos) + return NULL; + + clock_id = tracecmd_clock_str2id(clock); + pname = protos->names; + while (*pname) { + for (proto = tsync_proto_list; proto; proto = proto->next) { + if (!(proto->roles & role)) + continue; + if (proto->supported_clocks && clock_id && + !(proto->supported_clocks & clock_id)) + continue; + if (strncmp(proto->proto_name, *pname, TRACECMD_TSYNC_PNAME_LENGTH)) + continue; + if (selected) { + if (selected->accuracy > proto->accuracy) + selected = proto; + } else + selected = proto; + } + pname++; + } + + if (selected) + return selected->proto_name; + + return NULL; +} + +/** + * tracecmd_tsync_proto_getall - Returns list of all supported + * time sync protocols + * @protos: return, allocated list of time sync protocol names, + * supported by the peer. Must be freed by free() + * @clock: selected trace clock + * @role: supported protocol role + * + * If completed successfully 0 is returned and allocated list of strings in @protos. + * The last list entry is NULL. In case of an error, -1 is returned. + * @protos must be freed with free() + */ +int tracecmd_tsync_proto_getall(struct tracecmd_tsync_protos **protos, const char *clock, int role) +{ + struct tracecmd_tsync_protos *plist = NULL; + struct tsync_proto *proto; + int clock_id = 0; + int count = 1; + int i; + + if (clock) + clock_id = tracecmd_clock_str2id(clock); + for (proto = tsync_proto_list; proto; proto = proto->next) { + if (!(proto->roles & role)) + continue; + if (proto->supported_clocks && clock_id && + !(proto->supported_clocks & clock_id)) + continue; + count++; + } + plist = calloc(1, sizeof(struct tracecmd_tsync_protos)); + if (!plist) + goto error; + plist->names = calloc(count, sizeof(char *)); + if (!plist->names) + return -1; + + for (i = 0, proto = tsync_proto_list; proto && i < (count - 1); proto = proto->next) { + if (!(proto->roles & role)) + continue; + if (proto->supported_clocks && clock_id && + !(proto->supported_clocks & clock_id)) + continue; + plist->names[i++] = proto->proto_name; + } + + *protos = plist; + return 0; + +error: + if (plist) { + free(plist->names); + free(plist); + } + return -1; +} + +static int get_first_cpu(cpu_set_t **pin_mask, size_t *m_size) +{ + int cpus = tracecmd_count_cpus(); + cpu_set_t *cpu_mask; + int mask_size; + int i; + + cpu_mask = CPU_ALLOC(cpus); + *pin_mask = CPU_ALLOC(cpus); + if (!cpu_mask || !*pin_mask || 1) + goto error; + + mask_size = CPU_ALLOC_SIZE(cpus); + CPU_ZERO_S(mask_size, cpu_mask); + CPU_ZERO_S(mask_size, *pin_mask); + + if (sched_getaffinity(0, mask_size, cpu_mask) == -1) + goto error; + + for (i = 0; i < cpus; i++) { + if (CPU_ISSET_S(i, mask_size, cpu_mask)) { + CPU_SET_S(i, mask_size, *pin_mask); + break; + } + } + + if (CPU_COUNT_S(mask_size, *pin_mask) < 1) + goto error; + + CPU_FREE(cpu_mask); + *m_size = mask_size; + return 0; + +error: + if (cpu_mask) + CPU_FREE(cpu_mask); + if (*pin_mask) + CPU_FREE(*pin_mask); + *pin_mask = NULL; + *m_size = 0; + return -1; +} + +static struct tracefs_instance * +clock_synch_create_instance(const char *clock, unsigned int cid) +{ + struct tracefs_instance *instance; + char inst_name[256]; + + snprintf(inst_name, 256, "clock_synch-%d", cid); + + instance = tracefs_instance_create(inst_name); + if (!instance) + return NULL; + + tracefs_instance_file_write(instance, "trace", "\0"); + if (clock) + tracefs_instance_file_write(instance, "trace_clock", clock); + return instance; +} + +static void +clock_synch_delete_instance(struct tracefs_instance *inst) +{ + if (!inst) + return; + tracefs_instance_destroy(inst); + tracefs_instance_free(inst); +} + +static int clock_context_init(struct tracecmd_time_sync *tsync, + struct tsync_proto **proto, bool guest) +{ + struct clock_sync_context *clock = NULL; + struct tsync_proto *protocol; + + if (tsync->context) + return 0; + + protocol = tsync_proto_find(tsync->proto_name); + if (!protocol || !protocol->clock_sync_calc) + return -1; + + clock = calloc(1, sizeof(struct clock_sync_context)); + if (!clock) + return -1; + clock->is_guest = guest; + clock->is_server = clock->is_guest; + + clock->instance = clock_synch_create_instance(tsync->clock_str, + tsync->remote_id); + if (!clock->instance) + goto error; + + clock->cpu_count = tsync->vcpu_count; + if (clock->cpu_count) { + clock->offsets = calloc(clock->cpu_count, sizeof(struct clock_sync_offsets)); + if (!clock->offsets) + goto error; + } + + tsync->context = clock; + if (protocol->clock_sync_init && protocol->clock_sync_init(tsync) < 0) + goto error; + + *proto = protocol; + + return 0; +error: + tsync->context = NULL; + if (clock->instance) + clock_synch_delete_instance(clock->instance); + free(clock->offsets); + free(clock); + return -1; +} + +/** + * tracecmd_tsync_free - Free time sync context, allocated by + * tracecmd_tsync_with_host() or tracecmd_tsync_with_guest() APIs + * + * @tsync: Pointer to time sync context + * + */ +void tracecmd_tsync_free(struct tracecmd_time_sync *tsync) +{ + struct clock_sync_context *tsync_context; + struct tsync_proto *proto; + int i; + + if (!tsync) + return; + + tsync_context = (struct clock_sync_context *)tsync->context; + + proto = tsync_proto_find(tsync->proto_name); + if (proto && proto->clock_sync_free) + proto->clock_sync_free(tsync); + + + if (tsync_context) { + clock_synch_delete_instance(tsync_context->instance); + tsync_context->instance = NULL; + + if (tsync_context->cpu_count && tsync_context->offsets) { + for (i = 0; i < tsync_context->cpu_count; i++) { + free(tsync_context->offsets[i].sync_ts); + free(tsync_context->offsets[i].sync_offsets); + free(tsync_context->offsets[i].sync_scalings); + free(tsync_context->offsets[i].sync_frac); + tsync_context->offsets[i].sync_ts = NULL; + tsync_context->offsets[i].sync_offsets = NULL; + tsync_context->offsets[i].sync_scalings = NULL; + tsync_context->offsets[i].sync_frac = NULL; + tsync_context->offsets[i].sync_count = 0; + tsync_context->offsets[i].sync_size = 0; + } + free(tsync_context->offsets); + tsync_context->offsets = NULL; + } + } + + if (tsync->msg_handle) + tracecmd_msg_handle_close(tsync->msg_handle); + + /* These are only created from the host */ + if (tsync->guest_pid) { + pthread_mutex_destroy(&tsync->lock); + pthread_cond_destroy(&tsync->cond); + pthread_barrier_destroy(&tsync->first_sync); + } + + free(tsync->clock_str); + free(tsync->proto_name); + free(tsync); +} + +static cpu_set_t *pin_to_cpu(int cpu) +{ + static size_t size; + static int cpus; + cpu_set_t *mask = NULL; + cpu_set_t *old = NULL; + + if (!cpus) { + cpus = tracecmd_count_cpus(); + size = CPU_ALLOC_SIZE(cpus); + } + if (cpu >= cpus) + goto error; + + mask = CPU_ALLOC(cpus); + if (!mask) + goto error; + old = CPU_ALLOC(cpus); + if (!old) + goto error; + + CPU_ZERO_S(size, mask); + CPU_SET_S(cpu, size, mask); + if (pthread_getaffinity_np(pthread_self(), size, old)) + goto error; + if (pthread_setaffinity_np(pthread_self(), size, mask)) + goto error; + + CPU_FREE(mask); + return old; + +error: + if (mask) + CPU_FREE(mask); + if (old) + CPU_FREE(old); + return NULL; +} + +static void restore_pin_to_cpu(cpu_set_t *mask) +{ + static size_t size; + + if (!size) + size = CPU_ALLOC_SIZE(tracecmd_count_cpus()); + + pthread_setaffinity_np(pthread_self(), size, mask); + CPU_FREE(mask); +} + +static int tsync_send(struct tracecmd_time_sync *tsync, + struct tsync_proto *proto, unsigned int cpu) +{ + cpu_set_t *old_set = NULL; + long long timestamp = 0; + long long scaling = 0; + long long offset = 0; + long long frac = 0; + int ret; + + old_set = pin_to_cpu(cpu); + ret = proto->clock_sync_calc(tsync, &offset, &scaling, &frac, ×tamp, cpu); + if (old_set) + restore_pin_to_cpu(old_set); + + return ret; +} + +static void tsync_with_host(struct tracecmd_time_sync *tsync) +{ + char protocol[TRACECMD_TSYNC_PNAME_LENGTH]; + struct tsync_probe_request_msg probe; + struct tsync_proto *proto; + unsigned int command; + unsigned int size; + char *msg; + int ret; + + clock_context_init(tsync, &proto, true); + if (!tsync->context) + return; + + msg = (char *)&probe; + size = sizeof(probe); + while (true) { + memset(&probe, 0, size); + ret = tracecmd_msg_recv_time_sync(tsync->msg_handle, + protocol, &command, + &size, &msg); + + if (ret || strncmp(protocol, TRACECMD_TSYNC_PROTO_NONE, TRACECMD_TSYNC_PNAME_LENGTH) || + command != TRACECMD_TIME_SYNC_CMD_PROBE) + break; + ret = tsync_send(tsync, proto, probe.cpu); + if (ret) + break; + } +} + +static int record_sync_sample(struct clock_sync_offsets *offsets, int array_step, + long long offset, long long scaling, long long frac, long long ts) +{ + long long *sync_scalings = NULL; + long long *sync_offsets = NULL; + long long *sync_frac = NULL; + long long *sync_ts = NULL; + + if (offsets->sync_count >= offsets->sync_size) { + sync_ts = realloc(offsets->sync_ts, + (offsets->sync_size + array_step) * sizeof(long long)); + sync_offsets = realloc(offsets->sync_offsets, + (offsets->sync_size + array_step) * sizeof(long long)); + sync_scalings = realloc(offsets->sync_scalings, + (offsets->sync_size + array_step) * sizeof(long long)); + sync_frac = realloc(offsets->sync_frac, + (offsets->sync_size + array_step) * sizeof(long long)); + + if (!sync_ts || !sync_offsets || !sync_scalings || !sync_frac) { + free(sync_ts); + free(sync_offsets); + free(sync_scalings); + free(sync_frac); + return -1; + } + offsets->sync_size += array_step; + offsets->sync_ts = sync_ts; + offsets->sync_offsets = sync_offsets; + offsets->sync_scalings = sync_scalings; + offsets->sync_frac = sync_frac; + } + + offsets->sync_ts[offsets->sync_count] = ts; + offsets->sync_offsets[offsets->sync_count] = offset; + offsets->sync_scalings[offsets->sync_count] = scaling; + offsets->sync_frac[offsets->sync_count] = frac; + offsets->sync_count++; + + return 0; +} + +static int tsync_get_sample(struct tracecmd_time_sync *tsync, unsigned int cpu, + struct tsync_proto *proto, int array_step) +{ + struct clock_sync_context *clock; + long long timestamp = 0; + long long scaling = 0; + long long offset = 0; + long long frac = 0; + int ret; + + ret = proto->clock_sync_calc(tsync, &offset, &scaling, &frac, ×tamp, cpu); + if (ret) { + tracecmd_warning("Failed to synchronize timestamps with guest"); + return -1; + } + if (!offset || !timestamp || !scaling) + return 0; + clock = tsync->context; + if (!clock || cpu >= clock->cpu_count || !clock->offsets) + return -1; + return record_sync_sample(&clock->offsets[cpu], array_step, + offset, scaling, frac, timestamp); +} + +#define TIMER_SEC_NANO 1000000000LL +static inline void get_ts_loop_delay(struct timespec *timeout, int delay_ms) +{ + memset(timeout, 0, sizeof(struct timespec)); + clock_gettime(CLOCK_REALTIME, timeout); + + timeout->tv_nsec += ((unsigned long long)delay_ms * 1000000LL); + + if (timeout->tv_nsec >= TIMER_SEC_NANO) { + timeout->tv_sec += timeout->tv_nsec / TIMER_SEC_NANO; + timeout->tv_nsec %= TIMER_SEC_NANO; + } +} + +#define CLOCK_TS_ARRAY 5 +static int tsync_with_guest(struct tracecmd_time_sync *tsync) +{ + struct tsync_probe_request_msg probe; + int ts_array_size = CLOCK_TS_ARRAY; + struct tsync_proto *proto; + struct timespec timeout; + bool first = true; + bool end = false; + int ret; + int i; + + clock_context_init(tsync, &proto, false); + if (!tsync->context) { + pthread_barrier_wait(&tsync->first_sync); + return -1; + } + + if (tsync->loop_interval > 0 && + tsync->loop_interval < (CLOCK_TS_ARRAY * 1000)) + ts_array_size = (CLOCK_TS_ARRAY * 1000) / tsync->loop_interval; + + while (true) { + pthread_mutex_lock(&tsync->lock); + for (i = 0; i < tsync->vcpu_count; i++) { + probe.cpu = i; + ret = tracecmd_msg_send_time_sync(tsync->msg_handle, + TRACECMD_TSYNC_PROTO_NONE, + TRACECMD_TIME_SYNC_CMD_PROBE, + sizeof(probe), (char *)&probe); + ret = tsync_get_sample(tsync, i, proto, ts_array_size); + if (ret) + break; + } + if (first) { + first = false; + pthread_barrier_wait(&tsync->first_sync); + } + if (end || i < tsync->vcpu_count) { + pthread_mutex_unlock(&tsync->lock); + break; + } + if (tsync->loop_interval > 0) { + get_ts_loop_delay(&timeout, tsync->loop_interval); + ret = pthread_cond_timedwait(&tsync->cond, &tsync->lock, &timeout); + pthread_mutex_unlock(&tsync->lock); + if (ret && ret != ETIMEDOUT) + break; + else if (!ret) + end = true; + } else { + pthread_cond_wait(&tsync->cond, &tsync->lock); + end = true; + pthread_mutex_unlock(&tsync->lock); + } + }; + + tracecmd_msg_send_time_sync(tsync->msg_handle, + TRACECMD_TSYNC_PROTO_NONE, + TRACECMD_TIME_SYNC_CMD_STOP, + 0, NULL); + return 0; +} + +static void *tsync_host_thread(void *data) +{ + struct tracecmd_time_sync *tsync = data; + + tsync_with_guest(tsync); + pthread_exit(0); +} + +/** + * tracecmd_tsync_with_guest - Synchronize timestamps with guest + * + * @trace_id: Local ID for the current trace session + * @fd: file descriptor of guest + * @guest_pid: PID of the host OS process, running the guest + * @guest_cpus: Number of the guest VCPUs + * @proto_name: Name of the negotiated time synchronization protocol + * @clock: Trace clock, used for that session + * + * On success, a pointer to time sync context is returned, or NULL in + * case of an error. The context must be freed with tracecmd_tsync_free() + * + * This API spawns a pthread, which performs time stamps synchronization + * until tracecmd_tsync_with_guest_stop() is called. + */ +struct tracecmd_time_sync * +tracecmd_tsync_with_guest(unsigned long long trace_id, int loop_interval, + unsigned int fd, int guest_pid, + int guest_cpus, const char *proto_name, const char *clock) +{ + struct tracecmd_time_sync *tsync; + cpu_set_t *pin_mask = NULL; + pthread_attr_t attrib; + size_t mask_size = 0; + int ret; + + if (!proto_name) + return NULL; + + tsync = calloc(1, sizeof(*tsync)); + if (!tsync) + return NULL; + + tsync->trace_id = trace_id; + tsync->loop_interval = loop_interval; + tsync->proto_name = strdup(proto_name); + + tsync->msg_handle = tracecmd_msg_handle_alloc(fd, 0); + if (!tsync->msg_handle) { + ret = -1; + goto error; + } + tsync->guest_pid = guest_pid; + tsync->vcpu_count = guest_cpus; + + if (clock) + tsync->clock_str = strdup(clock); + pthread_mutex_init(&tsync->lock, NULL); + pthread_cond_init(&tsync->cond, NULL); + pthread_barrier_init(&tsync->first_sync, NULL, 2); + pthread_attr_init(&attrib); + pthread_attr_setdetachstate(&attrib, PTHREAD_CREATE_JOINABLE); + + ret = pthread_create(&tsync->thread, &attrib, tsync_host_thread, tsync); + if (ret) + goto error; + tsync->thread_running = true; + + if (!get_first_cpu(&pin_mask, &mask_size)) + pthread_setaffinity_np(tsync->thread, mask_size, pin_mask); + pthread_barrier_wait(&tsync->first_sync); + + if (pin_mask) + CPU_FREE(pin_mask); + pthread_attr_destroy(&attrib); + + return tsync; + +error: + if (tsync->msg_handle) + tracecmd_msg_handle_close(tsync->msg_handle); + else if (fd >= 0) + close(fd); + free(tsync); + + return NULL; +} + +/** + * tracecmd_write_guest_time_shift - Write collected timestamp corrections in a file + * + * @handle: Handle to a trace file, where timestamp corrections will be saved + * @tsync: Time sync context with collected timestamp corrections + * + * Returns 0 on success, or -1 in case of an error. + * + * This API writes collected timestamp corrections in the metadata of the + * trace file, as TRACECMD_OPTION_TIME_SHIFT option. + */ +int tracecmd_write_guest_time_shift(struct tracecmd_output *handle, + struct tracecmd_time_sync *tsync) +{ + struct iovec *vector = NULL; + unsigned int flags; + long long *scalings = NULL; + long long *offsets = NULL; + long long *frac = NULL; + long long *ts = NULL; + int vcount; + int count; + int i, j; + int ret = -1; + + if (!tsync->vcpu_count) + return -1; + vcount = 3 + (5 * tsync->vcpu_count); + vector = calloc(vcount, sizeof(struct iovec)); + if (!vector) + return -1; + ret = tsync_get_proto_flags(tsync, &flags); + if (ret < 0) + goto out; + + j = 0; + vector[j].iov_len = 8; + vector[j++].iov_base = &tsync->trace_id; + vector[j].iov_len = 4; + vector[j++].iov_base = &flags; + vector[j].iov_len = 4; + vector[j++].iov_base = &tsync->vcpu_count; + for (i = 0; i < tsync->vcpu_count; i++) { + if (j >= vcount) + break; + ret = tracecmd_tsync_get_offsets(tsync, i, &count, + &ts, &offsets, &scalings, NULL); + if (ret < 0 || !count || !ts || !offsets || !scalings) + break; + vector[j].iov_len = 4; + vector[j++].iov_base = &count; + vector[j].iov_len = 8 * count; + vector[j++].iov_base = ts; + vector[j].iov_len = 8 * count; + vector[j++].iov_base = offsets; + vector[j].iov_len = 8 * count; + vector[j++].iov_base = scalings; + } + if (i < tsync->vcpu_count) { + ret = -1; + goto out; + } + /* + * Writing fraction bits into the option is implemented in a separate loop for + * backward compatibility. In the trace-cmd 2.9 release, this option has only offset + * and scaling. That legacy code must work with the new extended option. + * + */ + for (i = 0; i < tsync->vcpu_count; i++) { + if (j >= vcount) + break; + ret = tracecmd_tsync_get_offsets(tsync, i, NULL, + NULL, NULL, NULL, &frac); + if (ret < 0) + break; + vector[j].iov_len = 8 * count; + vector[j++].iov_base = frac; + } + if (i < tsync->vcpu_count) { + ret = -1; + goto out; + } + + tracecmd_add_option_v(handle, TRACECMD_OPTION_TIME_SHIFT, vector, vcount); +#ifdef TSYNC_DEBUG + if (count > 1) + printf("Got %d timestamp synch samples in %lld ns trace\n\r", + count, ts[count - 1] - ts[0]); +#endif + ret = 0; +out: + free(vector); + return ret; +} + +/** + * tracecmd_tsync_with_guest_stop - Stop the time sync session with a guest + * + * @tsync: Time sync context, representing a running time sync session + * + * Returns 0 on success, or -1 in case of an error. + * + */ +int tracecmd_tsync_with_guest_stop(struct tracecmd_time_sync *tsync) +{ + if (!tsync || !tsync->thread_running) + return -1; + + /* Signal the time synchronization thread to complete and wait for it */ + pthread_mutex_lock(&tsync->lock); + pthread_cond_signal(&tsync->cond); + pthread_mutex_unlock(&tsync->lock); + pthread_join(tsync->thread, NULL); + return 0; +} + +static void *tsync_agent_thread(void *data) +{ + struct tracecmd_time_sync *tsync = data; + long ret = 0; + int sd; + + while (true) { + tracecmd_debug("Listening on fd:%d\n", tsync->msg_handle->fd); + sd = accept(tsync->msg_handle->fd, NULL, NULL); + tracecmd_debug("Accepted fd:%d\n", sd); + if (sd < 0) { + if (errno == EINTR) + continue; + ret = -1; + goto out; + } + break; + } + close(tsync->msg_handle->fd); + tsync->msg_handle->fd = sd; + + tsync_with_host(tsync); + +out: + pthread_exit((void *)ret); +} + +/** + * tracecmd_tsync_with_host - Synchronize timestamps with host + * @fd: File descriptor connecting with the host + * @tsync_protos: List of tsync protocols, supported by the host + * @clock: Trace clock, used for that session + * @port: returned, VSOCKET port, on which the guest listens for tsync requests + * @remote_id: Identifier to uniquely identify the remote host + * @local_id: Identifier to uniquely identify the local machine + * + * On success, a pointer to time sync context is returned, or NULL in + * case of an error. The context must be freed with tracecmd_tsync_free() + * + * This API spawns a pthread, which performs time stamps synchronization + * until tracecmd_tsync_with_host_stop() is called. + */ +struct tracecmd_time_sync * +tracecmd_tsync_with_host(int fd, + const struct tracecmd_tsync_protos *tsync_protos, + const char *clock, int remote_id, int local_id) +{ + struct tracecmd_time_sync *tsync; + cpu_set_t *pin_mask = NULL; + pthread_attr_t attrib; + size_t mask_size = 0; + const char *proto; + int ret; + + tsync = calloc(1, sizeof(struct tracecmd_time_sync)); + if (!tsync) + return NULL; + + proto = tsync_proto_select(tsync_protos, clock, + TRACECMD_TIME_SYNC_ROLE_GUEST); + if (!proto) + goto error; + tsync->proto_name = strdup(proto); + tsync->msg_handle = tracecmd_msg_handle_alloc(fd, 0); + if (clock) + tsync->clock_str = strdup(clock); + + tsync->remote_id = remote_id; + tsync->local_id = local_id; + + pthread_attr_init(&attrib); + tsync->vcpu_count = tracecmd_count_cpus(); + pthread_attr_setdetachstate(&attrib, PTHREAD_CREATE_JOINABLE); + + ret = pthread_create(&tsync->thread, &attrib, tsync_agent_thread, tsync); + if (ret) { + pthread_attr_destroy(&attrib); + goto error; + } + tsync->thread_running = true; + if (!get_first_cpu(&pin_mask, &mask_size)) + pthread_setaffinity_np(tsync->thread, mask_size, pin_mask); + + if (pin_mask) + CPU_FREE(pin_mask); + pthread_attr_destroy(&attrib); + return tsync; + +error: + if (tsync) { + if (tsync->msg_handle) { + /* Do not close the fd that was passed it */ + tsync->msg_handle->fd = -1; + tracecmd_msg_handle_close(tsync->msg_handle); + } + free(tsync->clock_str); + free(tsync); + } + + return NULL; + +} + +/** + * tracecmd_tsync_with_host_stop - Stop the time sync session with a host + * + * @tsync: Time sync context, representing a running time sync session + * + * Returns 0 on success, or error number in case of an error. + * + */ +int tracecmd_tsync_with_host_stop(struct tracecmd_time_sync *tsync) +{ + return pthread_join(tsync->thread, NULL); +} + +/** + * tracecmd_tsync_get_selected_proto - Return the seleceted time sync protocol + * @tsync: Time sync context, representing a running time sync session + * @selected_proto: return, name of the selected time sync protocol for this session + * + * Returns 0 on success, or -1 in case of an error. + * + */ +int tracecmd_tsync_get_selected_proto(struct tracecmd_time_sync *tsync, + char **selected_proto) +{ + if (!tsync) + return -1; + + if (selected_proto) { + if (!tsync->proto_name) + return -1; + (*selected_proto) = strdup(tsync->proto_name); + } + return 0; +} |