Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ ignore = [
"RUSTSEC-2024-0436",
# https://rustsec.org/advisories/RUSTSEC-2025-0134.html
"RUSTSEC-2025-0134",
# https://rustsec.org/advisories/RUSTSEC-2026-0049.html
"RUSTSEC-2026-0049",
]

[bans]
Expand Down
2 changes: 2 additions & 0 deletions lib/api_projects/src/reports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ async fn post_inner(
let new_run_report = NewRunReport {
report: json_report,
#[cfg(feature = "plus")]
is_claimed: true,
#[cfg(feature = "plus")]
testbed: RunTestbed::Explicit,
#[cfg(feature = "plus")]
spec_reset: false,
Expand Down
5 changes: 2 additions & 3 deletions lib/api_run/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ publish = false

[features]
default = []
plus = ["bencher_endpoint/plus", "bencher_json/plus", "bencher_otel?/plus", "bencher_schema/plus", "dep:http"]
plus = ["bencher_endpoint/plus", "bencher_json/plus", "bencher_schema/plus", "dep:http"]
sentry = ["bencher_schema/sentry"]
otel = ["dep:bencher_otel", "bencher_schema/otel"]
otel = ["bencher_schema/otel"]

[dependencies]
bencher_endpoint.workspace = true
bencher_json = { workspace = true, features = ["server", "schema", "db"] }
bencher_otel = { workspace = true, optional = true }
bencher_rbac.workspace = true
bencher_schema.workspace = true
dropshot.workspace = true
Expand Down
8 changes: 2 additions & 6 deletions lib/api_run/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,13 @@ async fn post_inner(
) -> Result<JsonReport, HttpError> {
match public_user {
PublicUser::Public(remote_ip) => {
#[cfg(feature = "otel")]
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::RunUnclaimed);

if let Some(remote_ip) = remote_ip {
slog::info!(log, "Unclaimed run request from remote IP address"; "remote_ip" => ?remote_ip);
#[cfg(feature = "plus")]
context.rate_limiting.unclaimed_run(*remote_ip)?;
}
},
PublicUser::Auth(auth_user) => {
#[cfg(feature = "otel")]
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::RunClaimed);

#[cfg(feature = "plus")]
context.rate_limiting.claimed_run(auth_user.user.uuid)?;

Expand Down Expand Up @@ -174,6 +168,8 @@ async fn post_inner(
let new_run_report = NewRunReport {
report: json_run.into(),
#[cfg(feature = "plus")]
is_claimed,
#[cfg(feature = "plus")]
testbed,
#[cfg(feature = "plus")]
spec_reset,
Expand Down
6 changes: 1 addition & 5 deletions lib/bencher_schema/src/context/rate_limiting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,11 @@ impl RateLimiting {
}
.inspect(|()| {
#[cfg(feature = "otel")]
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::Create(
bencher_otel::IntervalKind::Day,
authorization_kind,
));
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::Create(authorization_kind));
})
.inspect_err(|_| {
#[cfg(feature = "otel")]
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::CreateMax(
bencher_otel::IntervalKind::Day,
authorization_kind,
));
})
Expand Down
39 changes: 28 additions & 11 deletions lib/bencher_schema/src/model/organization/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use bencher_billing::{Biller, CustomerId};
use bencher_json::{
DateTime, Entitlements, JsonPlan, Jwt, LicensedPlanId, MeteredPlanId, OrganizationUuid,
PlanLevel, project::Visibility,
PlanLevel, Priority, project::Visibility,
};
use bencher_license::Licensor;
use diesel::{BelongingToDsl as _, ExpressionMethods as _, QueryDsl as _, RunQueryDsl as _};
Expand Down Expand Up @@ -245,6 +245,20 @@ pub enum PlanKindError {
}

impl PlanKind {
pub fn priority(&self, is_claimed: bool) -> Priority {
if !is_claimed {
return Priority::Unclaimed;
}
match self {
Self::None => Priority::Free,
Self::Metered(_) => Priority::Plus,
Self::Licensed(license_usage) => match license_usage.level {
PlanLevel::Free => Priority::Free,
PlanLevel::Team | PlanLevel::Enterprise => Priority::Plus,
},
}
}

async fn new(
context: &ApiContext,
biller: Option<&Biller>,
Expand Down Expand Up @@ -383,16 +397,19 @@ impl PlanKind {
PlanKindError::NoBiller,
));
};
biller
.record_metrics_usage(&customer_id, usage)
.await
.map_err(|e| {
issue_error(
"Failed to record usage",
&format!("Failed to record usage ({usage}) for project ({project:?})."),
e,
)
})?;
if let Err(e) = biller.record_metrics_usage(&customer_id, usage).await {
#[cfg(feature = "otel")]
bencher_otel::ApiMeter::increment(
bencher_otel::ApiCounter::MetricsBilledFailed,
);
return Err(issue_error(
"Failed to record usage",
&format!("Failed to record usage ({usage}) for project ({project:?})."),
e,
));
}
#[cfg(feature = "otel")]
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::MetricsBilled);
},
Self::Licensed(LicenseUsage {
entitlements,
Expand Down
21 changes: 21 additions & 0 deletions lib/bencher_schema/src/model/project/report/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ use crate::{
pub struct NewRunReport {
pub report: JsonNewReport,
#[cfg(feature = "plus")]
pub is_claimed: bool,
#[cfg(feature = "plus")]
pub testbed: RunTestbed,
#[cfg(feature = "plus")]
pub spec_reset: bool,
Expand Down Expand Up @@ -142,6 +144,8 @@ impl QueryReport {

let NewRunReport {
report: mut json_report,
#[cfg(feature = "plus")]
is_claimed,
#[cfg(feature = "plus")]
testbed: run_testbed,
#[cfg(feature = "plus")]
Expand All @@ -150,6 +154,12 @@ impl QueryReport {
job: new_run_job,
} = new_run_report;

#[cfg(feature = "plus")]
let priority = plan_kind.priority(is_claimed);

#[cfg(all(feature = "otel", feature = "plus"))]
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::Run(priority));

#[cfg(feature = "plus")]
let run_job = new_run_job
.as_ref()
Expand Down Expand Up @@ -324,6 +334,8 @@ impl QueryReport {
#[cfg(feature = "plus")]
plan_kind,
#[cfg(feature = "plus")]
priority,
#[cfg(feature = "plus")]
query_project,
)
.await?;
Expand Down Expand Up @@ -421,6 +433,7 @@ impl QueryReport {
adapter: Adapter,
settings: JsonReportSettings,
#[cfg(feature = "plus")] plan_kind: PlanKind,
#[cfg(feature = "plus")] priority: bencher_json::Priority,
#[cfg(feature = "plus")] query_project: &QueryProject,
) -> Result<(), HttpError> {
#[cfg(feature = "plus")]
Expand All @@ -446,6 +459,14 @@ impl QueryReport {
)
.await;

#[cfg(all(feature = "otel", feature = "plus"))]
if usage > 0 {
bencher_otel::ApiMeter::increment_by(
bencher_otel::ApiCounter::MetricsCreate(priority),
u64::from(usage),
);
}

#[cfg(feature = "plus")]
plan_kind
.check_usage(context.biller.as_ref(), query_project, usage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ impl PreparedDetection {

let boundary_id = diesel::select(last_insert_rowid()).get_result::<BoundaryId>(conn)?;

#[cfg(feature = "otel")]
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::MetricCreate);

if !ignore_benchmark && let Some(boundary_limit) = outlier {
InsertAlert::insert(conn, boundary_id, boundary_limit)?;
}
Expand Down
46 changes: 13 additions & 33 deletions lib/bencher_schema/src/model/runner/job.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::sync::Arc;

use bencher_json::{
DateTime, ImageDigest, JobStatus, JobUuid, JsonJob, JsonJobConfig, PlanLevel, Priority,
Timeout, project::report::JsonReportSettings, runner::JsonIterationOutput,
runner::job::JsonNewRunJob,
DateTime, ImageDigest, JobStatus, JobUuid, JsonJob, JsonJobConfig, Priority, Timeout,
project::report::JsonReportSettings, runner::JsonIterationOutput, runner::job::JsonNewRunJob,
};
use diesel::{
BoolExpressionMethods as _, Connection as _, ExpressionMethods as _, QueryDsl as _,
Expand Down Expand Up @@ -148,6 +147,7 @@ impl QueryJob {
query_report.adapter,
settings,
plan_kind,
self.priority,
&query_project,
)
.await?;
Expand Down Expand Up @@ -329,7 +329,7 @@ impl PendingInsertJob {
.await?;

// 2. Determine priority
let priority = determine_priority(plan_kind, is_claimed);
let priority = plan_kind.priority(is_claimed);

// 3. Resolve timeout (clamped by plan tier)
let timeout = resolve_timeout(new_run_job.timeout, plan_kind, is_claimed);
Expand Down Expand Up @@ -438,20 +438,6 @@ fn resolve_timeout(requested: Option<Timeout>, plan_kind: &PlanKind, is_claimed:
}
}

fn determine_priority(plan_kind: &PlanKind, is_claimed: bool) -> Priority {
if !is_claimed {
return Priority::Unclaimed;
}
match plan_kind {
PlanKind::None => Priority::Free,
PlanKind::Metered(_) => Priority::Plus,
PlanKind::Licensed(license_usage) => match license_usage.level {
PlanLevel::Free => Priority::Free,
PlanLevel::Team | PlanLevel::Enterprise => Priority::Plus,
},
}
}

/// Insert the job duration summary for a report.
///
/// Uses `on_conflict.do_nothing()` for idempotency — the first write wins.
Expand All @@ -475,7 +461,7 @@ fn insert_job_duration(

#[cfg(test)]
mod tests {
use bencher_json::{DateTime, Entitlements};
use bencher_json::{DateTime, Entitlements, PlanLevel};
use diesel::{Connection as _, QueryDsl as _};
use pretty_assertions::assert_eq;

Expand Down Expand Up @@ -571,55 +557,49 @@ mod tests {
assert_eq!(u32::from(timeout), 86400);
}

// --- determine_priority tests ---
// --- PlanKind::priority tests ---

#[test]
fn priority_unclaimed() {
assert_eq!(
determine_priority(&PlanKind::None, false),
Priority::Unclaimed
);
assert_eq!(PlanKind::None.priority(false), Priority::Unclaimed);
}

#[test]
fn priority_unclaimed_ignores_plan() {
// Even with a Plus plan, unclaimed is always Unclaimed
assert_eq!(
determine_priority(&metered_plan(), false),
Priority::Unclaimed
);
assert_eq!(metered_plan().priority(false), Priority::Unclaimed);
}

#[test]
fn priority_free() {
assert_eq!(determine_priority(&PlanKind::None, true), Priority::Free);
assert_eq!(PlanKind::None.priority(true), Priority::Free);
}

#[test]
fn priority_metered() {
assert_eq!(determine_priority(&metered_plan(), true), Priority::Plus);
assert_eq!(metered_plan().priority(true), Priority::Plus);
}

#[test]
fn priority_licensed_free() {
assert_eq!(
determine_priority(&licensed_plan(PlanLevel::Free), true),
licensed_plan(PlanLevel::Free).priority(true),
Priority::Free
);
}

#[test]
fn priority_licensed_team() {
assert_eq!(
determine_priority(&licensed_plan(PlanLevel::Team), true),
licensed_plan(PlanLevel::Team).priority(true),
Priority::Plus
);
}

#[test]
fn priority_licensed_enterprise() {
assert_eq!(
determine_priority(&licensed_plan(PlanLevel::Enterprise), true),
licensed_plan(PlanLevel::Enterprise).priority(true),
Priority::Plus
);
}
Expand Down
2 changes: 1 addition & 1 deletion plus/api_runners/src/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ async fn bill_stripe_best_effort(
if let Err(e) = biller.record_runner_usage(&customer_id, delta).await {
slog::warn!(log, "Failed to record runner billing"; "job_id" => ?job_id, "delta" => delta, "error" => %e);
#[cfg(feature = "otel")]
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::RunnerMinutesBillingFailed);
bencher_otel::ApiMeter::increment(bencher_otel::ApiCounter::RunnerMinutesBilledFailed);
#[cfg(feature = "sentry")]
billing_state.report_err(&e);
} else {
Expand Down
Loading
Loading