/*
 * Copyright 2008, The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Binary implementation of the original opcontrol script due to missing tools
 * like awk, test, etc.
 */

#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "op_config.h"

#define verbose(fmt...) if (verbose_print) printf(fmt)

/* Experiments found that using a small interval may hang the device, and the
 * more events tracked simultaneously, the longer the interval has to be.
 */

#if defined(__i386__) || defined(__x86_64__)
#define MAX_EVENTS 2
int min_count[MAX_EVENTS] = {60000, 100000};
#elif !defined(WITH_ARM_V7_A)
#define MAX_EVENTS 3
int min_count[MAX_EVENTS] = {150000, 200000, 250000};
#else
#define MAX_EVENTS 5
int min_count[MAX_EVENTS] = {150000, 20000, 25000, 30000, 35000};
#endif

int verbose_print;
int list_events; 
int show_usage;
int setup;
int quick;
int timer;
int num_events;
int start;
int stop;
int reset;

int selected_events[MAX_EVENTS];
int selected_counts[MAX_EVENTS];

char callgraph[8];
char kernel_range[512];
char vmlinux[512];

struct option long_options[] = {
    {"help", 0, &show_usage, 1},
    {"list-events", 0, &list_events, 1},
    {"reset", 0, &reset, 1},
    {"setup", 0, &setup, 1},
    {"quick", 0, &quick, 1},
    {"timer", 0, &timer, 1},
    {"callgraph", 1, 0, 'c'},
    {"event", 1, 0, 'e'},
    {"vmlinux", 1, 0, 'v'},
    {"kernel-range", 1, 0, 'r'},
    {"start", 0, &start, 1},
    {"stop", 0, &stop, 1},
    {"dump", 0, 0, 'd'},
    {"shutdown", 0, 0, 'h'},
    {"status", 0, 0, 't'},
    {"verbose", 0, 0, 'V'},
    {"verbose-log", 1, 0, 'l'},
    {0, 0, 0, 0},
};

struct event_info {
    int id;
    int um;
    const char *name;
    const char *explanation;
} event_info[] = {
#if defined(__i386__) || defined(__x86_64__)
    /* INTEL_ARCH_PERFMON events */

    /* 0x3c counters:cpuid um:zero minimum:6000 filter:0 name:CPU_CLK_UNHALTED :
     * Clock cycles when not halted
     */
    {0x3c, 0, "CPU_CLK_UNHALTED",
     "Clock cycles when not halted" },

    /* event:0x3c counters:cpuid um:one minimum:6000 filter:2 name:UNHALTED_REFERENCE_CYCLES :
     * Unhalted reference cycles
     */
    {0x3c, 1, "UNHALTED_REFERENCE_CYCLES",
      "Unhalted reference cycles" },

    /* event:0xc0 counters:cpuid um:zero minimum:6000 filter:1 name:INST_RETIRED :
     * number of instructions retired
     */
     {0xc0, 0, "INST_RETIRED",
       "number of instructions retired"},

    /* event:0x2e counters:cpuid um:x41 minimum:6000 filter:5 name:LLC_MISSES :
     * Last level cache demand requests from this core that missed the LLC
     */
     {0x2e, 0x41, "LLC_MISSES",
       "Last level cache demand requests from this core that missed the LLC"},

    /* event:0x2e counters:cpuid um:x4f minimum:6000 filter:4 name:LLC_REFS :
     * Last level cache demand requests from this core
     */
     {0x2e, 0x4f, "LLC_REFS",
      "Last level cache demand requests from this core"},

    /* event:0xc4 counters:cpuid um:zero minimum:500 filter:6 name:BR_INST_RETIRED :
     * number of branch instructions retired
     */
     {0xc4, 0, "BR_INST_RETIRED",
       "number of branch instructions retired"},

    /* event:0xc5 counters:cpuid um:zero minimum:500 filter:7 name:BR_MISS_PRED_RETIRED :
     * number of mispredicted branches retired (precise)
     */
     {0xc5, 0, "BR_MISS_PRED_RETIRED",
       "number of mispredicted branches retired (precise)"},

#elif !defined(WITH_ARM_V7_A)
    /* ARM V6 events */
    {0x00, 0, "IFU_IFETCH_MISS", 
     "number of instruction fetch misses"},
    {0x01, 0, "CYCLES_IFU_MEM_STALL", 
     "cycles instruction fetch pipe is stalled"},
    {0x02, 0, "CYCLES_DATA_STALL", 
     "cycles stall occurs for due to data dependency"},
    {0x03, 0, "ITLB_MISS", 
     "number of Instruction MicroTLB misses"},
    {0x04, 0, "DTLB_MISS", 
     "number of Data MicroTLB misses"},
    {0x05, 0, "BR_INST_EXECUTED", 
     "branch instruction executed w/ or w/o program flow change"},
    {0x06, 0, "BR_INST_MISS_PRED", 
     "branch mispredicted"},
    {0x07, 0, "INSN_EXECUTED", 
     "instructions executed"},
    {0x09, 0, "DCACHE_ACCESS", 
     "data cache access, cacheable locations"},
    {0x0a, 0, "DCACHE_ACCESS_ALL", 
     "data cache access, all locations"},
    {0x0b, 0, "DCACHE_MISS", 
     "data cache miss"},
    {0x0c, 0, "DCACHE_WB", 
     "data cache writeback, 1 event for every half cacheline"},
    {0x0d, 0, "PC_CHANGE", 
     "number of times the program counter was changed without a mode switch"},
    {0x0f, 0, "TLB_MISS", 
     "Main TLB miss"},
    {0x10, 0, "EXP_EXTERNAL", 
     "Explicit external data access"},
    {0x11, 0, "LSU_STALL", 
     "cycles stalled because Load Store request queue is full"},
    {0x12, 0, "WRITE_DRAIN", 
     "Times write buffer was drained"},
    {0xff, 0, "CPU_CYCLES", 
     "clock cycles counter"}, 
#else
    /* ARM V7 events */
    {0x00, 0, "PMNC_SW_INCR",
     "Software increment of PMNC registers"},
    {0x01, 0, "IFETCH_MISS",
     "Instruction fetch misses from cache or normal cacheable memory"},
    {0x02, 0, "ITLB_MISS",
     "Instruction fetch misses from TLB"},
    {0x03, 0, "DCACHE_REFILL",
     "Data R/W operation that causes a refill from cache or normal cacheable"
     "memory"},
    {0x04, 0, "DCACHE_ACCESS",
     "Data R/W from cache"},
    {0x05, 0, "DTLB_REFILL",
     "Data R/W that causes a TLB refill"},
    {0x06, 0, "DREAD",
     "Data read architecturally executed (note: architecturally executed = for"
     "instructions that are unconditional or that pass the condition code)"},
    {0x07, 0, "DWRITE",
     "Data write architecturally executed"},
    {0x08, 0, "INSTR_EXECUTED",
     "All executed instructions"},
    {0x09, 0, "EXC_TAKEN",
     "Exception taken"},
    {0x0A, 0, "EXC_EXECUTED",
     "Exception return architecturally executed"},
    {0x0B, 0, "CID_WRITE",
     "Instruction that writes to the Context ID Register architecturally"
     "executed"},
    {0x0C, 0, "PC_WRITE",
     "SW change of PC, architecturally executed (not by exceptions)"},
    {0x0D, 0, "PC_IMM_BRANCH",
     "Immediate branch instruction executed (taken or not)"},
    {0x0E, 0, "PC_PROC_RETURN",
     "Procedure return architecturally executed (not by exceptions)"},
    {0x0F, 0, "UNALIGNED_ACCESS",
     "Unaligned access architecturally executed"},
    {0x10, 0, "PC_BRANCH_MIS_PRED",
     "Branch mispredicted or not predicted. Counts pipeline flushes because of"
     "misprediction"},
    {0x12, 0, "PC_BRANCH_MIS_USED",
    "Branch or change in program flow that could have been predicted"},
    {0x40, 0, "WRITE_BUFFER_FULL",
     "Any write buffer full cycle"},
    {0x41, 0, "L2_STORE_MERGED",
     "Any store that is merged in L2 cache"},
    {0x42, 0, "L2_STORE_BUFF",
     "Any bufferable store from load/store to L2 cache"},
    {0x43, 0, "L2_ACCESS",
     "Any access to L2 cache"},
    {0x44, 0, "L2_CACH_MISS",
     "Any cacheable miss in L2 cache"},
    {0x45, 0, "AXI_READ_CYCLES",
     "Number of cycles for an active AXI read"},
    {0x46, 0, "AXI_WRITE_CYCLES",
     "Number of cycles for an active AXI write"},
    {0x47, 0, "MEMORY_REPLAY",
     "Any replay event in the memory subsystem"},
    {0x48, 0, "UNALIGNED_ACCESS_REPLAY",
     "Unaligned access that causes a replay"},
    {0x49, 0, "L1_DATA_MISS",
     "L1 data cache miss as a result of the hashing algorithm"},
    {0x4A, 0, "L1_INST_MISS",
     "L1 instruction cache miss as a result of the hashing algorithm"},
    {0x4B, 0, "L1_DATA_COLORING",
     "L1 data access in which a page coloring alias occurs"},
    {0x4C, 0, "L1_NEON_DATA",
     "NEON data access that hits L1 cache"},
    {0x4D, 0, "L1_NEON_CACH_DATA",
     "NEON cacheable data access that hits L1 cache"},
    {0x4E, 0, "L2_NEON",
     "L2 access as a result of NEON memory access"},
    {0x4F, 0, "L2_NEON_HIT",
     "Any NEON hit in L2 cache"},
    {0x50, 0, "L1_INST",
     "Any L1 instruction cache access, excluding CP15 cache accesses"},
    {0x51, 0, "PC_RETURN_MIS_PRED",
     "Return stack misprediction at return stack pop"
     "(incorrect target address)"},
    {0x52, 0, "PC_BRANCH_FAILED",
     "Branch prediction misprediction"},
    {0x53, 0, "PC_BRANCH_TAKEN",
     "Any predicted branch that is taken"},
    {0x54, 0, "PC_BRANCH_EXECUTED",
     "Any taken branch that is executed"},
    {0x55, 0, "OP_EXECUTED",
     "Number of operations executed"
     "(in instruction or mutli-cycle instruction)"},
    {0x56, 0, "CYCLES_INST_STALL",
     "Cycles where no instruction available"},
    {0x57, 0, "CYCLES_INST",
     "Number of instructions issued in a cycle"},
    {0x58, 0, "CYCLES_NEON_DATA_STALL",
     "Number of cycles the processor waits on MRC data from NEON"},
    {0x59, 0, "CYCLES_NEON_INST_STALL",
     "Number of cycles the processor waits on NEON instruction queue or"
     "NEON load queue"},
    {0x5A, 0, "NEON_CYCLES",
     "Number of cycles NEON and integer processors are not idle"},
    {0x70, 0, "PMU0_EVENTS",
     "Number of events from external input source PMUEXTIN[0]"},
    {0x71, 0, "PMU1_EVENTS",
     "Number of events from external input source PMUEXTIN[1]"},
    {0x72, 0, "PMU_EVENTS",
     "Number of events from both external input sources PMUEXTIN[0]"
     "and PMUEXTIN[1]"},
    {0xFF, 0, "CPU_CYCLES",
     "Number of CPU cycles"},
#endif
};

void usage()
{
    printf("\nopcontrol: usage:\n"
           "   --list-events    list event types\n"
           "   --help           this message\n"
           "   --verbose        show extra status\n"
           "   --verbose-log=lvl set daemon logging verbosity during setup\n"
           "                    levels are: all,sfile,arcs,samples,module,misc\n"
           "   --setup          setup directories\n"
#if defined(__i386__) || defined(__x86_64__)
           "   --quick          setup and select CPU_CLK_UNHALTED:60000\n"
#else
           "   --quick          setup and select CPU_CYCLES:150000\n"
#endif
           "   --timer          timer-based profiling\n"
           "   --status         show configuration\n"
           "   --start          start data collection\n"
           "   --stop           stop data collection\n"
           "   --reset          clears out data from current session\n"
           "   --shutdown       kill the oprofile daeman\n"
           "   --callgraph=depth callgraph depth\n"
           "   --event=eventspec\n"
           "      Choose an event. May be specified multiple times.\n"
           "      eventspec is in the form of name[:count], where :\n"
           "        name:  event name, see \"opcontrol --list-events\"\n"
           "        count: reset counter value\n" 
           "   --vmlinux=file   vmlinux kernel image\n"
           "   --kernel-range=start,end\n"
           "                    kernel range vma address in hexadecimal\n"
          );
}

void setup_session_dir()
{
    int fd;

    fd = open(OP_DATA_DIR, O_RDONLY);
    if (fd != -1) {
        system("rm -r "OP_DATA_DIR);
        close(fd);
    }

    if (mkdir(OP_DATA_DIR, 0755)) {
        fprintf(stderr, "Cannot create directory \"%s\": %s\n",
                OP_DATA_DIR, strerror(errno));
    }
    if (mkdir(OP_DATA_DIR"/samples", 0755)) {
        fprintf(stderr, "Cannot create directory \"%s\": %s\n",
                OP_DATA_DIR"/samples", strerror(errno));
    }
}

int read_num(const char* file)
{
    char buffer[256];
    int fd = open(file, O_RDONLY);
    if (fd<0) return -1;
    int rd = read(fd, buffer, sizeof(buffer)-1);
    buffer[rd] = 0;
    close(fd);
    return atoi(buffer);
}

int do_setup()
{
    char dir[1024];

    /*
     * Kill the old daemon so that setup can be done more than once to achieve
     * the same effect as reset.
     */
    int num = read_num(OP_DATA_DIR"/lock");
    if (num >= 0) {
        printf("Terminating the old daemon...\n");
        kill(num, SIGTERM);
        sleep(5);
    }

    setup_session_dir();

    if (mkdir(OP_DRIVER_BASE, 0755)) {
        if (errno != EEXIST) {
            fprintf(stderr, "Cannot create directory "OP_DRIVER_BASE": %s\n",
                    strerror(errno));
            return -1;
        }
    }

    if (access(OP_DRIVER_BASE"/stats", F_OK)) {
        if (system("mount -t oprofilefs nodev "OP_DRIVER_BASE)) {
            return -1;
        }
    }
    return 0;
}

void do_list_events()
{
    unsigned int i;

    printf("%-20s: %s\n", "name", "meaning");
    printf("----------------------------------------"
           "--------------------------------------\n");
    for (i = 0; i < sizeof(event_info)/sizeof(struct event_info); i++) {
        printf("%-20s: %s\n", event_info[i].name, event_info[i].explanation);
    }
}

int find_event_idx_from_name(const char *name)
{
    unsigned int i;

    for (i = 0; i < sizeof(event_info)/sizeof(struct event_info); i++) {
        if (!strcmp(name, event_info[i].name)) {
            return i;
        }
    }
    return -1;
}

const char * find_event_name_from_id(int id)
{
    unsigned int i;

    for (i = 0; i < sizeof(event_info)/sizeof(struct event_info); i++) {
        if (event_info[i].id == id) {
            return event_info[i].name;
        }
    }
    return NULL;
}

int process_event(const char *event_spec)
{
    char event_name[512];
    char count_name[512];
    unsigned int i;
    int event_idx;
    int count_val;

    strncpy(event_name, event_spec, 512);
    count_name[0] = 0;

    /* First, check if the name is followed by ":" */
    for (i = 0; i < strlen(event_name); i++) {
        if (event_name[i] == 0) {
            break;
        }
        if (event_name[i] == ':') {
            strncpy(count_name, event_name+i+1, 512);
            event_name[i] = 0;
            break;
        }
    }
    event_idx = find_event_idx_from_name(event_name);
    if (event_idx == -1) {
        fprintf(stderr, "Unknown event name: %s\n", event_name);
        return -1;
    }

    /* Use default count */
    if (count_name[0] == 0) {
        count_val = min_count[0];
    } else {
        count_val = atoi(count_name);
    }

    selected_events[num_events] = event_idx;
    selected_counts[num_events++] = count_val;
    verbose("event_id is %d\n", event_info[event_idx].id);
    verbose("count_val is %d\n", count_val);
    return 0;
}

int echo_dev(const char* str, int val, const char* file, int counter)
{
    char fullname[512];
    char content[128];
    int fd;
    
    if (counter >= 0) {
        snprintf(fullname, 512, OP_DRIVER_BASE"/%d/%s", counter, file);
    }
    else {
        snprintf(fullname, 512, OP_DRIVER_BASE"/%s", file);
    }
    fd = open(fullname, O_WRONLY);
    if (fd<0) {
        fprintf(stderr, "Cannot open %s: %s\n", fullname, strerror(errno));
        return fd;
    }
    if (str == 0) {
        sprintf(content, "%d", val);
    }
    else {
        strncpy(content, str, 128);
    }
    verbose("Configure %s (%s)\n", fullname, content);
    write(fd, content, strlen(content));
    close(fd);
    return 0;
}

void do_status()
{
    int num;
    char fullname[512];
    int i;

    printf("Driver directory: %s\n", OP_DRIVER_BASE);
    printf("Session directory: %s\n", OP_DATA_DIR);
    for (i = 0; i < MAX_EVENTS; i++) {
        sprintf(fullname, OP_DRIVER_BASE"/%d/enabled", i);
        num = read_num(fullname);
        if (num > 0) {
            printf("Counter %d:\n", i);

            /* event name */
            sprintf(fullname, OP_DRIVER_BASE"/%d/event", i);
            num = read_num(fullname);
            printf("    name: %s\n", find_event_name_from_id(num));

            /* profile interval */
            sprintf(fullname, OP_DRIVER_BASE"/%d/count", i);
            num = read_num(fullname);
            printf("    count: %d\n", num);
        }
        else {
            printf("Counter %d disabled\n", i);
        }
    }

    num = read_num(OP_DATA_DIR"/lock");
    if (num >= 0) {
        int fd;
        /* Still needs to check if this lock is left-over */
        sprintf(fullname, "/proc/%d", num);
        fd = open(fullname, O_RDONLY);
        if (fd == -1) {
            printf("OProfile daemon exited prematurely - redo setup"
                   " before you continue\n");
            return;
        }
        else {
            close(fd);

            printf("oprofiled pid: %d\n", num);
            num = read_num(OP_DRIVER_BASE"/enable");

            printf("profiler is%s running\n", num == 0 ? " not" : "");

            DIR* dir = opendir(OP_DRIVER_BASE"/stats");
            if (dir) {
                for (struct dirent* dirent; !!(dirent = readdir(dir));) {
                    if (strlen(dirent->d_name) >= 4 && memcmp(dirent->d_name, "cpu", 3) == 0) {
                        char cpupath[256];
                        strcpy(cpupath, OP_DRIVER_BASE"/stats/");
                        strcat(cpupath, dirent->d_name);

                        strcpy(fullname, cpupath);
                        strcat(fullname, "/sample_received");
                        num = read_num(fullname);
                        printf("  %s %9u samples received\n", dirent->d_name, num);

                        strcpy(fullname, cpupath);
                        strcat(fullname, "/sample_lost_overflow");
                        num = read_num(fullname);
                        printf("  %s %9u samples lost overflow\n", dirent->d_name, num);

                        strcpy(fullname, cpupath);
                        strcat(fullname, "/sample_invalid_eip");
                        num = read_num(fullname);
                        printf("  %s %9u samples invalid eip\n", dirent->d_name, num);

                        strcpy(fullname, cpupath);
                        strcat(fullname, "/backtrace_aborted");
                        num = read_num(fullname);
                        printf("  %s %9u backtrace aborted\n", dirent->d_name, num);
                    }
                }
                closedir(dir);
            }

            num = read_num(OP_DRIVER_BASE"/backtrace_depth");
            printf("backtrace_depth: %u\n", num);
        }
    }
    else {
        printf("oprofiled is not running\n");
    }
}

void do_reset() 
{
    /*
     * Sending SIGHUP will result in the following crash in oprofiled when
     * profiling subsequent runs:
     * Stack Trace:
     * RELADDR   FUNCTION                         FILE:LINE
     *   00008cd8  add_node+12                    oprofilelibdb/db_insert.c:32
     *   00008d69  odb_update_node_with_offset+60 oprofilelibdb/db_insert.c:102
     *
     * However without sending SIGHUP oprofile cannot be restarted successfully.
     * As a temporary workaround, change do_reset into a no-op for now and kill
     * the old daemon in do_setup to start all over again as a heavy-weight
     * reset.
     */
#if 0
    int fd;

    fd = open(OP_DATA_DIR"/samples/current", O_RDONLY);
    if (fd == -1) {
        return;
    }
    close(fd);
    system("rm -r "OP_DATA_DIR"/samples/current");
    int num = read_num(OP_DATA_DIR"/lock");

    if (num >= 0) {
        printf("Signalling daemon...\n");
        kill(num, SIGHUP);
    }
#endif
}

int main(int argc, char * const argv[])
{
    int option_index;
    bool show_status = false;
    char* verbose_log = NULL;

    /* Initialize default strings */
    strcpy(vmlinux, "--no-vmlinux");
    strcpy(kernel_range, "");

    while (1) {
        int c = getopt_long(argc, argv, "c:e:v:r:dhVtl:", long_options, &option_index);
        if (c == -1) {
            break;
        }
        switch (c) {
            case 0:
                break;
            /* --callgraph */
            case 'c':
                strncpy(callgraph, optarg, sizeof(callgraph));
                break;
            /* --event */
            case 'e':   
                if (num_events == MAX_EVENTS) {
                    fprintf(stderr, "More than %d events specified\n",
                            MAX_EVENTS);
                    exit(1);
                }
                if (process_event(optarg)) {
                    exit(1);
                }
                break;
            /* --vmlinux */
            case 'v':
                sprintf(vmlinux, "-k %s", optarg);
                break;
            /* --kernel-range */
            case 'r':
                sprintf(kernel_range, "-r %s", optarg);
                break;
            case 'd':
            /* --dump */ {
                int pid = read_num(OP_DATA_DIR"/lock");
                echo_dev("1", 0, "dump", -1);
                break;
            }
            /* --shutdown */
            case 'h': {
                int pid = read_num(OP_DATA_DIR"/lock");
                if (pid >= 0) {
                    kill(pid, SIGHUP); /* Politely ask the daemon to close files */
                    sleep(1);
                    kill(pid, SIGTERM);/* Politely ask the daemon to die */
                    sleep(1);
                    kill(pid, SIGKILL);
                }
                setup_session_dir();
                break;
            }
            /* --verbose */
            case 'V':
                verbose_print++;
                break;
            /* --verbose-log */
            case 'l':
                verbose_log = strdup(optarg);
                break;
            /* --status */
            case 't':
                show_status = true;
                break;
            default:
                usage();
                exit(1);
        }
    }
    verbose("list_events = %d\n", list_events);
    verbose("setup = %d\n", setup);

    if (list_events) {
        do_list_events();
    }

    if (quick) {
#if defined(__i386__) || defined(__x86_64__)
        process_event("CPU_CLK_UNHALTED");
#else
        process_event("CPU_CYCLES");
#endif
        setup = 1;
    }

    if (timer) {
        setup = 1;
    }

    if (reset) {
        do_reset();
    }

    if (show_usage) {
        usage();
    }

    if (setup) {
        if (do_setup()) {
            fprintf(stderr, "do_setup failed");
            exit(1);
        }
    }

    if (strlen(callgraph)) {
        echo_dev(callgraph, 0, "backtrace_depth", -1);
    }

    if (num_events != 0 || timer != 0) {
        char command[1024];
        int i;

        strcpy(command, argv[0]);
        char* slash = strrchr(command, '/');
        strcpy(slash ? slash + 1 : command, "oprofiled --session-dir="OP_DATA_DIR);

#if defined(__i386__) || defined(__x86_64__)
        /* Nothing */
#elif !defined(WITH_ARM_V7_A)
        /* Since counter #3 can only handle CPU_CYCLES, check and shuffle the 
         * order a bit so that the maximal number of events can be profiled
         * simultaneously
         */
        if (num_events == 3) {
            for (i = 0; i < num_events; i++) {
                int event_idx = selected_events[i];

                if (event_info[event_idx].id == 0xff) {
                    break;
                }
            }

            /* No CPU_CYCLES is found */
            if (i == 3) {
                fprintf(stderr, "You can only specify three events if one of "
                                "them is CPU_CYCLES\n");
                exit(1);
            }
            /* Swap CPU_CYCLES to counter #2 (starting from #0)*/
            else if (i != 2) {
                int temp;

                temp = selected_events[2];
                selected_events[2] = selected_events[i];
                selected_events[i] = temp;

                temp = selected_counts[2];
                selected_counts[2] = selected_counts[i];
                selected_counts[i] = temp;
            }
        }
#endif

        /* Configure the counters and enable them */
        for (i = 0; i < num_events; i++) {
            int event_idx = selected_events[i];
            int setup_result = 0;

            if (i == 0) {
                snprintf(command + strlen(command), sizeof(command) - strlen(command),
                        " --events=");
            } else {
                snprintf(command + strlen(command), sizeof(command) - strlen(command), ",");
            }
            /* Compose name:id:count:unit_mask:kernel:user, something like
             * --events=CYCLES_DATA_STALL:2:0:200000:0:1:1,....
             */
            snprintf(command + strlen(command), sizeof(command) - strlen(command),
                     "%s:%d:%d:%d:%d:1:1",
                     event_info[event_idx].name,
                     event_info[event_idx].id,
                     i,
                     selected_counts[i],
                     event_info[event_idx].um);

            setup_result |= echo_dev("1", 0, "user", i);
            setup_result |= echo_dev("1", 0, "kernel", i);
            setup_result |= echo_dev(NULL, event_info[event_idx].um, "unit_mask", i);
            setup_result |= echo_dev("1", 0, "enabled", i);
            setup_result |= echo_dev(NULL, selected_counts[i], "count", i);
            setup_result |= echo_dev(NULL, event_info[event_idx].id, 
                                     "event", i);
            if (setup_result) {
                fprintf(stderr, "Counter configuration failed for %s\n",
                        event_info[event_idx].name);
                fprintf(stderr, "Did you do \"opcontrol --setup\" first?\n");
                exit(1);
            }
        }

        if (timer == 0) {
            /* If not in timer mode, disable unused counters */
            for (i = num_events; i < MAX_EVENTS; i++) {
                echo_dev("0", 0, "enabled", i);
            }
        } else {
            /* Timer mode uses empty event list */
            snprintf(command + strlen(command), sizeof(command) - strlen(command),
                    " --events=");
        }

        snprintf(command + strlen(command), sizeof(command) - strlen(command),
                " %s", vmlinux);
        if (kernel_range[0]) {
            snprintf(command + strlen(command), sizeof(command) - strlen(command),
                    " %s", kernel_range);
        }

        if (verbose_log) {
            snprintf(command + strlen(command), sizeof(command) - strlen(command),
                    " --verbose=%s", verbose_log);
        }

        printf("Starting oprofiled...\n");
        verbose("command: %s\n", command);

        int rc = system(command);
        if (rc) {
            fprintf(stderr, "Failed, oprofile returned exit code: %d\n", rc);
        } else {
            sleep(2);
            printf("Ready\n");
        }
    }

    if (start) {
        echo_dev("1", 0, "enable", -1);
        int num = read_num(OP_DATA_DIR"/lock");

        if (num >= 0) {
            kill(num, SIGUSR1);
        }
    }

    if (stop) {
        echo_dev("1", 0, "dump", -1);
        echo_dev("0", 0, "enable", -1);
    }

    if (show_status) {
        do_status();
    }
}