Split Rust code into domain modules
This commit is contained in:
371
src/app/view/stats.rs
Normal file
371
src/app/view/stats.rs
Normal file
@@ -0,0 +1,371 @@
|
||||
use super::*;
|
||||
use iced::widget::column;
|
||||
|
||||
impl App {
|
||||
pub(super) fn stats_dashboard(&self) -> Element<'_, Message> {
|
||||
let stats = &self.metrics_snapshot;
|
||||
let context_fraction = if stats.context_limit == 0 {
|
||||
0.0
|
||||
} else {
|
||||
stats.context_used.min(stats.context_limit) as f32 / stats.context_limit as f32
|
||||
};
|
||||
let cache_fraction = if stats.last_prompt_tokens == 0 {
|
||||
0.0
|
||||
} else {
|
||||
stats.last_cached_tokens as f32 / stats.last_prompt_tokens as f32
|
||||
};
|
||||
let cache_hit_fraction = if stats.kv_lookups == 0 {
|
||||
0.0
|
||||
} else {
|
||||
stats.kv_hits as f32 / stats.kv_lookups as f32
|
||||
};
|
||||
let endpoint = if stats.server_listening {
|
||||
format!("Listening · 127.0.0.1:{}", stats.server_port)
|
||||
} else {
|
||||
"Stopped".to_owned()
|
||||
};
|
||||
let phase_color = match stats.phase {
|
||||
crate::metrics::RuntimePhase::Generating => Color::from_rgb8(84, 170, 255),
|
||||
crate::metrics::RuntimePhase::Prefilling | crate::metrics::RuntimePhase::Loading => {
|
||||
Color::from_rgb8(240, 180, 70)
|
||||
}
|
||||
crate::metrics::RuntimePhase::Ready => Color::from_rgb8(72, 176, 112),
|
||||
crate::metrics::RuntimePhase::Failed => Color::from_rgb8(220, 80, 86),
|
||||
crate::metrics::RuntimePhase::Unloaded => muted_text(),
|
||||
};
|
||||
let heading = container(
|
||||
row![
|
||||
column![
|
||||
text("Runtime observability").size(24),
|
||||
text(format!(
|
||||
"{} · {} · uptime {}",
|
||||
stats.model,
|
||||
stats.source.label(),
|
||||
format_duration(stats.uptime_seconds as f64)
|
||||
))
|
||||
.size(12)
|
||||
.color(muted_text()),
|
||||
]
|
||||
.spacing(5),
|
||||
Space::with_width(Length::Fill),
|
||||
container(text(stats.phase.label()).size(12).color(phase_color))
|
||||
.padding([7, 11])
|
||||
.style(move |_| status_badge_style(phase_color)),
|
||||
]
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.padding(16)
|
||||
.style(overview_style);
|
||||
|
||||
let headline = column![
|
||||
row![
|
||||
metric_card(
|
||||
"DECODE",
|
||||
format!("{:.1} tok/s", stats.decode_tokens_per_second),
|
||||
format!(
|
||||
"{} completion tokens total",
|
||||
format_count(stats.completion_tokens)
|
||||
),
|
||||
),
|
||||
metric_card(
|
||||
"PREFILL",
|
||||
format!("{:.1} tok/s", stats.prefill_tokens_per_second),
|
||||
format!("{} prompt tokens total", format_count(stats.prompt_tokens)),
|
||||
),
|
||||
]
|
||||
.spacing(10),
|
||||
row![
|
||||
metric_card(
|
||||
"CONTEXT",
|
||||
format!(
|
||||
"{} / {}",
|
||||
format_count(u64::from(stats.context_used)),
|
||||
format_count(u64::from(stats.context_limit))
|
||||
),
|
||||
format!("{:.0}% occupied", context_fraction * 100.0),
|
||||
),
|
||||
metric_card(
|
||||
"WORK",
|
||||
format!(
|
||||
"{} active · {} queued",
|
||||
stats.http_active, stats.queue_depth
|
||||
),
|
||||
format!("{} runtime requests", format_count(stats.runtime_requests)),
|
||||
),
|
||||
]
|
||||
.spacing(10),
|
||||
]
|
||||
.spacing(10);
|
||||
|
||||
let throughput = stats_panel(
|
||||
"MODEL ACTIVITY · LAST 24 SECONDS",
|
||||
column![
|
||||
mini_chart(
|
||||
&self.metrics_history,
|
||||
|point| point.decode_tokens_per_second,
|
||||
Color::from_rgb8(84, 170, 255),
|
||||
),
|
||||
row![
|
||||
text("Decode")
|
||||
.size(12)
|
||||
.color(Color::from_rgb8(84, 170, 255)),
|
||||
Space::with_width(Length::Fill),
|
||||
text(format!("{:.1} tok/s", stats.decode_tokens_per_second))
|
||||
.size(12)
|
||||
.color(muted_text()),
|
||||
],
|
||||
mini_chart(
|
||||
&self.metrics_history,
|
||||
|point| point.prefill_tokens_per_second,
|
||||
Color::from_rgb8(157, 119, 255),
|
||||
),
|
||||
row![
|
||||
text("Prefill")
|
||||
.size(12)
|
||||
.color(Color::from_rgb8(157, 119, 255)),
|
||||
Space::with_width(Length::Fill),
|
||||
text(format!("{:.1} tok/s", stats.prefill_tokens_per_second))
|
||||
.size(12)
|
||||
.color(muted_text()),
|
||||
],
|
||||
]
|
||||
.spacing(7)
|
||||
.into(),
|
||||
);
|
||||
let requests = stats_panel(
|
||||
"SERVER REQUEST RATE · LAST 24 SECONDS",
|
||||
column![
|
||||
mini_chart(
|
||||
&self.metrics_history,
|
||||
|point| point.http_requests_per_second,
|
||||
Color::from_rgb8(72, 176, 112),
|
||||
),
|
||||
row![
|
||||
text(format!("{} requests", format_count(stats.http_requests))).size(12),
|
||||
Space::with_width(Length::Fill),
|
||||
text(format!(
|
||||
"{} errors · {} streaming",
|
||||
stats.http_errors, stats.http_streaming_requests
|
||||
))
|
||||
.size(12)
|
||||
.color(muted_text()),
|
||||
],
|
||||
]
|
||||
.spacing(7)
|
||||
.into(),
|
||||
);
|
||||
let latest = self.metrics_history.back().copied().unwrap_or_default();
|
||||
let kv_io = stats_panel(
|
||||
"KV CHECKPOINT I/O · LAST 24 SECONDS",
|
||||
column![
|
||||
mini_chart(
|
||||
&self.metrics_history,
|
||||
|point| point.kv_read_bytes_per_second,
|
||||
Color::from_rgb8(67, 194, 203),
|
||||
),
|
||||
row![
|
||||
text(if stats.kv_read_active {
|
||||
"Disk read · active"
|
||||
} else {
|
||||
"Disk read"
|
||||
})
|
||||
.size(12)
|
||||
.color(Color::from_rgb8(67, 194, 203)),
|
||||
Space::with_width(Length::Fill),
|
||||
text(format_rate(latest.kv_read_bytes_per_second))
|
||||
.size(12)
|
||||
.color(muted_text()),
|
||||
],
|
||||
mini_chart(
|
||||
&self.metrics_history,
|
||||
|point| point.kv_write_bytes_per_second,
|
||||
Color::from_rgb8(240, 180, 70),
|
||||
),
|
||||
row![
|
||||
text(if stats.kv_write_active {
|
||||
"Disk write · active"
|
||||
} else {
|
||||
"Disk write"
|
||||
})
|
||||
.size(12)
|
||||
.color(Color::from_rgb8(240, 180, 70)),
|
||||
Space::with_width(Length::Fill),
|
||||
text(format_rate(latest.kv_write_bytes_per_second))
|
||||
.size(12)
|
||||
.color(muted_text()),
|
||||
],
|
||||
]
|
||||
.spacing(7)
|
||||
.into(),
|
||||
);
|
||||
|
||||
let model = stats_panel(
|
||||
"MODEL CORE",
|
||||
column![
|
||||
metric_row("State", stats.phase.label()),
|
||||
metric_row("Loaded model", stats.model),
|
||||
metric_row("Mapped weights", format_bytes(stats.model_bytes)),
|
||||
metric_row("Tensors", format_count(stats.tensor_count)),
|
||||
metric_row("Vocabulary", format_count(stats.vocabulary_size)),
|
||||
metric_row("Last load", format_milliseconds(stats.model_load_ms)),
|
||||
metric_row(
|
||||
"Lifecycle",
|
||||
format!(
|
||||
"{} loads · {} unloads",
|
||||
stats.model_loads, stats.model_unloads
|
||||
),
|
||||
),
|
||||
]
|
||||
.spacing(9)
|
||||
.into(),
|
||||
);
|
||||
let runtime = stats_panel(
|
||||
"GENERATION",
|
||||
column![
|
||||
metric_row("Last runtime", format_milliseconds(stats.last_runtime_ms)),
|
||||
metric_row(
|
||||
"Average runtime",
|
||||
format_milliseconds(stats.average_runtime_ms)
|
||||
),
|
||||
metric_row("Last prompt", format_count(stats.last_prompt_tokens)),
|
||||
metric_row("Last reused", format_count(stats.last_cached_tokens)),
|
||||
metric_row(
|
||||
"Last completion",
|
||||
format_count(stats.last_completion_tokens)
|
||||
),
|
||||
metric_row("Cached tokens total", format_count(stats.cached_tokens)),
|
||||
metric_row(
|
||||
"Cache reuse",
|
||||
format!("{:.0}% of last prompt", cache_fraction * 100.0),
|
||||
),
|
||||
metric_row(
|
||||
"Results",
|
||||
format!(
|
||||
"{} completed · {} failed",
|
||||
stats.completed_requests, stats.failed_requests
|
||||
),
|
||||
),
|
||||
]
|
||||
.spacing(9)
|
||||
.into(),
|
||||
);
|
||||
let cache = stats_panel(
|
||||
"KV CACHE",
|
||||
column![
|
||||
metric_row(
|
||||
"Total",
|
||||
format!(
|
||||
"{} · {} files",
|
||||
format_bytes(stats.kv_bytes),
|
||||
stats.kv_files
|
||||
)
|
||||
),
|
||||
metric_row(
|
||||
"Local sessions",
|
||||
format!(
|
||||
"{} · {} files",
|
||||
format_bytes(stats.local_kv_bytes),
|
||||
stats.local_kv_files
|
||||
),
|
||||
),
|
||||
metric_row(
|
||||
"HTTP transient",
|
||||
format!(
|
||||
"{} · {} files",
|
||||
format_bytes(stats.http_kv_bytes),
|
||||
stats.http_kv_files
|
||||
),
|
||||
),
|
||||
metric_row("Checkpoint writes", format_count(stats.checkpoint_writes)),
|
||||
metric_row(
|
||||
"Exact hits",
|
||||
format!(
|
||||
"{} · {} memory / {} disk",
|
||||
stats.kv_hits, stats.kv_memory_hits, stats.kv_disk_hits
|
||||
),
|
||||
),
|
||||
metric_row(
|
||||
"Misses",
|
||||
format!("{} · {} invalid", stats.kv_misses, stats.kv_invalid),
|
||||
),
|
||||
metric_row("Lookups", format_count(stats.kv_lookups)),
|
||||
metric_row(
|
||||
"Exact hit rate",
|
||||
format!("{:.1}%", cache_hit_fraction * 100.0)
|
||||
),
|
||||
metric_row("Prefix hits", format_count(stats.kv_prefix_hits)),
|
||||
metric_row(
|
||||
"Reads",
|
||||
format!(
|
||||
"{} · {} · {} errors · last {}",
|
||||
stats.kv_read_operations,
|
||||
format_bytes(stats.kv_read_bytes),
|
||||
stats.kv_read_errors,
|
||||
format_milliseconds(stats.last_kv_read_ms),
|
||||
),
|
||||
),
|
||||
metric_row(
|
||||
"Writes",
|
||||
format!(
|
||||
"{} · {} · {} errors · last {}",
|
||||
stats.kv_write_operations,
|
||||
format_bytes(stats.kv_write_bytes),
|
||||
stats.kv_write_errors,
|
||||
format_milliseconds(stats.last_kv_write_ms),
|
||||
),
|
||||
),
|
||||
progress_bar(0.0..=1.0, cache_fraction.min(1.0)).height(4),
|
||||
]
|
||||
.spacing(9)
|
||||
.into(),
|
||||
);
|
||||
let server = stats_panel(
|
||||
"LOCAL SERVER",
|
||||
column![
|
||||
metric_row("Endpoint", endpoint),
|
||||
metric_row("Active", format_count(u64::from(stats.http_active))),
|
||||
metric_row("Completed", format_count(stats.http_completed)),
|
||||
metric_row("Chat completions", format_count(stats.http_chat_requests)),
|
||||
metric_row("Model queries", format_count(stats.http_model_requests)),
|
||||
metric_row(
|
||||
"Runtime sources",
|
||||
format!(
|
||||
"{} local · {} HTTP",
|
||||
stats.local_requests, stats.endpoint_generations
|
||||
),
|
||||
),
|
||||
metric_row("Received", format_bytes(stats.http_bytes_received)),
|
||||
metric_row("Last latency", format_milliseconds(stats.last_http_ms)),
|
||||
metric_row(
|
||||
"Average latency",
|
||||
format_milliseconds(stats.average_http_ms)
|
||||
),
|
||||
]
|
||||
.spacing(9)
|
||||
.into(),
|
||||
);
|
||||
|
||||
scrollable(
|
||||
container(
|
||||
column![
|
||||
heading,
|
||||
headline,
|
||||
throughput,
|
||||
kv_io,
|
||||
requests,
|
||||
row![model, runtime].spacing(10),
|
||||
row![cache, server].spacing(10),
|
||||
text("Counters are published by the runtime with relaxed atomics and sampled by the UI every 200 ms.")
|
||||
.size(11)
|
||||
.color(muted_text()),
|
||||
]
|
||||
.spacing(12),
|
||||
)
|
||||
.padding(24)
|
||||
.max_width(960)
|
||||
.center_x(Length::Fill),
|
||||
)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user