mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 02:01:24 +01:00
Signed-off-by: Olaf Hering <olaf@aepfle.de> Reviewed-by: George Dunlap <george.dunlap@cloud.com>
1240 lines
34 KiB
C
1240 lines
34 KiB
C
/******************************************************************************
|
|
* tools/xentrace/xentrace.c
|
|
*
|
|
* Tool for collecting trace buffer data from Xen.
|
|
*
|
|
* Copyright (C) 2004 by Intel Research Cambridge
|
|
*
|
|
* Author: Mark Williamson, mark.a.williamson@intel.com
|
|
* Date: February 2004
|
|
*/
|
|
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <poll.h>
|
|
#include <sys/statvfs.h>
|
|
|
|
#include <xen/xen.h>
|
|
#include <xen/trace.h>
|
|
|
|
#define XC_WANT_COMPAT_MAP_FOREIGN_API
|
|
#include <xenevtchn.h>
|
|
#include <xenctrl.h>
|
|
|
|
#define PERROR(_m, _a...) \
|
|
do { \
|
|
int __saved_errno = errno; \
|
|
fprintf(stderr, "ERROR: " _m " (%d = %s)\n" , ## _a , \
|
|
__saved_errno, strerror(__saved_errno)); \
|
|
errno = __saved_errno; \
|
|
} while (0)
|
|
|
|
|
|
/***** Compile time configuration of defaults ********************************/
|
|
|
|
/* sleep for this long (milliseconds) between checking the trace buffers */
|
|
#define POLL_SLEEP_MILLIS 100
|
|
|
|
#define DEFAULT_TBUF_SIZE 32
|
|
/***** The code **************************************************************/
|
|
|
|
typedef struct settings_st {
|
|
char *outfile;
|
|
unsigned long poll_sleep; /* milliseconds to sleep between polls */
|
|
uint32_t evt_mask;
|
|
char *cpu_mask_str;
|
|
unsigned long tbuf_size;
|
|
unsigned long disk_rsvd;
|
|
unsigned long timeout;
|
|
unsigned long memory_buffer;
|
|
uint8_t discard:1,
|
|
disable_tracing:1,
|
|
start_disabled:1;
|
|
} settings_t;
|
|
|
|
struct t_struct {
|
|
const struct t_info *t_info; /* Structure with information about individual buffers */
|
|
struct t_buf **meta; /* Pointers to trace buffer metadata */
|
|
unsigned char **data; /* Pointers to trace buffer data areas */
|
|
};
|
|
|
|
settings_t opts;
|
|
|
|
int interrupted = 0; /* gets set if we get a SIGHUP */
|
|
|
|
static xc_interface *xc_handle;
|
|
static xenevtchn_handle *xce_handle = NULL;
|
|
static int virq_port = -1;
|
|
static int outfd = 1;
|
|
|
|
static void close_handler(int signal)
|
|
{
|
|
interrupted = 1;
|
|
}
|
|
|
|
static struct {
|
|
char * buf;
|
|
unsigned long prod, cons, size;
|
|
unsigned long pending_size, pending_prod;
|
|
} membuf = { 0 };
|
|
|
|
#define MEMBUF_INDEX_RESET_THRESHOLD (1<<29)
|
|
|
|
/* FIXME -- make a power of 2 so we can mask instead. */
|
|
#define MEMBUF_POINTER(_i) (membuf.buf + ((_i) % membuf.size))
|
|
#define MEMBUF_CONS_INCREMENT(_n) \
|
|
do { \
|
|
membuf.cons += (_n); \
|
|
} while(0)
|
|
#define MEMBUF_PROD_SET(_x) \
|
|
do { \
|
|
if ( (_x) < membuf.prod ) { \
|
|
fprintf(stderr, "%s: INTERNAL_ERROR: prod %lu, trying to set to %lu!\n", \
|
|
__func__, membuf.prod, (unsigned long)(_x)); \
|
|
exit(1); \
|
|
} \
|
|
membuf.prod = (_x); \
|
|
if ( (_x) > MEMBUF_INDEX_RESET_THRESHOLD ) \
|
|
{ \
|
|
membuf.prod %= membuf.size; \
|
|
membuf.cons %= membuf.size; \
|
|
if( membuf.prod < membuf.cons ) \
|
|
membuf.prod += membuf.size; \
|
|
} \
|
|
} while(0)
|
|
|
|
struct cpu_change_record {
|
|
uint32_t header;
|
|
struct {
|
|
int cpu;
|
|
unsigned window_size;
|
|
} data;
|
|
};
|
|
|
|
#define CPU_CHANGE_HEADER \
|
|
(TRC_TRACE_CPU_CHANGE \
|
|
| (((sizeof(struct cpu_change_record)/sizeof(uint32_t)) - 1) \
|
|
<< TRACE_EXTRA_SHIFT) )
|
|
|
|
void membuf_alloc(unsigned long size)
|
|
{
|
|
membuf.buf = malloc(size);
|
|
|
|
if(!membuf.buf)
|
|
{
|
|
fprintf(stderr, "%s: Couldn't malloc %lu bytes!\n",
|
|
__func__, size);
|
|
exit(1);
|
|
}
|
|
|
|
membuf.prod = membuf.cons = 0;
|
|
membuf.size = size;
|
|
}
|
|
|
|
/*
|
|
* Reserve a new window in the buffer. Move the 'consumer' forward size
|
|
* bytes, re-adjusting the cpu window sizes as necessary, and insert a
|
|
* cpu_change record.
|
|
*/
|
|
void membuf_reserve_window(unsigned cpu, unsigned long window_size)
|
|
{
|
|
struct cpu_change_record *rec;
|
|
long need_to_consume, free, freed;
|
|
int last_cpu = -1;
|
|
|
|
if ( membuf.pending_size > 0 )
|
|
{
|
|
fprintf(stderr, "%s: INTERNAL_ERROR: pending_size %lu\n",
|
|
__func__, membuf.pending_size);
|
|
exit(1);
|
|
}
|
|
|
|
need_to_consume = window_size + sizeof(*rec);
|
|
|
|
if ( window_size > membuf.size )
|
|
{
|
|
fprintf(stderr, "%s: reserve size %lu larger than buffer size %lu!\n",
|
|
__func__, window_size, membuf.size);
|
|
exit(1);
|
|
}
|
|
|
|
/* Subtract free space already in buffer. */
|
|
free = membuf.size - (membuf.prod - membuf.cons);
|
|
if( need_to_consume < free)
|
|
goto start_window;
|
|
|
|
need_to_consume -= free;
|
|
|
|
/*
|
|
* "Free" up full windows until we have enough for this window.
|
|
* It's a bit wasteful to throw away partial buffers, but the only
|
|
* other option is to scan throught he buffer headers. Since the
|
|
* common case is that it's going to be thrown away next anyway, I
|
|
* think minimizing the overall impact is more important.
|
|
*/
|
|
do {
|
|
rec = (struct cpu_change_record *)MEMBUF_POINTER(membuf.cons);
|
|
if( rec->header != CPU_CHANGE_HEADER )
|
|
{
|
|
fprintf(stderr, "%s: INTERNAL ERROR: no cpu_change record at consumer!\n",
|
|
__func__);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
freed = sizeof(*rec) + rec->data.window_size;
|
|
|
|
if ( need_to_consume > 0 )
|
|
{
|
|
last_cpu = rec->data.cpu;
|
|
MEMBUF_CONS_INCREMENT(freed);
|
|
need_to_consume -= freed;
|
|
}
|
|
} while( need_to_consume > 0 );
|
|
|
|
/* For good tsc consistency, we need to start at a low-cpu buffer. Keep
|
|
* skipping until the cpu goes down or stays the same. */
|
|
rec = (struct cpu_change_record *)MEMBUF_POINTER(membuf.cons);
|
|
while ( rec->data.cpu > last_cpu )
|
|
{
|
|
last_cpu = rec->data.cpu;
|
|
|
|
freed = sizeof(*rec) + rec->data.window_size;
|
|
|
|
MEMBUF_CONS_INCREMENT(freed);
|
|
rec = (struct cpu_change_record *)MEMBUF_POINTER(membuf.cons);
|
|
}
|
|
|
|
start_window:
|
|
/*
|
|
* Start writing "pending" data. Update prod once all this data is
|
|
* written.
|
|
*/
|
|
membuf.pending_prod = membuf.prod;
|
|
membuf.pending_size = window_size;
|
|
|
|
rec = (struct cpu_change_record *)MEMBUF_POINTER(membuf.pending_prod);
|
|
|
|
rec->header = CPU_CHANGE_HEADER;
|
|
rec->data.cpu = cpu;
|
|
rec->data.window_size = window_size;
|
|
|
|
membuf.pending_prod += sizeof(*rec);
|
|
}
|
|
|
|
void membuf_write(void *start, unsigned long size) {
|
|
char * p;
|
|
unsigned long wsize;
|
|
|
|
if( (membuf.size - (membuf.prod - membuf.cons)) < size )
|
|
{
|
|
fprintf(stderr, "%s: INTERNAL ERROR: need %lu bytes, only have %lu!\n",
|
|
__func__, size, membuf.prod - membuf.cons);
|
|
exit(1);
|
|
}
|
|
|
|
if( size > membuf.pending_size )
|
|
{
|
|
fprintf(stderr, "%s: INTERNAL ERROR: size %lu, pending %lu!\n",
|
|
__func__, size, membuf.pending_size);
|
|
exit(1);
|
|
}
|
|
|
|
wsize = size;
|
|
p = MEMBUF_POINTER(membuf.pending_prod);
|
|
|
|
/* If the buffer overlaps the "wrap", do an extra write */
|
|
if ( p + size > membuf.buf + membuf.size )
|
|
{
|
|
int usize = ( membuf.buf + membuf.size ) - p;
|
|
|
|
memcpy(p, start, usize);
|
|
|
|
start += usize;
|
|
wsize -= usize;
|
|
p = membuf.buf;
|
|
}
|
|
|
|
memcpy(p, start, wsize);
|
|
|
|
membuf.pending_prod += size;
|
|
membuf.pending_size -= size;
|
|
|
|
if ( membuf.pending_size == 0 )
|
|
{
|
|
MEMBUF_PROD_SET(membuf.pending_prod);
|
|
}
|
|
}
|
|
|
|
void membuf_dump(void) {
|
|
/* Dump circular memory buffer */
|
|
int cons, prod, wsize, written;
|
|
char * wstart;
|
|
|
|
fprintf(stderr, "Dumping memory buffer.\n");
|
|
|
|
cons = membuf.cons % membuf.size;
|
|
prod = membuf.prod % membuf.size;
|
|
|
|
if(prod > cons)
|
|
{
|
|
/* Write in one go */
|
|
wstart = membuf.buf + cons;
|
|
wsize = prod - cons;
|
|
|
|
written = write(outfd, wstart, wsize);
|
|
if ( written != wsize )
|
|
goto fail;
|
|
}
|
|
else
|
|
{
|
|
/* Write in two pieces: cons->end, beginning->prod. */
|
|
wstart = membuf.buf + cons;
|
|
wsize = membuf.size - cons;
|
|
|
|
written = write(outfd, wstart, wsize);
|
|
if ( written != wsize )
|
|
{
|
|
fprintf(stderr, "Write failed! (size %d, returned %d)\n",
|
|
wsize, written);
|
|
goto fail;
|
|
}
|
|
|
|
wstart = membuf.buf;
|
|
wsize = prod;
|
|
|
|
written = write(outfd, wstart, wsize);
|
|
if ( written != wsize )
|
|
{
|
|
fprintf(stderr, "Write failed! (size %d, returned %d)\n",
|
|
wsize, written);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
membuf.cons = membuf.prod = 0;
|
|
|
|
return;
|
|
fail:
|
|
exit(1);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* write_buffer - write a section of the trace buffer
|
|
* @cpu - source buffer CPU ID
|
|
* @start
|
|
* @size - size of write (may be less than total window size)
|
|
* @total_size - total size of the window (0 on 2nd write of wrapped windows)
|
|
* @out - output stream
|
|
*
|
|
* Outputs the trace buffer to a filestream, prepending the CPU and size
|
|
* of the buffer write.
|
|
*/
|
|
static void write_buffer(unsigned int cpu, unsigned char *start, int size,
|
|
int total_size)
|
|
{
|
|
struct statvfs stat;
|
|
size_t written = 0;
|
|
|
|
if ( opts.memory_buffer == 0 && opts.disk_rsvd != 0 )
|
|
{
|
|
unsigned long long freespace;
|
|
|
|
/* Check that filesystem has enough space. */
|
|
if ( fstatvfs (outfd, &stat) )
|
|
{
|
|
fprintf(stderr, "Statfs failed!\n");
|
|
goto fail;
|
|
}
|
|
|
|
freespace = stat.f_frsize * (unsigned long long)stat.f_bfree;
|
|
|
|
if ( total_size )
|
|
freespace -= total_size;
|
|
else
|
|
freespace -= size;
|
|
|
|
freespace >>= 20; /* Convert to MB */
|
|
|
|
if ( freespace <= opts.disk_rsvd )
|
|
{
|
|
fprintf(stderr, "Disk space limit reached (free space: %lluMB, limit: %luMB).\n", freespace, opts.disk_rsvd);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/* Write a CPU_BUF record on each buffer "window" written. Wrapped
|
|
* windows may involve two writes, so only write the record on the
|
|
* first write. */
|
|
if ( total_size != 0 )
|
|
{
|
|
if ( opts.memory_buffer )
|
|
{
|
|
membuf_reserve_window(cpu, total_size);
|
|
}
|
|
else
|
|
{
|
|
struct cpu_change_record rec;
|
|
|
|
rec.header = CPU_CHANGE_HEADER;
|
|
rec.data.cpu = cpu;
|
|
rec.data.window_size = total_size;
|
|
|
|
written = write(outfd, &rec, sizeof(rec));
|
|
if ( written != sizeof(rec) )
|
|
{
|
|
fprintf(stderr, "Cannot write cpu change (write returned %zd)\n",
|
|
written);
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( opts.memory_buffer )
|
|
{
|
|
membuf_write(start, size);
|
|
}
|
|
else
|
|
{
|
|
written = write(outfd, start, size);
|
|
if ( written != size )
|
|
{
|
|
fprintf(stderr, "Write failed! (size %d, returned %zd)\n",
|
|
size, written);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
fail:
|
|
PERROR("Failed to write trace data");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
static void disable_tbufs(void)
|
|
{
|
|
xc_interface *xc_handle = xc_interface_open(0,0,0);
|
|
|
|
if ( !xc_handle )
|
|
{
|
|
perror("Couldn't open xc handle to disable tbufs.");
|
|
return;
|
|
}
|
|
|
|
if ( xc_tbuf_disable(xc_handle) != 0 )
|
|
{
|
|
perror("Couldn't disable trace buffers");
|
|
}
|
|
|
|
xc_interface_close(xc_handle);
|
|
}
|
|
|
|
static void get_tbufs(unsigned long *mfn, unsigned long *size)
|
|
{
|
|
int ret;
|
|
|
|
if(!opts.tbuf_size)
|
|
opts.tbuf_size = DEFAULT_TBUF_SIZE;
|
|
|
|
ret = xc_tbuf_enable(xc_handle, opts.tbuf_size, mfn, size);
|
|
|
|
if ( ret != 0 )
|
|
{
|
|
perror("Couldn't enable trace buffers");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* map_tbufs - memory map Xen trace buffers into user space
|
|
* @tbufs_mfn: mfn of the trace buffers
|
|
* @num: number of trace buffers to map
|
|
* @size: size of each trace buffer
|
|
*
|
|
* Maps the Xen trace buffers them into process address space.
|
|
*/
|
|
static struct t_struct *map_tbufs(unsigned long tbufs_mfn, unsigned int num,
|
|
unsigned long tinfo_size)
|
|
{
|
|
static struct t_struct tbufs = { 0 };
|
|
int i;
|
|
|
|
/* Map t_info metadata structure */
|
|
tbufs.t_info = xc_map_foreign_range(xc_handle, DOMID_XEN, tinfo_size,
|
|
PROT_READ, tbufs_mfn);
|
|
|
|
if ( tbufs.t_info == 0 )
|
|
{
|
|
PERROR("Failed to mmap trace buffers");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if ( tbufs.t_info->tbuf_size == 0 )
|
|
{
|
|
fprintf(stderr, "%s: tbuf_size 0!\n", __func__);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Map per-cpu buffers. NB that if a cpu is offline, it may have
|
|
* no trace buffers. In this case, the respective mfn_offset will
|
|
* be 0, and the index should be ignored.
|
|
*/
|
|
tbufs.meta = (struct t_buf **)calloc(num, sizeof(struct t_buf *));
|
|
tbufs.data = (unsigned char **)calloc(num, sizeof(unsigned char *));
|
|
if ( tbufs.meta == NULL || tbufs.data == NULL )
|
|
{
|
|
PERROR( "Failed to allocate memory for buffer pointers\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
for(i=0; i<num; i++)
|
|
{
|
|
const uint32_t *mfn_list;
|
|
int j;
|
|
xen_pfn_t pfn_list[tbufs.t_info->tbuf_size];
|
|
|
|
if ( !tbufs.t_info->mfn_offset[i] )
|
|
continue;
|
|
|
|
mfn_list = (const uint32_t *)tbufs.t_info + tbufs.t_info->mfn_offset[i];
|
|
for ( j=0; j<tbufs.t_info->tbuf_size; j++)
|
|
pfn_list[j] = (xen_pfn_t)mfn_list[j];
|
|
|
|
tbufs.meta[i] = xc_map_foreign_pages(xc_handle, DOMID_XEN,
|
|
PROT_READ | PROT_WRITE,
|
|
pfn_list,
|
|
tbufs.t_info->tbuf_size);
|
|
if ( tbufs.meta[i] == NULL )
|
|
{
|
|
PERROR("Failed to map cpu buffer!");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
tbufs.data[i] = (unsigned char *)(tbufs.meta[i]+1);
|
|
}
|
|
|
|
return &tbufs;
|
|
}
|
|
|
|
void print_cpu_mask(xc_cpumap_t map)
|
|
{
|
|
unsigned int v, had_printed = 0;
|
|
int i;
|
|
|
|
fprintf(stderr, "change cpumask to 0x");
|
|
|
|
for ( i = xc_get_cpumap_size(xc_handle); i >= 0; i-- )
|
|
{
|
|
v = map[i];
|
|
if ( v || had_printed || !i ) {
|
|
if (had_printed)
|
|
fprintf(stderr,"%02x", v);
|
|
else
|
|
fprintf(stderr,"%x", v);
|
|
had_printed = 1;
|
|
}
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
static int set_cpu_mask(xc_cpumap_t map)
|
|
{
|
|
int ret = xc_tbuf_set_cpu_mask(xc_handle, map);
|
|
|
|
if ( ret == 0 )
|
|
{
|
|
print_cpu_mask(map);
|
|
return 0;
|
|
}
|
|
PERROR("Failure to get trace buffer pointer from Xen and set the new mask");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/**
|
|
* set_mask - set the event mask in HV
|
|
* @mask: the new mask
|
|
* @type: the new mask type,0-event mask, 1-cpu mask
|
|
*
|
|
*/
|
|
static void set_evt_mask(uint32_t mask)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = xc_tbuf_set_evt_mask(xc_handle, mask);
|
|
fprintf(stderr, "change evtmask to 0x%x\n", mask);
|
|
|
|
if ( ret != 0 )
|
|
{
|
|
PERROR("Failure to get trace buffer pointer from Xen and set the new mask");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_num_cpus - get the number of logical CPUs
|
|
*/
|
|
static unsigned int get_num_cpus(void)
|
|
{
|
|
xc_physinfo_t physinfo;
|
|
int ret;
|
|
|
|
ret = xc_physinfo(xc_handle, &physinfo);
|
|
|
|
if ( ret != 0 )
|
|
{
|
|
PERROR("Failure to get logical CPU count from Xen");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
return physinfo.max_cpu_id + 1;
|
|
}
|
|
|
|
/**
|
|
* event_init - setup to receive the VIRQ_TBUF event
|
|
*/
|
|
static void event_init(void)
|
|
{
|
|
int rc;
|
|
|
|
xce_handle = xenevtchn_open(NULL, 0);
|
|
if (xce_handle == NULL) {
|
|
perror("event channel open");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
rc = xenevtchn_bind_virq(xce_handle, VIRQ_TBUF);
|
|
if (rc == -1) {
|
|
PERROR("failed to bind to VIRQ port");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
virq_port = rc;
|
|
}
|
|
|
|
/**
|
|
* wait_for_event_or_timeout - sleep for the specified number of milliseconds,
|
|
* or until an VIRQ_TBUF event occurs
|
|
*/
|
|
static void wait_for_event_or_timeout(unsigned long milliseconds)
|
|
{
|
|
int rc;
|
|
struct pollfd fd = { .fd = xenevtchn_fd(xce_handle),
|
|
.events = POLLIN | POLLERR };
|
|
int port;
|
|
|
|
rc = poll(&fd, 1, milliseconds);
|
|
if (rc == -1) {
|
|
if (errno == EINTR)
|
|
return;
|
|
PERROR("poll exitted with an error");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (rc == 1) {
|
|
port = xenevtchn_pending(xce_handle);
|
|
if (port == -1) {
|
|
PERROR("failed to read port from evtchn");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (port != virq_port) {
|
|
fprintf(stderr,
|
|
"unexpected port returned from evtchn (got %d vs expected %d)\n",
|
|
port, virq_port);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
rc = xenevtchn_unmask(xce_handle, port);
|
|
if (rc == -1) {
|
|
PERROR("failed to write port to evtchn");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* monitor_tbufs - monitor the contents of tbufs and output to a file
|
|
* @logfile: the FILE * representing the file to log to
|
|
*/
|
|
static void monitor_tbufs(void)
|
|
{
|
|
int i;
|
|
|
|
struct t_struct *tbufs; /* Pointer to hypervisor maps */
|
|
struct t_buf **meta; /* pointers to the trace buffer metadata */
|
|
unsigned char **data; /* pointers to the trace buffer data areas
|
|
* where they are mapped into user space. */
|
|
unsigned long tbufs_mfn; /* mfn of the tbufs */
|
|
unsigned int num; /* number of trace buffers / logical CPUS */
|
|
unsigned long tinfo_size; /* size of t_info metadata map */
|
|
unsigned long size; /* size of a single trace buffer */
|
|
|
|
unsigned long data_size;
|
|
|
|
int last_read = 1;
|
|
|
|
/* prepare to listen for VIRQ_TBUF */
|
|
event_init();
|
|
|
|
/* get number of logical CPUs (and therefore number of trace buffers) */
|
|
num = get_num_cpus();
|
|
|
|
/* setup access to trace buffers */
|
|
get_tbufs(&tbufs_mfn, &tinfo_size);
|
|
|
|
if ( opts.start_disabled )
|
|
disable_tbufs();
|
|
|
|
tbufs = map_tbufs(tbufs_mfn, num, tinfo_size);
|
|
|
|
size = tbufs->t_info->tbuf_size * XC_PAGE_SIZE;
|
|
|
|
data_size = size - sizeof(struct t_buf);
|
|
|
|
meta = tbufs->meta;
|
|
data = tbufs->data;
|
|
|
|
if ( opts.discard )
|
|
for ( i = 0; i < num; i++ )
|
|
if ( meta[i] )
|
|
meta[i]->cons = meta[i]->prod;
|
|
|
|
/* now, scan buffers for events */
|
|
while ( 1 )
|
|
{
|
|
for ( i = 0; i < num; i++ )
|
|
{
|
|
unsigned long start_offset, end_offset, window_size, cons, prod;
|
|
|
|
if ( !meta[i] )
|
|
continue;
|
|
|
|
/* Read window information only once. */
|
|
cons = meta[i]->cons;
|
|
prod = meta[i]->prod;
|
|
xen_rmb(); /* read prod, then read item. */
|
|
|
|
if ( cons == prod )
|
|
continue;
|
|
|
|
assert(cons < 2*data_size);
|
|
assert(prod < 2*data_size);
|
|
|
|
// NB: if (prod<cons), then (prod-cons)%data_size will not yield
|
|
// the correct answer because data_size is not a power of 2.
|
|
if ( prod < cons )
|
|
window_size = (prod + 2*data_size) - cons;
|
|
else
|
|
window_size = prod - cons;
|
|
assert(window_size > 0);
|
|
assert(window_size <= data_size);
|
|
|
|
start_offset = cons % data_size;
|
|
end_offset = prod % data_size;
|
|
|
|
if ( end_offset > start_offset )
|
|
{
|
|
/* If window does not wrap, write in one big chunk */
|
|
write_buffer(i, data[i]+start_offset,
|
|
window_size,
|
|
window_size);
|
|
}
|
|
else
|
|
{
|
|
/* If wrapped, write in two chunks:
|
|
* - first, start to the end of the buffer
|
|
* - second, start of buffer to end of window
|
|
*/
|
|
write_buffer(i, data[i] + start_offset,
|
|
data_size - start_offset,
|
|
window_size);
|
|
write_buffer(i, data[i],
|
|
end_offset,
|
|
0);
|
|
}
|
|
|
|
xen_mb(); /* read buffer, then update cons. */
|
|
meta[i]->cons = prod;
|
|
|
|
}
|
|
|
|
if ( interrupted )
|
|
{
|
|
if ( last_read )
|
|
{
|
|
/* Disable tracing, then read through all the buffers one last time */
|
|
if ( opts.disable_tracing )
|
|
disable_tbufs();
|
|
last_read = 0;
|
|
continue;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
wait_for_event_or_timeout(opts.poll_sleep);
|
|
}
|
|
|
|
if ( opts.memory_buffer )
|
|
membuf_dump();
|
|
|
|
/* cleanup */
|
|
free(meta);
|
|
free(data);
|
|
/* don't need to munmap - cleanup is automatic */
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Command line handling
|
|
*****************************************************************************/
|
|
|
|
#define xstr(x) str(x)
|
|
#define str(x) #x
|
|
|
|
const char *program_version = "xentrace v1.2";
|
|
const char *program_bug_address = "<mark.a.williamson@intel.com>";
|
|
|
|
static void usage(void)
|
|
{
|
|
#define USAGE_STR \
|
|
"Usage: xentrace [OPTION...] [output file]\n" \
|
|
"Tool to capture Xen trace buffer data\n" \
|
|
"\n" \
|
|
" -c, --cpu-mask=c Set cpu-mask, using either hex, CPU ranges, or\n" \
|
|
" for all CPUs\n" \
|
|
" -e, --evt-mask=e Set evt-mask\n" \
|
|
" -s, --poll-sleep=p Set sleep time, p, in milliseconds between\n" \
|
|
" polling the trace buffer for new data\n" \
|
|
" (default " xstr(POLL_SLEEP_MILLIS) ").\n" \
|
|
" -S, --trace-buf-size=N Set trace buffer size in pages (default " \
|
|
xstr(DEFAULT_TBUF_SIZE) ").\n" \
|
|
" N.B. that the trace buffer cannot be resized.\n" \
|
|
" if it has already been set this boot cycle,\n" \
|
|
" this argument will be ignored.\n" \
|
|
" -D --discard-buffers Discard all records currently in the trace\n" \
|
|
" buffers before beginning.\n" \
|
|
" -x --dont-disable-tracing\n" \
|
|
" By default, xentrace will disable tracing when\n" \
|
|
" it exits. Selecting this option will tell it to\n" \
|
|
" keep tracing on. Traces will be collected in\n" \
|
|
" Xen's trace buffers until they become full.\n" \
|
|
" -X --start-disabled Setup trace buffers and listen, but don't enable\n" \
|
|
" tracing. (Useful if tracing will be enabled by\n" \
|
|
" else.)\n" \
|
|
" -T --time-interval=s Run xentrace for s seconds and quit.\n" \
|
|
" -?, --help Show this message\n" \
|
|
" -V, --version Print program version\n" \
|
|
" -M, --memory-buffer=b Copy trace records to a circular memory buffer.\n" \
|
|
" Dump to file on exit.\n" \
|
|
" -r --reserve-disk-space=n Before writing trace records to disk, check to see\n" \
|
|
" that after the write there will be at least n space\n" \
|
|
" left on the disk.\n" \
|
|
"\n" \
|
|
"This tool is used to capture trace buffer data from Xen. The\n" \
|
|
"data is output in a binary format, in the following order:\n" \
|
|
"\n" \
|
|
" CPU(uint) TSC(uint64_t) EVENT(uint32_t) D1 D2 D3 D4 D5 (all uint32_t)\n" \
|
|
"\n" \
|
|
"The output should be parsed using the tool xentrace_format,\n" \
|
|
"which can produce human-readable output in ASCII format.\n"
|
|
|
|
printf(USAGE_STR);
|
|
printf("\nReport bugs to %s\n", program_bug_address);
|
|
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* convert the argument string pointed to by arg to a long int representation,
|
|
* including suffixes such as 'M' and 'k'. */
|
|
#define MB (1024*1024)
|
|
#define KB (1024)
|
|
long sargtol(const char *restrict arg, int base)
|
|
{
|
|
char *endp;
|
|
long val;
|
|
|
|
errno = 0;
|
|
val = strtol(arg, &endp, base);
|
|
|
|
if ( errno != 0 )
|
|
{
|
|
fprintf(stderr, "Invalid option argument: %s\n", arg);
|
|
fprintf(stderr, "Error: %s\n\n", strerror(errno));
|
|
usage();
|
|
}
|
|
else if (endp == arg)
|
|
{
|
|
goto invalid;
|
|
}
|
|
|
|
switch(*endp)
|
|
{
|
|
case '\0':
|
|
break;
|
|
case 'M':
|
|
val *= MB;
|
|
break;
|
|
case 'K':
|
|
case 'k':
|
|
val *= KB;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown suffix %c\n", *endp);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
return val;
|
|
|
|
invalid:
|
|
fprintf(stderr, "Invalid option argument: %s\n\n", arg);
|
|
usage();
|
|
return 0; /* not actually reached */
|
|
}
|
|
|
|
/* convert the argument string pointed to by arg to a long int representation */
|
|
static long argtol(const char *restrict arg, int base)
|
|
{
|
|
char *endp;
|
|
long val;
|
|
|
|
errno = 0;
|
|
val = strtol(arg, &endp, base);
|
|
|
|
if (errno != 0) {
|
|
fprintf(stderr, "Invalid option argument: %s\n", arg);
|
|
fprintf(stderr, "Error: %s\n\n", strerror(errno));
|
|
usage();
|
|
} else if (endp == arg || *endp != '\0') {
|
|
fprintf(stderr, "Invalid option argument: %s\n\n", arg);
|
|
usage();
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int parse_evtmask(char *arg)
|
|
{
|
|
/* search filtering class */
|
|
if (strcmp(arg, "gen") == 0){
|
|
opts.evt_mask |= TRC_GEN;
|
|
} else if(strcmp(arg, "sched") == 0){
|
|
opts.evt_mask |= TRC_SCHED;
|
|
} else if(strcmp(arg, "dom0op") == 0){
|
|
opts.evt_mask |= TRC_DOM0OP;
|
|
} else if(strcmp(arg, "hvm") == 0){
|
|
opts.evt_mask |= TRC_HVM;
|
|
} else if(strcmp(arg, "all") == 0){
|
|
opts.evt_mask |= TRC_ALL;
|
|
} else {
|
|
opts.evt_mask = argtol(arg, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define ZERO_DIGIT '0'
|
|
|
|
#define is_terminator(c) ((c)=='\0' || (c)==',')
|
|
|
|
static int parse_cpumask_range(const char *mask_str, xc_cpumap_t map)
|
|
{
|
|
unsigned int a, b;
|
|
int nmaskbits;
|
|
unsigned char c;
|
|
int in_range;
|
|
const char *s;
|
|
|
|
nmaskbits = xc_get_max_cpus(xc_handle);
|
|
if ( nmaskbits <= 0 )
|
|
{
|
|
fprintf(stderr, "Failed to get max number of CPUs! rc: %d\n", nmaskbits);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
c = 0;
|
|
s = mask_str;
|
|
do {
|
|
in_range = 0;
|
|
a = b = 0;
|
|
|
|
/* Process until we find a range terminator */
|
|
for ( c=*s++; !is_terminator(c); c=*s++ )
|
|
{
|
|
if ( c == '-' )
|
|
{
|
|
if ( in_range )
|
|
goto err_out;
|
|
b = 0;
|
|
in_range = 1;
|
|
continue;
|
|
}
|
|
|
|
if ( !isdigit(c) )
|
|
{
|
|
fprintf(stderr, "Invalid character in cpumask: %s\n", mask_str);
|
|
goto err_out;
|
|
}
|
|
|
|
b = b * 10 + (c - ZERO_DIGIT);
|
|
if ( !in_range )
|
|
a = b;
|
|
}
|
|
|
|
/* Syntax: <digit>-[,] - expand to number of CPUs. */
|
|
if ( b == 0 && in_range )
|
|
b = nmaskbits-1;
|
|
|
|
if ( a > b )
|
|
{
|
|
fprintf(stderr, "Wrong order of %d and %d\n", a, b);
|
|
goto err_out;
|
|
}
|
|
|
|
if ( b >= nmaskbits )
|
|
{
|
|
fprintf(stderr, "Specified higher value then there are CPUS!\n");
|
|
goto err_out;
|
|
}
|
|
|
|
while ( a <= b )
|
|
{
|
|
xc_cpumap_setcpu(a, map);
|
|
a++;
|
|
}
|
|
} while ( c );
|
|
|
|
return 0;
|
|
err_out:
|
|
errno = EINVAL;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/**
|
|
* Figure out which of the CPU types the user has provided - either the hex
|
|
* variant, the cpu-list, or 'all'. Once done set the CPU mask.
|
|
*/
|
|
static int parse_cpu_mask(void)
|
|
{
|
|
int i, ret = EXIT_FAILURE;
|
|
xc_cpumap_t map;
|
|
|
|
map = xc_cpumap_alloc(xc_handle);
|
|
if ( !map )
|
|
goto out;
|
|
|
|
if ( strlen(opts.cpu_mask_str) < 1 )
|
|
{
|
|
errno = ENOSPC;
|
|
goto out;
|
|
}
|
|
|
|
ret = 0;
|
|
if ( strncmp("0x", opts.cpu_mask_str, 2) == 0 )
|
|
{
|
|
uint32_t v;
|
|
|
|
v = argtol(opts.cpu_mask_str, 0);
|
|
/*
|
|
* If mask is set, copy the bits out of it. This still works for
|
|
* systems with more than 32 cpus, as the shift will just shift
|
|
* mask down to zero.
|
|
*/
|
|
for ( i = 0; i < sizeof(uint32_t); i++ )
|
|
map[i] = (v >> (i * 8)) & 0xff;
|
|
}
|
|
else if ( strcmp("all", opts.cpu_mask_str) == 0 )
|
|
{
|
|
for ( i = 0; i < xc_get_cpumap_size(xc_handle); i++ )
|
|
map[i] = 0xff;
|
|
}
|
|
else
|
|
ret = parse_cpumask_range(opts.cpu_mask_str, map);
|
|
|
|
if ( !ret )
|
|
ret = set_cpu_mask(map);
|
|
out:
|
|
/* We don't use them pass this point. */
|
|
free(map);
|
|
free(opts.cpu_mask_str);
|
|
opts.cpu_mask_str = NULL;
|
|
return ret;
|
|
}
|
|
|
|
/* parse command line arguments */
|
|
static void parse_args(int argc, char **argv)
|
|
{
|
|
int option;
|
|
static struct option long_options[] = {
|
|
{ "log-thresh", required_argument, 0, 't' },
|
|
{ "poll-sleep", required_argument, 0, 's' },
|
|
{ "cpu-mask", required_argument, 0, 'c' },
|
|
{ "evt-mask", required_argument, 0, 'e' },
|
|
{ "trace-buf-size", required_argument, 0, 'S' },
|
|
{ "reserve-disk-space", required_argument, 0, 'r' },
|
|
{ "time-interval", required_argument, 0, 'T' },
|
|
{ "memory-buffer", required_argument, 0, 'M' },
|
|
{ "discard-buffers", no_argument, 0, 'D' },
|
|
{ "dont-disable-tracing", no_argument, 0, 'x' },
|
|
{ "start-disabled", no_argument, 0, 'X' },
|
|
{ "help", no_argument, 0, '?' },
|
|
{ "version", no_argument, 0, 'V' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
while ( (option = getopt_long(argc, argv, "t:s:c:e:S:r:T:M:DxX?V",
|
|
long_options, NULL)) != -1)
|
|
{
|
|
switch ( option )
|
|
{
|
|
case 's': /* set sleep time (given in milliseconds) */
|
|
opts.poll_sleep = argtol(optarg, 0);
|
|
break;
|
|
|
|
case 'c': /* set new cpu mask for filtering (when xch is set). */
|
|
opts.cpu_mask_str = strdup(optarg);
|
|
break;
|
|
case 'e': /* set new event mask for filtering*/
|
|
parse_evtmask(optarg);
|
|
break;
|
|
|
|
case 'S': /* set tbuf size (given in pages) */
|
|
opts.tbuf_size = argtol(optarg, 0);
|
|
break;
|
|
|
|
case 'V': /* print program version */
|
|
printf("%s\n", program_version);
|
|
exit(EXIT_SUCCESS);
|
|
break;
|
|
|
|
case 'D': /* Discard traces currently in buffer */
|
|
opts.discard = 1;
|
|
break;
|
|
|
|
case 'r': /* Disk-space reservation */
|
|
opts.disk_rsvd = argtol(optarg, 0);
|
|
break;
|
|
|
|
case 'x': /* Don't disable tracing */
|
|
opts.disable_tracing = 0;
|
|
break;
|
|
|
|
case 'X': /* Start disabled */
|
|
opts.start_disabled = 1;
|
|
break;
|
|
|
|
case 'T':
|
|
opts.timeout = argtol(optarg, 0);
|
|
break;
|
|
|
|
case 'M':
|
|
opts.memory_buffer = sargtol(optarg, 0);
|
|
break;
|
|
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
/* get outfile (optional last argument) */
|
|
if (argc > optind)
|
|
opts.outfile = argv[optind];
|
|
}
|
|
|
|
/* *BSD has no O_LARGEFILE */
|
|
#ifndef O_LARGEFILE
|
|
#define O_LARGEFILE 0
|
|
#endif
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct sigaction act;
|
|
|
|
opts.outfile = 0;
|
|
opts.poll_sleep = POLL_SLEEP_MILLIS;
|
|
opts.evt_mask = 0;
|
|
opts.cpu_mask_str = NULL;
|
|
opts.disk_rsvd = 0;
|
|
opts.disable_tracing = 1;
|
|
opts.start_disabled = 0;
|
|
opts.timeout = 0;
|
|
|
|
parse_args(argc, argv);
|
|
|
|
xc_handle = xc_interface_open(0,0,0);
|
|
if ( !xc_handle )
|
|
{
|
|
perror("xenctrl interface open");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if ( opts.evt_mask != 0 )
|
|
set_evt_mask(opts.evt_mask);
|
|
|
|
if ( opts.cpu_mask_str )
|
|
{
|
|
if ( parse_cpu_mask() )
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if ( opts.timeout != 0 )
|
|
alarm(opts.timeout);
|
|
|
|
if ( opts.outfile )
|
|
outfd = open(opts.outfile,
|
|
O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE,
|
|
0644);
|
|
|
|
if ( outfd < 0 )
|
|
{
|
|
perror("Could not open output file");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if ( isatty(outfd) )
|
|
{
|
|
fprintf(stderr, "Cannot output to a TTY, specify a log file.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if ( opts.memory_buffer > 0 )
|
|
membuf_alloc(opts.memory_buffer);
|
|
|
|
/* ensure that if we get a signal, we'll do cleanup, then exit */
|
|
act.sa_handler = close_handler;
|
|
act.sa_flags = 0;
|
|
sigemptyset(&act.sa_mask);
|
|
sigaction(SIGHUP, &act, NULL);
|
|
sigaction(SIGTERM, &act, NULL);
|
|
sigaction(SIGINT, &act, NULL);
|
|
sigaction(SIGALRM, &act, NULL);
|
|
|
|
monitor_tbufs();
|
|
|
|
close(outfd);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* mode: C
|
|
* c-file-style: "BSD"
|
|
* c-basic-offset: 4
|
|
* tab-width: 4
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*/
|