+//
+// SummaryPrinting.hpp
+// kdprof
+//
+// Created by James McIlree on 4/19/13.
+// Copyright (c) 2013 Apple. All rights reserved.
+//
+
+#ifndef kdprof_Printing_hpp
+#define kdprof_Printing_hpp
+
+void print_summary_header(const Globals& globals);
+
+struct SummaryLineData {
+ protected:
+ static constexpr const char* const indent_string[] = { "", " ", " ", " " };
+ static const uint32_t MAX_INDENT_LEVEL = 3; // Need to know this for time indenting to work correctly
+
+ uint32_t _indent_level;
+ const char* _name;
+
+ public:
+
+ enum class SummaryType {
+ Unknown,
+ CPU,
+ Process,
+ Thread
+ };
+
+ SummaryLineData(const char* name, uint32_t indent_level) :
+ _indent_level(indent_level),
+ _name(name),
+ should_print_timestamp(true),
+ num_intr_events(0),
+ context_switches(0),
+ actual_process_count(0),
+ wanted_process_count(0),
+ actual_thread_count(0),
+ wanted_thread_count(0),
+ num_vm_fault_events(0),
+ num_io_events(0),
+ io_bytes_completed(0),
+ num_jetsam_pids(0),
+ percent_multiplier(100.0),
+ type(SummaryType::Unknown),
+ is_colored(false),
+ begin_color(NULL),
+ end_color(NULL)
+ {
+ ASSERT(_indent_level <= MAX_INDENT_LEVEL, "Sanity");
+ ASSERT(_name && strlen(_name) > 0, "Sanity");
+ }
+
+ bool should_print_timestamp;
+ AbsTime total_time;
+ AbsTime total_run_time;
+ AbsTime total_idle_time;
+ AbsTime total_intr_time;
+ AbsTime total_wanted_run_time;
+ AbsTime total_wallclock_run_time;
+ AbsTime total_all_cpus_idle_time;
+ AbsTime total_vm_fault_time;
+ AbsTime total_io_time;
+ AbsTime total_jetsam_time;
+ uint32_t num_intr_events;
+ uint32_t context_switches;
+ uint32_t actual_process_count;
+ uint32_t wanted_process_count;
+ uint32_t actual_thread_count;
+ uint32_t wanted_thread_count;
+ uint32_t num_vm_fault_events;
+ uint32_t num_io_events;
+ uint64_t io_bytes_completed;
+ uint32_t num_jetsam_pids;
+ double percent_multiplier;
+ SummaryType type;
+ bool is_colored;
+ const char* begin_color;
+ const char* end_color;
+
+ const char* name() { return _name; }
+ const char* outdent() { return indent_string[MAX_INDENT_LEVEL - _indent_level]; }
+ const char* indent() { return indent_string[_indent_level]; }
+
+ bool is_unknown() { return type == SummaryType::Unknown; }
+ bool is_cpu() { return type == SummaryType::CPU; }
+ bool is_process() { return type == SummaryType::Process; }
+ bool is_thread() { return type == SummaryType::Thread; }
+};
+
+template <typename SIZE>
+void print_summary_line(const Globals& globals, const Machine<SIZE>& machine, AbsInterval summary_interval, struct SummaryLineData& line_data)
+{
+ // Header is...
+ // Avg Actual Wanted Actual Wanted Jetsam
+ // All CPU Thr Avg Actual Wanted Concurrency Processes To Run Threads To Run VMFault VMFault IO Wait # IO IO Bytes Jetsam Proc
+ // [Time(mS)] Name Run% Idle% Intr% Idle% #Intr #CSW On CPU/µS CPU/mS CPU/mS (# CPU) Ran Processes Ran Threads Count Time (mS) Time (mS) Ops Completed Time (mS) Count
+ // 123456789abcdef0 123456789012345678901234567890 1234567 1234567 1234567 1234567 1234567 12345678 123456789 123456789abc 123456789abc 123456789ab 123456789 123456789 1234567 1234567 1234567 123456789abc 123456789abc 1234567 1234567890 123456789 123456
+ // 1119100000.00 76.58 16.53 6.89 0.00 230 112 10000.00 100000.00 100000.00 1.55 2 3 12 13 2280 230.48 1998.22 3318 123.40 MB 0.00
+
+ ASSERT(!line_data.is_unknown(), "Sanity");
+
+ //
+ // It turns out that calling dprintf is very expensive; we're going to
+ // accumulate to a string buffer and then flush once at the end.
+ //
+ char line[1024];
+ char* cursor = line;
+ char* line_end = line + sizeof(line);
+
+ //
+ // Begin line coloring (if any)
+ //
+ if (line_data.is_colored) {
+ ASSERT(line_data.begin_color && line_data.end_color, "Sanity");
+ cursor += snprintf(cursor, line_end - cursor, "%s", line_data.begin_color);
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+
+ if (line_data.should_print_timestamp) {
+
+ //
+ // Time and Name get a special indent treatment, so they come out
+ // as heirarchically aligned, while not disturbing the rest of the
+ // columns. The time value is actually outdented, the name value
+ // is indented.
+ //
+ // The result is that you get something like this:
+ //
+ // [Time(mS)] Name Run%
+ // 123456789abcdef0 123456789012345678901234567890 1234567
+ //
+ // 1000.00 INDENT-LEVEL-0 ##.##
+ // 1000.00 INDENT-LEVEL-1 ##.##
+ // 1000.00 INDENT-LEVEL-2 ##.##
+ // 1000.00 INDENT-LEVEL-3 ##.##
+ //
+
+ char time_buffer[64];
+
+ //
+ // Time
+ //
+ if (globals.should_print_mach_absolute_timestamps()) {
+ if (globals.beginning_of_time().value() == 0)
+ snprintf(time_buffer, sizeof(time_buffer), "%llX%s", (summary_interval.location() - globals.beginning_of_time()).value(), line_data.outdent());
+ else
+ snprintf(time_buffer, sizeof(time_buffer), "%llu%s", (summary_interval.location() - globals.beginning_of_time()).value(), line_data.outdent());
+ } else {
+ NanoTime ntime = (summary_interval.location() - globals.beginning_of_time()).nano_time(globals.timebase());
+ snprintf(time_buffer, sizeof(time_buffer), "%3.2f%s", (double)ntime.value() / 1000000.0, line_data.outdent());
+ }
+
+ cursor += snprintf(cursor, line_end - cursor, "%16s ", time_buffer);
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+
+ //
+ // Name
+ //
+
+ {
+ char name_buffer[64];
+ snprintf(name_buffer, sizeof(name_buffer), "%s%s", line_data.indent(), line_data.name());
+
+ cursor += snprintf(cursor, line_end - cursor, "%-30s ", name_buffer);
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+
+ //
+ // Run% Idle% Intr% All-CPUs-Idle% #Intr
+ //
+
+ // Special case for process/thread summary lines, print idle/intr as "-";
+ if (line_data.is_process() || line_data.is_thread()) {
+ double run_percent = 0.0;
+
+ if (line_data.total_time.value() > 0)
+ run_percent = line_data.total_run_time.double_value() / line_data.total_time.double_value() * line_data.percent_multiplier;
+
+ cursor += snprintf(cursor, line_end - cursor, "%7.2f %7s %7s %7s %7u ",
+ run_percent,
+ "-",
+ "-",
+ "-",
+ line_data.num_intr_events);
+ } else {
+ ASSERT(line_data.total_time.value() > 0, "Sanity");
+
+ cursor += snprintf(cursor, line_end - cursor, "%7.2f %7.2f %7.2f %7.2f %7u ",
+ line_data.total_run_time.double_value() / line_data.total_time.double_value() * line_data.percent_multiplier,
+ line_data.total_idle_time.double_value() / line_data.total_time.double_value() * line_data.percent_multiplier,
+ line_data.total_intr_time.double_value() / line_data.total_time.double_value() * line_data.percent_multiplier,
+ line_data.total_all_cpus_idle_time.double_value() / line_data.total_time.double_value() * line_data.percent_multiplier,
+ line_data.num_intr_events);
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // #context-switches avg-on-cpu/µS
+ //
+ if (line_data.context_switches > 0) {
+ double avg_on_cpu_uS = (line_data.total_run_time / AbsTime(line_data.context_switches)).nano_time(globals.timebase()).value() / 1000.0;
+ cursor += snprintf(cursor, line_end - cursor, "%8u %9.2f ", line_data.context_switches, avg_on_cpu_uS);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%8u %9s ", line_data.context_switches, "-");
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // Actual CPU/mS, Wanted CPU/mS
+ //
+ if (line_data.total_wanted_run_time > 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%12.2f %12.2f ",
+ (double)line_data.total_run_time.nano_time(globals.timebase()).value() / 1000000.0,
+ (double)(line_data.total_run_time + line_data.total_wanted_run_time).nano_time(globals.timebase()).value() / 1000000.0);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%12.2f %12s ",
+ (double)line_data.total_run_time.nano_time(globals.timebase()).value() / 1000000.0,
+ "-");
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // Proc Avg Concurrency
+ //
+
+ if (line_data.total_wallclock_run_time > 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%11.2f ", (double)line_data.total_run_time.value() / (double)line_data.total_wallclock_run_time.value());
+ // cursor += snprintf(cursor, line_end - cursor, "%11.2f ", (double)line_data.total_wallclock_run_time.nano_time(globals.timebase()).value() / 1000000.0);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%11s ", "-");
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // Actual Processes, Wanted Processes
+ //
+ if (line_data.is_thread()) {
+ cursor += snprintf(cursor, line_end - cursor, "%9s %9s ", "-", "-");
+ } else {
+ if (line_data.total_run_time > 0 && line_data.total_wanted_run_time > 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%9u %9u ", (uint32_t)line_data.actual_process_count, (uint32_t)line_data.wanted_process_count);
+ } else if (line_data.total_run_time > 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%9u %9s ", (uint32_t)line_data.actual_process_count, "-");
+ } else if (line_data.total_wanted_run_time > 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%9s %9u ", "-", (uint32_t)line_data.wanted_process_count);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%9s %9s ", "-", "-");
+ }
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // Actual Threads, Wanted Threads
+ //
+ if (line_data.total_run_time > 0 && line_data.total_wanted_run_time > 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%7u %7u ", (uint32_t)line_data.actual_thread_count, (uint32_t)line_data.wanted_thread_count);
+ } else if (line_data.total_run_time > 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%7u %7s ", (uint32_t)line_data.actual_thread_count, "-");
+ } else if (line_data.total_wanted_run_time > 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%7s %7u ", "-", (uint32_t)line_data.wanted_thread_count);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%7s %7s ", "-", "-");
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+
+ //
+ // #vmfaults, mS blocked in vmfault
+ //
+ if (line_data.num_vm_fault_events == 0 && line_data.total_vm_fault_time.value() == 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%7s %12s ", "-", "-");
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%7u %12.2f ",
+ line_data.num_vm_fault_events,
+ (double)line_data.total_vm_fault_time.nano_time(globals.timebase()).value() / 1000000.0);
+ }
+
+ //
+ // mS blocked on IO activity
+ //
+ if (line_data.total_io_time.value() == 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%12s ", "-");
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%12.2f ",
+ (double)line_data.total_io_time.nano_time(globals.timebase()).value() / 1000000.0);
+ }
+
+ //
+ // # IO operations
+ //
+ if (line_data.num_io_events == 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%7s ", "-");
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%7u ", line_data.num_io_events);
+ }
+
+ //
+ // IO bytes completed
+ //
+ if (line_data.io_bytes_completed == 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%10s ", "-");
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%10s ", formated_byte_size(line_data.io_bytes_completed).c_str());
+ }
+
+ //
+ // Jetsam time
+ //
+ if (line_data.total_jetsam_time == 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%9s ", "-");
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%9.2f ",
+ (double)line_data.total_jetsam_time.nano_time(globals.timebase()).value() / 1000000.0);
+ }
+
+ //
+ // Jetsam count
+ //
+ if (line_data.is_cpu()) {
+ if (line_data.num_jetsam_pids == 0) {
+ cursor += snprintf(cursor, line_end - cursor, "%6s", "-");
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%6u", line_data.num_jetsam_pids);
+ }
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, "%6s", "");
+ }
+
+ //
+ // End line coloring (if any)
+ //
+ if (line_data.is_colored) {
+ cursor += snprintf(cursor, line_end - cursor, "%s", line_data.end_color);
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+
+ dprintf(globals.output_fd(), "%s\n", line);
+}
+
+template <typename SIZE>
+void print_cpu_summary_with_name_and_indent(const Globals& globals, const Machine<SIZE>& machine, AbsInterval summary_interval, const CPUSummary<SIZE>& master_summary, const CPUSummary<SIZE>& cpu_summary, const char* name, uint32_t indent)
+{
+ struct SummaryLineData data(name, indent);
+
+ data.should_print_timestamp = (globals.is_summary_start_set() || globals.is_summary_stop_set() || globals.is_summary_step_set());
+ data.total_time = master_summary.total_time();
+ data.total_run_time = cpu_summary.total_run_time();
+ data.total_idle_time = cpu_summary.total_idle_time();
+ data.total_intr_time = cpu_summary.total_intr_time();
+ data.total_wanted_run_time = cpu_summary.total_future_run_time();
+ data.total_wallclock_run_time = cpu_summary.total_wallclock_run_time();
+ data.total_all_cpus_idle_time = cpu_summary.total_all_cpus_idle_time();
+ data.total_vm_fault_time = cpu_summary.total_vm_fault_time();
+ data.total_io_time = cpu_summary.total_io_time();
+ data.total_jetsam_time = cpu_summary.total_jetsam_time();
+ data.context_switches = cpu_summary.context_switches();
+ data.num_intr_events = cpu_summary.num_intr_events();
+ data.num_vm_fault_events = cpu_summary.num_vm_fault_events();
+ data.num_io_events = cpu_summary.num_io_events();
+ data.num_jetsam_pids = cpu_summary.num_processes_jetsammed();
+ data.io_bytes_completed = cpu_summary.io_bytes_completed();
+ data.type = SummaryLineData::SummaryType::CPU;
+
+ for (auto& process_summary : cpu_summary.process_summaries()) {
+
+ if (process_summary.total_run_time() > 0) {
+ data.actual_process_count++;
+ data.wanted_process_count++;
+ } else if (process_summary.total_future_run_time() > 0) {
+ data.wanted_process_count++;
+ } else {
+ // ASSERT(cpu_summary.total_vm_fault_time() > 0, "Process in summary no actual or wanted run time, and no vm_fault time");
+ }
+
+ for (auto& thread_summary : process_summary.thread_summaries()) {
+ if (thread_summary.total_run_time() > 0) {
+ data.actual_thread_count++;
+ data.wanted_thread_count++;
+ } else if (thread_summary.total_future_run_time() > 0) {
+ data.wanted_thread_count++;
+ } else {
+ // ASSERT((thread_summary.total_vm_fault_time() > 0) || (thread_summary.total_pgin_time() > 0), "Thread in summary no actual or wanted run time, and no vm_fault or pgin time");
+ }
+ }
+ }
+
+ data.percent_multiplier *= (double)master_summary.active_cpus();
+
+ print_summary_line(globals, machine, summary_interval, data);
+}
+
+template <typename SIZE>
+void print_process_summary_with_name_and_indent(const Globals& globals, const Machine<SIZE>& machine, AbsInterval summary_interval, const CPUSummary<SIZE>& master_summary, const ProcessSummary<SIZE>& process_summary, const char* name, uint32_t indent)
+{
+ struct SummaryLineData data(name, indent);
+
+ data.should_print_timestamp = (globals.is_summary_start_set() || globals.is_summary_stop_set() || globals.is_summary_step_set());
+ data.total_run_time = process_summary.total_run_time();
+ data.total_wanted_run_time = process_summary.total_future_run_time();
+ data.total_wallclock_run_time = process_summary.total_wallclock_run_time();
+ data.total_vm_fault_time = process_summary.total_vm_fault_time();
+ data.total_io_time = process_summary.total_io_time();
+ data.total_jetsam_time = process_summary.total_jetsam_time();
+ data.context_switches = process_summary.context_switches();
+ data.num_intr_events = process_summary.num_intr_events();
+ data.actual_process_count = 1;
+ data.wanted_process_count = 1;
+ data.num_vm_fault_events = process_summary.num_vm_fault_events();
+ data.num_io_events = process_summary.num_io_events();
+ data.num_jetsam_pids = process_summary.num_processes_jetsammed();
+ data.io_bytes_completed = process_summary.io_bytes_completed();
+ data.total_time = master_summary.total_time();
+ // This causes the line printer to put "-" in the idle and intr % columns.
+ data.type = SummaryLineData::SummaryType::Process;
+ data.percent_multiplier *= (double)master_summary.active_cpus();
+
+ // We have to walk the threads to decide actual vs wanted to run
+ for (auto& thread_summary : process_summary.thread_summaries()) {
+ if (thread_summary.total_run_time() > 0) {
+ data.actual_thread_count++;
+ data.wanted_thread_count++;
+ } else if (thread_summary.total_future_run_time() > 0) {
+ data.wanted_thread_count++;
+ } else {
+ // ASSERT(thread_summary.total_vm_fault_time() > 0, "Thread in summary no actual or wanted run time, and no vm_fault time");
+ }
+ }
+
+ print_summary_line(globals, machine, summary_interval, data);
+}
+
+template <typename SIZE>
+void print_thread_summary_with_name_and_indent(const Globals& globals, const Machine<SIZE>& machine, AbsInterval summary_interval, const CPUSummary<SIZE>& master_summary, const ThreadSummary<SIZE>& thread_summary, const char* name, uint32_t indent)
+{
+ struct SummaryLineData data(name, indent);
+
+ /*data.is_colored = true;
+ data.begin_color = TerminalColorStringFor(kTerminalColor::GREEN, true, false);
+ data.end_color = TerminalColorResetString();*/
+
+ data.should_print_timestamp = (globals.is_summary_start_set() || globals.is_summary_stop_set() || globals.is_summary_step_set());
+ data.total_run_time = thread_summary.total_run_time();
+ data.total_wanted_run_time = thread_summary.total_future_run_time();
+ data.total_vm_fault_time = thread_summary.total_vm_fault_time();
+ data.total_io_time = thread_summary.total_io_time();
+ data.total_jetsam_time = thread_summary.total_jetsam_time();
+ data.context_switches = thread_summary.context_switches();
+ data.num_intr_events = thread_summary.num_intr_events();
+ data.num_vm_fault_events = thread_summary.num_vm_fault_events();
+ data.num_io_events = thread_summary.num_io_events();
+ data.num_jetsam_pids = 0;
+ data.io_bytes_completed = thread_summary.io_bytes_completed();
+ data.total_time = master_summary.total_time();
+ data.percent_multiplier *= (double)master_summary.active_cpus();
+ data.actual_thread_count = 1;
+ data.wanted_thread_count = 1;
+
+ // This causes the line printer to put "-" in various columns that don't make sense for a thread summary
+ data.type = SummaryLineData::SummaryType::Thread;
+
+ print_summary_line(globals, machine, summary_interval, data);
+}
+
+template <typename SIZE>
+static void sort_processes(const Globals& globals, const CPUSummary<SIZE>& summary, std::vector<const MachineProcess<SIZE>*>& processes) {
+ switch (globals.sort_key()) {
+ case kSortKey::CPU:
+ // Sort by Actual CPU, Future CPU, pid
+ std::sort(processes.begin(), processes.end(), [&summary](const MachineProcess<SIZE>* p0, const MachineProcess<SIZE>* p1) -> bool {
+ auto p0_summary = summary.process_summary(p0);
+ auto p1_summary = summary.process_summary(p1);
+
+ AbsTime p0_run_time = p0_summary->total_run_time();
+ AbsTime p1_run_time = p1_summary->total_run_time();
+
+ if (p0_run_time == p1_run_time) {
+ AbsTime p0_future_run_time = p0_summary->total_future_run_time();
+ AbsTime p1_future_run_time = p1_summary->total_future_run_time();
+
+ if (p0_future_run_time == p1_future_run_time)
+ return p0->pid() < p1->pid();
+
+ return p1_future_run_time < p0_future_run_time;
+ }
+
+ return p1_run_time < p0_run_time;
+ });
+ break;
+
+ case kSortKey::VMFault:
+ // Sort by VMFault time, #-faults, pid
+ std::sort(processes.begin(), processes.end(), [&summary](const MachineProcess<SIZE>* p0, const MachineProcess<SIZE>* p1) -> bool {
+ auto p0_summary = summary.process_summary(p0);
+ auto p1_summary = summary.process_summary(p1);
+
+ AbsTime p0_vm_fault_time = p0_summary->total_vm_fault_time();
+ AbsTime p1_vm_fault_time = p1_summary->total_vm_fault_time();
+
+ if (p0_vm_fault_time == p1_vm_fault_time) {
+ uint32_t p0_vm_fault_count = p0_summary->num_vm_fault_events();
+ uint32_t p1_vm_fault_count = p1_summary->num_vm_fault_events();
+
+ if (p0_vm_fault_count == p1_vm_fault_count)
+ return p0->pid() < p1->pid();
+
+ return p1_vm_fault_count < p0_vm_fault_count;
+ }
+
+ return p1_vm_fault_time < p0_vm_fault_time;
+ });
+ break;
+
+ case kSortKey::IO_Wait:
+ // Sort by IO time, pid
+ std::sort(processes.begin(), processes.end(), [&summary](const MachineProcess<SIZE>* p0, const MachineProcess<SIZE>* p1) -> bool {
+ auto p0_summary = summary.process_summary(p0);
+ auto p1_summary = summary.process_summary(p1);
+
+ AbsTime p0_io_time = p0_summary->total_io_time();
+ AbsTime p1_io_time = p1_summary->total_io_time();
+
+ if (p0_io_time == p1_io_time) {
+ uint32_t p0_io_ops = p0_summary->num_io_events();
+ uint32_t p1_io_ops = p1_summary->num_io_events();
+
+ if (p0_io_ops == p1_io_ops)
+ return p0->pid() < p1->pid();
+
+ return p1_io_ops < p0_io_ops;
+ }
+
+ return p1_io_time < p0_io_time;
+ });
+ break;
+
+ case kSortKey::IO_Ops:
+ // Sort by IO time, pid
+ std::sort(processes.begin(), processes.end(), [&summary](const MachineProcess<SIZE>* p0, const MachineProcess<SIZE>* p1) -> bool {
+ auto p0_summary = summary.process_summary(p0);
+ auto p1_summary = summary.process_summary(p1);
+
+ uint32_t p0_io_ops = p0_summary->num_io_events();
+ uint32_t p1_io_ops = p1_summary->num_io_events();
+
+ if (p0_io_ops == p1_io_ops) {
+ AbsTime p0_io_time = p0_summary->total_io_time();
+ AbsTime p1_io_time = p1_summary->total_io_time();
+
+ if (p0_io_time == p1_io_time)
+ return p0->pid() < p1->pid();
+
+ return p1_io_time < p0_io_time;
+ }
+
+ return p1_io_ops < p0_io_ops;
+ });
+ break;
+
+ case kSortKey::IO_Size:
+ // Sort by IO time, pid
+ std::sort(processes.begin(), processes.end(), [&summary](const MachineProcess<SIZE>* p0, const MachineProcess<SIZE>* p1) -> bool {
+ auto p0_summary = summary.process_summary(p0);
+ auto p1_summary = summary.process_summary(p1);
+
+ uint64_t p0_io_bytes_completed = p0_summary->io_bytes_completed();
+ uint64_t p1_io_bytes_completed = p1_summary->io_bytes_completed();
+
+ if (p0_io_bytes_completed == p1_io_bytes_completed) {
+ AbsTime p0_io_time = p0_summary->total_io_time();
+ AbsTime p1_io_time = p1_summary->total_io_time();
+
+ if (p0_io_time == p1_io_time)
+ return p0->pid() < p1->pid();
+
+ return p1_io_time < p0_io_time;
+ }
+
+ return p1_io_bytes_completed < p0_io_bytes_completed;
+ });
+ break;
+
+ case kSortKey::ID:
+ // Sort by pid
+ std::sort(processes.begin(), processes.end(), [](const MachineProcess<SIZE>* p0, const MachineProcess<SIZE>* p1) -> bool {
+ return p0->pid() < p1->pid();
+ });
+ break;
+ }
+}
+
+template <typename SIZE>
+static void sort_threads(const Globals& globals, const ProcessSummary<SIZE>& summary, std::vector<const MachineThread<SIZE>*>& threads) {
+ switch (globals.sort_key()) {
+ case kSortKey::CPU:
+ std::sort(threads.begin(), threads.end(), [&summary](const MachineThread<SIZE>* t0, const MachineThread<SIZE>* t1) -> bool {
+ auto t0_summary = summary.thread_summary(t0);
+ auto t1_summary = summary.thread_summary(t1);
+
+ AbsTime t0_run_time = t0_summary->total_run_time();
+ AbsTime t1_run_time = t1_summary->total_run_time();
+
+ if (t0_run_time == t1_run_time) {
+ AbsTime t0_future_run_time = t0_summary->total_future_run_time();
+ AbsTime t1_future_run_time = t1_summary->total_future_run_time();
+
+ if (t0_future_run_time == t1_future_run_time)
+ return t0->tid() < t1->tid();
+
+ return t1_future_run_time < t0_future_run_time;
+ }
+
+ return t1_run_time < t0_run_time;
+ });
+ break;
+
+ case kSortKey::VMFault:
+ // Sort by VMFault time, #-faults, pid
+ std::sort(threads.begin(), threads.end(), [&summary](const MachineThread<SIZE>* t0, const MachineThread<SIZE>* t1) -> bool {
+ auto t0_summary = summary.thread_summary(t0);
+ auto t1_summary = summary.thread_summary(t1);
+
+ AbsTime t0_vm_fault_time = t0_summary->total_vm_fault_time();
+ AbsTime t1_vm_fault_time = t1_summary->total_vm_fault_time();
+
+ if (t0_vm_fault_time == t1_vm_fault_time) {
+ uint32_t t0_vm_fault_count = t0_summary->num_vm_fault_events();
+ uint32_t t1_vm_fault_count = t1_summary->num_vm_fault_events();
+
+ if (t0_vm_fault_count == t1_vm_fault_count)
+ return t0->tid() < t1->tid();
+
+ return t1_vm_fault_count < t0_vm_fault_count;
+ }
+
+ return t1_vm_fault_time < t0_vm_fault_time;
+ });
+ break;
+
+ case kSortKey::IO_Wait:
+ // Sort by IO time, pid
+ std::sort(threads.begin(), threads.end(), [&summary](const MachineThread<SIZE>* t0, const MachineThread<SIZE>* t1) -> bool {
+ auto t0_summary = summary.thread_summary(t0);
+ auto t1_summary = summary.thread_summary(t1);
+
+ AbsTime t0_io_time = t0_summary->total_io_time();
+ AbsTime t1_io_time = t1_summary->total_io_time();
+
+ if (t0_io_time == t1_io_time) {
+ uint32_t t0_io_ops = t0_summary->num_io_events();
+ uint32_t t1_io_ops = t1_summary->num_io_events();
+
+ if (t0_io_ops == t1_io_ops)
+ return t0->tid() < t1->tid();
+
+ return t1_io_ops < t0_io_ops;
+ }
+
+ return t1_io_time < t0_io_time;
+ });
+ break;
+
+ case kSortKey::IO_Ops:
+ // Sort by IO time, pid
+ std::sort(threads.begin(), threads.end(), [&summary](const MachineThread<SIZE>* t0, const MachineThread<SIZE>* t1) -> bool {
+ auto t0_summary = summary.thread_summary(t0);
+ auto t1_summary = summary.thread_summary(t1);
+
+ uint32_t t0_io_ops = t0_summary->num_io_events();
+ uint32_t t1_io_ops = t1_summary->num_io_events();
+
+ if (t0_io_ops == t1_io_ops) {
+ AbsTime t0_io_time = t0_summary->total_io_time();
+ AbsTime t1_io_time = t1_summary->total_io_time();
+
+ if (t0_io_time == t1_io_time)
+ return t0->tid() < t1->tid();
+
+ return t1_io_time < t0_io_time;
+ }
+
+ return t1_io_ops < t0_io_ops;
+ });
+ break;
+
+ case kSortKey::IO_Size:
+ // Sort by IO time, pid
+ std::sort(threads.begin(), threads.end(), [&summary](const MachineThread<SIZE>* t0, const MachineThread<SIZE>* t1) -> bool {
+ auto t0_summary = summary.thread_summary(t0);
+ auto t1_summary = summary.thread_summary(t1);
+
+ uint64_t t0_io_bytes_completed = t0_summary->io_bytes_completed();
+ uint64_t t1_io_bytes_completed = t1_summary->io_bytes_completed();
+
+ if (t0_io_bytes_completed == t1_io_bytes_completed) {
+ AbsTime t0_io_time = t0_summary->total_io_time();
+ AbsTime t1_io_time = t1_summary->total_io_time();
+
+ if (t0_io_time == t1_io_time)
+ return t0->tid() < t1->tid();
+
+ return t1_io_time < t0_io_time;
+ }
+
+ return t1_io_bytes_completed < t0_io_bytes_completed;
+ });
+ break;
+
+ case kSortKey::ID:
+ std::sort(threads.begin(), threads.end(), [](const MachineThread<SIZE>* t0, const MachineThread<SIZE>* t1) -> bool {
+ return t0->tid() < t1->tid();
+ });
+ break;
+ }
+}
+
+template <typename SIZE>
+void print_machine_summary(const Globals& globals, const Machine<SIZE>& machine) {
+ AbsInterval machine_timespan = machine.timespan();
+
+ AbsTime start(globals.summary_start(machine_timespan));
+ AbsTime stop(globals.summary_stop(machine_timespan));
+ AbsTime step(globals.summary_step(machine_timespan));
+
+ print_summary_header(globals);
+
+ AbsInterval start_stop_timespan(start, stop - start);
+ AbsInterval clipped_start_stop_timespan(start_stop_timespan.intersection_range(machine_timespan));
+
+ start = clipped_start_stop_timespan.location();
+ stop = clipped_start_stop_timespan.max();
+
+ while (start < stop) {
+ AbsInterval base_interval(start, step);
+ AbsInterval summary_interval(base_interval.intersection_range(clipped_start_stop_timespan));
+
+ //
+ // TOTAL summary
+ //
+ CPUSummary<SIZE> summary = machine.summary_for_timespan(summary_interval, NULL);
+
+ //
+ // We want the TOTAL to include the number of ms elapsed, so print a duration
+ //
+ char total_buffer[64];
+ if (globals.should_print_mach_absolute_timestamps()) {
+ if (globals.beginning_of_time().value() == 0)
+ snprintf(total_buffer, sizeof(total_buffer), "TOTAL (0x%llXmabs)", summary_interval.length().value());
+ else
+ snprintf(total_buffer, sizeof(total_buffer), "TOTAL (%llumabs)", summary_interval.length().value());
+ } else {
+ NanoTime ntime = summary_interval.length().nano_time(globals.timebase());
+ snprintf(total_buffer, sizeof(total_buffer), "TOTAL (%3.2fms)", (double)ntime.value() / 1000000.0);
+ }
+ print_cpu_summary_with_name_and_indent(globals, machine, summary_interval, summary, summary, total_buffer, 0);
+
+ std::vector<CPUSummary<SIZE>> per_cpu_summaries;
+
+ //
+ // TOTAL per cpu summary
+ //
+ if (globals.should_print_cpu_summaries()) {
+ // summary.cpus() is unordered, we want to display sorted by cpu_id.
+ std::vector<const MachineCPU<SIZE>*> sorted_cpus;
+
+ for (auto& cpu : summary.cpus()) {
+ sorted_cpus.emplace_back(cpu);
+ }
+
+ std::sort(sorted_cpus.begin(), sorted_cpus.end(), [](MachineCPU<SIZE> const* cpu0, MachineCPU<SIZE> const* cpu1) -> bool {
+ return cpu0->id() < cpu1->id();
+ });
+
+ for (auto cpu : sorted_cpus) {
+ per_cpu_summaries.push_back(machine.summary_for_timespan(summary_interval, cpu));
+
+ char name[16];
+ snprintf(name, sizeof(name), "CPU%d", cpu->id());
+ print_cpu_summary_with_name_and_indent(globals, machine, summary_interval, summary, per_cpu_summaries.back(), name, 1);
+ }
+ }
+
+ //
+ // PER PROCESS summary
+ //
+ if (globals.should_print_process_summaries()) {
+ //
+ // We want to sort the list of processes by PID, so they always display in the same order.
+ //
+ std::vector<const MachineProcess<SIZE>*> sorted_processes;
+ for (auto& process_summary : summary.process_summaries()) {
+ sorted_processes.emplace_back(process_summary.process());
+ }
+
+ sort_processes(globals, summary, sorted_processes);
+
+ for (auto process : sorted_processes) {
+ ASSERT(summary.process_summary(process), "Unable to find process summary by pointer lookup");
+ if (const ProcessSummary<SIZE>* process_summary = summary.process_summary(process)) {
+ char name[32];
+ snprintf(name, sizeof(name), "%s (%d)%s", process->name(), process->pid(), process->is_exit_by_jetsam() ? " *" : "");
+ print_process_summary_with_name_and_indent(globals, machine, summary_interval, summary, *process_summary, name, 1);
+
+ if (globals.should_print_cpu_summaries()) {
+ //
+ // PER PROCESS per cpu summary
+ //
+ for (auto& cpu_summary : per_cpu_summaries) {
+ if (const ProcessSummary<SIZE>* per_cpu_process_summary = cpu_summary.process_summary(process)) {
+ char name[32];
+ snprintf(name, sizeof(name), "CPU%d %s (%d)", (*cpu_summary.cpus().begin())->id(), process->name(), process->pid());
+ print_process_summary_with_name_and_indent(globals, machine, summary_interval, summary, *per_cpu_process_summary, name, 2);
+ }
+ }
+ }
+
+ if (globals.should_print_thread_summaries()) {
+ //
+ // PER PROCESS per thread summary
+ //
+ std::vector<const MachineThread<SIZE>*> sorted_threads;
+ for (auto& thread_summary : process_summary->thread_summaries()) {
+ sorted_threads.emplace_back(thread_summary.thread());
+ }
+
+ sort_threads(globals, *process_summary, sorted_threads);
+
+ for (auto thread : sorted_threads) {
+ ASSERT(process_summary->thread_summary(thread), "Unable to find thread summary by pointer lookup");
+ if (const ThreadSummary<SIZE>* thread_summary = process_summary->thread_summary(thread)) {
+ char name[32];
+ snprintf(name, sizeof(name), "tid-%llX", (uint64_t)thread->tid());
+ print_thread_summary_with_name_and_indent(globals, machine, summary_interval, summary, *thread_summary, name, 2);
+
+ if (globals.should_print_cpu_summaries()) {
+ //
+ // PER PROCESS per thread per cpu summary
+ //
+ for (auto& cpu_summary : per_cpu_summaries) {
+ if (const ProcessSummary<SIZE>* per_cpu_process_summary = cpu_summary.process_summary(process)) {
+ if (const ThreadSummary<SIZE>* per_cpu_thread_summary = per_cpu_process_summary->thread_summary(thread)) {
+ char name[32];
+ snprintf(name, sizeof(name), "CPU%d tid-%llX", (*cpu_summary.cpus().begin())->id(), (uint64_t)thread->tid());
+ print_thread_summary_with_name_and_indent(globals, machine, summary_interval, summary, *per_cpu_thread_summary, name, 3);
+ }
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+ }
+
+ start += step;
+ }
+}
+
+
+template <typename SIZE>
+void print_machine_csv_summary_header(const Globals& globals,
+ const Machine<SIZE>& machine,
+ std::vector<const MachineCPU<SIZE>*>& all_cpus,
+ std::vector<const MachineProcess<SIZE>*>& all_processes,
+ std::unordered_map<const MachineProcess<SIZE>*, std::vector<const MachineThread<SIZE>*>>& all_threads,
+ const char* header_type)
+{
+ // Header is...
+ //
+ // "", header_type
+ //
+ // "", "TOTAL", "CPU0", "CPU1", "proc1", "proc1-tid1", "proc1-tid2", "proc2", etc..
+
+ //
+ // It turns out that calling dprintf is very expensive; we're going to
+ // accumulate to a string buffer and then flush once at the end.
+ //
+ char line[16384]; // Header lines can be big!
+ char* cursor = line;
+ char* line_end = line + sizeof(line);
+
+ //
+ // header + TOTAL
+ //
+ cursor += snprintf(cursor, line_end - cursor, "%s\n\nTIME, TOTAL", header_type);
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // TOTAL per cpu summary
+ //
+ if (globals.should_print_cpu_summaries()) {
+ for (auto cpu : all_cpus) {
+ cursor += snprintf(cursor, line_end - cursor, ", CPU%d", cpu->id());
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+
+ //
+ // PER PROCESS summary
+ //
+ if (globals.should_print_process_summaries()) {
+ for (auto process : all_processes) {
+ cursor += snprintf(cursor, line_end - cursor, ", %s (%d)", process->name(), process->pid());
+ if (cursor > line_end)
+ cursor = line_end;
+
+ if (globals.should_print_cpu_summaries()) {
+ //
+ // PER PROCESS per cpu summary
+ //
+ for (auto cpu : all_cpus) {
+ cursor += snprintf(cursor, line_end - cursor, ", CPU%d %s (%d)", cpu->id(), process->name(), process->pid());
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+
+ if (globals.should_print_thread_summaries()) {
+ //
+ // PER PROCESS per thread summary
+ //
+ for (auto thread : all_threads[process]) {
+ cursor += snprintf(cursor, line_end - cursor, ", tid-%llX", (uint64_t)thread->tid());
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // PER PROCESS per thread per cpu summary
+ //
+ for (auto cpu : all_cpus) {
+ cursor += snprintf(cursor, line_end - cursor, ", CPU%d tid-%llX", cpu->id(), (uint64_t)thread->tid());
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+ }
+ }
+ }
+
+ dprintf(globals.output_fd(), "%s\n", line);
+}
+
+template <typename SIZE>
+void print_machine_csv_summary_actual_cpu_ms_line(const Globals& globals,
+ const Machine<SIZE>& machine,
+ AbsInterval summary_interval,
+ std::vector<const MachineCPU<SIZE>*>& all_cpus,
+ std::vector<const MachineProcess<SIZE>*>& all_processes,
+ std::unordered_map<const MachineProcess<SIZE>*, std::vector<const MachineThread<SIZE>*>>& all_threads,
+ CPUSummary<SIZE>& master_summary,
+ std::vector<CPUSummary<SIZE>>& per_cpu_summaries)
+{
+ char line[16384]; // Header lines can be big!
+ char* cursor = line;
+ char* line_end = line + sizeof(line);
+
+ //
+ // Time
+ //
+
+ if (globals.should_print_mach_absolute_timestamps()) {
+ if (globals.beginning_of_time().value() == 0)
+ cursor += snprintf(cursor, line_end - cursor, "%llX", (summary_interval.location() - globals.beginning_of_time()).value());
+ else
+ cursor += snprintf(cursor, line_end - cursor, "%llu", (summary_interval.location() - globals.beginning_of_time()).value());
+ } else {
+ NanoTime ntime = (summary_interval.location() - globals.beginning_of_time()).nano_time(globals.timebase());
+ cursor += snprintf(cursor, line_end - cursor, "%3.2f", (double)ntime.value() / 1000000.0);
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // TOTAL
+ //
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)master_summary.total_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // TOTAL per cpu summary
+ //
+ if (globals.should_print_cpu_summaries()) {
+ for (auto& cpu_summary : per_cpu_summaries) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)cpu_summary.total_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+
+ //
+ // PER PROCESS summary
+ //
+ if (globals.should_print_process_summaries()) {
+ for (auto process : all_processes) {
+ const ProcessSummary<SIZE>* process_summary;
+
+ // Not all summaries will have a matching process entry!
+ if ((process_summary = master_summary.process_summary(process))) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)process_summary->total_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, ",");
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ if (globals.should_print_cpu_summaries()) {
+ //
+ // PER PROCESS per cpu summary
+ //
+ for (auto& cpu_summary : per_cpu_summaries) {
+ if (const auto& process_summary = cpu_summary.process_summary(process)) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)process_summary->total_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, ",");
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+
+ if (globals.should_print_thread_summaries()) {
+ //
+ // PER PROCESS per thread summary
+ //
+
+ //
+ // We again have to do a bit more work, sometime a process is missing and we still need to print empty slots for its threads.
+
+
+ for (auto thread : all_threads[process]) {
+ if (process_summary) {
+ if (const auto& thread_summary = process_summary->thread_summary(thread)) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)thread_summary->total_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+ } else
+ cursor += snprintf(cursor, line_end - cursor, ",");
+ } else
+ cursor += snprintf(cursor, line_end - cursor, ",");
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+
+ if (globals.should_print_cpu_summaries()) {
+ //
+ // PER PROCESS per thread per cpu summary
+ //
+ for (auto& cpu_summary : per_cpu_summaries) {
+ if (const auto& per_cpu_process_summary = cpu_summary.process_summary(process)) {
+ if (const auto& per_cpu_thread_summary = per_cpu_process_summary->thread_summary(thread)) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)per_cpu_thread_summary->total_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+ } else
+ cursor += snprintf(cursor, line_end - cursor, ",");
+ } else
+ cursor += snprintf(cursor, line_end - cursor, ",");
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ dprintf(globals.output_fd(), "%s\n", line);
+}
+
+template <typename SIZE>
+void print_machine_csv_summary_wanted_cpu_ms_line(const Globals& globals,
+ const Machine<SIZE>& machine,
+ AbsInterval summary_interval,
+ std::vector<const MachineCPU<SIZE>*>& all_cpus,
+ std::vector<const MachineProcess<SIZE>*>& all_processes,
+ std::unordered_map<const MachineProcess<SIZE>*, std::vector<const MachineThread<SIZE>*>>& all_threads,
+ CPUSummary<SIZE>& master_summary,
+ std::vector<CPUSummary<SIZE>>& per_cpu_summaries)
+{
+ char line[16384]; // Header lines can be big!
+ char* cursor = line;
+ char* line_end = line + sizeof(line);
+
+ //
+ // Time
+ //
+
+ if (globals.should_print_mach_absolute_timestamps()) {
+ if (globals.beginning_of_time().value() == 0)
+ cursor += snprintf(cursor, line_end - cursor, "%llX", (summary_interval.location() - globals.beginning_of_time()).value());
+ else
+ cursor += snprintf(cursor, line_end - cursor, "%llu", (summary_interval.location() - globals.beginning_of_time()).value());
+ } else {
+ NanoTime ntime = (summary_interval.location() - globals.beginning_of_time()).nano_time(globals.timebase());
+ cursor += snprintf(cursor, line_end - cursor, "%3.2f", (double)ntime.value() / 1000000.0);
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // TOTAL
+ //
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)master_summary.total_future_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ //
+ // TOTAL per cpu summary
+ //
+ if (globals.should_print_cpu_summaries()) {
+ for (auto& cpu_summary : per_cpu_summaries) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)cpu_summary.total_future_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+
+ //
+ // PER PROCESS summary
+ //
+ if (globals.should_print_process_summaries()) {
+ for (auto process : all_processes) {
+ const ProcessSummary<SIZE>* process_summary;
+
+ // Not all summaries will have a matching process entry!
+ if ((process_summary = master_summary.process_summary(process))) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)process_summary->total_future_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, ",");
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+ if (globals.should_print_cpu_summaries()) {
+ //
+ // PER PROCESS per cpu summary
+ //
+ for (auto& cpu_summary : per_cpu_summaries) {
+ if (const auto& process_summary = cpu_summary.process_summary(process)) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)process_summary->total_future_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+ } else {
+ cursor += snprintf(cursor, line_end - cursor, ",");
+ }
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+
+ if (globals.should_print_thread_summaries()) {
+ //
+ // PER PROCESS per thread summary
+ //
+
+ //
+ // We again have to do a bit more work, sometime a process is missing and we still need to print empty slots for its threads.
+
+
+ for (auto thread : all_threads[process]) {
+ if (process_summary) {
+ if (const auto& thread_summary = process_summary->thread_summary(thread)) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)thread_summary->total_future_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+ } else
+ cursor += snprintf(cursor, line_end - cursor, ",");
+ } else
+ cursor += snprintf(cursor, line_end - cursor, ",");
+
+ if (cursor > line_end)
+ cursor = line_end;
+
+
+ if (globals.should_print_cpu_summaries()) {
+ //
+ // PER PROCESS per thread per cpu summary
+ //
+ for (auto& cpu_summary : per_cpu_summaries) {
+ if (const auto& per_cpu_process_summary = cpu_summary.process_summary(process)) {
+ if (const auto& per_cpu_thread_summary = per_cpu_process_summary->thread_summary(thread)) {
+ cursor += snprintf(cursor, line_end - cursor, ", %3.2f",
+ (double)per_cpu_thread_summary->total_future_run_time().nano_time(globals.timebase()).value() / 1000000.0);
+ } else
+ cursor += snprintf(cursor, line_end - cursor, ",");
+ } else
+ cursor += snprintf(cursor, line_end - cursor, ",");
+
+ if (cursor > line_end)
+ cursor = line_end;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ dprintf(globals.output_fd(), "%s\n", line);
+}
+
+template <typename SIZE>
+void print_machine_csv_summary(const Globals& globals, const Machine<SIZE>& machine) {
+ AbsInterval machine_timespan = machine.timespan();
+
+ AbsTime start(globals.summary_start(machine_timespan));
+ AbsTime stop(globals.summary_stop(machine_timespan));
+ AbsTime step(globals.summary_step(machine_timespan));
+
+ AbsInterval start_stop_timespan(start, stop - start);
+ AbsInterval clipped_start_stop_timespan(start_stop_timespan.intersection_range(machine_timespan));
+
+ start = clipped_start_stop_timespan.location();
+ stop = clipped_start_stop_timespan.max();
+
+ //
+ // While printing a csv summary, we need to use the entire set of processes/threads/cpus
+ // from the range, even though they may not run in each sample. We first gather a summary
+ // for the entire time, to get the master list.
+ //
+ CPUSummary<SIZE> start_stop_summary = machine.summary_for_timespan(clipped_start_stop_timespan, NULL);
+
+ std::vector<const MachineProcess<SIZE>*> all_processes;
+ std::vector<const MachineCPU<SIZE>*> all_cpus;
+ std::unordered_map<const MachineProcess<SIZE>*, std::vector<const MachineThread<SIZE>*>> all_threads;
+
+ //
+ // gather all processes
+ //
+ {
+ for (auto& process_summary : start_stop_summary.process_summaries()) {
+ all_processes.emplace_back(process_summary.process());
+ }
+
+ sort_processes(globals, start_stop_summary, all_processes);
+ }
+
+ //
+ // gather all cpus
+ //
+ if (globals.should_print_cpu_summaries()) {
+ for (auto& cpu : start_stop_summary.cpus()) {
+ all_cpus.emplace_back(cpu);
+ }
+
+ std::sort(all_cpus.begin(), all_cpus.end(), [](MachineCPU<SIZE> const* cpu0, MachineCPU<SIZE> const* cpu1) -> bool {
+ return cpu0->id() < cpu1->id();
+ });
+ }
+
+ //
+ // gather all threads
+ //
+ if (globals.should_print_thread_summaries()) {
+ for (auto process : all_processes) {
+ ASSERT(start_stop_summary.process_summary(process), "Unable to find process summary by pointer lookup");
+ if (const ProcessSummary<SIZE>* process_summary = start_stop_summary.process_summary(process)) {
+ //
+ // PER PROCESS per thread summary
+ //
+ auto& sorted_threads = all_threads[process];
+ for (auto& thread_summary : process_summary->thread_summaries()) {
+ sorted_threads.emplace_back(thread_summary.thread());
+ }
+
+ sort_threads(globals, *process_summary, sorted_threads);
+ }
+ }
+ }
+
+ print_machine_csv_summary_header(globals, machine, all_cpus, all_processes, all_threads, "Actual CPU/ms");
+
+ while (start < stop) {
+ AbsInterval base_interval(start, step);
+ AbsInterval summary_interval(base_interval.intersection_range(clipped_start_stop_timespan));
+
+ //
+ // TOTAL summary
+ //
+ CPUSummary<SIZE> summary = machine.summary_for_timespan(summary_interval, NULL);
+
+ //
+ // Per CPU summaries...
+ //
+ std::vector<CPUSummary<SIZE>> per_cpu_summaries;
+ if (globals.should_print_cpu_summaries()) {
+ for (auto cpu : all_cpus) {
+ per_cpu_summaries.push_back(machine.summary_for_timespan(summary_interval, cpu));
+ }
+ }
+
+ print_machine_csv_summary_actual_cpu_ms_line(globals, machine, summary_interval, all_cpus, all_processes, all_threads, summary, per_cpu_summaries);
+
+ start += step;
+ }
+
+
+ //
+ // Now print Wanted CPU/ms
+ //
+ start = clipped_start_stop_timespan.location();
+ stop = clipped_start_stop_timespan.max();
+
+ dprintf(globals.output_fd(), "\n");
+ print_machine_csv_summary_header(globals, machine, all_cpus, all_processes, all_threads, "Wanted CPU/ms");
+
+ while (start < stop) {
+ AbsInterval base_interval(start, step);
+ AbsInterval summary_interval(base_interval.intersection_range(clipped_start_stop_timespan));
+
+ //
+ // TOTAL summary
+ //
+ CPUSummary<SIZE> summary = machine.summary_for_timespan(summary_interval, NULL);
+
+ //
+ // Per CPU summaries...
+ //
+ std::vector<CPUSummary<SIZE>> per_cpu_summaries;
+ if (globals.should_print_cpu_summaries()) {
+ for (auto cpu : all_cpus) {
+ per_cpu_summaries.push_back(machine.summary_for_timespan(summary_interval, cpu));
+ }
+ }
+
+ print_machine_csv_summary_wanted_cpu_ms_line(globals, machine, summary_interval, all_cpus, all_processes, all_threads, summary, per_cpu_summaries);
+
+ start += step;
+ }
+}
+
+template <typename SIZE>
+void print_process_start_stop_timestamps(const Globals& globals, const Machine<SIZE>& machine) {
+ for (auto process : machine.processes()) {
+
+ //
+ // Skip processes with no events
+ //
+
+ if (process->timespan().length() == 0) {
+ // Skip processes with nothing in them.
+ // The assert may be too strong.
+ ASSERT(process->is_created_by_thread_map(), "Expected a zero length process to be from the thread map");
+ continue;
+ }
+
+ //
+ // Don't print the kernel process, it will occupy the entire trace
+ //
+ if (process->is_kernel())
+ continue;
+
+ //
+ // Time
+ //
+ char time_buffer[64];
+ if (globals.beginning_of_time().value() == 0)
+ snprintf(time_buffer, sizeof(time_buffer), "%llumabs", process->timespan().location().value());
+ else
+ snprintf(time_buffer, sizeof(time_buffer), "%llumabs", (process->timespan().location() - globals.beginning_of_time()).value());
+
+ //
+ // End time
+ //
+ char end_time_buffer[64];
+ if (globals.beginning_of_time().value() == 0)
+ snprintf(end_time_buffer, sizeof(end_time_buffer), "%llumabs", process->timespan().max().value());
+ else
+ snprintf(end_time_buffer, sizeof(end_time_buffer), "%llumabs", (process->timespan().max() - globals.beginning_of_time()).value());
+
+ const char* create_reason;
+ if (process->is_created_by_thread_map())
+ create_reason = "Threadmap Entry";
+ else if (process->is_created_by_previous_machine_state())
+ create_reason = "Prev Machine State";
+ else if (process->is_created_by_fork_exec())
+ create_reason = "ForkExec";
+ else if (process->is_created_by_exec())
+ create_reason = "Exec";
+ else
+ create_reason = "???";
+
+ if (globals.is_verbose()) {
+ printf(" %30s (%6d) --start %-16s --stop %-16s\tCreated by %-18s %s\n",
+ process->name(),
+ process->pid(),
+ time_buffer,
+ end_time_buffer,
+ create_reason,
+ process->is_trace_terminated() ? "EXITED" : "");
+ } else {
+ printf(" %30s (%6d) --start %s --stop %s\n",
+ process->name(),
+ process->pid(),
+ time_buffer,
+ end_time_buffer);
+ }
+ }
+}
+
+template <typename SIZE>
+void print_verbose_machine_info(const Globals& globals, const Machine<SIZE>& machine, uint32_t threadmap_count, uint32_t cpumap_count) {
+ dprintf(globals.output_fd(), "\tEvent data is %s, and appears to be from %s\n", SIZE::is_64_bit ? "K64" : "K32", machine.is_ios() ? "iOS" : "OSX");
+ dprintf(globals.output_fd(), "\tUsing a%stimebase of %d/%d\n", globals.is_timebase_set() ? " [User Set] " : " ", globals.timebase().numer, globals.timebase().denom);
+
+ if (threadmap_count) {
+ dprintf(globals.output_fd(), "\tA threadmap is present, and contains %u entries\n", threadmap_count);
+ } else {
+ dprintf(globals.output_fd(), "\tA threadmap is not present");
+ }
+
+ if (cpumap_count) {
+ dprintf(globals.output_fd(), "\tA cpumap is present, and contains %u entries\n", cpumap_count);
+
+ } else {
+ dprintf(globals.output_fd(), "\tA cpumap is not present, the system provided a default with %u cpus and %u iops\n", globals.cpu_count(), globals.iop_count());
+ }
+
+ dprintf(globals.output_fd(), "\tFound %u active cpus in trace data\n", machine.active_cpus());
+
+ if (globals.is_summary_start_set()) {
+ AbsInterval machine_timespan = machine.timespan();
+
+ if (globals.should_print_mach_absolute_timestamps()) {
+ if (globals.beginning_of_time().value() == 0)
+ dprintf(globals.output_fd(), "\tUsing a --start value of 0x%llXmabs (raw)\n", globals.summary_start(machine_timespan).value());
+ else
+ dprintf(globals.output_fd(), "\tUsing a --start value of %llumabs\n", (globals.summary_start(machine_timespan) - machine_timespan.location()).value());
+ } else {
+ NanoTime ntime = (globals.summary_start(machine_timespan) - machine_timespan.location()).nano_time(globals.timebase());
+ dprintf(globals.output_fd(), "\tUsing a --start value of %3.2fms\n", (double)ntime.value() / 1000000.0);
+ }
+ }
+
+ if (globals.is_summary_stop_set()) {
+ AbsInterval machine_timespan = machine.timespan();
+
+ if (globals.should_print_mach_absolute_timestamps()) {
+ if (globals.beginning_of_time().value() == 0)
+ dprintf(globals.output_fd(), "\tUsing a --stop value of 0x%llXmabs (raw)\n", globals.summary_stop(machine_timespan).value());
+ else
+ dprintf(globals.output_fd(), "\tUsing a --stop value of %llumabs\n", (globals.summary_stop(machine_timespan) - machine_timespan.location()).value());
+ } else {
+ NanoTime ntime = (globals.summary_stop(machine_timespan) - machine_timespan.location()).nano_time(globals.timebase());
+ dprintf(globals.output_fd(), "\tUsing a --stop value of %3.2fms\n", (double)ntime.value() / 1000000.0);
+ }
+ }
+
+ if (globals.is_summary_step_set()) {
+ AbsInterval machine_timespan = machine.timespan();
+
+ if (globals.should_print_mach_absolute_timestamps()) {
+ if (globals.beginning_of_time().value() == 0)
+ dprintf(globals.output_fd(), "\tUsing a --step value of 0x%llXmabs (raw)\n", globals.summary_step(machine_timespan).value());
+ else
+ dprintf(globals.output_fd(), "\tUsing a --step value of %llumabs\n", globals.summary_step(machine_timespan).value());
+ } else {
+ NanoTime ntime = globals.summary_step(machine_timespan).nano_time(globals.timebase());
+ dprintf( globals.output_fd(), "\tUsing a --step value of %3.2fms\n", (double)ntime.value() / 1000000.0);
+ }
+ }
+}
+
+#endif