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