X-Git-Url: https://git.saurik.com/apple/system_cmds.git/blobdiff_plain/1a7e3f61d38d679bba59130891c2031b5a0092b6..bd6521f0fc816ab056bc71376f9706a69b3b52c1:/kdprof/kdprof.cpp diff --git a/kdprof/kdprof.cpp b/kdprof/kdprof.cpp new file mode 100644 index 0000000..cd14502 --- /dev/null +++ b/kdprof/kdprof.cpp @@ -0,0 +1,444 @@ +// +// main.cpp +// kdprof +// +// Created by James McIlree on 4/15/13. +// Copyright (c) 2013 Apple. All rights reserved. +// + +#include "global.h" + +// Generated by agvtool +extern const unsigned char __kdprofVersionString[]; + +bool shouldPrintVersion = false; + +__attribute__((noreturn)) void usage(const char *errorMsg) { + if (errorMsg) { + fprintf(stderr, "%s\n", errorMsg); + exit(1); + } + + const char* BOLD = "\033[1m"; + const char* UNBOLD = "\033[0m"; + + // printf("01234567890123456789012345678901234567890123456789012345678901234567890123456789\n"); + printf("kdprof [options] [path/trace.codes ...] [path/data.trace ...]\n\n"); + printf(" GLOBAL OPTIONS\n\n"); + printf(" -h, --help Print this message\n"); + printf(" --version Print version info\n"); + printf(" -v, --verbose Print additional information\n"); + printf(" --presort-events Sort events before processing. IOP workaround\n"); + printf(" -N, --no-default-codes Do not read the default trace codes\n"); + printf("\n"); + printf(" LIVE TRACING OPTIONS\n\n"); + printf(" -i, --intialize [#] Initialize the trace buffer, with opt buf count\n"); + printf(" -r, --remove Remove the trace buffer\n"); + printf(" -n, --no-wrap Do not allow the trace buffer to wrap\n"); + printf(" -g, --print-kdbg-state Print the current kdbg state\n"); + printf(" -e, --enable Enable collection of events\n"); + printf(" -d, --disable Disable collection of events\n"); + printf(" -t, --collect Collect and print the trace buffer\n"); + printf(" --save path Collect and save the trace buffer to path\n"); + printf(" -S, --sleep # Wait for a specified interval\n"); + printf("\n"); + printf(" OUTPUT OPTIONS\n\n"); + printf(" -o, --output path Print output to path\n"); + printf(" --summary Print calculated data (default true)\n"); + printf(" --no-summary Do not print calculated data\n"); + printf(" --csv Print a csv formatted summary for use in numbers\n"); + printf(" --no-csv Do not print a csv formatted summary\n"); + printf(" --step # Step by # time units in summary output\n"); + printf(" --process Include per-process summary data\n"); + printf(" --no-process Do not include per-process summary data\n"); + printf(" --thread Include per-thread summary data\n"); + printf(" --cpu Include per-cpu summary data\n"); + printf(" --sort-by-cpu Sort process/thread lists by cpu usage\n"); + printf(" --sort-by-vmfault Sort process/thread lists by vmfault time\n"); + printf(" --sort-by-io-wait Sort process/thread lists by IO time\n"); + printf(" --sort-by-io-ops Sort process/thread lists by # IO Ops\n"); + printf(" --sort-by-io-size Sort process/thread lists by IO bytes\n"); + printf(" --sort-by-pid Sort process/thread lists by pid/tid\n"); + printf(" --events Enable individual event printing\n"); + printf(" --no-events Disable individual event printing\n"); + printf(" --raw-timestamps Print timestamps as raw values, not deltas\n"); + printf(" --mach-absolute-time Print timestamps in mach absolute time\n"); + printf(" --event-index Print the index of each event\n"); + printf(" --no-codes Print hex trace codes, not symbolic\n"); + printf(" --process-start-stop Print start/stop information about each process\n"); + printf("\n"); + printf(" DEPRECATED OPTIONS\n\n"); + printf(" -X, --k32 Trace data is from a 32 bit kernel\n"); + printf(" --k64 Trace data is from a 64 bit kernel\n"); + printf(" --codes path read trace codes from path\n"); + printf(" --trace path read trace data from path\n"); + printf(" --ios Treat data as coming from an iOS device\n"); + printf(" --timebase #/# Set the mach_timebase\n"); + printf(" --cpus # Set the # of cpus.\n"); + printf(" --iops # Set the # of iops.\n"); + printf("\n"); + printf(" OPTION ARGUMENTS\n\n"); + printf(" All arguments that specifiy a time value may use the following postfixes\n\n"); + printf(" s Seconds\n"); + printf(" ms Milliseconds\n"); + printf(" us Microseconds\n"); + printf(" ns Nanoseconds\n"); + printf(" mabs Mach Absolute Time Units\n"); + printf("\n"); + // printf("01234567890123456789012345678901234567890123456789012345678901234567890123456789\n"); + printf(" USAGE\n"); + printf("\n"); + printf(" Arguments are parsed in order. Long form flags are not case sensitive.\n"); + printf(" Live tracing and trace file arguments are pushed onto an execution stack\n"); + printf(" and processed after argument parsing has completed.\n"); + printf("\n"); + printf(" Files ending in .trace or .codes may omit the --trace or --codes flag\n"); + printf(" In most cases, you do not need to specify a kernel size or timebase, it is\n"); + printf(" determined automatically.\n"); + printf("\n"); + printf(" Modern trace(s) have an embedded ap/iop cpu count. If you need to parse\n"); + printf(" an older file, you will want to set these. Typically you would set the AP\n"); + printf(" cpu count to the number of active cpus, and the IOP cpu count to zero.\n"); + printf("\n"); + printf(" EXAMPLES\n"); + printf("\n"); + printf(" %skdprof InterestingData.trace%s\n", BOLD, UNBOLD); + printf(" Print a summary of per process cpu usage in InterestingData.trace\n"); + printf("\n"); + printf(" %skdprof --step 100ms InterestingData.trace%s\n", BOLD, UNBOLD); + printf(" Print summaries of the per process cpu usage in InterestingData.trace,\n"); + printf(" one for each 100ms of time\n"); + printf("\n"); + printf(" %skdprof --thread --step 100ms InterestingData.trace%s\n", BOLD, UNBOLD); + printf(" Print summaries of the per process and per thread cpu usage in\n"); + printf(" InterestingData.trace, one for each 100ms of time\n"); + printf("\n"); + printf(" %skdprof -r -i 100000 -e -S 1 -d -t%s\n", BOLD, UNBOLD); + printf(" Reinit the trace buffer with 100000 entries, enable it, wait 1 second,\n"); + printf(" and then collect/print the trace buffer\n"); + printf("\n"); + printf(" %skdprof --events foo.trace%s\n", BOLD, UNBOLD); + printf(" Print the events in foo.trace\n"); + printf("\n"); + exit(1); +} + +static void add_trace_codes_path(const char* path, Globals& globals) { + if (Path::is_file(path, true)) { + char resolved_path[PATH_MAX]; + if (realpath(path, resolved_path)) { + globals.append_trace_codes_at_path(resolved_path); + return; + } + } + char* errmsg = NULL; + asprintf(&errmsg, "Trace codes path %s is not valid", path); + usage(errmsg); +} + +static std::unique_ptr create_trace_file_action(const char* trace_file_path) { + if (Path::is_file(trace_file_path, true)) { + char resolved_path[PATH_MAX]; + if (realpath(trace_file_path, resolved_path)) { + return std::make_unique(resolved_path); + } + } + char* errmsg = NULL; + asprintf(&errmsg, "Trace data path %s is not valid", trace_file_path); + usage(errmsg); +} + +// +// Must take globals so it can do the timebase conversions for mabs values! +// +static NanoTime parse_time(Globals& globals, const char* arg) { + + char* units; + uint64_t value = strtoull(arg, &units, 0); + + // Unspecified units are treated as seconds + if (*units == 0 || strcmp(units, "s") == 0) { + return NanoTime(value * NANOSECONDS_PER_SECOND); + } + + if (strcmp(units, "ms") == 0) + return NanoTime(value * NANOSECONDS_PER_MILLISECOND); + + if (strcmp(units, "us") == 0) + return NanoTime(value * NANOSECONDS_PER_MICROSECOND); + + if (strcmp(units, "ns") == 0) + return NanoTime(value); + + if (strcmp(units, "mabs") == 0) { + return AbsTime(value).nano_time(globals.timebase()); + } + + usage("Unable to parse units on time value"); +} + +static std::vector> parse_arguments(int argc, const char* argv[], Globals& globals) { + int i = 1; + bool cpus_set = false; + bool iops_set = false; + + std::vector> actions; + + while (i < argc) { + const char* arg = argv[i]; + if ((strcmp(arg, "-h") == 0) || (strcasecmp(arg, "--help") == 0)) { + usage(NULL); + } else if ((strcasecmp(arg, "--version") == 0)) { + shouldPrintVersion = true; + } else if ((strcmp(arg, "-v") == 0) || strcasecmp(arg, "--verbose") == 0) { + globals.set_is_verbose(true); + } else if ((strcasecmp(arg, "--summary") == 0)) { + globals.set_should_print_summary(true); + } else if ((strcasecmp(arg, "--no-summary") == 0)) { + globals.set_should_print_summary(false); + } else if ((strcasecmp(arg, "--csv") == 0)) { + globals.set_should_print_csv_summary(true); + } else if ((strcasecmp(arg, "--no-csv") == 0)) { + globals.set_should_print_csv_summary(false); + } else if (strcasecmp(arg, "--step") == 0) { + if (++i >= argc) + usage("--step requires an argument"); + + globals.set_summary_step(argv[i]); + // Force a blow up now if the arg is unparseable + globals.summary_step(AbsInterval(AbsTime(1), AbsTime(1))); + } else if (strcasecmp(arg, "--start") == 0) { + if (++i >= argc) + usage("--start requires an argument"); + + globals.set_summary_start(argv[i]); + // Force a blow up now if the arg is unparseable + globals.summary_start(AbsInterval(AbsTime(1), AbsTime(1))); + } else if (strcasecmp(arg, "--stop") == 0) { + if (++i >= argc) + usage("--stop requires an argument"); + + globals.set_summary_stop(argv[i]); + // Force a blow up now if the arg is unparseable + globals.summary_stop(AbsInterval(AbsTime(1), AbsTime(1))); + } else if ((strcasecmp(arg, "--cpu") == 0)) { + globals.set_should_print_cpu_summaries(true); + } else if ((strcasecmp(arg, "--processes") == 0) || (strcasecmp(arg, "--process") == 0)) { + globals.set_should_print_process_summaries(true); + } else if ((strcasecmp(arg, "--no-processes") == 0) || (strcasecmp(arg, "--no-process") == 0)) { + globals.set_should_print_process_summaries(false); + } else if ((strcasecmp(arg, "--threads") == 0) || (strcasecmp(arg, "--thread") == 0)) { + globals.set_should_print_thread_summaries(true); + } else if ((strcasecmp(arg, "--sort-by-cpu") == 0)) { + globals.set_sort_key(kSortKey::CPU); + } else if ((strcasecmp(arg, "--sort-by-pid") == 0)) { + globals.set_sort_key(kSortKey::ID); + } else if ((strcasecmp(arg, "--sort-by-vmfault") == 0)) { + globals.set_sort_key(kSortKey::VMFault); + } else if ((strcasecmp(arg, "--sort-by-io") == 0)) { + globals.set_sort_key(kSortKey::IO_Wait); + } else if ((strcasecmp(arg, "--sort-by-io-wait") == 0)) { + globals.set_sort_key(kSortKey::IO_Wait); + } else if ((strcasecmp(arg, "--sort-by-io-ops") == 0)) { + globals.set_sort_key(kSortKey::IO_Ops); + } else if ((strcasecmp(arg, "--sort-by-io-size") == 0)) { + globals.set_sort_key(kSortKey::IO_Size); + } else if ((strcasecmp(arg, "--events") == 0)) { + globals.set_should_print_events(true); + } else if ((strcasecmp(arg, "--no-events") == 0)) { + globals.set_should_print_events(false); + } else if ((strcasecmp(arg, "--presort-events") == 0)) { + globals.set_should_presort_events(true); + } else if ((strcmp(arg, "-N") == 0) || strcasecmp(arg, "--no-default-codes") == 0) { + globals.set_should_read_default_trace_codes(false); + } else if (strcasecmp(arg, "--codes") == 0) { + if (++i >= argc) + usage("--codes requires an argument"); + add_trace_codes_path(argv[i], globals); + } else if (strcasecmp(arg, "--trace") == 0) { + if (++i >= argc) + usage("--trace requires an argument"); + + actions.push_back(create_trace_file_action(argv[i])); + } else if ((strcmp(arg, "-i") == 0) || strcasecmp(arg, "--initialize") == 0) { + // The buffers argument is optional + uint32_t buffers_default = 0; + + if (i + 1 < argc) { + arg = argv[i+1]; + char* endptr; + uint32_t temp = (uint32_t)strtoul(arg, &endptr, 0); + if (*endptr == 0) { + // Consume the following argument if the conversion worked + buffers_default = temp; + i++; + } + + } + actions.push_back(std::make_unique(buffers_default)); + } else if ((strcmp(arg, "-r") == 0) || strcasecmp(arg, "--remove") == 0) { + actions.push_back(std::make_unique()); + } else if ((strcmp(arg, "-n") == 0) || strcasecmp(arg, "--no-wrap") == 0) { + actions.push_back(std::make_unique()); + } else if ((strcmp(arg, "-g") == 0) || strcasecmp(arg, "--print-kdbg-state") == 0) { + actions.push_back(std::make_unique()); + } else if ((strcmp(arg, "-e") == 0) || strcasecmp(arg, "--enable") == 0) { + actions.push_back(std::make_unique()); + } else if ((strcmp(arg, "-d") == 0) || strcasecmp(arg, "--disable") == 0) { + actions.push_back(std::make_unique()); + } else if ((strcmp(arg, "-t") == 0) || strcasecmp(arg, "--collect") == 0) { + actions.push_back(std::make_unique()); + } else if (strcasecmp(arg, "--save") == 0) { + if (++i >= argc) + usage("--save requires an argument"); + + FileDescriptor desc(argv[i], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (!desc.is_open()) { + char* errmsg = NULL; + asprintf(&errmsg, "Unable to create save file at %s", argv[i]); + usage(errmsg); + } + actions.push_back(std::make_unique(std::move(desc))); + } else if ((strcmp(arg, "-S") == 0) || strcasecmp(arg, "--sleep") == 0) { + if (++i >= argc) + usage("--sleep requires an argument"); + + actions.push_back(std::make_unique(parse_time(globals, argv[i]))); + } else if (strcasecmp(arg, "--ios") == 0) { + globals.set_timebase({ 125, 3 }, true); + /* + if (!cpus_set && !iops_set) { + globals.set_default_cpu_count(2); // Good guess for most devices + globals.set_default_iop_count(4); // Pure speculation... + }*/ + } else if ((strcmp(arg, "-X") == 0) || strcasecmp(arg, "--k32") == 0) { + globals.set_kernel_size(KernelSize::k32); + } else if (strcasecmp(arg, "--k64") == 0) { + globals.set_kernel_size(KernelSize::k64); + } else if (strcasecmp(arg, "--timebase") == 0) { + if (++i >= argc) + usage("--timebase requires an argument"); + arg = argv[i]; + + mach_timebase_info_data_t timebase; + if (sscanf(arg, "%u/%u", &timebase.numer, &timebase.denom) != 2) { + usage("Unable to parse --timebase argument"); + } + globals.set_timebase(timebase, true); + } else if (strcasecmp(arg, "--cpus") == 0) { + cpus_set = true; + if (++i >= argc) + usage("--cpus requires an argument"); + char* endptr; + uint32_t cpus = (uint32_t)strtoul(argv[i], &endptr, 0); + if (*endptr != 0) + usage("Unable to parse --cpus argument"); + globals.set_cpu_count(cpus); + } else if (strcasecmp(arg, "--iops") == 0) { + iops_set = true; + if (++i >= argc) + usage("--iops requires an argument"); + char* endptr; + uint32_t iops = (uint32_t)strtoul(argv[i], &endptr, 0); + if (*endptr != 0) + usage("Unable to parse --iops argument"); + globals.set_iop_count(iops); + } else if (strcasecmp(arg, "--raw-timestamps") == 0) { + globals.set_should_zero_base_timestamps(false); + } else if (strcasecmp(arg, "--mach-absolute-time") == 0) { + globals.set_should_print_mach_absolute_timestamps(true); + } else if (strcasecmp(arg, "--event-index") == 0) { + globals.set_should_print_event_index(true); + } else if (strcasecmp(arg, "--no-codes") == 0) { + globals.set_should_print_symbolic_event_codes(false); + } else if ((strcasecmp(arg, "--process-start-stop") == 0) || (strcasecmp(arg, "--process-start-stops") == 0)) { + globals.set_should_print_process_start_stop_timestamps(true); + } else if ((strcmp(arg, "-o") == 0) || strcasecmp(arg, "--output") == 0) { + if (++i >= argc) + usage("--output requires an argument"); + + FileDescriptor desc(argv[i], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (!desc.is_open()) { + char* errmsg = NULL; + asprintf(&errmsg, "Unable to create output file at %s", argv[i]); + usage(errmsg); + } + globals.set_output_fd(std::move(desc)); + } else { + // + // Last attempts to divine argument type/intent. + // + std::string temp(arg); + + // Is it a .codes file? + if (ends_with(temp, ".codes")) { + add_trace_codes_path(arg, globals); + goto no_error; + } + + if (ends_with(temp, ".trace")) { + actions.push_back(create_trace_file_action(argv[i])); + goto no_error; + } + + // + // ERROR! + // + char error_buffer[PATH_MAX]; + snprintf(error_buffer, sizeof(error_buffer), "Unhandled argument: %s", arg); + usage(error_buffer); + } + no_error: + + i++; + } + + return actions; +} + +int main (int argc, const char * argv[]) +{ + // + // Use host values as defaults. + // User overrides as needed via flags. + // + Globals globals; + auto actions = parse_arguments(argc, argv, globals); + + if (shouldPrintVersion) { + printf("%s version: %s", argv[0], __kdprofVersionString); + exit(0); + } + + globals.resolve_trace_codes(); + + // 0x24000004 PPT_test + + // Validate start/stop, if they are both set. + // + // The timebase isn't set for the tracefile at this point. This + // can sometimes fail when using a desktop timebase and mixed + // units (ms & mabs, for example) + if (globals.is_summary_start_set() && globals.is_summary_stop_set()) { + AbsInterval checker(AbsTime(1), AbsTime(1)); + if (globals.summary_stop(checker) <= globals.summary_start(checker)) { + usage("The current --stop value is less than or equal to the --start value"); + } + } + + // If the user didn't ask for anything, set them up with a basic full trace summary + if (!globals.should_print_summary() && + !globals.should_print_events() && + !globals.should_print_csv_summary() && + !globals.should_print_process_start_stop_timestamps() && + !globals.is_should_print_summary_set()) + { + globals.set_should_print_summary(true); + } + + for (auto& action : actions) { + action->execute(globals); + } + + return 0; +}