C++程序  |  804行  |  22.9 KB

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

struct event_info {
    int id;
    int counters;
    int um;
    const char *name;
    const char *explanation;
};

#define CTR(n)  (1<<(n))

#if defined(__i386__) || defined(__x86_64__)
struct event_info event_info_arch_perfmon[] = {
    #include "../events/i386/arch_perfmon/events.h"
};

#define MAX_EVENTS 2
int min_count[MAX_EVENTS] = {60000, 100000};

const char *default_event = "CPU_CLK_UNHALTED";
#endif

#if defined(__arm__)
#if !defined(WITH_ARM_V7_A)
struct event_info event_info_armv6[] = {
    #include "../events/arm/armv6/events.h"
};

#define MAX_EVENTS 3
int min_count[MAX_EVENTS] = {150000, 200000, 250000};

#else
struct event_info event_info_armv7[] = {
    #include "../events/arm/armv7/events.h"
};

#define MAX_EVENTS 5
int min_count[MAX_EVENTS] = {150000, 20000, 25000, 30000, 35000};
#endif

const char *default_event = "CPU_CYCLES";
#endif

#if defined(__mips__)
struct event_info event_info_24K[] = {
    #include "../events/mips/24K/events.h"
};
struct event_info event_info_34K[] = {
    #include "../events/mips/34K/events.h"
};
struct event_info event_info_74K[] = {
    #include "../events/mips/74K/events.h"
};
struct event_info event_info_1004K[] = {
    #include "../events/mips/1004K/events.h"
};

#define MAX_EVENTS 4
int min_count[MAX_EVENTS] = {150000, 20000, 25000, 30000};

const char *default_event = "CYCLES";
#endif /* defined(__mips__) */

#define ARRAYSZ(x) (sizeof(x)/sizeof((x)[0]))

struct cpuevents {
    const char *cpu;
    struct event_info *event_info;
    unsigned int nevents;
} cpuevents[] = {
#if defined(__i386__) || defined(__x86_64__)
    {"i386/arch_perfmon", event_info_arch_perfmon, ARRAYSZ(event_info_arch_perfmon)},
#endif /* defined(__i386__) || defined(__x86_64__) */
#if defined(__arm__)
#if !defined(WITH_ARM_V7_A)
    {"arm/armv6", event_info_armv6, ARRAYSZ(event_info_armv6)},
#else
    {"arm/armv7", event_info_armv7, ARRAYSZ(event_info_armv7)},
#endif
#endif /* defined(__arm__) */
#if defined(__mips__)
    {"mips/24K", event_info_24K, ARRAYSZ(event_info_24K)},
    {"mips/34K", event_info_34K, ARRAYSZ(event_info_34K)},
    {"mips/74K", event_info_74K, ARRAYSZ(event_info_74K)},
    {"mips/1004K", event_info_1004K, ARRAYSZ(event_info_1004K)},
#endif /* defined(__mips__) */
};

struct cpuevents *cpuevent;
#define event_info cpuevent->event_info
#define NEVENTS cpuevent->nevents

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];
int 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},
};


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"
#elif defined(__arm__)
           "   --quick          setup and select CPU_CYCLES:150000\n"
#elif defined(__mips__)
           "   --quick          setup and select 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 daemon\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"
          );
}

int setup_device(void)
{
    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;
        }
    }

    /* Selecting the event information by cpu_type has only been tested on MIPS */
#if defined(__mips__)
    /* Use cpu_type to select the events */
    int fd = open(OP_DRIVER_BASE "/cpu_type", O_RDONLY);
    if (fd < 0) {
	fprintf(stderr, OP_DRIVER_BASE "/cpu_type: %s\n",
		strerror(errno));
	return -1;
    }

    char buf[512];
    int n = read(fd, buf, sizeof(buf)-1);
    close(fd);
    if (n < 0) {
	fprintf(stderr, OP_DRIVER_BASE "/cpu_type: %s\n",
		strerror(errno));
	return -1;
    }
    buf[n] = '\0';
    for (unsigned int i = 0; i < ARRAYSZ(cpuevents); i++) {
	if (strcmp(buf, cpuevents[i].cpu) == 0) {
	    cpuevent = &cpuevents[i];
	}
    }
    if (cpuevent == NULL) {
	fprintf(stderr, "Unrecognised CPU type %s\n", buf);
	return -1;
    }
    for (max_events = 0; max_events < MAX_EVENTS; max_events++) {
	snprintf(buf, sizeof(buf), OP_DRIVER_BASE"/%d", max_events);
	if (access(buf, F_OK) < 0)
	    break;
    }
#else
    max_events = MAX_EVENTS;
    cpuevent = &cpuevents[0];
#endif
    return 0;
}

void setup_session_dir()
{
    if (access(OP_DATA_DIR, F_OK) == 0)
        system("rm -r "OP_DATA_DIR);

    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();

    return 0;
}

void stringify_counters(char *ctr_string, int ctr_mask)
{
    int i, n, len;
    char *p = ctr_string;

    *p = '\0';
    for (i=0; i<32; ++i) {
        if (ctr_mask & (1<<i)) {
	    p += sprintf(p, "%d,", i);
	}
    }
    if (p != ctr_string) {
        *(p-1) = '\0';  /* erase the final comma */
    }
}

void do_list_events()
{
    unsigned int i;
    char ctrs[32*3+1];

    printf("%-12s | %-30s: %s\n", "counter", "name", "meaning");
    printf("----------------------------------------"
           "--------------------------------------\n");
    for (i = 0; i < NEVENTS; i++) {
        stringify_counters(ctrs, event_info[i].counters);
        printf("%-12s | %-30s: %s\n", ctrs, event_info[i].name, event_info[i].explanation);
    }
}

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

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

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

    for (i = 0; i < NEVENTS; i++) {
	if (event_info[i].id == id && (event_info[i].counters == 0 || (event_info[i].counters & mask))) {
            return event_info[i].name;
        }
    }
    return "Undefined Event";
}

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;
    }

    /*
     * check that the named event is valid for this event counter
     * 'num_events' represents the cpu internal counter number
     */
    verbose("idx: %d, name: %s, mask: %02x, ctr#: %d\n",
            event_idx, event_info[event_idx].name,
            event_info[event_idx].counters, num_events);
    if (event_info[event_idx].counters != 0 &&
	(event_info[event_idx].counters & CTR(num_events)) == 0) {
	fprintf(stderr, "Bad event name: %s for counter %d, see --list\n",
		event_name, num_events);
	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, CTR(i)));

            /* 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) {
        /* Still needs to check if this lock is left-over */
        sprintf(fullname, "/proc/%d", num);
        if (access(fullname, R_OK) != 0) {
            printf("OProfile daemon exited prematurely - redo setup"
                   " before you continue\n");
            return;
        }
        else {

            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 pid = read_num(OP_DATA_DIR"/lock");
    if (pid >= 0)
        kill(pid, SIGHUP);  /* HUP makes oprofiled close its sample files */

    if (access(OP_DATA_DIR"/samples/current", R_OK) == 0)
      system("rm -r "OP_DATA_DIR"/samples/current");
#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, "");

    setup_device();

    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) {
        process_event(default_event);
        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(__arm__) && !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();
    }
}