blob: 57baff399bd3ef464be7f6a7f732a12869613073 [file] [log] [blame]
// 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.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;
}