From b99ce2aa107a4cebd6cbb90a60d91950055bab53 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Sat, 5 Sep 2026 08:17:07 +0200 Subject: [PATCH] fix for stats and model selection --- .../down.sql | 13 ++++++ .../up.sql | 11 +++++ src/app.rs | 46 +++++++++++-------- src/app/view/stats.rs | 26 ++++++++--- src/database.rs | 44 ++++++++++++++++++ 5 files changed, 116 insertions(+), 24 deletions(-) create mode 100644 migrations/20260905090000_allow_qwen_session_model/down.sql create mode 100644 migrations/20260905090000_allow_qwen_session_model/up.sql diff --git a/migrations/20260905090000_allow_qwen_session_model/down.sql b/migrations/20260905090000_allow_qwen_session_model/down.sql new file mode 100644 index 0000000..e0b4fbc --- /dev/null +++ b/migrations/20260905090000_allow_qwen_session_model/down.sql @@ -0,0 +1,13 @@ +ALTER TABLE sessions ADD COLUMN model_without_qwen TEXT + CHECK (model_without_qwen IS NULL OR model_without_qwen IN ( + 'deepseek-v4-flash-0731', + 'deepseek-v4-pro', + 'glm-5.2', + 'glm-5.3-flash' + )); +UPDATE sessions SET model_without_qwen = CASE + WHEN model = 'qwen3.8-flash-next' THEN NULL + ELSE model +END; +ALTER TABLE sessions DROP COLUMN model; +ALTER TABLE sessions RENAME COLUMN model_without_qwen TO model; diff --git a/migrations/20260905090000_allow_qwen_session_model/up.sql b/migrations/20260905090000_allow_qwen_session_model/up.sql new file mode 100644 index 0000000..cbd1dcd --- /dev/null +++ b/migrations/20260905090000_allow_qwen_session_model/up.sql @@ -0,0 +1,11 @@ +ALTER TABLE sessions ADD COLUMN model_with_qwen TEXT + CHECK (model_with_qwen IS NULL OR model_with_qwen IN ( + 'deepseek-v4-flash-0731', + 'deepseek-v4-pro', + 'glm-5.2', + 'glm-5.3-flash', + 'qwen3.8-flash-next' + )); +UPDATE sessions SET model_with_qwen = model; +ALTER TABLE sessions DROP COLUMN model; +ALTER TABLE sessions RENAME COLUMN model_with_qwen TO model; diff --git a/src/app.rs b/src/app.rs index 771ce2b..89dcd92 100644 --- a/src/app.rs +++ b/src/app.rs @@ -148,8 +148,9 @@ pub(crate) struct App { pub(super) kv_cache_report: KvCacheReport, last_cache_scan: Instant, last_http_requests: u64, - last_ssd_selected_requests: u64, - last_ssd_requested_bytes: u64, + last_ssd_cache_misses: u64, + last_ssd_cache_evictions: u64, + last_ssd_pread_bytes: u64, last_ssd_wait_ms: u64, #[cfg(target_os = "macos")] generation_service: Option, @@ -344,8 +345,9 @@ pub(super) struct MetricsPoint { pub(super) http_requests_per_second: f32, pub(super) kv_read_bytes_per_second: f32, pub(super) kv_write_bytes_per_second: f32, - pub(super) ssd_requests_per_second: f32, - pub(super) ssd_bytes_per_second: f32, + pub(super) ssd_misses_per_second: f32, + pub(super) ssd_evictions_per_second: f32, + pub(super) ssd_pread_bytes_per_second: f32, pub(super) ssd_wait_ms_per_second: f32, } @@ -685,8 +687,9 @@ impl App { kv_cache_report: KvCacheReport::default(), last_cache_scan: Instant::now() - CACHE_SCAN_INTERVAL, last_http_requests: 0, - last_ssd_selected_requests: 0, - last_ssd_requested_bytes: 0, + last_ssd_cache_misses: 0, + last_ssd_cache_evictions: 0, + last_ssd_pread_bytes: 0, last_ssd_wait_ms: 0, #[cfg(target_os = "macos")] generation_service, @@ -848,8 +851,9 @@ impl App { kv_cache_report: KvCacheReport::default(), last_cache_scan: Instant::now() - CACHE_SCAN_INTERVAL, last_http_requests: 0, - last_ssd_selected_requests: 0, - last_ssd_requested_bytes: 0, + last_ssd_cache_misses: 0, + last_ssd_cache_evictions: 0, + last_ssd_pread_bytes: 0, last_ssd_wait_ms: 0, #[cfg(target_os = "macos")] generation_service, @@ -2280,21 +2284,26 @@ impl App { let kv_write_bytes_per_second = kv_write_bytes as f32 / METRICS_SAMPLE_INTERVAL.as_secs_f32(); let sample_seconds = METRICS_SAMPLE_INTERVAL.as_secs_f32(); - let ssd_requests_per_second = snapshot - .ssd_selected_requests - .saturating_sub(self.last_ssd_selected_requests) + let ssd_misses_per_second = snapshot + .ssd_cache_misses + .saturating_sub(self.last_ssd_cache_misses) as f32 + / sample_seconds; + let ssd_evictions_per_second = snapshot + .ssd_cache_evictions + .saturating_sub(self.last_ssd_cache_evictions) as f32 / sample_seconds; - let ssd_bytes_per_second = snapshot - .ssd_requested_bytes - .saturating_sub(self.last_ssd_requested_bytes) + let ssd_pread_bytes_per_second = snapshot + .ssd_pread_bytes + .saturating_sub(self.last_ssd_pread_bytes) as f32 / sample_seconds; let ssd_wait_ms_per_second = snapshot.ssd_wait_ms.saturating_sub(self.last_ssd_wait_ms) as f32 / sample_seconds; self.last_http_requests = snapshot.http_requests; - self.last_ssd_selected_requests = snapshot.ssd_selected_requests; - self.last_ssd_requested_bytes = snapshot.ssd_requested_bytes; + self.last_ssd_cache_misses = snapshot.ssd_cache_misses; + self.last_ssd_cache_evictions = snapshot.ssd_cache_evictions; + self.last_ssd_pread_bytes = snapshot.ssd_pread_bytes; self.last_ssd_wait_ms = snapshot.ssd_wait_ms; self.metrics_history.push_back(MetricsPoint { decode_tokens_per_second: if snapshot.phase == crate::metrics::RuntimePhase::Generating @@ -2307,8 +2316,9 @@ impl App { http_requests_per_second, kv_read_bytes_per_second, kv_write_bytes_per_second, - ssd_requests_per_second, - ssd_bytes_per_second, + ssd_misses_per_second, + ssd_evictions_per_second, + ssd_pread_bytes_per_second, ssd_wait_ms_per_second, }); if self.metrics_history.len() > 120 { diff --git a/src/app/view/stats.rs b/src/app/view/stats.rs index f0e79d6..11fdfa3 100644 --- a/src/app/view/stats.rs +++ b/src/app/view/stats.rs @@ -250,29 +250,43 @@ impl App { column![ mini_chart( &self.metrics_history, - |point| point.ssd_requests_per_second, + |point| point.ssd_misses_per_second, Color::from_rgb8(240, 180, 70), ), row![ - text("Selected loads") + text("Cache misses") .size(12) .color(Color::from_rgb8(240, 180, 70)), Space::new().width(Length::Fill), - text(format!("{:.1}/s", latest.ssd_requests_per_second)) + text(format!("{:.1}/s", latest.ssd_misses_per_second)) .size(12) .color(muted_text()), ], mini_chart( &self.metrics_history, - |point| point.ssd_bytes_per_second, + |point| point.ssd_pread_bytes_per_second, Color::from_rgb8(67, 194, 203), ), row![ - text("Requested expert data") + text("Direct SSD reads") .size(12) .color(Color::from_rgb8(67, 194, 203)), Space::new().width(Length::Fill), - text(format_rate(latest.ssd_bytes_per_second)) + text(format_rate(latest.ssd_pread_bytes_per_second)) + .size(12) + .color(muted_text()), + ], + mini_chart( + &self.metrics_history, + |point| point.ssd_evictions_per_second, + Color::from_rgb8(157, 119, 255), + ), + row![ + text("Cache evictions") + .size(12) + .color(Color::from_rgb8(157, 119, 255)), + Space::new().width(Length::Fill), + text(format!("{:.1}/s", latest.ssd_evictions_per_second)) .size(12) .color(muted_text()), ], diff --git a/src/database.rs b/src/database.rs index d5564d5..27d150d 100644 --- a/src/database.rs +++ b/src/database.rs @@ -945,6 +945,50 @@ mod tests { fs::remove_file(path).unwrap(); } + #[test] + fn session_model_migration_preserves_data_and_accepts_catalog() { + let id = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!("ds4-session-models-{id}.sqlite3")); + let mut database = Database::open(&path).unwrap(); + let project = database + .create_project("DS4", "/tmp/ds4-session-models") + .unwrap(); + let existing = database + .create_session( + project.id, + "Existing", + PermissionMode::Ai, + crate::model::ModelChoice::DeepSeekV4Flash0731, + ) + .unwrap(); + + database + .connection + .revert_last_migration(MIGRATIONS) + .unwrap(); + database + .connection + .run_pending_migrations(MIGRATIONS) + .unwrap(); + assert_eq!( + database.load_projects().unwrap()[0].sessions[0].model(), + existing.model() + ); + + for model in crate::model::MODEL_CHOICES { + let session = database + .create_session(project.id, model.id(), PermissionMode::Ai, model) + .unwrap(); + assert_eq!(session.model(), Some(model)); + } + + drop(database); + fs::remove_file(path).unwrap(); + } + #[test] fn sessions_are_ordered_by_recent_use_within_sidebar_groups() { let id = SystemTime::now()