Files
xen/tools/xentrace/xentrace.c
T
2023-05-31 17:06:58 +01:00

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:
*/