blob: 38246d1f7d8dce4fc73febc0d3ad996b8ea6169c [file]
// Copyright 2026 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "iree/tooling/profile/explain.h"
#include <stdlib.h>
#include <string.h>
#include "iree/tooling/profile/dispatch.h"
#include "iree/tooling/profile/memory.h"
#include "iree/tooling/profile/reader.h"
#include "iree/tooling/profile/summary.h"
#define IREE_PROFILE_EXPLAIN_TOP_FUNCTION_COUNT 10
typedef enum iree_profile_explain_timing_source_e {
// Device dispatch event timestamps.
IREE_PROFILE_EXPLAIN_TIMING_SOURCE_DEVICE_DISPATCH = 0,
// Host execution event timestamps for CPU/local dispatch spans.
IREE_PROFILE_EXPLAIN_TIMING_SOURCE_HOST_EXECUTION = 1,
} iree_profile_explain_timing_source_t;
typedef struct iree_profile_explain_function_rank_t {
// Timing source used to rank this function.
iree_profile_explain_timing_source_t timing_source;
// Session-local physical device ordinal for this function aggregate.
uint32_t physical_device_ordinal;
// Producer-local executable identifier for this function aggregate.
uint64_t executable_id;
// Function ordinal for this function aggregate.
uint32_t function_ordinal;
// Total dispatch count for this function aggregate.
uint64_t dispatch_count;
// Valid dispatch count for this function aggregate.
uint64_t valid_count;
// Invalid dispatch count for this function aggregate.
uint64_t invalid_count;
// Maximum valid dispatch duration in raw device ticks.
uint64_t maximum_ticks;
// Total valid dispatch duration in raw device ticks.
uint64_t total_ticks;
// Total tiles reported by host dispatch spans, or 0 when unavailable.
uint64_t total_tile_count;
// Sum of valid per-tile duration totals in nanoseconds, or 0 when absent.
int64_t total_tile_duration_sum_ns;
// Average valid dispatch duration in nanoseconds when available.
double average_ns;
// Maximum valid dispatch duration in nanoseconds when available.
int64_t maximum_ns;
// Total valid dispatch duration in nanoseconds when available.
int64_t total_ns;
// True when nanosecond duration values are available for this rank.
bool has_duration_ns;
} iree_profile_explain_function_rank_t;
typedef struct iree_profile_explain_interval_t {
// Inclusive dispatch start tick for this interval.
uint64_t start_tick;
// Exclusive dispatch end tick for this interval.
uint64_t end_tick;
} iree_profile_explain_interval_t;
typedef struct iree_profile_explain_queue_summary_t {
// Number of queue submissions with at least one matching dispatch aggregate.
uint64_t submission_count;
// Number of queue submissions with at least one valid dispatch timestamp.
uint64_t valid_submission_count;
// Number of queue submissions with at least one invalid dispatch timestamp.
uint64_t invalid_submission_count;
// Unioned busy span for valid dispatch intervals in raw device ticks.
double busy_ticks;
// Sum of valid dispatch durations in raw device ticks.
uint64_t total_dispatch_ticks;
// Number of observed gaps between unioned valid dispatch intervals.
uint64_t gap_count;
// Sum of observed gaps in raw device ticks.
double total_gap_ticks;
// Largest observed gap in raw device ticks.
double max_gap_ticks;
} iree_profile_explain_queue_summary_t;
typedef struct iree_profile_explain_device_row_t {
// Device metadata row represented by these derived metrics.
const iree_profile_model_device_t* device;
// True when the device has a driver host CPU timestamp clock fit.
bool has_clock_fit;
// Nanoseconds per device tick when |has_clock_fit| is true.
double nanoseconds_per_tick;
// Device tick frequency in Hz when |has_clock_fit| is true.
double tick_frequency_hz;
// Visible span covered by valid queue dispatch intervals in device ticks.
double visible_span_ticks;
// Sum of valid dispatch durations for this device in device ticks.
uint64_t total_dispatch_ticks;
// Ratio of summed dispatch time to the visible queue span.
double active_over_visible_ratio;
// True when |total_dispatch_time_ns| was derived from the clock fit.
bool has_total_dispatch_time_ns;
// Sum of valid dispatch durations mapped to nanoseconds.
int64_t total_dispatch_time_ns;
} iree_profile_explain_device_row_t;
typedef struct iree_profile_explain_queue_row_t {
// Queue metadata row represented by these derived metrics.
const iree_hal_profile_queue_record_t* queue;
// Aggregate dispatch timing and gap summary for the queue.
iree_profile_explain_queue_summary_t summary;
// True when the queue device has a driver host CPU timestamp clock fit.
bool has_clock_fit;
// Nanoseconds per device tick when |has_clock_fit| is true.
double nanoseconds_per_tick;
// True when |total_dispatch_time_ns| was derived from the clock fit.
bool has_total_dispatch_time_ns;
// Sum of valid dispatch durations mapped to nanoseconds.
int64_t total_dispatch_time_ns;
// True when queue event records were present for gap analysis context.
bool gap_analysis_available;
} iree_profile_explain_queue_row_t;
typedef struct iree_profile_explain_queue_operation_totals_t {
// Event counts indexed by iree_hal_profile_queue_event_type_t.
uint64_t event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_HOST_CALL + 1];
// Transfer payload bytes indexed by iree_hal_profile_queue_event_type_t.
uint64_t payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_HOST_CALL + 1];
// Event counts indexed by iree_hal_profile_queue_dependency_strategy_t.
uint64_t strategy_counts
[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_SOFTWARE_DEFER + 1];
} iree_profile_explain_queue_operation_totals_t;
static double iree_profile_explain_function_rank_score(
const iree_profile_explain_function_rank_t* rank) {
return rank->has_duration_ns ? (double)rank->total_ns
: (double)rank->total_ticks;
}
static int iree_profile_explain_compare_function_rank(const void* lhs,
const void* rhs) {
const iree_profile_explain_function_rank_t* a =
(const iree_profile_explain_function_rank_t*)lhs;
const iree_profile_explain_function_rank_t* b =
(const iree_profile_explain_function_rank_t*)rhs;
const double a_score = iree_profile_explain_function_rank_score(a);
const double b_score = iree_profile_explain_function_rank_score(b);
if (a_score < b_score) return 1;
if (a_score > b_score) return -1;
return 0;
}
static int iree_profile_explain_compare_top_event(const void* lhs,
const void* rhs) {
const iree_profile_dispatch_top_event_t* a =
(const iree_profile_dispatch_top_event_t*)lhs;
const iree_profile_dispatch_top_event_t* b =
(const iree_profile_dispatch_top_event_t*)rhs;
if (a->duration_ticks < b->duration_ticks) return 1;
if (a->duration_ticks > b->duration_ticks) return -1;
return 0;
}
static int iree_profile_explain_compare_top_host_event(const void* lhs,
const void* rhs) {
const iree_profile_host_dispatch_top_event_t* a =
(const iree_profile_host_dispatch_top_event_t*)lhs;
const iree_profile_host_dispatch_top_event_t* b =
(const iree_profile_host_dispatch_top_event_t*)rhs;
if (a->duration_ns < b->duration_ns) return 1;
if (a->duration_ns > b->duration_ns) return -1;
return 0;
}
static int iree_profile_explain_compare_interval(const void* lhs,
const void* rhs) {
const iree_profile_explain_interval_t* a =
(const iree_profile_explain_interval_t*)lhs;
const iree_profile_explain_interval_t* b =
(const iree_profile_explain_interval_t*)rhs;
if (a->start_tick < b->start_tick) return -1;
if (a->start_tick > b->start_tick) return 1;
if (a->end_tick < b->end_tick) return -1;
if (a->end_tick > b->end_tick) return 1;
return 0;
}
static bool iree_profile_explain_try_fit_driver_host_cpu_clock(
const iree_profile_dispatch_context_t* context,
uint32_t physical_device_ordinal,
iree_profile_model_clock_fit_t* out_clock_fit) {
const iree_profile_model_device_t* device =
iree_profile_model_find_device(&context->model, physical_device_ordinal);
return iree_profile_model_device_try_fit_clock_exact(
device, IREE_PROFILE_MODEL_CLOCK_TIME_DOMAIN_HOST_CPU_TIMESTAMP_NS,
out_clock_fit);
}
static const char* iree_profile_explain_timing_source_string(
iree_profile_explain_timing_source_t timing_source) {
switch (timing_source) {
case IREE_PROFILE_EXPLAIN_TIMING_SOURCE_DEVICE_DISPATCH:
return "device_dispatch_event";
case IREE_PROFILE_EXPLAIN_TIMING_SOURCE_HOST_EXECUTION:
return "host_execution_event";
}
return "unknown";
}
static iree_status_t iree_profile_explain_collect_function_ranks(
const iree_profile_dispatch_context_t* context,
iree_allocator_t host_allocator,
iree_profile_explain_function_rank_t** out_ranks,
iree_host_size_t* out_rank_count) {
*out_ranks = NULL;
*out_rank_count = 0;
const bool use_host_execution = context->valid_dispatch_count == 0 &&
context->valid_host_dispatch_count != 0;
const iree_host_size_t candidate_count =
use_host_execution ? context->host_dispatch_aggregate_count
: context->aggregate_count;
if (candidate_count == 0) return iree_ok_status();
iree_profile_explain_function_rank_t* ranks = NULL;
iree_status_t status = iree_allocator_malloc_array_uninitialized(
host_allocator, candidate_count, sizeof(ranks[0]), (void**)&ranks);
iree_host_size_t rank_count = 0;
if (iree_status_is_ok(status)) {
if (use_host_execution) {
for (iree_host_size_t i = 0; i < context->host_dispatch_aggregate_count;
++i) {
const iree_profile_host_dispatch_aggregate_t* aggregate =
&context->host_dispatch_aggregates[i];
if (aggregate->valid_count == 0) continue;
iree_profile_explain_function_rank_t* rank = &ranks[rank_count++];
memset(rank, 0, sizeof(*rank));
rank->timing_source = IREE_PROFILE_EXPLAIN_TIMING_SOURCE_HOST_EXECUTION;
rank->physical_device_ordinal = aggregate->physical_device_ordinal;
rank->executable_id = aggregate->executable_id;
rank->function_ordinal = aggregate->function_ordinal;
rank->dispatch_count = aggregate->dispatch_count;
rank->valid_count = aggregate->valid_count;
rank->invalid_count = aggregate->invalid_count;
rank->maximum_ns = aggregate->maximum_ns;
rank->total_ns = aggregate->total_ns;
rank->average_ns = aggregate->mean_ns;
rank->total_tile_count = aggregate->total_tile_count;
rank->total_tile_duration_sum_ns =
aggregate->total_tile_duration_sum_ns;
rank->has_duration_ns = true;
}
} else {
for (iree_host_size_t i = 0; i < context->aggregate_count; ++i) {
const iree_profile_dispatch_aggregate_t* aggregate =
&context->aggregates[i];
if (aggregate->valid_count == 0) continue;
const iree_profile_model_device_t* device =
iree_profile_model_find_device(&context->model,
aggregate->physical_device_ordinal);
iree_profile_model_clock_fit_t clock_fit;
const bool has_clock_fit =
iree_profile_model_device_try_fit_clock_exact(
device,
IREE_PROFILE_MODEL_CLOCK_TIME_DOMAIN_HOST_CPU_TIMESTAMP_NS,
&clock_fit);
iree_profile_explain_function_rank_t* rank = &ranks[rank_count++];
memset(rank, 0, sizeof(*rank));
rank->timing_source =
IREE_PROFILE_EXPLAIN_TIMING_SOURCE_DEVICE_DISPATCH;
rank->physical_device_ordinal = aggregate->physical_device_ordinal;
rank->executable_id = aggregate->executable_id;
rank->function_ordinal = aggregate->function_ordinal;
rank->dispatch_count = aggregate->dispatch_count;
rank->valid_count = aggregate->valid_count;
rank->invalid_count = aggregate->invalid_count;
rank->maximum_ticks = aggregate->maximum_ticks;
rank->total_ticks = aggregate->total_ticks;
if (has_clock_fit &&
iree_profile_model_clock_fit_scale_ticks_to_ns(
&clock_fit, aggregate->maximum_ticks, &rank->maximum_ns) &&
iree_profile_model_clock_fit_scale_ticks_to_ns(
&clock_fit, aggregate->total_ticks, &rank->total_ns)) {
rank->has_duration_ns = true;
rank->average_ns =
aggregate->mean_ticks *
iree_profile_model_clock_fit_ns_per_tick(&clock_fit);
}
}
}
}
if (iree_status_is_ok(status)) {
qsort(ranks, rank_count, sizeof(ranks[0]),
iree_profile_explain_compare_function_rank);
*out_ranks = ranks;
*out_rank_count = rank_count;
} else {
iree_allocator_free(host_allocator, ranks);
}
return status;
}
static void iree_profile_explain_compute_queue_operation_totals(
const iree_profile_dispatch_context_t* context,
iree_profile_explain_queue_operation_totals_t* out_totals) {
memset(out_totals, 0, sizeof(*out_totals));
for (iree_host_size_t i = 0; i < context->queue_query.queue_event_count;
++i) {
const iree_hal_profile_queue_event_t* event =
&context->queue_query.queue_events[i].record;
if (event->type <= IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_HOST_CALL) {
++out_totals->event_counts[event->type];
out_totals->payload_bytes[event->type] += event->payload_length;
}
if (event->dependency_strategy <=
IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_SOFTWARE_DEFER) {
++out_totals->strategy_counts[event->dependency_strategy];
}
}
}
static double iree_profile_explain_visible_span_ticks(
const iree_profile_dispatch_context_t* context,
uint32_t physical_device_ordinal) {
uint64_t earliest_start_tick = UINT64_MAX;
uint64_t latest_end_tick = 0;
for (iree_host_size_t i = 0; i < context->queue_aggregate_count; ++i) {
const iree_profile_dispatch_queue_aggregate_t* aggregate =
&context->queue_aggregates[i];
if (aggregate->physical_device_ordinal != physical_device_ordinal ||
aggregate->valid_count == 0) {
continue;
}
earliest_start_tick =
iree_min(earliest_start_tick, aggregate->earliest_start_tick);
latest_end_tick = iree_max(latest_end_tick, aggregate->latest_end_tick);
}
return iree_profile_model_span_ticks(earliest_start_tick, latest_end_tick);
}
static bool iree_profile_explain_accumulate_ticks(uint64_t* total_ticks,
uint64_t duration_ticks) {
if (duration_ticks > UINT64_MAX - *total_ticks) return false;
*total_ticks += duration_ticks;
return true;
}
static iree_status_t iree_profile_explain_total_dispatch_ticks_for_device(
const iree_profile_dispatch_context_t* context,
uint32_t physical_device_ordinal, uint64_t* out_total_ticks) {
*out_total_ticks = 0;
for (iree_host_size_t i = 0; i < context->aggregate_count; ++i) {
const iree_profile_dispatch_aggregate_t* aggregate =
&context->aggregates[i];
if (aggregate->physical_device_ordinal == physical_device_ordinal) {
if (!iree_profile_explain_accumulate_ticks(out_total_ticks,
aggregate->total_ticks)) {
return iree_make_status(
IREE_STATUS_OUT_OF_RANGE,
"device summary dispatch tick total overflow device=%u",
physical_device_ordinal);
}
}
}
return iree_ok_status();
}
static iree_status_t iree_profile_explain_resolve_device_row(
const iree_profile_dispatch_context_t* context,
const iree_profile_model_device_t* device,
iree_profile_explain_device_row_t* out_row) {
memset(out_row, 0, sizeof(*out_row));
out_row->device = device;
iree_profile_model_clock_fit_t clock_fit;
out_row->has_clock_fit = iree_profile_model_device_try_fit_clock_exact(
device, IREE_PROFILE_MODEL_CLOCK_TIME_DOMAIN_HOST_CPU_TIMESTAMP_NS,
&clock_fit);
if (out_row->has_clock_fit) {
out_row->nanoseconds_per_tick =
iree_profile_model_clock_fit_ns_per_tick(&clock_fit);
out_row->tick_frequency_hz =
iree_profile_model_clock_fit_tick_frequency_hz(&clock_fit);
}
out_row->visible_span_ticks = iree_profile_explain_visible_span_ticks(
context, device->physical_device_ordinal);
IREE_RETURN_IF_ERROR(iree_profile_explain_total_dispatch_ticks_for_device(
context, device->physical_device_ordinal,
&out_row->total_dispatch_ticks));
out_row->active_over_visible_ratio =
out_row->visible_span_ticks > 0.0
? (double)out_row->total_dispatch_ticks / out_row->visible_span_ticks
: 0.0;
out_row->has_total_dispatch_time_ns =
out_row->has_clock_fit && out_row->total_dispatch_ticks != 0 &&
iree_profile_model_clock_fit_scale_ticks_to_ns(
&clock_fit, out_row->total_dispatch_ticks,
&out_row->total_dispatch_time_ns);
return iree_ok_status();
}
static bool iree_profile_explain_queue_aggregate_matches(
const iree_hal_profile_queue_record_t* queue,
const iree_profile_dispatch_queue_aggregate_t* aggregate) {
return aggregate->physical_device_ordinal == queue->physical_device_ordinal &&
aggregate->queue_ordinal == queue->queue_ordinal &&
aggregate->stream_id == queue->stream_id;
}
static iree_status_t iree_profile_explain_collect_queue_intervals(
const iree_profile_dispatch_context_t* context,
const iree_hal_profile_queue_record_t* queue,
iree_profile_explain_interval_t* intervals,
iree_host_size_t* inout_interval_count,
iree_profile_explain_queue_summary_t* summary) {
for (iree_host_size_t i = 0; i < context->queue_aggregate_count; ++i) {
const iree_profile_dispatch_queue_aggregate_t* aggregate =
&context->queue_aggregates[i];
if (!iree_profile_explain_queue_aggregate_matches(queue, aggregate)) {
continue;
}
++summary->submission_count;
if (!iree_profile_explain_accumulate_ticks(&summary->total_dispatch_ticks,
aggregate->total_ticks)) {
return iree_make_status(
IREE_STATUS_OUT_OF_RANGE,
"queue summary dispatch tick total overflow device=%u queue=%u"
" stream=%" PRIu64,
queue->physical_device_ordinal, queue->queue_ordinal,
queue->stream_id);
}
if (aggregate->valid_count != 0) {
++summary->valid_submission_count;
intervals[(*inout_interval_count)++] = (iree_profile_explain_interval_t){
aggregate->earliest_start_tick, aggregate->latest_end_tick};
}
if (aggregate->invalid_count != 0) {
++summary->invalid_submission_count;
}
}
return iree_ok_status();
}
static void iree_profile_explain_merge_queue_intervals(
iree_profile_explain_interval_t* intervals, iree_host_size_t interval_count,
iree_profile_explain_queue_summary_t* summary) {
if (interval_count == 0) return;
qsort(intervals, interval_count, sizeof(intervals[0]),
iree_profile_explain_compare_interval);
uint64_t merged_start_tick = intervals[0].start_tick;
uint64_t merged_end_tick = intervals[0].end_tick;
for (iree_host_size_t i = 1; i < interval_count; ++i) {
if (intervals[i].start_tick <= merged_end_tick) {
merged_end_tick = iree_max(merged_end_tick, intervals[i].end_tick);
continue;
}
summary->busy_ticks += (double)(merged_end_tick - merged_start_tick);
const uint64_t gap_ticks = intervals[i].start_tick - merged_end_tick;
++summary->gap_count;
summary->total_gap_ticks += (double)gap_ticks;
summary->max_gap_ticks =
iree_max(summary->max_gap_ticks, (double)gap_ticks);
merged_start_tick = intervals[i].start_tick;
merged_end_tick = intervals[i].end_tick;
}
summary->busy_ticks += (double)(merged_end_tick - merged_start_tick);
}
static iree_status_t iree_profile_explain_summarize_queue(
const iree_profile_dispatch_context_t* context,
const iree_hal_profile_queue_record_t* queue,
iree_allocator_t host_allocator,
iree_profile_explain_queue_summary_t* out_summary) {
memset(out_summary, 0, sizeof(*out_summary));
if (context->queue_aggregate_count == 0) return iree_ok_status();
iree_profile_explain_interval_t* intervals = NULL;
iree_status_t status = iree_allocator_malloc_array_uninitialized(
host_allocator, context->queue_aggregate_count, sizeof(intervals[0]),
(void**)&intervals);
iree_host_size_t interval_count = 0;
if (iree_status_is_ok(status)) {
status = iree_profile_explain_collect_queue_intervals(
context, queue, intervals, &interval_count, out_summary);
}
if (iree_status_is_ok(status)) {
iree_profile_explain_merge_queue_intervals(intervals, interval_count,
out_summary);
}
iree_allocator_free(host_allocator, intervals);
return status;
}
static iree_status_t iree_profile_explain_resolve_queue_row(
const iree_profile_dispatch_context_t* context,
const iree_hal_profile_queue_record_t* queue,
iree_allocator_t host_allocator,
iree_profile_explain_queue_row_t* out_row) {
memset(out_row, 0, sizeof(*out_row));
out_row->queue = queue;
IREE_RETURN_IF_ERROR(iree_profile_explain_summarize_queue(
context, queue, host_allocator, &out_row->summary));
iree_profile_model_clock_fit_t clock_fit;
out_row->has_clock_fit = iree_profile_explain_try_fit_driver_host_cpu_clock(
context, queue->physical_device_ordinal, &clock_fit);
if (out_row->has_clock_fit) {
out_row->nanoseconds_per_tick =
iree_profile_model_clock_fit_ns_per_tick(&clock_fit);
}
out_row->has_total_dispatch_time_ns =
out_row->has_clock_fit && out_row->summary.valid_submission_count != 0 &&
iree_profile_model_clock_fit_scale_ticks_to_ns(
&clock_fit, out_row->summary.total_dispatch_ticks,
&out_row->total_dispatch_time_ns);
out_row->gap_analysis_available = context->queue_query.queue_event_count != 0;
return iree_ok_status();
}
static void iree_profile_explain_print_hint_text(const char* severity,
const char* category,
const char* message,
FILE* file) {
fprintf(file, " [%s] %s: %s\n", severity, category, message);
}
static void iree_profile_explain_print_hint_jsonl(const char* severity,
const char* category,
const char* message,
FILE* file) {
fprintf(file, "{\"type\":\"explain_hint\",\"severity\":");
iree_profile_fprint_json_string(file, iree_make_cstring_view(severity));
fprintf(file, ",\"category\":");
iree_profile_fprint_json_string(file, iree_make_cstring_view(category));
fprintf(file, ",\"message\":");
iree_profile_fprint_json_string(file, iree_make_cstring_view(message));
fputs("}\n", file);
}
static bool iree_profile_explain_has_pool_waits(
const iree_profile_memory_context_t* memory_context) {
for (iree_host_size_t i = 0; i < memory_context->device_count; ++i) {
if (memory_context->devices[i].pool_wait_count != 0) return true;
}
return false;
}
static void iree_profile_explain_copy_sorted_top_dispatches(
const iree_profile_dispatch_context_t* dispatch_context,
iree_profile_dispatch_top_event_t* out_top_dispatches) {
memcpy(out_top_dispatches, dispatch_context->top_dispatches,
dispatch_context->top_dispatch_count * sizeof(out_top_dispatches[0]));
qsort(out_top_dispatches, dispatch_context->top_dispatch_count,
sizeof(out_top_dispatches[0]), iree_profile_explain_compare_top_event);
}
static void iree_profile_explain_copy_sorted_top_host_dispatches(
const iree_profile_dispatch_context_t* dispatch_context,
iree_profile_host_dispatch_top_event_t* out_top_dispatches) {
memcpy(out_top_dispatches, dispatch_context->top_host_dispatches,
dispatch_context->top_host_dispatch_count *
sizeof(out_top_dispatches[0]));
qsort(out_top_dispatches, dispatch_context->top_host_dispatch_count,
sizeof(out_top_dispatches[0]),
iree_profile_explain_compare_top_host_event);
}
static void iree_profile_explain_print_text_header(
const iree_profile_summary_t* summary,
const iree_profile_dispatch_context_t* dispatch_context,
const iree_profile_memory_context_t* memory_context, FILE* file) {
fprintf(file, "IREE HAL profile explain\n");
fprintf(file,
"health: records=%" PRIu64 " chunks=%" PRIu64
" unknown_records=%" PRIu64 " unknown_chunks=%" PRIu64
" truncated_chunks=%" PRIu64 " dropped_records=%" PRIu64 "\n",
summary->file_record_count, summary->chunk_count,
summary->unknown_record_count, summary->unknown_chunk_count,
summary->truncated_chunk_count, summary->dropped_record_count);
if (summary->non_ok_session_end_count != 0) {
fprintf(file,
"session_status: non_ok_session_end=%" PRIu64
" first_session_id=%" PRIu64 " first_stream_id=%" PRIu64
" first_event_id=%" PRIu64
" first_status_code=%u"
" first_status=%s\n",
summary->non_ok_session_end_count, summary->first_non_ok_session_id,
summary->first_non_ok_stream_id, summary->first_non_ok_event_id,
(uint32_t)summary->first_non_ok_session_status_code,
iree_profile_status_code_name(
(uint32_t)summary->first_non_ok_session_status_code));
}
fprintf(file,
"coverage: dispatches=%" PRIu64 " valid=%" PRIu64 " invalid=%" PRIu64
" host_dispatches=%" PRIu64 " valid_host=%" PRIu64
" invalid_host=%" PRIu64 " queues=%" PRIhsz " queue_events=%" PRIhsz
" command_buffers=%" PRIhsz " command_operations=%" PRIhsz
" memory_events=%" PRIu64 "\n",
dispatch_context->matched_dispatch_count,
dispatch_context->valid_dispatch_count,
dispatch_context->invalid_dispatch_count,
dispatch_context->matched_host_dispatch_count,
dispatch_context->valid_host_dispatch_count,
dispatch_context->invalid_host_dispatch_count,
dispatch_context->model.queue_count,
dispatch_context->queue_query.queue_event_count,
dispatch_context->model.command_buffer_count,
dispatch_context->model.command_operation_count,
memory_context->matched_event_count);
}
static iree_status_t iree_profile_explain_print_text_devices(
const iree_profile_dispatch_context_t* dispatch_context, FILE* file) {
fprintf(file, "devices:\n");
for (iree_host_size_t i = 0; i < dispatch_context->model.device_count; ++i) {
const iree_profile_model_device_t* device =
&dispatch_context->model.devices[i];
iree_profile_explain_device_row_t row;
IREE_RETURN_IF_ERROR(iree_profile_explain_resolve_device_row(
dispatch_context, device, &row));
fprintf(file,
" device[%u]: clock_fit=%s clock_samples=%" PRIu64
" visible_span_ticks=%.3f active_dispatch_ticks=%" PRIu64
" active_over_visible=%.3f\n",
row.device->physical_device_ordinal,
row.has_clock_fit ? "true" : "false",
row.device->clock_sample_count, row.visible_span_ticks,
row.total_dispatch_ticks, row.active_over_visible_ratio);
if (row.has_total_dispatch_time_ns) {
fprintf(file,
" visible_span_ns=%.3f active_dispatch_ns=%" PRId64
" tick_frequency_hz=%.3f\n",
row.visible_span_ticks * row.nanoseconds_per_tick,
row.total_dispatch_time_ns, row.tick_frequency_hz);
}
}
return iree_ok_status();
}
static iree_status_t iree_profile_explain_print_text_queues(
const iree_profile_dispatch_context_t* dispatch_context,
iree_allocator_t host_allocator, FILE* file) {
fprintf(file, "queues:\n");
for (iree_host_size_t i = 0; i < dispatch_context->model.queue_count; ++i) {
const iree_hal_profile_queue_record_t* queue =
&dispatch_context->model.queues[i].record;
iree_profile_explain_queue_row_t row;
IREE_RETURN_IF_ERROR(iree_profile_explain_resolve_queue_row(
dispatch_context, queue, host_allocator, &row));
fprintf(file,
" queue device=%u ordinal=%u stream=%" PRIu64
": submissions=%" PRIu64 " valid=%" PRIu64 " invalid=%" PRIu64
" busy_ticks=%.3f"
" total_dispatch_ticks=%" PRIu64,
row.queue->physical_device_ordinal, row.queue->queue_ordinal,
row.queue->stream_id, row.summary.submission_count,
row.summary.valid_submission_count,
row.summary.invalid_submission_count, row.summary.busy_ticks,
row.summary.total_dispatch_ticks);
if (row.has_total_dispatch_time_ns) {
fprintf(file, " busy_ns=%.3f total_dispatch_ns=%" PRId64,
row.summary.busy_ticks * row.nanoseconds_per_tick,
row.total_dispatch_time_ns);
}
if (row.gap_analysis_available) {
fprintf(file, " gaps=%" PRIu64 " total_gap_ticks=%.3f max_gap_ticks=%.3f",
row.summary.gap_count, row.summary.total_gap_ticks,
row.summary.max_gap_ticks);
if (row.has_clock_fit) {
fprintf(file, " total_gap_ns=%.3f max_gap_ns=%.3f",
row.summary.total_gap_ticks * row.nanoseconds_per_tick,
row.summary.max_gap_ticks * row.nanoseconds_per_tick);
}
} else {
fprintf(file, " gaps=unavailable_without_queue_events");
}
fputc('\n', file);
}
return iree_ok_status();
}
static iree_status_t iree_profile_explain_print_text_top_functions(
const iree_profile_dispatch_context_t* dispatch_context,
const iree_profile_explain_function_rank_t* function_ranks,
iree_host_size_t function_rank_count, FILE* file) {
fprintf(file, "top functions by total dispatch time:\n");
const iree_host_size_t top_function_count =
iree_min(function_rank_count,
(iree_host_size_t)IREE_PROFILE_EXPLAIN_TOP_FUNCTION_COUNT);
for (iree_host_size_t i = 0; i < top_function_count; ++i) {
const iree_profile_explain_function_rank_t* rank = &function_ranks[i];
char numeric_buffer[128];
iree_string_view_t key = iree_string_view_empty();
IREE_RETURN_IF_ERROR(iree_profile_model_resolve_dispatch_key(
&dispatch_context->model, rank->physical_device_ordinal,
rank->executable_id, rank->function_ordinal, numeric_buffer,
sizeof(numeric_buffer), &key));
fprintf(file,
" #%" PRIhsz " %.*s device=%u executable=%" PRIu64
" function=%u count=%" PRIu64 " valid=%" PRIu64 " invalid=%" PRIu64
" timing_source=%s",
i + 1, (int)key.size, key.data, rank->physical_device_ordinal,
rank->executable_id, rank->function_ordinal, rank->dispatch_count,
rank->valid_count, rank->invalid_count,
iree_profile_explain_timing_source_string(rank->timing_source));
if (rank->timing_source ==
IREE_PROFILE_EXPLAIN_TIMING_SOURCE_DEVICE_DISPATCH) {
fprintf(file, " total_ticks=%" PRIu64 " max_ticks=%" PRIu64,
rank->total_ticks, rank->maximum_ticks);
} else {
fprintf(file,
" total_tile_count=%" PRIu64
" total_tile_duration_sum_ns=%" PRId64,
rank->total_tile_count, rank->total_tile_duration_sum_ns);
}
if (rank->has_duration_ns) {
fprintf(file, " total_ns=%" PRId64 " avg_ns=%.3f max_ns=%" PRId64,
rank->total_ns, rank->average_ns, rank->maximum_ns);
}
fputc('\n', file);
}
return iree_ok_status();
}
static iree_status_t iree_profile_explain_print_text_top_dispatches(
const iree_profile_dispatch_context_t* dispatch_context, FILE* file) {
fprintf(file, "top individual dispatches:\n");
if (dispatch_context->top_dispatch_count != 0) {
iree_profile_dispatch_top_event_t
top_dispatches[IREE_PROFILE_DISPATCH_TOP_EVENT_COUNT];
iree_profile_explain_copy_sorted_top_dispatches(dispatch_context,
top_dispatches);
for (iree_host_size_t i = 0; i < dispatch_context->top_dispatch_count;
++i) {
const iree_profile_dispatch_top_event_t* top_event = &top_dispatches[i];
iree_profile_model_clock_fit_t clock_fit;
const bool has_clock_fit =
iree_profile_explain_try_fit_driver_host_cpu_clock(
dispatch_context, top_event->physical_device_ordinal, &clock_fit);
int64_t duration_ns = 0;
const bool has_duration_ns =
has_clock_fit &&
iree_profile_model_clock_fit_scale_ticks_to_ns(
&clock_fit, top_event->duration_ticks, &duration_ns);
char numeric_buffer[128];
iree_string_view_t key = iree_string_view_empty();
IREE_RETURN_IF_ERROR(iree_profile_model_resolve_dispatch_key(
&dispatch_context->model, top_event->physical_device_ordinal,
top_event->event.executable_id, top_event->event.function_ordinal,
numeric_buffer, sizeof(numeric_buffer), &key));
fprintf(file,
" #%" PRIhsz " event=%" PRIu64 " submission=%" PRIu64
" %.*s device=%u queue=%u stream=%" PRIu64
" timing_source=%s duration_ticks=%" PRIu64,
i + 1, top_event->event.event_id, top_event->event.submission_id,
(int)key.size, key.data, top_event->physical_device_ordinal,
top_event->queue_ordinal, top_event->stream_id,
iree_profile_explain_timing_source_string(
IREE_PROFILE_EXPLAIN_TIMING_SOURCE_DEVICE_DISPATCH),
top_event->duration_ticks);
if (has_duration_ns) {
fprintf(file, " duration_ns=%" PRId64, duration_ns);
}
fputc('\n', file);
}
return iree_ok_status();
}
iree_profile_host_dispatch_top_event_t
top_dispatches[IREE_PROFILE_DISPATCH_TOP_EVENT_COUNT];
iree_profile_explain_copy_sorted_top_host_dispatches(dispatch_context,
top_dispatches);
for (iree_host_size_t i = 0; i < dispatch_context->top_host_dispatch_count;
++i) {
const iree_profile_host_dispatch_top_event_t* top_event =
&top_dispatches[i];
char numeric_buffer[128];
iree_string_view_t key = iree_string_view_empty();
IREE_RETURN_IF_ERROR(iree_profile_model_resolve_dispatch_key(
&dispatch_context->model, top_event->physical_device_ordinal,
top_event->event.executable_id, top_event->event.function_ordinal,
numeric_buffer, sizeof(numeric_buffer), &key));
fprintf(file,
" #%" PRIhsz " event=%" PRIu64 " submission=%" PRIu64
" %.*s device=%u queue=%u stream=%" PRIu64
" timing_source=%s duration_ns=%" PRId64 " tile_count=%" PRIu64
" tile_duration_sum_ns=%" PRId64,
i + 1, top_event->event.event_id, top_event->event.submission_id,
(int)key.size, key.data, top_event->physical_device_ordinal,
top_event->queue_ordinal, top_event->stream_id,
iree_profile_explain_timing_source_string(
IREE_PROFILE_EXPLAIN_TIMING_SOURCE_HOST_EXECUTION),
top_event->duration_ns, top_event->event.tile_count,
top_event->event.tile_duration_sum_ns);
if (top_event->event.command_buffer_id != 0) {
fprintf(file, " command_buffer=%" PRIu64 " command_index=%u",
top_event->event.command_buffer_id,
top_event->event.command_index);
}
fputc('\n', file);
}
return iree_ok_status();
}
static void iree_profile_explain_print_text_queue_operations(
const iree_profile_dispatch_context_t* dispatch_context, FILE* file) {
iree_profile_explain_queue_operation_totals_t operation_totals;
iree_profile_explain_compute_queue_operation_totals(dispatch_context,
&operation_totals);
const uint64_t* event_counts = operation_totals.event_counts;
const uint64_t* payload_bytes = operation_totals.payload_bytes;
const uint64_t* strategy_counts = operation_totals.strategy_counts;
fprintf(file,
"queue operations: events=%" PRIhsz
" strategies none/inline/device_barrier/software_defer=%" PRIu64
"/%" PRIu64 "/%" PRIu64 "/%" PRIu64 "\n",
dispatch_context->queue_query.queue_event_count,
strategy_counts[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_NONE],
strategy_counts[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_INLINE],
strategy_counts
[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_DEVICE_BARRIER],
strategy_counts
[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_SOFTWARE_DEFER]);
fprintf(file,
" transfer_payload_bytes copy/fill/update/read/write=%" PRIu64
"/%" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64 "\n",
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_COPY],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_FILL],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_UPDATE],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_READ],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_WRITE]);
fprintf(
file,
" operation_counts dispatch/execute/alloca/dealloca/host_call=%" PRIu64
"/%" PRIu64 "/%" PRIu64 "/%" PRIu64 "/%" PRIu64 "\n",
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_DISPATCH],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_EXECUTE],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_ALLOCA],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_DEALLOCA],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_HOST_CALL]);
}
static void iree_profile_explain_print_text_memory_pressure(
const iree_profile_memory_context_t* memory_context, FILE* file) {
fprintf(file, "memory pressure:\n");
for (iree_host_size_t i = 0; i < memory_context->device_count; ++i) {
const iree_profile_memory_device_t* device = &memory_context->devices[i];
fprintf(file,
" device[%u]: slab_high_water=%" PRIu64
" pool_reserved_high_water=%" PRIu64
" pool_materialized_high_water=%" PRIu64
" queue_inflight_high_water=%" PRIu64 " buffer_high_water=%" PRIu64
" pool_waits=%" PRIu64 "\n",
device->physical_device_ordinal,
device->slab_allocation_balance.high_water_bytes,
device->pool_reservation_balance.high_water_bytes,
device->pool_materialization_balance.high_water_bytes,
device->queue_inflight_balance.high_water_bytes,
device->buffer_allocation_balance.high_water_bytes,
device->pool_wait_count);
}
}
static void iree_profile_explain_print_text_hints(
const iree_profile_summary_t* summary,
const iree_profile_dispatch_context_t* dispatch_context,
const iree_profile_memory_context_t* memory_context, FILE* file) {
fprintf(file, "hints:\n");
if (summary->unknown_record_count != 0 || summary->unknown_chunk_count != 0) {
iree_profile_explain_print_hint_text(
"warning", "format",
"unknown records or chunks were present; newer producer data may be "
"missing from this analysis",
file);
}
if (summary->truncated_chunk_count != 0) {
iree_profile_explain_print_hint_text(
"error", "format",
"truncated chunks were present; timing and lifecycle totals may be "
"incomplete",
file);
}
if (summary->non_ok_session_end_count != 0) {
fprintf(file,
" [error] session: %" PRIu64
" profiling session end record(s) reported non-OK status; first "
"session_id=%" PRIu64 " stream_id=%" PRIu64 " event_id=%" PRIu64
" status=%s(%u)\n",
summary->non_ok_session_end_count, summary->first_non_ok_session_id,
summary->first_non_ok_stream_id, summary->first_non_ok_event_id,
iree_profile_status_code_name(
(uint32_t)summary->first_non_ok_session_status_code),
(uint32_t)summary->first_non_ok_session_status_code);
}
if (dispatch_context->invalid_dispatch_count != 0) {
iree_profile_explain_print_hint_text(
"warning", "dispatch",
"some dispatch events had missing or reversed timestamps and were "
"excluded from timing totals",
file);
}
if (dispatch_context->invalid_host_dispatch_count != 0) {
iree_profile_explain_print_hint_text(
"warning", "dispatch",
"some host execution dispatch spans had missing or reversed timestamps "
"and were excluded from timing totals",
file);
}
if (dispatch_context->queue_query.queue_event_count == 0) {
iree_profile_explain_print_hint_text(
"info", "queue",
"queue event records are absent; queue dependency and gap hints are "
"limited to dispatch timestamp intervals",
file);
}
if (iree_profile_explain_has_pool_waits(memory_context)) {
iree_profile_explain_print_hint_text(
"warning", "memory",
"pool wait events were observed; allocation readiness affected the "
"captured queue schedule",
file);
}
if (dispatch_context->valid_dispatch_count == 0 &&
dispatch_context->valid_host_dispatch_count != 0) {
iree_profile_explain_print_hint_text(
"info", "dispatch",
"device dispatch events are absent; top dispatch analysis is using "
"host execution dispatch spans",
file);
} else if (dispatch_context->valid_dispatch_count == 0) {
iree_profile_explain_print_hint_text(
"info", "dispatch",
"no valid dispatch events were captured; enable queue profiling on a "
"producer that emits device timestamps",
file);
}
}
static iree_status_t iree_profile_explain_print_text(
const iree_profile_summary_t* summary,
const iree_profile_dispatch_context_t* dispatch_context,
const iree_profile_memory_context_t* memory_context,
iree_allocator_t host_allocator, FILE* file) {
iree_profile_explain_function_rank_t* function_ranks = NULL;
iree_host_size_t function_rank_count = 0;
iree_status_t status = iree_profile_explain_collect_function_ranks(
dispatch_context, host_allocator, &function_ranks, &function_rank_count);
if (iree_status_is_ok(status)) {
iree_profile_explain_print_text_header(summary, dispatch_context,
memory_context, file);
status = iree_profile_explain_print_text_devices(dispatch_context, file);
}
if (iree_status_is_ok(status)) {
status = iree_profile_explain_print_text_queues(dispatch_context,
host_allocator, file);
}
if (iree_status_is_ok(status)) {
status = iree_profile_explain_print_text_top_functions(
dispatch_context, function_ranks, function_rank_count, file);
}
if (iree_status_is_ok(status)) {
status =
iree_profile_explain_print_text_top_dispatches(dispatch_context, file);
}
if (iree_status_is_ok(status)) {
iree_profile_explain_print_text_queue_operations(dispatch_context, file);
iree_profile_explain_print_text_memory_pressure(memory_context, file);
iree_profile_explain_print_text_hints(summary, dispatch_context,
memory_context, file);
}
iree_allocator_free(host_allocator, function_ranks);
return status;
}
static void iree_profile_explain_print_jsonl_summary(
const iree_profile_summary_t* summary,
const iree_profile_dispatch_context_t* dispatch_context,
const iree_profile_memory_context_t* memory_context, FILE* file) {
fprintf(file,
"{\"type\":\"explain_summary\",\"file_records\":%" PRIu64
",\"chunk_records\":%" PRIu64 ",\"unknown_records\":%" PRIu64
",\"unknown_chunks\":%" PRIu64 ",\"truncated_chunks\":%" PRIu64
",\"dropped_records\":%" PRIu64
",\"non_ok_session_end_records\":%" PRIu64 ",\"dispatches\":%" PRIu64
",\"valid_dispatches\":%" PRIu64 ",\"invalid_dispatches\":%" PRIu64
",\"host_dispatches\":%" PRIu64 ",\"valid_host_dispatches\":%" PRIu64
",\"invalid_host_dispatches\":%" PRIu64 ",\"queues\":%" PRIhsz
",\"queue_events\":%" PRIhsz ",\"command_buffers\":%" PRIhsz
",\"command_operations\":%" PRIhsz ",\"memory_events\":%" PRIu64,
summary->file_record_count, summary->chunk_count,
summary->unknown_record_count, summary->unknown_chunk_count,
summary->truncated_chunk_count, summary->dropped_record_count,
summary->non_ok_session_end_count,
dispatch_context->matched_dispatch_count,
dispatch_context->valid_dispatch_count,
dispatch_context->invalid_dispatch_count,
dispatch_context->matched_host_dispatch_count,
dispatch_context->valid_host_dispatch_count,
dispatch_context->invalid_host_dispatch_count,
dispatch_context->model.queue_count,
dispatch_context->queue_query.queue_event_count,
dispatch_context->model.command_buffer_count,
dispatch_context->model.command_operation_count,
memory_context->matched_event_count);
if (summary->non_ok_session_end_count != 0) {
fprintf(file,
",\"first_non_ok_session_id\":%" PRIu64
",\"first_non_ok_stream_id\":%" PRIu64
",\"first_non_ok_event_id\":%" PRIu64
",\"first_non_ok_status_code\":%u,\"first_non_ok_status\":\"%s\"",
summary->first_non_ok_session_id, summary->first_non_ok_stream_id,
summary->first_non_ok_event_id,
(uint32_t)summary->first_non_ok_session_status_code,
iree_profile_status_code_name(
(uint32_t)summary->first_non_ok_session_status_code));
}
fputs("}\n", file);
}
static iree_status_t iree_profile_explain_print_jsonl_devices(
const iree_profile_dispatch_context_t* dispatch_context, FILE* file) {
for (iree_host_size_t i = 0; i < dispatch_context->model.device_count; ++i) {
const iree_profile_model_device_t* device =
&dispatch_context->model.devices[i];
iree_profile_explain_device_row_t row;
IREE_RETURN_IF_ERROR(iree_profile_explain_resolve_device_row(
dispatch_context, device, &row));
fprintf(file,
"{\"type\":\"explain_device\",\"physical_device_ordinal\":%u"
",\"clock_fit_available\":%s,\"clock_samples\":%" PRIu64
",\"visible_span_ticks\":%.3f"
",\"active_dispatch_ticks\":%" PRIu64
",\"active_over_visible\":%.6f"
",\"tick_frequency_hz\":%.3f,\"visible_span_ns\":%.3f"
",\"active_dispatch_time_ns_available\":%s"
",\"active_dispatch_ns\":%" PRId64 "}\n",
row.device->physical_device_ordinal,
row.has_clock_fit ? "true" : "false",
row.device->clock_sample_count, row.visible_span_ticks,
row.total_dispatch_ticks, row.active_over_visible_ratio,
row.has_clock_fit ? row.tick_frequency_hz : 0.0,
row.has_clock_fit
? row.visible_span_ticks * row.nanoseconds_per_tick
: 0.0,
row.has_total_dispatch_time_ns ? "true" : "false",
row.has_total_dispatch_time_ns ? row.total_dispatch_time_ns : 0);
}
return iree_ok_status();
}
static iree_status_t iree_profile_explain_print_jsonl_queues(
const iree_profile_dispatch_context_t* dispatch_context,
iree_allocator_t host_allocator, FILE* file) {
for (iree_host_size_t i = 0; i < dispatch_context->model.queue_count; ++i) {
const iree_hal_profile_queue_record_t* queue =
&dispatch_context->model.queues[i].record;
iree_profile_explain_queue_row_t row;
IREE_RETURN_IF_ERROR(iree_profile_explain_resolve_queue_row(
dispatch_context, queue, host_allocator, &row));
fprintf(
file,
"{\"type\":\"explain_queue\",\"physical_device_ordinal\":%u"
",\"queue_ordinal\":%u,\"stream_id\":%" PRIu64
",\"submissions\":%" PRIu64 ",\"valid_submissions\":%" PRIu64
",\"invalid_submissions\":%" PRIu64
",\"busy_ticks\":%.3f,\"total_dispatch_ticks\":%" PRIu64
",\"clock_fit_available\":%s,\"busy_ns\":%.3f"
",\"total_dispatch_time_ns_available\":%s"
",\"total_dispatch_ns\":%" PRId64
",\"gap_analysis_available\":%s,\"gaps\":%" PRIu64
",\"total_gap_ticks\":%.3f,\"max_gap_ticks\":%.3f"
",\"total_gap_ns\":%.3f,\"max_gap_ns\":%.3f}\n",
row.queue->physical_device_ordinal, row.queue->queue_ordinal,
row.queue->stream_id, row.summary.submission_count,
row.summary.valid_submission_count,
row.summary.invalid_submission_count, row.summary.busy_ticks,
row.summary.total_dispatch_ticks, row.has_clock_fit ? "true" : "false",
row.has_clock_fit ? row.summary.busy_ticks * row.nanoseconds_per_tick
: 0.0,
row.has_total_dispatch_time_ns ? "true" : "false",
row.has_total_dispatch_time_ns ? row.total_dispatch_time_ns : 0,
row.gap_analysis_available ? "true" : "false",
row.gap_analysis_available ? row.summary.gap_count : 0,
row.gap_analysis_available ? row.summary.total_gap_ticks : 0.0,
row.gap_analysis_available ? row.summary.max_gap_ticks : 0.0,
row.gap_analysis_available && row.has_clock_fit
? row.summary.total_gap_ticks * row.nanoseconds_per_tick
: 0.0,
row.gap_analysis_available && row.has_clock_fit
? row.summary.max_gap_ticks * row.nanoseconds_per_tick
: 0.0);
}
return iree_ok_status();
}
static iree_status_t iree_profile_explain_print_jsonl_top_functions(
const iree_profile_dispatch_context_t* dispatch_context,
const iree_profile_explain_function_rank_t* function_ranks,
iree_host_size_t function_rank_count, FILE* file) {
const iree_host_size_t top_function_count =
iree_min(function_rank_count,
(iree_host_size_t)IREE_PROFILE_EXPLAIN_TOP_FUNCTION_COUNT);
for (iree_host_size_t i = 0; i < top_function_count; ++i) {
const iree_profile_explain_function_rank_t* rank = &function_ranks[i];
char numeric_buffer[128];
iree_string_view_t key = iree_string_view_empty();
IREE_RETURN_IF_ERROR(iree_profile_model_resolve_dispatch_key(
&dispatch_context->model, rank->physical_device_ordinal,
rank->executable_id, rank->function_ordinal, numeric_buffer,
sizeof(numeric_buffer), &key));
fprintf(file,
"{\"type\":\"explain_top_function\",\"rank\":%" PRIhsz
",\"physical_device_ordinal\":%u"
",\"executable_id\":%" PRIu64
",\"function_ordinal\":%u"
",\"key\":",
i + 1, rank->physical_device_ordinal, rank->executable_id,
rank->function_ordinal);
iree_profile_fprint_json_string(file, key);
fprintf(file,
",\"dispatches\":%" PRIu64 ",\"valid\":%" PRIu64
",\"invalid\":%" PRIu64 ",\"total_ticks\":%" PRIu64
",\"max_ticks\":%" PRIu64
",\"timing_source\":\"%s\""
",\"duration_ns_available\":%s"
",\"total_ns\":%" PRId64
",\"avg_ns\":%.3f"
",\"max_ns\":%" PRId64 ",\"total_tile_count\":%" PRIu64
",\"total_tile_duration_sum_ns\":%" PRId64 "}\n",
rank->dispatch_count, rank->valid_count, rank->invalid_count,
rank->total_ticks, rank->maximum_ticks,
iree_profile_explain_timing_source_string(rank->timing_source),
rank->has_duration_ns ? "true" : "false", rank->total_ns,
rank->average_ns, rank->maximum_ns, rank->total_tile_count,
rank->total_tile_duration_sum_ns);
}
return iree_ok_status();
}
static iree_status_t iree_profile_explain_print_jsonl_top_dispatches(
const iree_profile_dispatch_context_t* dispatch_context, FILE* file) {
if (dispatch_context->top_dispatch_count != 0) {
iree_profile_dispatch_top_event_t
top_dispatches[IREE_PROFILE_DISPATCH_TOP_EVENT_COUNT];
iree_profile_explain_copy_sorted_top_dispatches(dispatch_context,
top_dispatches);
for (iree_host_size_t i = 0; i < dispatch_context->top_dispatch_count;
++i) {
const iree_profile_dispatch_top_event_t* top_event = &top_dispatches[i];
iree_profile_model_clock_fit_t clock_fit;
const bool has_clock_fit =
iree_profile_explain_try_fit_driver_host_cpu_clock(
dispatch_context, top_event->physical_device_ordinal, &clock_fit);
int64_t duration_ns = 0;
const bool has_duration_ns =
has_clock_fit &&
iree_profile_model_clock_fit_scale_ticks_to_ns(
&clock_fit, top_event->duration_ticks, &duration_ns);
char numeric_buffer[128];
iree_string_view_t key = iree_string_view_empty();
IREE_RETURN_IF_ERROR(iree_profile_model_resolve_dispatch_key(
&dispatch_context->model, top_event->physical_device_ordinal,
top_event->event.executable_id, top_event->event.function_ordinal,
numeric_buffer, sizeof(numeric_buffer), &key));
fprintf(file,
"{\"type\":\"explain_top_dispatch\",\"rank\":%" PRIhsz
",\"event_id\":%" PRIu64 ",\"submission_id\":%" PRIu64
",\"physical_device_ordinal\":%u,\"queue_ordinal\":%u"
",\"stream_id\":%" PRIu64 ",\"key\":",
i + 1, top_event->event.event_id, top_event->event.submission_id,
top_event->physical_device_ordinal, top_event->queue_ordinal,
top_event->stream_id);
iree_profile_fprint_json_string(file, key);
fprintf(file,
",\"timing_source\":\"%s\""
",\"duration_ticks\":%" PRIu64
",\"duration_ns_available\":%s"
",\"duration_ns\":%" PRId64
",\"tile_count\":0,\"tile_duration_sum_ns\":0}\n",
iree_profile_explain_timing_source_string(
IREE_PROFILE_EXPLAIN_TIMING_SOURCE_DEVICE_DISPATCH),
top_event->duration_ticks, has_duration_ns ? "true" : "false",
has_duration_ns ? duration_ns : 0);
}
return iree_ok_status();
}
iree_profile_host_dispatch_top_event_t
top_dispatches[IREE_PROFILE_DISPATCH_TOP_EVENT_COUNT];
iree_profile_explain_copy_sorted_top_host_dispatches(dispatch_context,
top_dispatches);
for (iree_host_size_t i = 0; i < dispatch_context->top_host_dispatch_count;
++i) {
const iree_profile_host_dispatch_top_event_t* top_event =
&top_dispatches[i];
char numeric_buffer[128];
iree_string_view_t key = iree_string_view_empty();
IREE_RETURN_IF_ERROR(iree_profile_model_resolve_dispatch_key(
&dispatch_context->model, top_event->physical_device_ordinal,
top_event->event.executable_id, top_event->event.function_ordinal,
numeric_buffer, sizeof(numeric_buffer), &key));
fprintf(file,
"{\"type\":\"explain_top_dispatch\",\"rank\":%" PRIhsz
",\"event_id\":%" PRIu64 ",\"submission_id\":%" PRIu64
",\"physical_device_ordinal\":%u,\"queue_ordinal\":%u"
",\"stream_id\":%" PRIu64 ",\"command_buffer_id\":%" PRIu64
",\"command_index\":%u,\"key\":",
i + 1, top_event->event.event_id, top_event->event.submission_id,
top_event->physical_device_ordinal, top_event->queue_ordinal,
top_event->stream_id, top_event->event.command_buffer_id,
top_event->event.command_index);
iree_profile_fprint_json_string(file, key);
fprintf(file,
",\"timing_source\":\"%s\""
",\"duration_ticks\":0"
",\"duration_ns_available\":true"
",\"duration_ns\":%" PRId64 ",\"tile_count\":%" PRIu64
",\"tile_duration_sum_ns\":%" PRId64 "}\n",
iree_profile_explain_timing_source_string(
IREE_PROFILE_EXPLAIN_TIMING_SOURCE_HOST_EXECUTION),
top_event->duration_ns, top_event->event.tile_count,
top_event->event.tile_duration_sum_ns);
}
return iree_ok_status();
}
static void iree_profile_explain_print_jsonl_queue_operations(
const iree_profile_dispatch_context_t* dispatch_context, FILE* file) {
iree_profile_explain_queue_operation_totals_t operation_totals;
iree_profile_explain_compute_queue_operation_totals(dispatch_context,
&operation_totals);
const uint64_t* event_counts = operation_totals.event_counts;
const uint64_t* payload_bytes = operation_totals.payload_bytes;
const uint64_t* strategy_counts = operation_totals.strategy_counts;
fprintf(file,
"{\"type\":\"explain_queue_operations\""
",\"events\":%" PRIhsz ",\"strategy_none\":%" PRIu64
",\"strategy_inline\":%" PRIu64
",\"strategy_device_barrier\":%" PRIu64
",\"strategy_software_defer\":%" PRIu64 ",\"copy_bytes\":%" PRIu64
",\"fill_bytes\":%" PRIu64 ",\"update_bytes\":%" PRIu64
",\"read_bytes\":%" PRIu64 ",\"write_bytes\":%" PRIu64
",\"dispatches\":%" PRIu64 ",\"executes\":%" PRIu64
",\"allocas\":%" PRIu64 ",\"deallocas\":%" PRIu64
",\"host_calls\":%" PRIu64 "}\n",
dispatch_context->queue_query.queue_event_count,
strategy_counts[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_NONE],
strategy_counts[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_INLINE],
strategy_counts
[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_DEVICE_BARRIER],
strategy_counts
[IREE_HAL_PROFILE_QUEUE_DEPENDENCY_STRATEGY_SOFTWARE_DEFER],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_COPY],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_FILL],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_UPDATE],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_READ],
payload_bytes[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_WRITE],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_DISPATCH],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_EXECUTE],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_ALLOCA],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_DEALLOCA],
event_counts[IREE_HAL_PROFILE_QUEUE_EVENT_TYPE_HOST_CALL]);
}
static void iree_profile_explain_print_jsonl_memory_pressure(
const iree_profile_memory_context_t* memory_context, FILE* file) {
for (iree_host_size_t i = 0; i < memory_context->device_count; ++i) {
const iree_profile_memory_device_t* device = &memory_context->devices[i];
fprintf(file,
"{\"type\":\"explain_memory_pressure\""
",\"physical_device_ordinal\":%u"
",\"slab_high_water_bytes\":%" PRIu64
",\"pool_reserved_high_water_bytes\":%" PRIu64
",\"pool_materialized_high_water_bytes\":%" PRIu64
",\"queue_inflight_high_water_bytes\":%" PRIu64
",\"buffer_high_water_bytes\":%" PRIu64 ",\"pool_waits\":%" PRIu64
"}\n",
device->physical_device_ordinal,
device->slab_allocation_balance.high_water_bytes,
device->pool_reservation_balance.high_water_bytes,
device->pool_materialization_balance.high_water_bytes,
device->queue_inflight_balance.high_water_bytes,
device->buffer_allocation_balance.high_water_bytes,
device->pool_wait_count);
}
}
static void iree_profile_explain_print_jsonl_hints(
const iree_profile_summary_t* summary,
const iree_profile_dispatch_context_t* dispatch_context,
const iree_profile_memory_context_t* memory_context, FILE* file) {
if (summary->unknown_record_count != 0 || summary->unknown_chunk_count != 0) {
iree_profile_explain_print_hint_jsonl(
"warning", "format",
"unknown records or chunks were present; newer producer data may be "
"missing from this analysis",
file);
}
if (summary->truncated_chunk_count != 0) {
iree_profile_explain_print_hint_jsonl(
"error", "format",
"truncated chunks were present; timing and lifecycle totals may be "
"incomplete",
file);
}
if (summary->non_ok_session_end_count != 0) {
fprintf(file,
"{\"type\":\"explain_hint\",\"severity\":\"error\""
",\"category\":\"session\",\"message\":\"profiling session end "
"records reported non-OK status\""
",\"non_ok_session_end_records\":%" PRIu64
",\"first_non_ok_session_id\":%" PRIu64
",\"first_non_ok_stream_id\":%" PRIu64
",\"first_non_ok_event_id\":%" PRIu64
",\"first_non_ok_status_code\":%u,\"first_non_ok_status\":\"%s\""
"}\n",
summary->non_ok_session_end_count, summary->first_non_ok_session_id,
summary->first_non_ok_stream_id, summary->first_non_ok_event_id,
(uint32_t)summary->first_non_ok_session_status_code,
iree_profile_status_code_name(
(uint32_t)summary->first_non_ok_session_status_code));
}
if (dispatch_context->invalid_dispatch_count != 0) {
iree_profile_explain_print_hint_jsonl(
"warning", "dispatch",
"some dispatch events had missing or reversed timestamps and were "
"excluded from timing totals",
file);
}
if (dispatch_context->invalid_host_dispatch_count != 0) {
iree_profile_explain_print_hint_jsonl(
"warning", "dispatch",
"some host execution dispatch spans had missing or reversed timestamps "
"and were excluded from timing totals",
file);
}
if (dispatch_context->queue_query.queue_event_count == 0) {
iree_profile_explain_print_hint_jsonl(
"info", "queue",
"queue event records are absent; queue dependency and gap hints are "
"limited to dispatch timestamp intervals",
file);
}
if (iree_profile_explain_has_pool_waits(memory_context)) {
iree_profile_explain_print_hint_jsonl(
"warning", "memory",
"pool wait events were observed; allocation readiness affected the "
"captured queue schedule",
file);
}
if (dispatch_context->valid_dispatch_count == 0 &&
dispatch_context->valid_host_dispatch_count != 0) {
iree_profile_explain_print_hint_jsonl(
"info", "dispatch",
"device dispatch events are absent; top dispatch analysis is using "
"host execution dispatch spans",
file);
} else if (dispatch_context->valid_dispatch_count == 0) {
iree_profile_explain_print_hint_jsonl(
"info", "dispatch",
"no valid dispatch events were captured; enable queue profiling on a "
"producer that emits device timestamps",
file);
}
}
static iree_status_t iree_profile_explain_print_jsonl(
const iree_profile_summary_t* summary,
const iree_profile_dispatch_context_t* dispatch_context,
const iree_profile_memory_context_t* memory_context,
iree_allocator_t host_allocator, FILE* file) {
iree_profile_explain_function_rank_t* function_ranks = NULL;
iree_host_size_t function_rank_count = 0;
iree_status_t status = iree_profile_explain_collect_function_ranks(
dispatch_context, host_allocator, &function_ranks, &function_rank_count);
if (iree_status_is_ok(status)) {
iree_profile_explain_print_jsonl_summary(summary, dispatch_context,
memory_context, file);
status = iree_profile_explain_print_jsonl_devices(dispatch_context, file);
}
if (iree_status_is_ok(status)) {
status = iree_profile_explain_print_jsonl_queues(dispatch_context,
host_allocator, file);
}
if (iree_status_is_ok(status)) {
status = iree_profile_explain_print_jsonl_top_functions(
dispatch_context, function_ranks, function_rank_count, file);
}
if (iree_status_is_ok(status)) {
status =
iree_profile_explain_print_jsonl_top_dispatches(dispatch_context, file);
}
if (iree_status_is_ok(status)) {
iree_profile_explain_print_jsonl_queue_operations(dispatch_context, file);
iree_profile_explain_print_jsonl_memory_pressure(memory_context, file);
iree_profile_explain_print_jsonl_hints(summary, dispatch_context,
memory_context, file);
}
iree_allocator_free(host_allocator, function_ranks);
return status;
}
typedef struct iree_profile_explain_parse_context_t {
// File-level summary populated during the metadata pass.
iree_profile_summary_t* summary;
// Dispatch model state and event aggregates.
iree_profile_dispatch_context_t* dispatch_context;
// Memory event aggregates.
iree_profile_memory_context_t* memory_context;
// Optional glob filter applied to dispatch/event keys.
iree_string_view_t filter;
// Optional entity identifier filter, or -1 when disabled.
int64_t id_filter;
} iree_profile_explain_parse_context_t;
static iree_status_t iree_profile_explain_metadata_record(
void* user_data, const iree_hal_profile_file_record_t* record,
iree_host_size_t record_index) {
(void)record_index;
iree_profile_explain_parse_context_t* context =
(iree_profile_explain_parse_context_t*)user_data;
IREE_RETURN_IF_ERROR(
iree_profile_summary_process_record(context->summary, record));
return iree_profile_model_process_metadata_record(
&context->dispatch_context->model, record);
}
static iree_status_t iree_profile_explain_event_record(
void* user_data, const iree_hal_profile_file_record_t* record,
iree_host_size_t record_index) {
(void)record_index;
iree_profile_explain_parse_context_t* context =
(iree_profile_explain_parse_context_t*)user_data;
const iree_profile_dispatch_event_callback_t dispatch_event_callback = {0};
IREE_RETURN_IF_ERROR(iree_profile_dispatch_process_events_record(
context->dispatch_context, record, context->filter,
IREE_PROFILE_PROJECTION_MODE_DISPATCH, context->id_filter,
/*aggregate_events=*/true, dispatch_event_callback));
if (record->header.record_type == IREE_HAL_PROFILE_FILE_RECORD_TYPE_CHUNK &&
iree_string_view_equal(record->content_type,
IREE_HAL_PROFILE_CONTENT_TYPE_QUEUE_EVENTS)) {
IREE_RETURN_IF_ERROR(iree_profile_queue_event_query_process_record(
&context->dispatch_context->queue_query,
&context->dispatch_context->model, record, IREE_SV("*"),
/*id_filter=*/-1));
}
const iree_profile_memory_event_callback_t memory_event_callback = {0};
return iree_profile_memory_context_accumulate_record(
context->memory_context, record, IREE_SV("*"), /*id_filter=*/-1,
memory_event_callback);
}
iree_status_t iree_profile_explain_file(iree_string_view_t path,
iree_string_view_t format,
iree_string_view_t filter,
int64_t id_filter, FILE* file,
iree_allocator_t host_allocator) {
bool is_text = iree_string_view_equal(format, IREE_SV("text"));
bool is_jsonl = iree_string_view_equal(format, IREE_SV("jsonl"));
if (!is_text && !is_jsonl) {
return iree_make_status(IREE_STATUS_INVALID_ARGUMENT,
"unsupported profile output format '%.*s'",
(int)format.size, format.data);
}
iree_profile_file_t profile_file;
IREE_RETURN_IF_ERROR(
iree_profile_file_open(path, host_allocator, &profile_file));
iree_profile_summary_t summary;
iree_profile_summary_initialize(host_allocator, &summary);
iree_profile_dispatch_context_t dispatch_context;
iree_profile_dispatch_context_initialize(host_allocator, &dispatch_context);
iree_profile_memory_context_t memory_context;
iree_profile_memory_context_initialize(host_allocator, &memory_context);
iree_profile_explain_parse_context_t parse_context = {
.summary = &summary,
.dispatch_context = &dispatch_context,
.memory_context = &memory_context,
.filter = filter,
.id_filter = id_filter,
};
iree_profile_file_record_callback_t record_callback = {
.fn = iree_profile_explain_metadata_record,
.user_data = &parse_context,
};
iree_status_t status =
iree_profile_file_for_each_record(&profile_file, record_callback);
if (iree_status_is_ok(status)) {
record_callback.fn = iree_profile_explain_event_record;
status = iree_profile_file_for_each_record(&profile_file, record_callback);
}
if (iree_status_is_ok(status)) {
if (is_text) {
status = iree_profile_explain_print_text(
&summary, &dispatch_context, &memory_context, host_allocator, file);
} else {
status = iree_profile_explain_print_jsonl(
&summary, &dispatch_context, &memory_context, host_allocator, file);
}
}
iree_profile_memory_context_deinitialize(&memory_context);
iree_profile_dispatch_context_deinitialize(&dispatch_context);
iree_profile_summary_deinitialize(&summary);
iree_profile_file_close(&profile_file);
return status;
}