| // 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; |
| } |