Files
windmill/backend/windmill-common/tests/queue_stats.rs
T
Ruben FiszelandClaude Opus 5 569adb85c1 feat: live queue status per tag and bounded queue metric charts (#11067)
* feat: live per-tag queue status and bounded charts in the queues drawer

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ

* fix: drop stale chart failures and test the queue metrics series query

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ

* fix: name the queue status refresh, skip overlapping polls, soften the no-worker warning

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ

* feat: draw a stuck tag's queue delay exactly as it climbs (#11071)

* feat: store a stuck tag's queue delay as its head's wait start

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ

* fix: keep a climb's top inside a slot and stamp held delays exactly

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ

* fix: redraw a climb as soon as its head leaves, and document the lookup slack

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 18:40:54 +00:00

70 lines
2.5 KiB
Rust

use sqlx::{Pool, Postgres};
use windmill_common::queue::get_queue_stats;
const WORKSPACE: &str = "test-workspace";
async fn queue_job(
db: &Pool<Postgres>,
tag: &str,
priority: Option<i16>,
waited_secs: f64,
running: bool,
) {
sqlx::query(
"WITH job AS (
INSERT INTO v2_job (id, workspace_id, tag) VALUES (gen_random_uuid(), $1, $2)
RETURNING id
)
INSERT INTO v2_job_queue (id, workspace_id, tag, priority, running, scheduled_for)
SELECT id, $1, $2, $3, $4, now() - make_interval(secs => $5) FROM job",
)
.bind(WORKSPACE)
.bind(tag)
.bind(priority)
.bind(running)
.bind(waited_secs)
.execute(db)
.await
.expect("failed to queue job");
}
/// The delay reported for a tag is that of the job the worker pull takes first, ordered
/// `priority DESC NULLS LAST, scheduled_for`, not simply the oldest one waiting. Running jobs
/// and jobs less than 3 seconds past due are not part of the backlog at all.
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
async fn queue_stats_report_the_delay_of_the_job_pulled_next(db: Pool<Postgres>) {
// The oldest job has no priority, so every prioritized job runs before it.
queue_job(&db, "mixed", None, 900.0, false).await;
queue_job(&db, "mixed", Some(1), 600.0, false).await;
queue_job(&db, "mixed", Some(5), 300.0, false).await;
queue_job(&db, "mixed", Some(5), 100.0, false).await;
// Highest priority, but not backlog: already running, or not yet 3 seconds past due.
queue_job(&db, "mixed", Some(9), 1200.0, true).await;
queue_job(&db, "mixed", Some(9), 1.0, false).await;
queue_job(&db, "unprioritized", None, 500.0, false).await;
queue_job(&db, "unprioritized", None, 50.0, false).await;
let stats = get_queue_stats(&db).await.unwrap();
let now: f64 = sqlx::query_scalar("SELECT EXTRACT(EPOCH FROM now())::double precision")
.fetch_one(&db)
.await
.unwrap();
let mixed = &stats["mixed"];
assert_eq!(mixed.count, 4);
assert!(
(mixed.delay - 300.0).abs() < 5.0,
"expected the oldest job of the highest priority, got a delay of {}",
mixed.delay
);
// The same job's wait start, which the delay is measured from.
assert!((mixed.head_since + mixed.delay - now).abs() < 5.0);
let unprioritized = &stats["unprioritized"];
assert_eq!(unprioritized.count, 2);
assert!(
(unprioritized.delay - 500.0).abs() < 5.0,
"expected the oldest job, got a delay of {}",
unprioritized.delay
);
}