Trace Analysis and Metrics¶
After running a simulation, Schedsim can compute scheduling metrics from the
trace output. This tutorial covers the schedview CLI tool, the C++ metrics
API, and the Python equivalents.
Using schedview¶
The simplest way to analyze a trace is the schedview command-line tool:
# Basic metrics: job counts, deadline misses, preemptions
schedview trace.json
# Include energy breakdown
schedview trace.json --energy
# Include per-task response time statistics
schedview trace.json --response-times
# Full analysis
schedview trace.json --energy --response-times
File-Based Analysis¶
For programmatic access without running a simulation, load metrics directly from a trace file:
C++ Example¶
#include <schedsim/io/metrics.hpp>
#include <iostream>
using namespace schedsim;
int main() {
auto metrics = io::compute_metrics_from_file("trace.json");
std::cout << "Jobs: " << metrics.completed_jobs
<< "/" << metrics.total_jobs << "\n";
std::cout << "Deadline misses: " << metrics.deadline_misses << "\n";
std::cout << "Preemptions: " << metrics.preemptions << "\n";
std::cout << "Energy: " << metrics.total_energy_mj << " mJ\n";
return 0;
}
Python Example¶
import pyschedsim as sim
metrics = sim.compute_metrics_from_file("trace.json")
print(f"Jobs: {metrics.completed_jobs}/{metrics.total_jobs}")
print(f"Deadline misses: {metrics.deadline_misses}")
print(f"Energy: {metrics.total_energy_mj:.2f} mJ")
In-Memory Analysis¶
When running simulations programmatically, use a MemoryTraceWriter to
compute metrics without writing to disk:
C++ Example¶
#include <schedsim/io/metrics.hpp>
#include <schedsim/io/trace_writers.hpp>
// ... set up engine, scheduler, etc. ...
io::MemoryTraceWriter writer;
engine.set_trace_writer(&writer);
engine.run();
auto metrics = io::compute_metrics(writer.records());
Python Example¶
writer = sim.MemoryTraceWriter()
engine.set_trace_writer(writer)
engine.run()
metrics = writer.compute_metrics()
SimulationMetrics Fields¶
The SimulationMetrics struct contains:
Field |
Type |
Description |
|---|---|---|
|
int |
Total number of job arrivals |
|
int |
Jobs that finished execution |
|
int |
Jobs that missed their absolute deadline |
|
int |
Number of preemption events |
|
int |
Number of context switch events |
|
double |
Total energy consumption in millijoules |
|
double |
Average processor utilization across all processors |
|
int |
Job arrivals rejected by admission control |
|
int |
Tasks moved between clusters |
|
int |
Frequency transition events |
|
int |
C-state change requests |
|
int |
DVFS frequency change requests |
Map-based fields are accessed through getter methods:
# Per-processor utilization: {proc_id: utilization}
util_map = metrics.get_utilization_per_processor()
# Per-processor energy: {proc_id: energy_mJ}
energy_map = metrics.get_energy_per_processor()
# Per-task response times: {task_id: [rt1, rt2, ...]}
rt_map = metrics.get_response_times_per_task()
# Per-task deadline misses: {task_id: count}
dm_map = metrics.get_deadline_misses_per_task()
# Per-task waiting times: {task_id: [wt1, wt2, ...]}
wt_map = metrics.get_waiting_times_per_task()
// C++: direct field access
for (const auto& [proc_id, util] : metrics.utilization_per_processor) {
std::cout << "Proc " << proc_id << ": " << util << "\n";
}
Per-Task Response Time Statistics¶
Compute summary statistics for each task’s response times:
for (const auto& [task_id, times] : metrics.response_times_per_task) {
auto stats = io::compute_response_time_stats(times);
std::cout << "Task " << task_id
<< " min=" << stats.min
<< " max=" << stats.max
<< " mean=" << stats.mean
<< " p95=" << stats.percentile_95
<< " p99=" << stats.percentile_99 << "\n";
}
rt_map = metrics.get_response_times_per_task()
for task_id, times in rt_map.items():
stats = sim.compute_response_time_stats(times)
print(f"Task {task_id}: "
f"min={stats.min:.6f} max={stats.max:.6f} "
f"mean={stats.mean:.6f} p95={stats.percentile_95:.6f}")
The ResponseTimeStats struct contains: min, max, mean,
median, stddev, percentile_95, percentile_99.
Time-Series Analysis¶
Track how hardware configuration changes over time. These functions return lists of intervals, each with a start time, stop time, and the configuration value during that interval.
Frequency Changes¶
auto intervals = io::track_frequency_changes(writer.records());
for (const auto& iv : intervals) {
std::cout << "[" << iv.start << ", " << iv.stop << ") "
<< "cluster=" << iv.cluster_id
<< " freq=" << iv.frequency << " MHz\n";
}
intervals = writer.track_frequency_changes()
for iv in intervals:
print(f"[{iv.start:.6f}, {iv.stop:.6f}) "
f"cluster={iv.cluster_id} freq={iv.frequency:.0f} MHz")
Active Core Count¶
auto intervals = io::track_core_changes(writer.records());
for (const auto& iv : intervals) {
std::cout << "[" << iv.start << ", " << iv.stop << ") "
<< "cluster=" << iv.cluster_id
<< " active=" << iv.active_cores << "\n";
}
intervals = writer.track_core_changes()
for iv in intervals:
print(f"[{iv.start:.6f}, {iv.stop:.6f}) "
f"cluster={iv.cluster_id} active={iv.active_cores}")
Combined Configuration¶
track_config_changes() returns intervals with both frequency and active
core count:
intervals = writer.track_config_changes()
for iv in intervals:
print(f"[{iv.start:.6f}, {iv.stop:.6f}) "
f"freq={iv.frequency:.0f} MHz, cores={iv.active_cores}")
Complete Analysis Example¶
Putting it all together — run a simulation and extract all metrics:
import pyschedsim as sim
# Set up and run simulation
engine = sim.Engine()
sim.load_platform(engine, "platforms/exynos5422LITTLE.json")
scenario = sim.load_scenario("scenario.json")
tasks = sim.inject_scenario(engine, scenario)
for i, tp in enumerate(scenario.tasks):
sim.schedule_arrivals(engine, tasks[i], tp.jobs)
engine.enable_energy_tracking(True)
engine.platform.finalize()
procs = sim.get_all_processors(engine)
scheduler = sim.EdfScheduler(engine, procs)
for task in tasks:
scheduler.add_server(task)
scheduler.enable_grub()
allocator = sim.SingleSchedulerAllocator(engine, scheduler)
writer = sim.MemoryTraceWriter()
engine.set_trace_writer(writer)
engine.run()
# --- Metrics ---
metrics = writer.compute_metrics()
print(f"Completed: {metrics.completed_jobs}/{metrics.total_jobs}")
print(f"Deadline misses: {metrics.deadline_misses}")
print(f"Energy: {metrics.total_energy_mj:.2f} mJ")
print(f"Preemptions: {metrics.preemptions}")
# Per-processor utilization
for proc_id, util in metrics.get_utilization_per_processor().items():
print(f" Proc {proc_id}: {util:.2%} utilization")
# Per-task response times
for task_id, times in metrics.get_response_times_per_task().items():
stats = sim.compute_response_time_stats(times)
print(f" Task {task_id}: mean RT = {stats.mean:.6f}s, "
f"max = {stats.max:.6f}s")
# Frequency time-series
for iv in writer.track_frequency_changes():
print(f" [{iv.start:.6f}, {iv.stop:.6f}) "
f"cluster {iv.cluster_id}: {iv.frequency:.0f} MHz")
Next Steps¶
Your First Simulation — if you haven’t built your first simulation yet
DVFS and Energy Management — enable DVFS/DPM to generate energy-related traces
Multi-Cluster Allocation — heterogeneous platforms with cluster migrations