Files
windmill/backend/windmill-common/src/bench.rs
Ruben Fiszel 424ca59dfe feat: make WINDMILL_DIR configurable via environment variable (#8215)
* fix: auto-heal corrupted python runtime cache on remote workers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Revert "fix: auto-heal corrupted python runtime cache on remote workers"

This reverts commit 0ea013a554.

* feat: make WINDMILL_DIR configurable via environment variable

Allow users to configure the base directory for Windmill's tmp/cache files
via the WINDMILL_DIR env var (default: /tmp/windmill). This fixes Python
runtime cache corruption on RHEL systems where systemd-tmpfiles-clean
removes files from /tmp.

Converts TMP_DIR (renamed to WINDMILL_DIR) and all derived cache directory
constants from compile-time const &str (concatcp!) to runtime lazy_static
String values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee ref

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee ref

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: deref ERROR_DIR lazy_static for AsRef<Path> and Display traits

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee ref to branch name for CI compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: deref lazy_static constants in all executor files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee ref

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee ref

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee ref

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: panic if WINDMILL_DIR has trailing slash

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: also reject trailing backslash in WINDMILL_DIR for Windows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: deref GO_BIN_CACHE_DIR in test utils

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: replace remaining hardcoded /tmp/windmill paths and validate empty WINDMILL_DIR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: nsjail powershell mount dst, Windows path assumptions, pwsh deref consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore Windows /tmp path translation in go and bun executors

The Windows path translation replaces /tmp with the Windows temp dir
(e.g. C:\tmp) before normalizing slashes. Without this, the default
WINDMILL_DIR=/tmp/windmill produces paths without a drive letter on
Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee-repo-ref to 6fd5a2ce908235a17975ad4dbdf0051cd89334f3

This commit updates the EE repository reference after PR #436 was merged in windmill-ee-private.

Previous ee-repo-ref: e8c03e16720833230ebd1878b4c63642ecc6c80f

New ee-repo-ref: 6fd5a2ce908235a17975ad4dbdf0051cd89334f3

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-03-04 08:53:25 +00:00

948 lines
44 KiB
Rust

use crate::{
worker::{write_file, WINDMILL_DIR},
DB,
};
use serde::Serialize;
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use tokio::time::Instant;
use uuid::Uuid;
static BENCHMARK_INITIALIZED: AtomicBool = AtomicBool::new(false);
static SHARED_BENCH_ITERS: std::sync::LazyLock<Arc<AtomicU64>> =
std::sync::LazyLock::new(|| Arc::new(AtomicU64::new(0)));
pub fn shared_bench_iters() -> Arc<AtomicU64> {
SHARED_BENCH_ITERS.clone()
}
#[derive(Serialize)]
pub struct PoolStats {
pub peak_active_conns: u32,
pub pool_saturation_histogram: Vec<u32>,
}
impl PoolStats {
pub fn new(pool_size: u32) -> Self {
PoolStats {
peak_active_conns: 0,
pool_saturation_histogram: vec![0; pool_size as usize + 1],
}
}
pub fn sample(&mut self, pool_size: u32, num_idle: u32) {
let active = pool_size.saturating_sub(num_idle);
if active > self.peak_active_conns {
self.peak_active_conns = active;
}
if let Some(bucket) = self.pool_saturation_histogram.get_mut(active as usize) {
*bucket += 1;
}
}
}
#[derive(Serialize)]
pub struct BenchmarkInfo {
#[serde(skip)]
pub start: Instant,
#[serde(skip)]
pub iters: u64,
#[serde(skip)]
seen_top_level: HashSet<Uuid>,
#[serde(skip)]
pub shared_iters: Arc<AtomicU64>,
timings: Vec<BenchmarkIter>,
pub iter_durations: Vec<u64>,
pub total_duration: Option<u64>,
pub pool_stats: Option<PoolStats>,
}
impl BenchmarkInfo {
pub fn new(shared_iters: Arc<AtomicU64>) -> Self {
BenchmarkInfo {
iters: 0,
seen_top_level: HashSet::new(),
shared_iters,
timings: vec![],
start: Instant::now(),
iter_durations: vec![],
total_duration: None,
pool_stats: None,
}
}
pub fn init_pool_stats(&mut self, pool_size: u32) {
self.pool_stats = Some(PoolStats::new(pool_size));
}
pub fn sample_pool(&mut self, pool_size: u32, num_idle: u32) {
if let Some(stats) = self.pool_stats.as_mut() {
stats.sample(pool_size, num_idle);
}
}
pub fn count_top_level(&mut self, job_id: Uuid) -> bool {
if self.seen_top_level.insert(job_id) {
self.iters += 1;
return true;
}
false
}
pub fn add_iter(&mut self, bench: BenchmarkIter, job_id: Uuid, is_top_level: bool) -> bool {
let newly_counted = is_top_level && self.seen_top_level.insert(job_id);
if newly_counted {
self.iters += 1;
}
let elapsed_total = bench.start.elapsed().as_nanos() as u64;
self.timings.push(bench);
self.iter_durations.push(elapsed_total);
newly_counted
}
pub fn write_to_file(&mut self, path: &str) -> anyhow::Result<()> {
let total_duration = self.start.elapsed().as_millis() as u64;
self.total_duration = Some(total_duration as u64);
let pool_info = self.pool_stats.as_ref().map_or(String::new(), |ps| {
format!(", peak active conns: {}", ps.peak_active_conns)
});
println!(
"Writing benchmark {path}, duration of benchmark: {total_duration}ms and RPS: {}{pool_info}",
self.iters as f64 / total_duration as f64 * 1000.0
);
write_file(&WINDMILL_DIR, path, &serde_json::to_string(&self).unwrap())
.expect("write profiling");
Ok(())
}
}
#[derive(Serialize)]
pub struct BenchmarkIter {
#[serde(skip)]
pub start: Instant,
#[serde(skip)]
last_instant: Instant,
last_step: String,
timings: Vec<(String, u32)>,
}
impl BenchmarkIter {
pub fn new() -> Self {
BenchmarkIter {
last_instant: Instant::now(),
timings: vec![],
start: Instant::now(),
last_step: String::new(),
}
}
pub fn add_timing(&mut self, name: &str) {
let elapsed = self.last_instant.elapsed().as_nanos() as u32;
self.timings
.push((format!("{}->{}", self.last_step, name), elapsed));
self.last_instant = Instant::now();
self.last_step = name.to_string();
}
}
pub async fn benchmark_verify(benchmark_jobs: i32, db: &DB) {
let benchmark_kind = std::env::var("BENCHMARK_KIND").unwrap_or("noop".to_string());
if benchmark_jobs <= 0 || benchmark_kind == "none" {
return;
}
// For flows, child jobs are created dynamically so only check top-level (parent_job IS NULL).
// "parallelflow" inserts only 1 top-level flow regardless of benchmark_jobs.
let expected_top_level = match benchmark_kind.as_str() {
"parallelflow" => 1i64,
_ => benchmark_jobs as i64,
};
let row = sqlx::query!(
"SELECT
COUNT(*) FILTER (WHERE status = 'success') AS succeeded,
COUNT(*) FILTER (WHERE status = 'failure') AS failed,
COUNT(*) FILTER (WHERE status = 'canceled') AS canceled
FROM v2_job_completed
JOIN v2_job USING (id)
WHERE v2_job.workspace_id = 'admins' AND v2_job.parent_job IS NULL",
)
.fetch_one(db)
.await
.expect("benchmark verify query failed");
let succeeded = row.succeeded.unwrap_or(0);
let failed = row.failed.unwrap_or(0);
let canceled = row.canceled.unwrap_or(0);
let total = succeeded + failed + canceled;
let remaining_in_queue =
sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_queue WHERE workspace_id = 'admins'",)
.fetch_one(db)
.await
.expect("benchmark verify queue query failed")
.unwrap_or(0);
println!("=== BENCHMARK VERIFICATION ===");
println!(" kind: {benchmark_kind}");
println!(" expected top-level: {expected_top_level}");
println!(" completed total: {total}");
println!(" succeeded: {succeeded}");
println!(" failed: {failed}");
println!(" canceled: {canceled}");
println!(" still in queue: {remaining_in_queue}");
if failed > 0 || canceled > 0 {
tracing::error!(
"BENCHMARK VERIFICATION FAILED: {failed} failed, {canceled} canceled out of {total} completed"
);
}
if remaining_in_queue > 0 {
tracing::warn!(
"BENCHMARK VERIFICATION: {remaining_in_queue} jobs still in queue after benchmark"
);
}
if succeeded != expected_top_level {
tracing::error!(
"BENCHMARK VERIFICATION FAILED: expected {expected_top_level} succeeded top-level jobs, got {succeeded}"
);
} else if failed == 0 && canceled == 0 && remaining_in_queue == 0 {
println!(" result: ALL PASSED");
}
println!("==============================");
}
pub async fn benchmark_init(benchmark_jobs: i32, db: &DB) {
use crate::{jobs::JobKind, scripts::ScriptLang};
// Only the first worker to reach this point runs init
if BENCHMARK_INITIALIZED.swap(true, Ordering::SeqCst) {
return;
}
let benchmark_kind = std::env::var("BENCHMARK_KIND").unwrap_or("noop".to_string());
if benchmark_jobs > 0 {
// Clean up data from previous benchmark runs
sqlx::query!("DELETE FROM v2_job_completed WHERE workspace_id = 'admins'")
.execute(db)
.await
.unwrap_or_else(|e| panic!("failed to clean up v2_job_completed: {e:#}"));
sqlx::query!("DELETE FROM v2_job_queue WHERE workspace_id = 'admins'")
.execute(db)
.await
.unwrap_or_else(|e| panic!("failed to clean up v2_job_queue: {e:#}"));
sqlx::query!("DELETE FROM v2_job_status WHERE id IN (SELECT id FROM v2_job WHERE workspace_id = 'admins')")
.execute(db)
.await
.unwrap_or_else(|e| panic!("failed to clean up v2_job_status: {e:#}"));
sqlx::query!("DELETE FROM v2_job_runtime WHERE id IN (SELECT id FROM v2_job WHERE workspace_id = 'admins')")
.execute(db)
.await
.unwrap_or_else(|e| panic!("failed to clean up v2_job_runtime: {e:#}"));
sqlx::query("DELETE FROM job_perms WHERE workspace_id = 'admins'")
.execute(db)
.await
.unwrap_or_else(|e| panic!("failed to clean up job_perms: {e:#}"));
sqlx::query!(
"DELETE FROM concurrency_key WHERE key LIKE 'bench_%' OR key LIKE 'u/admin/bench_%'"
)
.execute(db)
.await
.unwrap_or_else(|e| panic!("failed to clean up concurrency_key: {e:#}"));
sqlx::query!("DELETE FROM concurrency_counter WHERE concurrency_id LIKE 'bench_%' OR concurrency_id LIKE 'u/admin/bench_%'")
.execute(db)
.await
.unwrap_or_else(|e| panic!("failed to clean up concurrency_counter: {e:#}"));
sqlx::query!("DELETE FROM v2_job WHERE workspace_id = 'admins'")
.execute(db)
.await
.unwrap_or_else(|e| panic!("failed to clean up v2_job: {e:#}"));
let mut tx = db.begin().await.unwrap();
match benchmark_kind.as_str() {
"dedicated" => {
// you need to create the script first, check https://github.com/windmill-labs/windmill/blob/b76a92cfe454c686f005c65f534e29e039f3c706/benchmarks/lib.ts#L47
let hash = sqlx::query_scalar!(
"SELECT hash FROM script WHERE path = $1 AND workspace_id = $2",
"f/benchmarks/dedicated",
"admins"
)
.fetch_one(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert dedicated jobs"));
let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id",
hash,
"f/benchmarks/dedicated",
JobKind::Script as JobKind,
ScriptLang::Bun as ScriptLang,
"admins:f/benchmarks/dedicated",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
benchmark_jobs
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert dedicated jobs (1)"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "admins:f/benchmarks/dedicated")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert dedicated jobs (2)"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert dedicated jobs (3)"));
}
"parallelflow" => {
//create dedicated script
sqlx::query!("INSERT INTO script (summary, description, dedicated_worker, content, workspace_id, path, hash, language, tag, created_by, lock) VALUES ('', '', true, $1, $2, $3, $4, $5, $6, $7, '') ON CONFLICT (workspace_id, hash) DO NOTHING",
"export async function main() {
console.log('hello world');
}",
"admins",
"u/admin/parallelflow",
1234567890,
ScriptLang::Deno as ScriptLang,
"flow",
"admin",
)
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert parallelflow jobs {_e:#}"));
let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, 1)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::FlowPreview as JobKind,
ScriptLang::Deno as ScriptLang,
"flow",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
serde_json::from_str::<serde_json::Value>(r#"
{
"modules": [
{
"id": "a",
"value": {
"type": "forloopflow",
"modules": [
{
"id": "b",
"value": {
"path": "u/admin/parallelflow",
"type": "script",
"tag_override": "",
"input_transforms": {}
},
"summary": "calctest"
}
],
"iterator": {
"expr": "[...new Array(300)]",
"type": "javascript"
},
"parallel": true,
"parallelism": 10,
"skip_failures": true
}
}
],
"preprocessor_module": null
}
"#).unwrap(),
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert parallelflow jobs (1)"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "flow")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert parallelflow jobs (2)"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert parallelflow jobs (3)"));
sqlx::query!(
"INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
&uuids,
serde_json::from_str::<serde_json::Value>(
r#"
{
"step": 0,
"modules": [
{
"id": "a",
"type": "WaitingForPriorSteps"
}
],
"cleanup_module": {},
"failure_module": {
"id": "failure",
"type": "WaitingForPriorSteps"
},
"preprocessor_module": null
}
"#
)
.unwrap()
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert parallelflow jobs (4)"));
}
"sequentialflow" => {
let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::FlowPreview as JobKind,
ScriptLang::Deno as ScriptLang,
"flow",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
serde_json::from_str::<serde_json::Value>(r#"
{
"modules": [
{
"id": "a",
"value": {
"type": "rawscript",
"content": "export async function main() { return 'a'; }",
"language": "deno",
"input_transforms": {}
}
},
{
"id": "b",
"value": {
"type": "rawscript",
"content": "export async function main() { return 'b'; }",
"language": "deno",
"input_transforms": {}
}
},
{
"id": "c",
"value": {
"type": "rawscript",
"content": "export async function main() { return 'c'; }",
"language": "deno",
"input_transforms": {}
}
},
{
"id": "d",
"value": {
"type": "rawscript",
"content": "export async function main() { return 'd'; }",
"language": "deno",
"input_transforms": {}
}
}
],
"preprocessor_module": null
}
"#).unwrap(),
benchmark_jobs,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert sequentialflow jobs (1)"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "flow")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert sequentialflow jobs (2)"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert sequentialflow jobs (3)"));
sqlx::query!(
"INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
&uuids,
serde_json::from_str::<serde_json::Value>(
r#"
{
"step": 0,
"modules": [
{ "id": "a", "type": "WaitingForPriorSteps" },
{ "id": "b", "type": "WaitingForPriorSteps" },
{ "id": "c", "type": "WaitingForPriorSteps" },
{ "id": "d", "type": "WaitingForPriorSteps" }
],
"cleanup_module": {},
"failure_module": {
"id": "failure",
"type": "WaitingForPriorSteps"
},
"preprocessor_module": null
}
"#
)
.unwrap()
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert sequentialflow jobs (4)"));
}
"scriptlogs" => {
let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::Preview as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
"export async function main() { for (let i = 0; i < 1000; i++) { console.log('benchmark log line ' + i); } return 'done'; }",
benchmark_jobs,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert scriptlogs jobs (1)"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert scriptlogs jobs (2)"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert scriptlogs jobs (3)"));
}
"concurrencylimit" => {
let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code, concurrent_limit, concurrency_time_window_s) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 FROM generate_series(1, $13)) RETURNING id",
None::<i64>,
Some("u/admin/bench_conclimit"),
JobKind::Preview as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
"export async function main() { return 'done'; }",
2i32,
0i32,
benchmark_jobs,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert concurrencylimit jobs (1)"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert concurrencylimit jobs (2)"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert concurrencylimit jobs (3)"));
let concurrency_id = "u/admin/bench_conclimit";
sqlx::query!(
"INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING",
concurrency_id,
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert concurrencylimit counter"));
for uuid in &uuids {
sqlx::query!(
"INSERT INTO concurrency_key (key, ended_at, job_id) VALUES ($1, now() - INTERVAL '1 hour', $2)",
concurrency_id,
uuid,
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert concurrencylimit key"));
}
}
"concurrencykey" => {
let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code, concurrent_limit, concurrency_time_window_s) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 FROM generate_series(1, $13)) RETURNING id",
None::<i64>,
Some("u/admin/bench_conckey"),
JobKind::Preview as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
"export async function main() { return 'done'; }",
1i32,
0i32,
benchmark_jobs,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert concurrencykey jobs (1)"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert concurrencykey jobs (2)"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert concurrencykey jobs (3)"));
let concurrency_id = "bench_shared_concurrency_key";
sqlx::query!(
"INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING",
concurrency_id,
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert concurrencykey counter"));
for uuid in &uuids {
sqlx::query!(
"INSERT INTO concurrency_key (key, ended_at, job_id) VALUES ($1, now() - INTERVAL '1 hour', $2)",
concurrency_id,
uuid,
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert concurrencykey key"));
}
}
"mixed" => {
let portion = benchmark_jobs / 5;
let remainder = benchmark_jobs % 5;
// 1) noop jobs
let noop_count = portion + remainder;
let noop_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::Noop as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
noop_count,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed noop jobs"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &noop_uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed noop queue"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&noop_uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert mixed noop runtime"));
// 2) sequentialflow jobs
if portion > 0 {
let sf_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::FlowPreview as JobKind,
ScriptLang::Deno as ScriptLang,
"flow",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
serde_json::from_str::<serde_json::Value>(r#"{"modules":[{"id":"a","value":{"type":"rawscript","content":"export async function main() { return 'a'; }","language":"deno","input_transforms":{}}},{"id":"b","value":{"type":"rawscript","content":"export async function main() { return 'b'; }","language":"deno","input_transforms":{}}},{"id":"c","value":{"type":"rawscript","content":"export async function main() { return 'c'; }","language":"deno","input_transforms":{}}},{"id":"d","value":{"type":"rawscript","content":"export async function main() { return 'd'; }","language":"deno","input_transforms":{}}}],"preprocessor_module":null}"#).unwrap(),
portion,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow jobs"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sf_uuids, "admins", "flow")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow queue"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&sf_uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow runtime"));
sqlx::query!(
"INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
&sf_uuids,
serde_json::from_str::<serde_json::Value>(r#"{"step":0,"modules":[{"id":"a","type":"WaitingForPriorSteps"},{"id":"b","type":"WaitingForPriorSteps"},{"id":"c","type":"WaitingForPriorSteps"},{"id":"d","type":"WaitingForPriorSteps"}],"cleanup_module":{},"failure_module":{"id":"failure","type":"WaitingForPriorSteps"},"preprocessor_module":null}"#).unwrap()
)
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed sequentialflow status"));
}
// 3) scriptlogs jobs
if portion > 0 {
let sl_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::Preview as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
"export async function main() { for (let i = 0; i < 1000; i++) { console.log('benchmark log line ' + i); } return 'done'; }",
portion,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs jobs"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sl_uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs queue"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&sl_uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert mixed scriptlogs runtime"));
}
// 4) concurrencylimit jobs
if portion > 0 {
let cl_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code, concurrent_limit, concurrency_time_window_s) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 FROM generate_series(1, $13)) RETURNING id",
None::<i64>,
Some("u/admin/bench_conclimit"),
JobKind::Preview as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
"export async function main() { return 'done'; }",
2i32,
0i32,
portion,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit jobs"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &cl_uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit queue"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&cl_uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit runtime"));
let cl_concurrency_id = "u/admin/bench_conclimit";
sqlx::query!(
"INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING",
cl_concurrency_id,
)
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit counter"));
for uuid in &cl_uuids {
sqlx::query!(
"INSERT INTO concurrency_key (key, ended_at, job_id) VALUES ($1, now() - INTERVAL '1 hour', $2)",
cl_concurrency_id,
uuid,
)
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencylimit key"));
}
}
// 5) concurrencykey jobs
if portion > 0 {
let ck_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code, concurrent_limit, concurrency_time_window_s) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 FROM generate_series(1, $13)) RETURNING id",
None::<i64>,
Some("u/admin/bench_conckey"),
JobKind::Preview as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
"export async function main() { return 'done'; }",
1i32,
0i32,
portion,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey jobs"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &ck_uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey queue"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&ck_uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey runtime"));
let ck_concurrency_id = "bench_shared_concurrency_key";
sqlx::query!(
"INSERT INTO concurrency_counter (concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT (concurrency_id) DO NOTHING",
ck_concurrency_id,
)
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey counter"));
for uuid in &ck_uuids {
sqlx::query!(
"INSERT INTO concurrency_key (key, ended_at, job_id) VALUES ($1, now() - INTERVAL '1 hour', $2)",
ck_concurrency_id,
uuid,
)
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed concurrencykey key"));
}
}
}
"mixed_no_cc" => {
let portion = benchmark_jobs / 3;
let remainder = benchmark_jobs % 3;
// 1) noop jobs
let noop_count = portion + remainder;
let noop_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::Noop as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
noop_count,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop jobs"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &noop_uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop queue"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&noop_uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc noop runtime"));
// 2) sequentialflow jobs
if portion > 0 {
let sf_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_flow) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::FlowPreview as JobKind,
ScriptLang::Deno as ScriptLang,
"flow",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
serde_json::from_str::<serde_json::Value>(r#"{"modules":[{"id":"a","value":{"type":"rawscript","content":"export async function main() { return 'a'; }","language":"deno","input_transforms":{}}},{"id":"b","value":{"type":"rawscript","content":"export async function main() { return 'b'; }","language":"deno","input_transforms":{}}},{"id":"c","value":{"type":"rawscript","content":"export async function main() { return 'c'; }","language":"deno","input_transforms":{}}},{"id":"d","value":{"type":"rawscript","content":"export async function main() { return 'd'; }","language":"deno","input_transforms":{}}}],"preprocessor_module":null}"#).unwrap(),
portion,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow jobs"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sf_uuids, "admins", "flow")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow queue"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&sf_uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| {
panic!("failed to insert mixed_no_cc sequentialflow runtime")
});
sqlx::query!(
"INSERT INTO v2_job_status (id, flow_status) SELECT unnest($1::uuid[]), $2",
&sf_uuids,
serde_json::from_str::<serde_json::Value>(r#"{"step":0,"modules":[{"id":"a","type":"WaitingForPriorSteps"},{"id":"b","type":"WaitingForPriorSteps"},{"id":"c","type":"WaitingForPriorSteps"},{"id":"d","type":"WaitingForPriorSteps"}],"cleanup_module":{},"failure_module":{"id":"failure","type":"WaitingForPriorSteps"},"preprocessor_module":null}"#).unwrap()
)
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc sequentialflow status"));
}
// 3) scriptlogs jobs
if portion > 0 {
let sl_uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, raw_code) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 FROM generate_series(1, $11)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::Preview as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
"export async function main() { for (let i = 0; i < 1000; i++) { console.log('benchmark log line ' + i); } return 'done'; }",
portion,
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs jobs"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &sl_uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs queue"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&sl_uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert mixed_no_cc scriptlogs runtime"));
}
}
"none" => {}
_ => {
let uuids = sqlx::query_scalar!("INSERT INTO v2_job (id, runnable_id, runnable_path, kind, script_lang, tag, created_by, permissioned_as, permissioned_as_email, workspace_id) (SELECT gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9 FROM generate_series(1, $10)) RETURNING id",
None::<i64>,
None::<String>,
JobKind::Noop as JobKind,
ScriptLang::Deno as ScriptLang,
"deno",
"admin",
"u/admin",
"admin@windmill.dev",
"admins",
benchmark_jobs
)
.fetch_all(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert noop jobs (1)"));
sqlx::query!("INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) SELECT unnest($1::uuid[]), $2, now(), $3", &uuids, "admins", "deno")
.execute(&mut *tx)
.await.unwrap_or_else(|_e| panic!("failed to insert noop jobs (2)"));
sqlx::query!(
"INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
&uuids
)
.execute(&mut *tx)
.await
.unwrap_or_else(|_e| panic!("failed to insert noop jobs (3)"));
}
}
// Insert job_perms for all benchmark jobs so workers don't fall back to slow permission lookups
sqlx::query(
"INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, groups, folders, workspace_id)
SELECT id, 'admin@windmill.dev', 'admin', true, false, ARRAY['all']::text[], ARRAY[]::jsonb[], 'admins'
FROM v2_job WHERE workspace_id = 'admins'"
)
.execute(&mut *tx)
.await
.unwrap_or_else(|e| panic!("failed to insert job_perms: {e:#}"));
tx.commit().await.unwrap();
}
}