mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
* 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>
81 lines
3.0 KiB
Rust
81 lines
3.0 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use sqlx::{Pool, Postgres};
|
|
|
|
pub async fn get_queue_counts(db: &Pool<Postgres>) -> HashMap<String, u32> {
|
|
sqlx::query!(
|
|
"SELECT tag AS \"tag!\", count(*) AS \"count!\" FROM v2_job_queue WHERE
|
|
scheduled_for <= now() - ('3 seconds')::interval AND running = false
|
|
GROUP BY tag",
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.ok()
|
|
.map(|v| v.into_iter().map(|x| (x.tag, x.count as u32)).collect())
|
|
.unwrap_or_else(|| HashMap::new())
|
|
}
|
|
|
|
/// Backlog of a single tag: jobs waiting more than 3 seconds past their `scheduled_for`.
|
|
pub struct QueueStat {
|
|
pub count: u32,
|
|
/// How long the job that would be picked up next has already been waiting, in seconds.
|
|
pub delay: f64,
|
|
/// When that job started waiting (its `scheduled_for`), in epoch seconds.
|
|
pub head_since: f64,
|
|
}
|
|
|
|
/// Same backlog as [`get_queue_counts`], plus the delay of the job at the head of each
|
|
/// tag's queue. The head is picked with the same ordering the worker pull uses, so the
|
|
/// delay reported is the one a worker is about to observe.
|
|
///
|
|
/// Reads the queue of every workspace: a caller exposing the result MUST restrict it to
|
|
/// devops users, as `GET /workers/queue_counts` does. Unlike [`get_queue_counts`], a failed
|
|
/// read is an error rather than an empty map, which would read as every backlog draining.
|
|
pub async fn get_queue_stats(
|
|
db: &Pool<Postgres>,
|
|
) -> crate::error::Result<HashMap<String, QueueStat>> {
|
|
// Grouping by (tag, priority) first finds every head in the same single pass as the
|
|
// count. A per-tag `ORDER BY ... LIMIT 1` walks `queue_sort_v2`, whose `tag` column comes
|
|
// last, through every other tag's backlog queued ahead of it.
|
|
let rows = sqlx::query!(
|
|
"SELECT tag AS \"tag!\", count AS \"count!\",
|
|
EXTRACT(EPOCH FROM now() - head)::double precision AS \"delay!\",
|
|
EXTRACT(EPOCH FROM head)::double precision AS \"head_since!\"
|
|
FROM (
|
|
SELECT tag, sum(n)::bigint AS count,
|
|
(array_agg(head ORDER BY priority DESC NULLS LAST))[1] AS head
|
|
FROM (
|
|
SELECT tag, priority, count(*) AS n, min(scheduled_for) AS head
|
|
FROM v2_job_queue WHERE
|
|
scheduled_for <= now() - ('3 seconds')::interval AND running = false
|
|
GROUP BY tag, priority
|
|
) g
|
|
GROUP BY tag
|
|
) t",
|
|
)
|
|
.fetch_all(db)
|
|
.await?;
|
|
Ok(rows
|
|
.into_iter()
|
|
.map(|x| {
|
|
(
|
|
x.tag,
|
|
QueueStat { count: x.count as u32, delay: x.delay, head_since: x.head_since },
|
|
)
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
pub async fn get_queue_running_counts(db: &Pool<Postgres>) -> HashMap<String, u32> {
|
|
sqlx::query!(
|
|
"SELECT tag AS \"tag!\", count(*) AS \"count!\" FROM v2_job_queue WHERE
|
|
running = true
|
|
GROUP BY tag",
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.ok()
|
|
.map(|v| v.into_iter().map(|x| (x.tag, x.count as u32)).collect())
|
|
.unwrap_or_else(|| HashMap::new())
|
|
}
|