| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2021 Google Inc, Steven Rostedt <rostedt@goodmis.org> | |
| * Copyright (C) 2020, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> | |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| */ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include "trace-local.h" | |
| #include "trace-msg.h" | |
| struct trace_mapping { | |
| struct tep_event *kvm_entry; | |
| struct tep_format_field *vcpu_id; | |
| struct tep_format_field *common_pid; | |
| int *pids; | |
| int *map; | |
| int *vcpu; | |
| int max_cpus; | |
| }; | |
| static int cmp_tmap_vcpu(const void *A, const void *B) | |
| { | |
| const int *a = A; | |
| const int *b = B; | |
| if (*a < *b) | |
| return -1; | |
| return *a > *b; | |
| } | |
| static int map_kvm_vcpus(int guest_pid, struct trace_mapping *tmap) | |
| { | |
| struct dirent *entry; | |
| const char *debugfs; | |
| char *vm_dir_str = NULL; | |
| char *pid_file = NULL; | |
| char *kvm_dir; | |
| int pid_file_len; | |
| bool found = false; | |
| DIR *dir; | |
| int ret = -1; | |
| int i; | |
| tmap->vcpu = malloc(sizeof(*tmap->vcpu) * tmap->max_cpus); | |
| if (!tmap->vcpu) | |
| return -1; | |
| memset(tmap->vcpu, -1, sizeof(*tmap->vcpu) * tmap->max_cpus); | |
| debugfs = tracefs_debug_dir(); | |
| if (!debugfs) | |
| return -1; | |
| if (asprintf(&kvm_dir, "%s/kvm", debugfs) < 0) | |
| return -1; | |
| dir = opendir(kvm_dir); | |
| if (!dir) | |
| goto out; | |
| if (asprintf(&pid_file, "%d-", guest_pid) <= 0) | |
| goto out; | |
| pid_file_len = strlen(pid_file); | |
| while ((entry = readdir(dir))) { | |
| if (entry->d_type != DT_DIR || | |
| strncmp(entry->d_name, pid_file, pid_file_len) != 0) | |
| continue; | |
| if (asprintf(&vm_dir_str, "%s/%s", kvm_dir, entry->d_name) < 0) | |
| goto out; | |
| found = true; | |
| break; | |
| } | |
| if (!found) | |
| goto out; | |
| closedir(dir); | |
| dir = opendir(vm_dir_str); | |
| if (!dir) | |
| goto out; | |
| i = 0; | |
| while ((entry = readdir(dir))) { | |
| if (entry->d_type != DT_DIR || | |
| strncmp(entry->d_name, "vcpu", 4)) | |
| continue; | |
| if (i == tmap->max_cpus) | |
| goto out; | |
| tmap->vcpu[i] = strtol(entry->d_name + 4, NULL, 10); | |
| i++; | |
| } | |
| if (i < tmap->max_cpus) | |
| goto out; | |
| qsort(tmap->vcpu, tmap->max_cpus, sizeof(*tmap->vcpu), cmp_tmap_vcpu); | |
| ret = 0; | |
| out: | |
| if (dir) | |
| closedir(dir); | |
| free(vm_dir_str); | |
| free(pid_file); | |
| free(kvm_dir); | |
| return ret; | |
| } | |
| static int map_vcpus(struct tep_event *event, struct tep_record *record, | |
| int cpu, void *context) | |
| { | |
| struct trace_mapping *tmap = context; | |
| unsigned long long val; | |
| int *vcpu; | |
| int type; | |
| int pid; | |
| int ret; | |
| int i; | |
| /* Do we have junk in the buffer? */ | |
| type = tep_data_type(event->tep, record); | |
| if (type != tmap->kvm_entry->id) | |
| return 0; | |
| ret = tep_read_number_field(tmap->common_pid, record->data, &val); | |
| if (ret < 0) | |
| return 0; | |
| pid = (int)val; | |
| for (i = 0; tmap->pids[i] >= 0; i++) { | |
| if (pid == tmap->pids[i]) | |
| break; | |
| } | |
| /* Is this thread one we care about ? */ | |
| if (tmap->pids[i] < 0) | |
| return 0; | |
| ret = tep_read_number_field(tmap->vcpu_id, record->data, &val); | |
| if (ret < 0) | |
| return 0; | |
| cpu = (int)val; | |
| vcpu = bsearch(&cpu, tmap->vcpu, tmap->max_cpus, sizeof(cpu), cmp_tmap_vcpu); | |
| /* Sanity check, warn? */ | |
| if (!vcpu) | |
| return 0; | |
| cpu = vcpu - tmap->vcpu; | |
| /* Already have this one? Should we check if it is the same? */ | |
| if (tmap->map[cpu] >= 0) | |
| return 0; | |
| tmap->map[cpu] = pid; | |
| /* Did we get them all */ | |
| for (i = 0; i < tmap->max_cpus; i++) { | |
| if (tmap->map[i] < 0) | |
| break; | |
| } | |
| return i == tmap->max_cpus; | |
| } | |
| static void start_mapping_vcpus(struct trace_guest *guest) | |
| { | |
| char *pids = NULL; | |
| char *t; | |
| int len = 0; | |
| int s; | |
| int i; | |
| if (!guest->task_pids) | |
| return; | |
| guest->instance = tracefs_instance_create("map_guest_pids"); | |
| if (!guest->instance) | |
| return; | |
| for (i = 0; guest->task_pids[i] >= 0; i++) { | |
| s = snprintf(NULL, 0, "%d ", guest->task_pids[i]); | |
| t = realloc(pids, len + s + 1); | |
| if (!t) { | |
| free(pids); | |
| pids = NULL; | |
| break; | |
| } | |
| pids = t; | |
| sprintf(pids + len, "%d ", guest->task_pids[i]); | |
| len += s; | |
| } | |
| if (pids) { | |
| tracefs_instance_file_write(guest->instance, "set_event_pid", pids); | |
| free(pids); | |
| } | |
| tracefs_instance_file_write(guest->instance, "events/kvm/kvm_entry/enable", "1"); | |
| } | |
| static void stop_mapping_vcpus(int cpu_count, struct trace_guest *guest) | |
| { | |
| struct trace_mapping tmap = { }; | |
| struct tep_handle *tep; | |
| const char *systems[] = { "kvm", NULL }; | |
| int i; | |
| if (!guest->instance) | |
| return; | |
| tmap.pids = guest->task_pids; | |
| tmap.max_cpus = cpu_count; | |
| tmap.map = malloc(sizeof(*tmap.map) * tmap.max_cpus); | |
| if (!tmap.map) | |
| return; | |
| /* Check if the kvm vcpu mappings are the same */ | |
| if (map_kvm_vcpus(guest->pid, &tmap) < 0) | |
| goto out; | |
| for (i = 0; i < tmap.max_cpus; i++) | |
| tmap.map[i] = -1; | |
| tracefs_instance_file_write(guest->instance, "events/kvm/kvm_entry/enable", "0"); | |
| tep = tracefs_local_events_system(NULL, systems); | |
| if (!tep) | |
| goto out; | |
| tmap.kvm_entry = tep_find_event_by_name(tep, "kvm", "kvm_entry"); | |
| if (!tmap.kvm_entry) | |
| goto out_free; | |
| tmap.vcpu_id = tep_find_field(tmap.kvm_entry, "vcpu_id"); | |
| if (!tmap.vcpu_id) | |
| goto out_free; | |
| tmap.common_pid = tep_find_any_field(tmap.kvm_entry, "common_pid"); | |
| if (!tmap.common_pid) | |
| goto out_free; | |
| tracefs_iterate_raw_events(tep, guest->instance, NULL, 0, map_vcpus, &tmap); | |
| for (i = 0; i < tmap.max_cpus; i++) { | |
| if (tmap.map[i] < 0) | |
| break; | |
| } | |
| /* We found all the mapped CPUs */ | |
| if (i == tmap.max_cpus) { | |
| guest->cpu_pid = tmap.map; | |
| guest->cpu_max = tmap.max_cpus; | |
| tmap.map = NULL; | |
| } | |
| out_free: | |
| tep_free(tep); | |
| out: | |
| free(tmap.vcpu); | |
| free(tmap.map); | |
| tracefs_instance_destroy(guest->instance); | |
| tracefs_instance_free(guest->instance); | |
| } | |
| /** | |
| * trace_tsync_as_host - tsync from the host side | |
| * @fd: The descriptor to the peer for tsync | |
| * @trace_id: The trace_id of the host | |
| * @loop_interval: The loop interval for tsyncs that do periodic syncs | |
| * @guest_id: The id for guests (negative if this is over network) | |
| * @guest_cpus: The number of CPUs the guest has | |
| * @proto_name: The protocol name to sync with | |
| * @clock: The clock name to use for tracing | |
| * | |
| * Start the time synchronization from the host side. | |
| * This will start the mapping of the virtual CPUs to host threads | |
| * if it is a vsocket connection (not a network). | |
| * | |
| * Returns a pointer to the tsync descriptor on success or NULL on error. | |
| */ | |
| struct tracecmd_time_sync * | |
| trace_tsync_as_host(int fd, unsigned long long trace_id, | |
| int loop_interval, int guest_id, | |
| int guest_cpus, const char *proto_name, | |
| const char *clock) | |
| { | |
| struct tracecmd_time_sync *tsync; | |
| struct trace_guest *guest; | |
| int guest_pid = -1; | |
| if (fd < 0) | |
| return NULL; | |
| if (guest_id >= 0) { | |
| guest = trace_get_guest(guest_id, NULL); | |
| if (guest == NULL) | |
| return NULL; | |
| guest_pid = guest->pid; | |
| start_mapping_vcpus(guest); | |
| } | |
| tsync = tracecmd_tsync_with_guest(trace_id, loop_interval, fd, | |
| guest_pid, guest_cpus, proto_name, | |
| clock); | |
| if (guest_id >= 0) | |
| stop_mapping_vcpus(guest_cpus, guest); | |
| return tsync; | |
| } | |
| /** | |
| * trace_tsync_a_guest - tsync from the guest side | |
| * @fd: The file descriptor to the peer for tsync | |
| * @tsync_proto: The protocol name to sync with | |
| * @clock: The clock name to use for tracing | |
| * @remote_id: The id to differentiate the remote server with | |
| * @loca_id: The id to differentiate the local machine with | |
| * | |
| * Start the time synchronization from the guest side. | |
| * | |
| * Returns a pointer to the tsync descriptor on success or NULL on error. | |
| */ | |
| struct tracecmd_time_sync * | |
| trace_tsync_as_guest(int fd, const char *tsync_proto, const char *clock, | |
| unsigned int remote_id, unsigned int local_id) | |
| { | |
| struct tracecmd_time_sync *tsync = NULL; | |
| if (fd < 0) | |
| return NULL; | |
| tsync = tracecmd_tsync_with_host(fd, tsync_proto, | |
| clock, remote_id, local_id); | |
| if (!tsync) { | |
| warning("Failed to negotiate timestamps synchronization with the host"); | |
| return NULL; | |
| } | |
| return tsync; | |
| } |