/* * (C) Copyright IBM Corp. 2006 * Contributed by Kevin Corry * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sellcopies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * * pfmsetup * * Very simple command-line tool to drive the perfmon2 kernel API. Inspired * by the dmsetup tool from device-mapper. * * Compile with: * gcc -Wall -o pfmsetup pfmsetup.c -lpfm * * Run with: * pfmsetup * * Available commands for the command_file: * * create_context [options] * Create a new context for accessing the performance counters. Each new * context automatically gets one event-set with an ID of 0. * - options: --system * --no-overflow-msg * --block-on-notify * --sampler * - : specify an integer that you want to associate with * the new context for use in other commands. * * load_context * Attach the specified context and event-set to the specified program. * - : ID that you specified when creating the context. * - : ID that you specified when creating an event-set * within the given context. All contexts automatically * have an event-set with ID of 0. * - : ID that you specified when starting a program * with the run_program command, or the number of * the CPU to attach to for system-wide mode. * * unload_context * Detach the specified context from the program that it's currently * attached to. * - : ID that you specified when creating the context. * * close_context * Clean up the specified context. After this call, the context_id will no * longer be valid. * - : ID that you specified when creating the context. * * write_pmc < >+ * Write one or more control register values. * - : ID that you specified when creating the context. * - : ID that you specified when creating an event-set * within the given context. All contexts automatically * have an event-set with ID of 0. * - : ID of the desired control register. See the register * mappings in the Perfmon kernel code to determine which * PMC represents the control register you're interested in. * - : Value to write into the specified PMC. You need to know * the exact numeric value - no translations are done from * event names or masks. Multiple PMC id/value pairs can * be given in one write_pmc command. * * write_pmd < >+ * Write one or more data register values. * - : ID that you specified when creating the context. * - : ID that you specified when creating an event-set * within the given context. All contexts automatically * have an event-set with ID of 0. * - : ID of the desired data register. See the register * mappings in the Perfmon kernel code to determine which * PMD represents the control register you're interested in. * - : Value to write into the specified PMD. Multiple PMD * id/value pairs can be given in one write_pmd command. * * read_pmd + * Read one or more data register values. * - : ID that you specified when creating the context. * - : ID that you specified when creating an event-set * within the given context. All contexts automatically * have an event-set with ID of 0. * - : ID of the desired data register. See the register * mappings in the Perfmon kernel code to determine which * PMD represents the control register you're interested in. * Multiple PMD IDs can be given in one read_pmd command. * * start_counting * Start counting using the specified context and event-set. * - : ID that you specified when creating the context. * - : ID that you specified when creating an event-set * within the given context. All contexts automatically * have an event-set with ID of 0. * * stop_counting * Stop counting on the specified context. * - : ID that you specified when creating the context. * * restart_counting * Restart counting on the specified context. * - : ID that you specified when creating the context. * * create_eventset [options] * Create a new event-set for an existing context. * - options: --next-set * --timeout * --switch-on-overflow * --exclude-idle * - : ID that you specified when creating the context. * - : specify an integer that you want to associate with * the new event-set for use in other commands. * * delete_eventset * Delete an existing event-set from an existing context. * - : ID that you specified when creating the context. * - : ID that you specified when creating the event-set. * * getinfo_eventset * Display information about an event-set. * - : ID that you specified when creating the context. * - : ID that you specified when creating the event-set. * * run_program * First step in starting a program to monitor. In order to allow time to * set up the counters to monitor the program, this command only forks a * child process. It then suspends itself using ptrace. You must call the * resume_program command to wake up the new child process and exec the * desired program. * - : Specify an integer that you want to associate with * the program for use in other commands. * - : Specify the program and its arguments * exactly as you would on the command * line. * * resume_program * When a program is 'run', a child process is forked, but the child is * ptrace'd before exec'ing the specified program. This gives you time to * do any necessary setup to monitor the program. This resume_program * command wakes up the child process and finishes exec'ing the desired * program. If a context has been loaded and started for this program, * then the counters will have actually started following this command. * - : ID that you specified when starting the program. * * wait_on_program * Wait for a program to complete and exit. After this call, the program_id * will no longer be valid. * - : ID that you specified when starting the program. * * sleep #include #include #include #include #include #include #include #include #include #include #include #define FALSE 0 #define TRUE 1 #define WHITESPACE " \t\n" #define MAX_TOKENS 32 #define PFMSETUP_NAME "pfmsetup" #define USAGE(f, x...) printf(PFMSETUP_NAME ": USAGE: " f "\n" , ## x) #define LOG_ERROR(f, x...) printf(PFMSETUP_NAME ": Error: %s: " f "\n", __FUNCTION__ , ## x) #define LOG_INFO(f, x...) printf(PFMSETUP_NAME ": " f "\n" , ## x) typedef int (*command_fn)(int argc, char **argv); struct command { const char *full_name; const char *short_name; const char *help; command_fn fn; int min_args; }; struct context { int id; int fd; int cpu; pfarg_ctx_t ctx_arg; pfm_dfl_smpl_arg_t smpl_arg; struct event_set *event_sets; struct context *next; }; struct event_set { int id; struct event_set *next; }; struct program { int id; pid_t pid; struct program *next; }; /* Global list of all contexts that have been created. List is ordered by * context id. Each context contains a list of event-sets belonging to that * context, which is ordered by event-set id. */ static struct context *contexts = NULL; /* Global list of all programs that have been started. * List is ordered by program id. */ static struct program *programs = NULL; /* * Routines to manipulate the context, event-set, and program lists. */ static struct context *find_context(int ctx_id) { struct context *ctx; for (ctx = contexts; ctx; ctx = ctx->next) { if (ctx->id == ctx_id) { break; } } return ctx; } static void insert_context(struct context *ctx) { struct context **next_ctx; for (next_ctx = &contexts; *next_ctx && (*next_ctx)->id < ctx->id; next_ctx = &((*next_ctx)->next)) { ; } ctx->next = *next_ctx; *next_ctx = ctx; } static void remove_context(struct context *ctx) { struct context **next_ctx; for (next_ctx = &contexts; *next_ctx; next_ctx = &((*next_ctx)->next)) { if (*next_ctx == ctx) { *next_ctx = ctx->next; break; } } } static struct event_set *find_event_set(struct context *ctx, int event_set_id) { struct event_set *evt; for (evt = ctx->event_sets; evt; evt = evt->next) { if (evt->id == event_set_id) { break; } } return evt; } static void insert_event_set(struct context *ctx, struct event_set *evt) { struct event_set **next_evt; for (next_evt = &ctx->event_sets; *next_evt && (*next_evt)->id < evt->id; next_evt = &((*next_evt)->next)) { ; } evt->next = *next_evt; *next_evt = evt; } static void remove_event_set(struct context *ctx, struct event_set *evt) { struct event_set **next_evt; for (next_evt = &ctx->event_sets; *next_evt; next_evt = &((*next_evt)->next)) { if (*next_evt == evt) { *next_evt = evt->next; break; } } } static struct program *find_program(int program_id) { struct program *prog; for (prog = programs; prog; prog = prog->next) { if (prog->id == program_id) { break; } } return prog; } static void insert_program(struct program *prog) { struct program **next_prog; for (next_prog = &programs; *next_prog && (*next_prog)->id < prog->id; next_prog = &((*next_prog)->next)) { ; } prog->next = *next_prog; *next_prog = prog; } static void remove_program(struct program *prog) { struct program **next_prog; for (next_prog = &programs; *next_prog; next_prog = &((*next_prog)->next)) { if (*next_prog == prog) { *next_prog = prog->next; break; } } } /** * set_affinity * * When loading or unloading a system-wide context, we must pin the pfmsetup * process to that CPU before making the system call. Also, get the current * affinity and return it to the caller so we can change it back later. **/ static int set_affinity(int cpu, cpu_set_t *old_cpu_set) { cpu_set_t new_cpu_set; int rc; rc = sched_getaffinity(0, sizeof(*old_cpu_set), old_cpu_set); if (rc) { rc = errno; LOG_ERROR("Can't get current process affinity mask: %d\n", rc); return rc; } CPU_ZERO(&new_cpu_set); CPU_SET(cpu, &new_cpu_set); rc = sched_setaffinity(0, sizeof(new_cpu_set), &new_cpu_set); if (rc) { rc = errno; LOG_ERROR("Can't set process affinity to CPU %d: %d\n", cpu, rc); return rc; } return 0; } /** * revert_affinity * * Reset the process affinity to the specified mask. **/ static void revert_affinity(cpu_set_t *old_cpu_set) { int rc; rc = sched_setaffinity(0, sizeof(*old_cpu_set), old_cpu_set); if (rc) { /* Not a fatal error if we can't reset the affinity. */ LOG_INFO("Can't revert process affinity to original value.\n"); } } /** * create_context * * Arguments: [options] * Options: --system * --no-overflow-msg * --block-on-notify * --sampler * * Call the pfm_create_context system-call to create a new perfmon context. * Add a new entry to the global 'contexts' list. **/ static int create_context(int argc, char **argv) { pfarg_ctx_t ctx_arg; pfm_dfl_smpl_arg_t smpl_arg; struct context *new_ctx = NULL; char *sampler_name = NULL; void *smpl_p; int no_overflow_msg = FALSE; int block_on_notify = FALSE; int system_wide = FALSE; int c, ctx_id = 0; int rc; size_t sz; struct option long_opts[] = { {"sampler", required_argument, NULL, 1}, {"system", no_argument, NULL, 2}, {"no-overflow-msg", no_argument, NULL, 3}, {"block-on-notify", no_argument, NULL, 4}, {NULL, 0, NULL, 0} }; memset(&ctx_arg, 0, sizeof(ctx_arg)); opterr = 0; optind = 0; while ((c = getopt_long_only(argc, argv, "", long_opts, NULL)) != EOF) { switch (c) { case 1: sampler_name = optarg; break; case 2: system_wide = TRUE; break; case 3: no_overflow_msg = TRUE; break; case 4: block_on_notify = TRUE; break; default: LOG_ERROR("invalid option: %c", optopt); rc = EINVAL; goto error; } } if (argc < optind + 1) { USAGE("create_context [options] "); rc = EINVAL; goto error; } ctx_id = strtoul(argv[optind], NULL, 0); if (ctx_id <= 0) { LOG_ERROR("Invalid context ID (%s). Must be a positive " "integer.", argv[optind]); rc = EINVAL; goto error; } /* Make sure we don't already have a context with this ID. */ new_ctx = find_context(ctx_id); if (new_ctx) { LOG_ERROR("Context with ID %d already exists.", ctx_id); rc = EINVAL; goto error; } if (sampler_name) { smpl_arg.buf_size = getpagesize(); smpl_p = &smpl_arg; sz = sizeof(smpl_arg); } else { smpl_p = NULL; sz = 0; } ctx_arg.ctx_flags = (system_wide ? PFM_FL_SYSTEM_WIDE : 0) | (no_overflow_msg ? PFM_FL_OVFL_NO_MSG : 0) | (block_on_notify ? PFM_FL_NOTIFY_BLOCK : 0); rc = pfm_create_context(&ctx_arg, sampler_name, smpl_p, sz); if (rc == -1) { rc = errno; LOG_ERROR("pfm_create_context system call returned " "an error: %d.", rc); goto error; } /* Allocate and initialize a new context structure and add it to the * global list. Every new context automatically gets one event_set * with an event ID of 0. */ new_ctx = calloc(1, sizeof(*new_ctx)); if (!new_ctx) { LOG_ERROR("Can't allocate structure for new context %d.", ctx_id); rc = ENOMEM; goto error; } new_ctx->event_sets = calloc(1, sizeof(*(new_ctx->event_sets))); if (!new_ctx->event_sets) { LOG_ERROR("Can't allocate event-set structure for new " "context %d.", ctx_id); rc = ENOMEM; goto error; } new_ctx->id = ctx_id; new_ctx->fd = rc; new_ctx->cpu = -1; new_ctx->ctx_arg = ctx_arg; new_ctx->smpl_arg = smpl_arg; insert_context(new_ctx); LOG_INFO("Created context %d with file-descriptor %d.", new_ctx->id, new_ctx->fd); return 0; error: if (new_ctx) { close(new_ctx->fd); free(new_ctx->event_sets); free(new_ctx); } return rc; } /** * load_context * * Arguments: * * Call the pfm_load_context system-call to load a perfmon context into the * system's performance monitoring unit. **/ static int load_context(int argc, char **argv) { struct context *ctx; struct event_set *evt; struct program *prog; pfarg_load_t load_arg; cpu_set_t old_cpu_set; int ctx_id, event_set_id, program_id; int system_wide, rc; ctx_id = strtoul(argv[1], NULL, 0); event_set_id = strtoul(argv[2], NULL, 0); program_id = strtoul(argv[3], NULL, 0); if (ctx_id <= 0 || event_set_id < 0 || program_id < 0) { LOG_ERROR("context ID, event-set ID, and program/CPU ID must " "be positive integers."); return EINVAL; } /* Find the context, event_set, and program in the global lists. */ ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } evt = find_event_set(ctx, event_set_id); if (!evt) { LOG_ERROR("Can't find event-set with ID %d in context %d.", event_set_id, ctx_id); return EINVAL; } load_arg.load_set = evt->id; system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide) { if (ctx->cpu >= 0) { LOG_ERROR("Trying to load context %d which is already " "loaded on CPU %d.\n", ctx_id, ctx->cpu); return EBUSY; } rc = set_affinity(program_id, &old_cpu_set); if (rc) { return rc; } /* Specify the CPU as the PID. */ load_arg.load_pid = program_id; } else { prog = find_program(program_id); if (!prog) { LOG_ERROR("Can't find program with ID %d.", program_id); return EINVAL; } load_arg.load_pid = prog->pid; } rc = pfm_load_context(ctx->fd, &load_arg); if (rc) { rc = errno; LOG_ERROR("pfm_load_context system call returned " "an error: %d.", rc); return rc; } if (system_wide) { /* Keep track of which CPU this context is loaded on. */ ctx->cpu = program_id; revert_affinity(&old_cpu_set); } LOG_INFO("Loaded context %d, event-set %d onto %s %d.", ctx_id, event_set_id, system_wide ? "cpu" : "program", program_id); return 0; } /** * unload_context * * Arguments: * * Call the pfm_unload_context system-call to unload a perfmon context from * the system's performance monitoring unit. **/ static int unload_context(int argc, char **argv) { struct context *ctx; cpu_set_t old_cpu_set; int system_wide; int ctx_id; int rc; ctx_id = strtoul(argv[1], NULL, 0); if (ctx_id <= 0) { LOG_ERROR("context ID must be a positive integer."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide) { if (ctx->cpu < 0) { /* This context isn't loaded on any CPU. */ LOG_ERROR("Trying to unload context %d that isn't " "loaded.\n", ctx_id); return EINVAL; } rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { return rc; } } rc = pfm_unload_context(ctx->fd); if (rc) { rc = errno; LOG_ERROR("pfm_unload_context system call returned " "an error: %d.", rc); return rc; } if (system_wide) { ctx->cpu = -1; revert_affinity(&old_cpu_set); } LOG_INFO("Unloaded context %d.", ctx_id); return 0; } /** * close_context * * Arguments: * * Close the context's file descriptor, remove it from the global list, and * free the context data structures. **/ static int close_context(int argc, char **argv) { struct context *ctx; struct event_set *evt, *next_evt; int ctx_id; ctx_id = strtoul(argv[1], NULL, 0); if (ctx_id <= 0) { LOG_ERROR("context ID must be a positive integer."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } /* There's no perfmon system-call to delete a context. We simply call * close on the file handle. */ close(ctx->fd); remove_context(ctx); for (evt = ctx->event_sets; evt; evt = next_evt) { next_evt = evt->next; free(evt); } free(ctx); LOG_INFO("Closed and freed context %d.", ctx_id); return 0; } /** * write_pmc * * Arguments: < >+ * * Write values to one or more control registers. **/ static int write_pmc(int argc, char **argv) { struct context *ctx; struct event_set *evt; pfarg_pmc_t *pmc_args = NULL; cpu_set_t old_cpu_set; int ctx_id, event_set_id; int pmc_id, num_pmcs; unsigned long long pmc_value; int system_wide, i, rc; ctx_id = strtoul(argv[1], NULL, 0); event_set_id = strtoul(argv[2], NULL, 0); if (ctx_id <= 0 || event_set_id < 0) { LOG_ERROR("context ID and event-set ID must be " "positive integers."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } evt = find_event_set(ctx, event_set_id); if (!evt) { LOG_ERROR("Can't find event-set with ID %d in context %d.", event_set_id, ctx_id); return EINVAL; } /* Allocate an array of PMC structures. */ num_pmcs = (argc - 3) / 2; pmc_args = calloc(num_pmcs, sizeof(*pmc_args)); if (!pmc_args) { LOG_ERROR("Can't allocate PMC argument array."); return ENOMEM; } for (i = 0; i < num_pmcs; i++) { pmc_id = strtoul(argv[3 + i*2], NULL, 0); pmc_value = strtoull(argv[4 + i*2], NULL, 0); if (pmc_id < 0) { LOG_ERROR("PMC ID must be a positive integer."); rc = EINVAL; goto out; } pmc_args[i].reg_num = pmc_id; pmc_args[i].reg_set = evt->id; pmc_args[i].reg_value = pmc_value; } system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { goto out; } } rc = pfm_write_pmcs(ctx->fd, pmc_args, num_pmcs); if (rc) { rc = errno; LOG_ERROR("pfm_write_pmcs system call returned " "an error: %d.", rc); goto out; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } out: free(pmc_args); return rc; } /** * write_pmd * * Arguments: < >+ * * FIXME: Add options for other fields in pfarg_pmd_t. **/ static int write_pmd(int argc, char **argv) { struct context *ctx; struct event_set *evt; pfarg_pmd_t *pmd_args = NULL; cpu_set_t old_cpu_set; int ctx_id, event_set_id; int pmd_id, num_pmds; unsigned long long pmd_value; int system_wide, i, rc; ctx_id = strtoul(argv[1], NULL, 0); event_set_id = strtoul(argv[2], NULL, 0); if (ctx_id <= 0 || event_set_id < 0) { LOG_ERROR("context ID and event-set ID must be " "positive integers."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } evt = find_event_set(ctx, event_set_id); if (!evt) { LOG_ERROR("Can't find event-set with ID %d in context %d.", event_set_id, ctx_id); return EINVAL; } /* Allocate an array of PMD structures. */ num_pmds = (argc - 3) / 2; pmd_args = calloc(num_pmds, sizeof(*pmd_args)); if (!pmd_args) { LOG_ERROR("Can't allocate PMD argument array."); return ENOMEM; } for (i = 0; i < num_pmds; i++) { pmd_id = strtoul(argv[3 + i*2], NULL, 0); pmd_value = strtoull(argv[4 + i*2], NULL, 0); if (pmd_id < 0) { LOG_ERROR("PMD ID must be a positive integer."); rc = EINVAL; goto out; } pmd_args[i].reg_num = pmd_id; pmd_args[i].reg_set = evt->id; pmd_args[i].reg_value = pmd_value; } system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { goto out; } } rc = pfm_write_pmds(ctx->fd, pmd_args, num_pmds); if (rc) { rc = errno; LOG_ERROR("pfm_write_pmds system call returned " "an error: %d.", rc); goto out; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } out: free(pmd_args); return rc; } /** * read_pmd * * Arguments: + * * FIXME: Add options for other fields in pfarg_pmd_t. **/ static int read_pmd(int argc, char **argv) { struct context *ctx; struct event_set *evt; pfarg_pmd_t *pmd_args = NULL; cpu_set_t old_cpu_set; int ctx_id, event_set_id; int pmd_id, num_pmds; int system_wide, i, rc; ctx_id = strtoul(argv[1], NULL, 0); event_set_id = strtoul(argv[2], NULL, 0); if (ctx_id <= 0 || event_set_id < 0) { LOG_ERROR("context ID and event-set ID must be " "positive integers."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } evt = find_event_set(ctx, event_set_id); if (!evt) { LOG_ERROR("Can't find event-set with ID %d in context %d.", event_set_id, ctx_id); return EINVAL; } /* Allocate an array of PMD structures. */ num_pmds = argc - 3; pmd_args = calloc(num_pmds, sizeof(*pmd_args)); if (!pmd_args) { LOG_ERROR("Can't allocate PMD argument array."); return ENOMEM; } for (i = 0; i < num_pmds; i++) { pmd_id = strtoul(argv[3 + i], NULL, 0); if (pmd_id < 0) { LOG_ERROR("PMD ID must be a positive integer."); rc = EINVAL; goto out; } pmd_args[i].reg_num = pmd_id; pmd_args[i].reg_set = evt->id; } system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { goto out; } } rc = pfm_read_pmds(ctx->fd, pmd_args, num_pmds); if (rc) { rc = errno; LOG_ERROR("pfm_read_pmds system call returned " "an error: %d.", rc); goto out; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } out: free(pmd_args); return rc; } /** * start_counting * * Arguments: * * Call the pfm_start system-call to start counting for a perfmon context * that was previously stopped. **/ static int start_counting(int argc, char **argv) { pfarg_start_t start_arg; struct context *ctx; struct event_set *evt; cpu_set_t old_cpu_set; int ctx_id, event_set_id; int system_wide, rc; memset(&start_arg, 0, sizeof(start_arg)); ctx_id = strtoul(argv[1], NULL, 0); event_set_id = strtoul(argv[2], NULL, 0); if (ctx_id <= 0 || event_set_id < 0) { LOG_ERROR("context ID and event-set ID must be " "positive integers."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } evt = find_event_set(ctx, event_set_id); if (!evt) { LOG_ERROR("Can't find event-set with ID %d in context %d.", event_set_id, ctx_id); return EINVAL; } start_arg.start_set = evt->id; system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { return rc; } } rc = pfm_start(ctx->fd, &start_arg); if (rc) { rc = errno; LOG_ERROR("pfm_start system call returned an error: %d.", rc); return rc; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } LOG_INFO("Started counting for context %d, event-set %d.", ctx_id, event_set_id); return 0; } /** * stop_counting * * Arguments: * * Call the pfm_stop system-call to stop counting for a perfmon context that * was previously loaded. **/ static int stop_counting(int argc, char **argv) { struct context *ctx; cpu_set_t old_cpu_set; int system_wide; int ctx_id; int rc; ctx_id = strtoul(argv[1], NULL, 0); if (ctx_id <= 0) { LOG_ERROR("context ID must be a positive integer."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { return rc; } } rc = pfm_stop(ctx->fd); if (rc) { rc = errno; LOG_ERROR("pfm_stop system call returned an error: %d.", rc); return rc; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } LOG_INFO("Stopped counting for context %d.", ctx_id); return 0; } /** * restart_counting * * Arguments: * * Call the pfm_restart system-call to clear the data counters and start * counting from zero for a perfmon context that was previously loaded. **/ static int restart_counting(int argc, char **argv) { struct context *ctx; cpu_set_t old_cpu_set; int system_wide; int ctx_id; int rc; ctx_id = strtoul(argv[1], NULL, 0); if (ctx_id <= 0) { LOG_ERROR("context ID must be a positive integer."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { return rc; } } rc = pfm_restart(ctx->fd); if (rc) { rc = errno; LOG_ERROR("pfm_restart system call returned an error: %d.", rc); return rc; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } LOG_INFO("Restarted counting for context %d.", ctx_id); return 0; } /** * create_eventset * * Arguments: [options] * Options: --timeout * --switch-on-overflow * --exclude-idle **/ static int create_eventset(int argc, char **argv) { pfarg_setdesc_t set_arg; struct context *ctx; struct event_set *evt; cpu_set_t old_cpu_set; int ctx_id, event_set_id; unsigned long timeout = 0; int switch_on_overflow = FALSE; int switch_on_timeout = FALSE; int exclude_idle = FALSE; int new_set = FALSE; int system_wide,c, rc; struct option long_opts[] = { {"next-set", required_argument, NULL, 1}, {"timeout", required_argument, NULL, 2}, {"switch-on-overflow", no_argument, NULL, 3}, {"exclude-idle", no_argument, NULL, 4}, {NULL, 0, NULL, 0} }; memset(&set_arg, 0, sizeof(set_arg)); opterr = 0; optind = 0; while ((c = getopt_long_only(argc, argv, "", long_opts, NULL)) != EOF) { switch (c) { case 1: timeout = strtoul(optarg, NULL, 0); if (!timeout) { LOG_ERROR("timeout must be a " "non-zero integer."); return EINVAL; } switch_on_timeout = TRUE; break; case 2: switch_on_overflow = TRUE; break; case 3: exclude_idle = TRUE; break; default: LOG_ERROR("invalid option: %c", optopt); return EINVAL; } } (void) exclude_idle; if (argc < optind + 2) { USAGE("create_eventset [options] "); return EINVAL; } ctx_id = strtoul(argv[optind], NULL, 0); event_set_id = strtoul(argv[optind+1], NULL, 0); if (ctx_id <= 0 || event_set_id < 0) { LOG_ERROR("context ID and event-set ID must be " "positive integers."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } if (switch_on_timeout && switch_on_overflow) { LOG_ERROR("Cannot switch set %d (context %d) on both " "timeout and overflow.", event_set_id, ctx_id); return EINVAL; } evt = find_event_set(ctx, event_set_id); if (!evt) { evt = calloc(1, sizeof(*evt)); if (!evt) { LOG_ERROR("Can't allocate structure for new event-set " "%d in context %d.", event_set_id, ctx_id); return ENOMEM; } evt->id = event_set_id; new_set = TRUE; } set_arg.set_id = event_set_id; set_arg.set_timeout = timeout; /* in nanseconds */ set_arg.set_flags = (switch_on_overflow ? PFM_SETFL_OVFL_SWITCH : 0) | (switch_on_timeout ? PFM_SETFL_TIME_SWITCH : 0); system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { free(evt); return rc; } } rc = pfm_create_evtsets(ctx->fd, &set_arg, 1); if (rc) { rc = errno; LOG_ERROR("pfm_create_evtsets system call returned " "an error: %d.", rc); free(evt); return rc; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } if (new_set) { insert_event_set(ctx, evt); } LOG_INFO("%s event-set %d in context %d.", new_set ? "Created" : "Modified", event_set_id, ctx_id); if (switch_on_timeout) { LOG_INFO(" Actual timeout set to %llu ns.", (unsigned long long)set_arg.set_timeout); } return 0; } /** * delete_eventset * * Arguments: **/ static int delete_eventset(int argc, char **argv) { pfarg_setdesc_t set_arg; struct context *ctx; struct event_set *evt; cpu_set_t old_cpu_set; int ctx_id, event_set_id; int system_wide, rc; memset(&set_arg, 0, sizeof(set_arg)); ctx_id = strtoul(argv[1], NULL, 0); event_set_id = strtoul(argv[2], NULL, 0); if (ctx_id <= 0 || event_set_id < 0) { LOG_ERROR("context ID and event-set ID must be " "positive integers."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } evt = find_event_set(ctx, event_set_id); if (!evt) { LOG_ERROR("Can't find event-set with ID %d in context %d.", event_set_id, ctx_id); return EINVAL; } set_arg.set_id = evt->id; system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { return rc; } } rc = pfm_delete_evtsets(ctx->fd, &set_arg, 1); if (rc) { rc = errno; LOG_ERROR("pfm_delete_evtsets system call returned " "an error: %d.", rc); return rc; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } remove_event_set(ctx, evt); free(evt); LOG_INFO("Deleted event-set %d from context %d.", event_set_id, ctx_id); return 0; } /** * getinfo_eventset * * Arguments: **/ static int getinfo_eventset(int argc, char **argv) { pfarg_setinfo_t set_arg; struct context *ctx; struct event_set *evt; cpu_set_t old_cpu_set; int ctx_id, event_set_id; int system_wide, rc; memset(&set_arg, 0, sizeof(set_arg)); ctx_id = strtoul(argv[1], NULL, 0); event_set_id = strtoul(argv[2], NULL, 0); if (ctx_id <= 0 || event_set_id < 0) { LOG_ERROR("context ID and event-set ID must be " "positive integers."); return EINVAL; } ctx = find_context(ctx_id); if (!ctx) { LOG_ERROR("Can't find context with ID %d.", ctx_id); return EINVAL; } evt = find_event_set(ctx, event_set_id); if (!evt) { LOG_ERROR("Can't find event-set with ID %d in context %d.", event_set_id, ctx_id); return EINVAL; } set_arg.set_id = evt->id; system_wide = ctx->ctx_arg.ctx_flags & PFM_FL_SYSTEM_WIDE; if (system_wide && ctx->cpu >= 0) { rc = set_affinity(ctx->cpu, &old_cpu_set); if (rc) { return rc; } } rc = pfm_getinfo_evtsets(ctx->fd, &set_arg, 1); if (rc) { rc = errno; LOG_ERROR("pfm_getinfo_evtsets system call returned " "an error: %d.", rc); return rc; } if (system_wide && ctx->cpu >= 0) { revert_affinity(&old_cpu_set); } LOG_INFO("Got info for event-set %d in context %d.", event_set_id, ctx_id); LOG_INFO(" Flags: 0x%x", set_arg.set_flags); LOG_INFO(" Runs: %llu", (unsigned long long)set_arg.set_runs); LOG_INFO(" Timeout: %"PRIu64, set_arg.set_timeout); return 0; } /** * run_program * * Arguments: * * Start the specified program. After fork'ing but before exec'ing, ptrace * the child so it will remain suspended until a corresponding resume_program * command. We do this so we can load a context for the program before it * actually starts running. This logic is taken from the task.c example in * the libpfm source code tree. **/ static int run_program(int argc, char **argv) { struct program *prog; int program_id; pid_t pid; int rc; program_id = strtoul(argv[1], NULL, 0); if (program_id <= 0) { LOG_ERROR("program ID must be a positive integer."); return EINVAL; } /* Make sure we haven't already started a program with this ID. */ prog = find_program(program_id); if (prog) { LOG_ERROR("Program with ID %d already exists.", program_id); return EINVAL; } prog = calloc(1, sizeof(*prog)); if (!prog) { LOG_ERROR("Can't allocate new program structure to run '%s'.", argv[2]); return ENOMEM; } prog->id = program_id; pid = fork(); if (pid == -1) { /* Error fork'ing. */ LOG_ERROR("Unable to fork child process."); return EINVAL; } else if (!pid) { /* Child */ /* This will cause the program to stop before executing the * first user level instruction. We can only load a context * if the program is in the STOPPED state. This child * process will sit here until we've process a resume_program * command. */ rc = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (rc) { rc = errno; LOG_ERROR("Error ptrace'ing '%s': %d", argv[2], rc); exit(rc); } execvp(argv[2], argv + 2); rc = errno; LOG_ERROR("Error exec'ing '%s': %d", argv[2], rc); exit(rc); } /* Parent */ prog->pid = pid; insert_program(prog); /* Wait for the child to exec. */ waitpid(pid, &rc, WUNTRACED); /* Check if process exited early. */ if (WIFEXITED(rc)) { LOG_ERROR("Program '%s' exited too early with status " "%d", argv[2], WEXITSTATUS(rc)); return WEXITSTATUS(rc); } LOG_INFO("Started program %d: '%s'.", program_id, argv[2]); return 0; } /** * resume_program * * Arguments: * * A program started with run_program must be 'resumed' before it actually * begins running. This allows us to load a context to the process and * start the counters before the program executes any code. **/ static int resume_program(int argc, char **argv) { struct program *prog; int program_id; int rc; program_id = strtoul(argv[1], NULL, 0); if (program_id <= 0) { LOG_ERROR("program ID must be a positive integer."); return EINVAL; } prog = find_program(program_id); if (!prog) { LOG_ERROR("Can't find program with ID %d.", program_id); return EINVAL; } /* Call ptrace to resume execution of the process. If a context has * been loaded and the counters started, this is where monitoring * is effectively activated. */ rc = ptrace(PTRACE_DETACH, prog->pid, NULL, 0); if (rc) { rc = errno; LOG_ERROR("Error detaching program %d.\n", prog->id); return rc; } LOG_INFO("Resumed program %d.", program_id); return 0; } /** * wait_on_program * * Arguments: * * Wait for the specified program to complete and exit. **/ static int wait_on_program(int argc, char **argv) { struct program *prog; int program_id; int rc; program_id = strtoul(argv[1], NULL, 0); if (program_id <= 0) { LOG_ERROR("program ID must be a positive integer."); return EINVAL; } prog = find_program(program_id); if (!prog) { LOG_ERROR("Can't find program with ID %d.", program_id); return EINVAL; } waitpid(prog->pid, &rc, 0); /* The program has exitted, but if there was a context loaded on that * process, it will still have the latest counts available to read. */ remove_program(prog); free(prog); LOG_INFO("Waited for program %d to complete.", program_id); return 0; } /** * _sleep * * Arguments: