| /** |
| * @file daemon/oprofiled.c |
| * Initialisation and setup |
| * |
| * @remark Copyright 2002, 2003 OProfile authors |
| * @remark Read the file COPYING |
| * |
| * @author John Levon |
| * @author Philippe Elie |
| * Modified by Aravind Menon for Xen |
| * These modifications are: |
| * Copyright (C) 2005 Hewlett-Packard Co. |
| */ |
| |
| #include "config.h" |
| |
| #include "oprofiled.h" |
| #include "opd_printf.h" |
| #include "opd_events.h" |
| #include "opd_extended.h" |
| |
| #include "op_config.h" |
| #include "op_version.h" |
| #include "op_hw_config.h" |
| #include "op_libiberty.h" |
| #include "op_file.h" |
| #include "op_abi.h" |
| #include "op_string.h" |
| #include "op_cpu_type.h" |
| #include "op_popt.h" |
| #include "op_lockfile.h" |
| #include "op_list.h" |
| #include "op_fileio.h" |
| |
| #include <sys/types.h> |
| #include <sys/resource.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <dirent.h> |
| #include <limits.h> |
| |
| sig_atomic_t signal_alarm; |
| sig_atomic_t signal_hup; |
| sig_atomic_t signal_term; |
| sig_atomic_t signal_child; |
| sig_atomic_t signal_usr1; |
| sig_atomic_t signal_usr2; |
| |
| uint op_nr_counters; |
| op_cpu cpu_type; |
| int no_event_ok; |
| int vsfile; |
| int vsamples; |
| int varcs; |
| int vmodule; |
| int vmisc; |
| int vext; |
| int separate_lib; |
| int separate_kernel; |
| int separate_thread; |
| int separate_cpu; |
| int no_vmlinux; |
| char * vmlinux; |
| char * kernel_range; |
| char * session_dir; |
| int no_xen; |
| char * xenimage; |
| char * xen_range; |
| static char * verbose; |
| static char * binary_name_filter; |
| static char * events; |
| static char * ext_feature; |
| static int showvers; |
| static struct oprofiled_ops * opd_ops; |
| extern struct oprofiled_ops opd_24_ops; |
| extern struct oprofiled_ops opd_26_ops; |
| |
| #define OPD_IMAGE_FILTER_HASH_SIZE 32 |
| static struct list_head images_filter[OPD_IMAGE_FILTER_HASH_SIZE]; |
| |
| static struct poptOption options[] = { |
| { "session-dir", 0, POPT_ARG_STRING, &session_dir, 0, "place sample database in dir instead of default location", "/var/lib/oprofile", }, |
| { "kernel-range", 'r', POPT_ARG_STRING, &kernel_range, 0, "Kernel VMA range", "start-end", }, |
| { "vmlinux", 'k', POPT_ARG_STRING, &vmlinux, 0, "vmlinux kernel image", "file", }, |
| { "no-vmlinux", 0, POPT_ARG_NONE, &no_vmlinux, 0, "vmlinux kernel image file not available", NULL, }, |
| { "xen-range", 0, POPT_ARG_STRING, &xen_range, 0, "Xen VMA range", "start-end", }, |
| { "xen-image", 0, POPT_ARG_STRING, &xenimage, 0, "Xen image", "file", }, |
| { "image", 0, POPT_ARG_STRING, &binary_name_filter, 0, "image name filter", "profile these comma separated image" }, |
| { "separate-lib", 0, POPT_ARG_INT, &separate_lib, 0, "separate library samples for each distinct application", "[0|1]", }, |
| { "separate-kernel", 0, POPT_ARG_INT, &separate_kernel, 0, "separate kernel samples for each distinct application", "[0|1]", }, |
| { "separate-thread", 0, POPT_ARG_INT, &separate_thread, 0, "thread-profiling mode", "[0|1]" }, |
| { "separate-cpu", 0, POPT_ARG_INT, &separate_cpu, 0, "separate samples for each CPU", "[0|1]" }, |
| { "events", 'e', POPT_ARG_STRING, &events, 0, "events list", "[events]" }, |
| { "version", 'v', POPT_ARG_NONE, &showvers, 0, "show version", NULL, }, |
| { "verbose", 'V', POPT_ARG_STRING, &verbose, 0, "be verbose in log file", "all,sfile,arcs,samples,module,misc", }, |
| { "ext-feature", 'x', POPT_ARG_STRING, &ext_feature, 1, "enable extended feature", "<extended-feature-name>:[args]", }, |
| POPT_AUTOHELP |
| { NULL, 0, 0, NULL, 0, NULL, NULL, }, |
| }; |
| |
| |
| void opd_open_logfile(void) |
| { |
| if (open(op_log_file, O_WRONLY|O_CREAT|O_NOCTTY|O_APPEND, 0644) == -1) { |
| perror("oprofiled: couldn't re-open stdout: "); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (dup2(1, 2) == -1) { |
| perror("oprofiled: couldn't dup stdout to stderr: "); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| |
| /** |
| * opd_fork - fork and return as child |
| * |
| * fork() and exit the parent with _exit(). |
| * Failure is fatal. |
| */ |
| static void opd_fork(void) |
| { |
| switch (fork()) { |
| case -1: |
| perror("oprofiled: fork() failed: "); |
| exit(EXIT_FAILURE); |
| break; |
| case 0: |
| break; |
| default: |
| /* parent */ |
| _exit(EXIT_SUCCESS); |
| break; |
| } |
| } |
| |
| |
| static void opd_go_daemon(void) |
| { |
| opd_fork(); |
| |
| if (chdir(op_session_dir)) { |
| fprintf(stderr, "oprofiled: opd_go_daemon: couldn't chdir to %s: %s", |
| op_session_dir, strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (setsid() < 0) { |
| perror("oprofiled: opd_go_daemon: couldn't setsid: "); |
| exit(EXIT_FAILURE); |
| } |
| |
| opd_fork(); |
| } |
| |
| |
| static void opd_write_abi(void) |
| { |
| char * cbuf; |
| |
| cbuf = xmalloc(strlen(op_session_dir) + 5); |
| strcpy(cbuf, op_session_dir); |
| strcat(cbuf, "/abi"); |
| op_write_abi_to_file(cbuf); |
| free(cbuf); |
| } |
| |
| |
| /** |
| * opd_alarm - sync files and report stats |
| */ |
| static void opd_alarm(int val __attribute__((unused))) |
| { |
| signal_alarm = 1; |
| } |
| |
| |
| /* re-open logfile for logrotate */ |
| static void opd_sighup(int val __attribute__((unused))) |
| { |
| signal_hup = 1; |
| } |
| |
| |
| static void opd_sigterm(int val __attribute__((unused))) |
| { |
| signal_term = 1; |
| } |
| |
| static void opd_sigchild(int val __attribute__((unused))) |
| { |
| signal_child = 1; |
| } |
| |
| |
| static void opd_sigusr1(int val __attribute__((unused))) |
| { |
| signal_usr1 = 1; |
| } |
| |
| |
| static void opd_sigusr2(int val __attribute__((unused))) |
| { |
| signal_usr2 = 1; |
| } |
| |
| |
| static void opd_setup_signals(void) |
| { |
| struct sigaction act; |
| |
| act.sa_handler = opd_alarm; |
| act.sa_flags = 0; |
| sigemptyset(&act.sa_mask); |
| |
| if (sigaction(SIGALRM, &act, NULL)) { |
| perror("oprofiled: install of SIGALRM handler failed: "); |
| exit(EXIT_FAILURE); |
| } |
| |
| act.sa_handler = opd_sighup; |
| act.sa_flags = 0; |
| sigemptyset(&act.sa_mask); |
| sigaddset(&act.sa_mask, SIGALRM); |
| |
| if (sigaction(SIGHUP, &act, NULL)) { |
| perror("oprofiled: install of SIGHUP handler failed: "); |
| exit(EXIT_FAILURE); |
| } |
| |
| act.sa_handler = opd_sigterm; |
| act.sa_flags = 0; |
| sigemptyset(&act.sa_mask); |
| sigaddset(&act.sa_mask, SIGTERM); |
| |
| if (sigaction(SIGTERM, &act, NULL)) { |
| perror("oprofiled: install of SIGTERM handler failed: "); |
| exit(EXIT_FAILURE); |
| } |
| |
| act.sa_handler = opd_sigchild; |
| act.sa_flags = 0; |
| sigemptyset(&act.sa_mask); |
| sigaddset(&act.sa_mask, SIGCHLD); |
| |
| if (sigaction(SIGCHLD, &act, NULL)) { |
| perror("oprofiled: install of SIGCHLD handler failed: "); |
| exit(EXIT_FAILURE); |
| } |
| |
| act.sa_handler = opd_sigusr1; |
| act.sa_flags = 0; |
| sigemptyset(&act.sa_mask); |
| sigaddset(&act.sa_mask, SIGTERM); |
| |
| if (sigaction(SIGUSR1, &act, NULL)) { |
| perror("oprofiled: install of SIGUSR1 handler failed: "); |
| exit(EXIT_FAILURE); |
| } |
| |
| act.sa_handler = opd_sigusr2; |
| act.sa_flags = 0; |
| sigemptyset(&act.sa_mask); |
| sigaddset(&act.sa_mask, SIGTERM); |
| |
| if (sigaction(SIGUSR2, &act, NULL)) { |
| perror("oprofiled: install of SIGUSR2 handler failed: "); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| |
| struct opd_hashed_name { |
| char * name; |
| struct list_head next; |
| }; |
| |
| |
| static void add_image_filter(char const * name) |
| { |
| size_t hash; |
| struct opd_hashed_name * elt = xmalloc(sizeof(struct opd_hashed_name)); |
| elt->name = xmalloc(PATH_MAX); |
| if (!realpath(name, elt->name)) { |
| free(elt->name); |
| free(elt); |
| return; |
| } |
| hash = op_hash_string(elt->name); |
| verbprintf(vmisc, "Adding to image filter: \"%s\"\n", elt->name); |
| list_add(&elt->next, &images_filter[hash % OPD_IMAGE_FILTER_HASH_SIZE]); |
| } |
| |
| |
| static void opd_parse_image_filter(void) |
| { |
| size_t i; |
| char const * last = binary_name_filter; |
| char const * cur = binary_name_filter; |
| |
| if (!binary_name_filter) |
| return; |
| |
| for (i = 0; i < OPD_IMAGE_FILTER_HASH_SIZE; ++i) |
| list_init(&images_filter[i]); |
| |
| while ((cur = strchr(last, ',')) != NULL) { |
| char * tmp = op_xstrndup(last, cur - last); |
| add_image_filter(tmp); |
| free(tmp); |
| last = cur + 1; |
| } |
| add_image_filter(last); |
| } |
| |
| |
| int is_image_ignored(char const * name) |
| { |
| size_t hash; |
| struct list_head * pos; |
| |
| if (!binary_name_filter) |
| return 0; |
| |
| hash = op_hash_string(name); |
| |
| list_for_each(pos, &images_filter[hash % OPD_IMAGE_FILTER_HASH_SIZE]) { |
| struct opd_hashed_name * hashed_name = |
| list_entry(pos, struct opd_hashed_name, next); |
| if (!strcmp(hashed_name->name, name)) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| |
| /** return the int in the given oprofilefs file */ |
| int opd_read_fs_int(char const * path, char const * name, int fatal) |
| { |
| char filename[PATH_MAX + 1]; |
| snprintf(filename, PATH_MAX, "%s/%s", path, name); |
| return op_read_int_from_file(filename, fatal); |
| } |
| |
| |
| static void opd_handle_verbose_option(char const * name) |
| { |
| if (!strcmp(name, "all")) { |
| vsfile = 1; |
| vsamples = 1; |
| varcs = 1; |
| vmodule = 1; |
| vmisc = 1; |
| vext= 1; |
| } else if (!strcmp(name, "sfile")) { |
| vsfile = 1; |
| } else if (!strcmp(name, "arcs")) { |
| varcs = 1; |
| } else if (!strcmp(name, "samples")) { |
| vsamples = 1; |
| } else if (!strcmp(name, "module")) { |
| vmodule = 1; |
| } else if (!strcmp(name, "misc")) { |
| vmisc = 1; |
| } else if (!strcmp(name, "ext")) { |
| vext= 1; |
| } else { |
| fprintf(stderr, "unknown verbose options\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| static void opd_parse_verbose(void) |
| { |
| char const * last = verbose; |
| char const * cur = verbose; |
| |
| if (!verbose) |
| return; |
| |
| while ((cur = strchr(last, ',')) != NULL) { |
| char * tmp = op_xstrndup(last, cur - last); |
| opd_handle_verbose_option(tmp); |
| free(tmp); |
| last = cur + 1; |
| } |
| opd_handle_verbose_option(last); |
| } |
| |
| |
| static void opd_options(int argc, char const * argv[]) |
| { |
| poptContext optcon; |
| char * tmp; |
| |
| optcon = op_poptGetContext(NULL, argc, argv, options, 0); |
| |
| if (showvers) |
| show_version(argv[0]); |
| |
| opd_parse_verbose(); |
| |
| if (separate_kernel) |
| separate_lib = 1; |
| |
| cpu_type = op_get_cpu_type(); |
| op_nr_counters = op_get_nr_counters(cpu_type); |
| |
| if (!no_vmlinux) { |
| if (!vmlinux || !strcmp("", vmlinux)) { |
| fprintf(stderr, "oprofiled: no vmlinux specified.\n"); |
| poptPrintHelp(optcon, stderr, 0); |
| exit(EXIT_FAILURE); |
| } |
| |
| /* canonicalise vmlinux filename. fix #637805 */ |
| tmp = xmalloc(PATH_MAX); |
| if (realpath(vmlinux, tmp)) |
| vmlinux = tmp; |
| else |
| free(tmp); |
| |
| if (!kernel_range || !strcmp("", kernel_range)) { |
| fprintf(stderr, "oprofiled: no kernel VMA range specified.\n"); |
| poptPrintHelp(optcon, stderr, 0); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| if(opd_ext_initialize(ext_feature) != EXIT_SUCCESS) |
| exit(EXIT_FAILURE); |
| |
| if (events == NULL && no_event_ok == 0) { |
| fprintf(stderr, "oprofiled: no events specified.\n"); |
| poptPrintHelp(optcon, stderr, 0); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (!xenimage || !strcmp("", xenimage)) { |
| no_xen = 1; |
| } else { |
| no_xen = 0; |
| |
| /* canonicalise xen image filename. */ |
| tmp = xmalloc(PATH_MAX); |
| if (realpath(xenimage, tmp)) |
| xenimage = tmp; |
| else |
| free(tmp); |
| |
| if (!xen_range || !strcmp("", xen_range)) { |
| fprintf(stderr, "oprofiled: no Xen VMA range specified.\n"); |
| poptPrintHelp(optcon, stderr, 0); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| if (events != NULL) |
| opd_parse_events(events); |
| |
| opd_parse_image_filter(); |
| |
| poptFreeContext(optcon); |
| } |
| |
| |
| /* determine what kernel we're running and which daemon |
| * to use |
| */ |
| static struct oprofiled_ops * get_ops(void) |
| { |
| switch (op_get_interface()) { |
| case OP_INTERFACE_24: |
| printf("Using 2.4 OProfile kernel interface.\n"); |
| return &opd_24_ops; |
| case OP_INTERFACE_26: |
| printf("Using 2.6+ OProfile kernel interface.\n"); |
| return &opd_26_ops; |
| default: |
| break; |
| } |
| |
| fprintf(stderr, "Couldn't determine kernel version.\n"); |
| exit(EXIT_FAILURE); |
| return NULL; |
| } |
| |
| |
| int main(int argc, char const * argv[]) |
| { |
| int err; |
| struct rlimit rlim = { 2048, 2048 }; |
| |
| opd_options(argc, argv); |
| init_op_config_dirs(session_dir); |
| |
| opd_setup_signals(); |
| |
| err = setrlimit(RLIMIT_NOFILE, &rlim); |
| if (err) |
| perror("warning: could not set RLIMIT_NOFILE to 2048: "); |
| |
| opd_write_abi(); |
| |
| opd_ops = get_ops(); |
| |
| opd_ops->init(); |
| |
| opd_go_daemon(); |
| |
| /* clean up every 10 minutes */ |
| alarm(60 * 10); |
| |
| if (op_write_lock_file(op_lock_file)) { |
| fprintf(stderr, "oprofiled: could not create lock file %s\n", |
| op_lock_file); |
| exit(EXIT_FAILURE); |
| } |
| |
| opd_ops->start(); |
| |
| opd_ops->exit(); |
| |
| return 0; |
| } |