mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-08 14:23:13 +00:00
feat: add ops profiler capability snapshots (#3629)
This commit is contained in:
@@ -28,6 +28,7 @@ pub enum ExtensionKind {
|
||||
TargetPlugin,
|
||||
S3Hook,
|
||||
OpsDiagnostics,
|
||||
OpsProfiler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
@@ -191,6 +192,22 @@ pub struct OpsProfilerContract {
|
||||
pub backends: Vec<OpsProfilerBackendCapability>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OpsProfilerRuntimeSnapshot {
|
||||
pub boundary: ExtensionRuntimeBoundary,
|
||||
pub disabled_by_default: bool,
|
||||
pub startup_fatal: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OpsProfilerCapabilitySnapshot {
|
||||
pub capability: ExtensionCapabilityRef,
|
||||
pub runtime: OpsProfilerRuntimeSnapshot,
|
||||
pub contract: OpsProfilerContract,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum ExtensionSchemaError {
|
||||
#[error("extension schema at index {index} has an empty extension id")]
|
||||
@@ -279,6 +296,15 @@ pub enum ExtensionContractError {
|
||||
|
||||
#[error("ops profiler backend {backend} has an empty collection boundary")]
|
||||
EmptyOpsProfilerCollectionBoundary { backend: String },
|
||||
|
||||
#[error("ops profiler snapshot has unsupported capability {capability}")]
|
||||
UnsupportedOpsProfilerCapability { capability: String },
|
||||
|
||||
#[error("ops profiler external runtime must be disabled by default")]
|
||||
OpsProfilerExternalRuntimeEnabledByDefault,
|
||||
|
||||
#[error("ops profiler runtime snapshot cannot add a startup fatal boundary")]
|
||||
OpsProfilerStartupFatalBoundary,
|
||||
}
|
||||
|
||||
pub fn validate_extension_schemas(schemas: &[ExtensionSchema]) -> Result<(), ExtensionSchemaError> {
|
||||
@@ -459,15 +485,34 @@ pub fn validate_ops_profiler_contract(contract: &OpsProfilerContract) -> Result<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_ops_profiler_capability_snapshot(snapshot: &OpsProfilerCapabilitySnapshot) -> Result<(), ExtensionContractError> {
|
||||
if snapshot.capability.as_str() != OPS_PROFILER_CAPABILITY {
|
||||
return Err(ExtensionContractError::UnsupportedOpsProfilerCapability {
|
||||
capability: snapshot.capability.as_str().to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if snapshot.runtime.boundary.requires_disabled_by_default() && !snapshot.runtime.disabled_by_default {
|
||||
return Err(ExtensionContractError::OpsProfilerExternalRuntimeEnabledByDefault);
|
||||
}
|
||||
|
||||
if snapshot.runtime.startup_fatal {
|
||||
return Err(ExtensionContractError::OpsProfilerStartupFatalBoundary);
|
||||
}
|
||||
|
||||
validate_ops_profiler_contract(&snapshot.contract)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
EXTENSION_SCHEMA_VERSION, ExtensionCapabilityRef, ExtensionContractError, ExtensionKind, ExtensionRuntimeBoundary,
|
||||
ExtensionRuntimeContract, ExtensionSchema, ExtensionSchemaError, OPS_DIAGNOSTICS_CAPABILITY, OPS_PROFILER_CAPABILITY,
|
||||
OpsDiagnosticSurface, OpsDiagnosticsContract, OpsProfilerBackendCapability, OpsProfilerBackendName,
|
||||
OpsProfilerBackendStatus, OpsProfilerContract, OpsProfilerContractMode, OpsProfilerProvenance, OpsProfilerRedactionField,
|
||||
OpsProfilerTrustLevel, S3_POST_AUTH_HOOK_CAPABILITY, S3HookContract, S3HookPoint, validate_extension_schemas,
|
||||
validate_ops_diagnostics_contract, validate_ops_profiler_contract, validate_s3_hook_contract,
|
||||
OpsProfilerBackendStatus, OpsProfilerCapabilitySnapshot, OpsProfilerContract, OpsProfilerContractMode,
|
||||
OpsProfilerProvenance, OpsProfilerRedactionField, OpsProfilerRuntimeSnapshot, OpsProfilerTrustLevel,
|
||||
S3_POST_AUTH_HOOK_CAPABILITY, S3HookContract, S3HookPoint, validate_extension_schemas, validate_ops_diagnostics_contract,
|
||||
validate_ops_profiler_capability_snapshot, validate_ops_profiler_contract, validate_s3_hook_contract,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -541,7 +586,7 @@ mod tests {
|
||||
display_name: "Ops Profiler".to_string(),
|
||||
provider: "rustfs".to_string(),
|
||||
version: "1.0.0".to_string(),
|
||||
kind: ExtensionKind::OpsDiagnostics,
|
||||
kind: ExtensionKind::OpsProfiler,
|
||||
runtime: ExtensionRuntimeContract {
|
||||
api_version: "rustfs.extension.v1".to_string(),
|
||||
boundary: ExtensionRuntimeBoundary::Builtin,
|
||||
@@ -803,6 +848,130 @@ mod tests {
|
||||
assert!(validate_ops_profiler_contract(&contract).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ops_profiler_capability_snapshot_preserves_runtime_states() {
|
||||
let snapshot = OpsProfilerCapabilitySnapshot {
|
||||
capability: ExtensionCapabilityRef::new(OPS_PROFILER_CAPABILITY),
|
||||
runtime: OpsProfilerRuntimeSnapshot {
|
||||
boundary: ExtensionRuntimeBoundary::Sidecar,
|
||||
disabled_by_default: true,
|
||||
startup_fatal: false,
|
||||
},
|
||||
contract: OpsProfilerContract {
|
||||
mode: OpsProfilerContractMode::CapabilityDescription,
|
||||
backends: vec![
|
||||
profiler_backend("cpu_pprof", OpsProfilerBackendStatus::Enabled, true),
|
||||
profiler_backend("memory_pprof", OpsProfilerBackendStatus::Disabled, false),
|
||||
profiler_backend("ebpf", OpsProfilerBackendStatus::Unsupported, false),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
assert!(validate_ops_profiler_capability_snapshot(&snapshot).is_ok());
|
||||
|
||||
let encoded = serde_json::to_string(&snapshot).expect("ops profiler snapshot should serialize");
|
||||
let decoded: OpsProfilerCapabilitySnapshot =
|
||||
serde_json::from_str(&encoded).expect("ops profiler snapshot should deserialize");
|
||||
|
||||
let states: Vec<_> = decoded.contract.backends.iter().map(|backend| backend.status).collect();
|
||||
assert_eq!(
|
||||
states,
|
||||
vec![
|
||||
OpsProfilerBackendStatus::Enabled,
|
||||
OpsProfilerBackendStatus::Disabled,
|
||||
OpsProfilerBackendStatus::Unsupported,
|
||||
]
|
||||
);
|
||||
assert_eq!(decoded.runtime.boundary, ExtensionRuntimeBoundary::Sidecar);
|
||||
assert!(decoded.runtime.disabled_by_default);
|
||||
assert!(!decoded.runtime.startup_fatal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ops_profiler_capability_snapshot_serializes_stable_json_shape() {
|
||||
let snapshot = OpsProfilerCapabilitySnapshot {
|
||||
capability: ExtensionCapabilityRef::new(OPS_PROFILER_CAPABILITY),
|
||||
runtime: OpsProfilerRuntimeSnapshot {
|
||||
boundary: ExtensionRuntimeBoundary::Builtin,
|
||||
disabled_by_default: false,
|
||||
startup_fatal: false,
|
||||
},
|
||||
contract: OpsProfilerContract {
|
||||
mode: OpsProfilerContractMode::CapabilityDescription,
|
||||
backends: vec![profiler_backend("cpu_pprof", OpsProfilerBackendStatus::Enabled, true)],
|
||||
},
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(snapshot).expect("ops profiler snapshot should serialize");
|
||||
|
||||
assert_eq!(
|
||||
value,
|
||||
json!({
|
||||
"capability": "ops.profiler.v1",
|
||||
"runtime": {
|
||||
"boundary": "builtin",
|
||||
"disabled_by_default": false,
|
||||
"startup_fatal": false
|
||||
},
|
||||
"contract": {
|
||||
"mode": "capability_description",
|
||||
"backends": [{
|
||||
"backend": "cpu_pprof",
|
||||
"status": "enabled",
|
||||
"supports_profile_export": true,
|
||||
"redaction_required": ["secret", "token", "local_path", "host"],
|
||||
"provenance": {
|
||||
"source": "rustfs.profiling",
|
||||
"collection_boundary": "rustfs-process",
|
||||
"trust_level": "runtime_trusted"
|
||||
}
|
||||
}]
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_ops_profiler_snapshot_wrong_capability_or_fatal_runtime() {
|
||||
let mut snapshot = OpsProfilerCapabilitySnapshot {
|
||||
capability: ExtensionCapabilityRef::new("ops.not-profiler.v1"),
|
||||
runtime: OpsProfilerRuntimeSnapshot {
|
||||
boundary: ExtensionRuntimeBoundary::Sidecar,
|
||||
disabled_by_default: true,
|
||||
startup_fatal: false,
|
||||
},
|
||||
contract: OpsProfilerContract {
|
||||
mode: OpsProfilerContractMode::CapabilityDescription,
|
||||
backends: vec![profiler_backend("cpu_pprof", OpsProfilerBackendStatus::Enabled, true)],
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
validate_ops_profiler_capability_snapshot(&snapshot).expect_err("only ops.profiler.v1 snapshots are accepted"),
|
||||
ExtensionContractError::UnsupportedOpsProfilerCapability {
|
||||
capability: "ops.not-profiler.v1".to_string()
|
||||
}
|
||||
);
|
||||
|
||||
snapshot.capability = ExtensionCapabilityRef::new(OPS_PROFILER_CAPABILITY);
|
||||
snapshot.runtime.disabled_by_default = false;
|
||||
|
||||
assert_eq!(
|
||||
validate_ops_profiler_capability_snapshot(&snapshot)
|
||||
.expect_err("external profiler runtimes must stay disabled by default"),
|
||||
ExtensionContractError::OpsProfilerExternalRuntimeEnabledByDefault
|
||||
);
|
||||
|
||||
snapshot.runtime.disabled_by_default = true;
|
||||
snapshot.runtime.startup_fatal = true;
|
||||
|
||||
assert_eq!(
|
||||
validate_ops_profiler_capability_snapshot(&snapshot)
|
||||
.expect_err("optional profiler runtimes must not become startup fatal"),
|
||||
ExtensionContractError::OpsProfilerStartupFatalBoundary
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_ops_profiler_execution_requests_and_empty_backends() {
|
||||
let mut contract = OpsProfilerContract {
|
||||
|
||||
@@ -5,15 +5,15 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
||||
## Current Context
|
||||
|
||||
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
|
||||
- Branch: `overtrue/arch-ops-profiler-schema`
|
||||
- Branch: `overtrue/arch-ops-profiler-capability-tests`
|
||||
- Baseline: latest `origin/main`
|
||||
(`71809ba02ea405c3a2f0e4cb82ff608915dd519c`).
|
||||
- PR type for this branch: `contract`
|
||||
- Runtime behavior changes: none.
|
||||
- Rust code changes: define the `ops.profiler.v1` extension schema contract for
|
||||
profiler capability reporting, backend state, redaction, and provenance.
|
||||
- Rust code changes: add the `ops.profiler.v1` extension capability snapshot
|
||||
contract for disabled, unsupported, and enabled profiler runtime states.
|
||||
- CI/script changes: none.
|
||||
- Docs changes: record the X-012 profiler schema contract slice.
|
||||
- Docs changes: record the X-013 profiler capability snapshot slice.
|
||||
|
||||
## Phase 0 Tasks
|
||||
|
||||
@@ -2000,20 +2000,36 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
||||
guards, diff hygiene, Rust risk scan, branch freshness check, and
|
||||
pre-commit quality gate.
|
||||
|
||||
- [x] `X-013` Add ops profiler capability snapshot contract.
|
||||
- Do: add `OpsProfilerCapabilitySnapshot` and `OpsProfilerRuntimeSnapshot`
|
||||
DTOs plus validation for the `ops.profiler.v1` capability, disabled
|
||||
external runtimes, and non-fatal profiler startup behavior.
|
||||
- Acceptance: disabled, unsupported, and enabled profiler backend states
|
||||
round-trip through the snapshot contract; sidecar/Wasm profiler runtimes
|
||||
remain disabled by default; profiler snapshots cannot declare a startup
|
||||
fatal boundary.
|
||||
- Must preserve: no plugin execution, no sidecar startup, no profile route,
|
||||
no admin API behavior changes, no runtime startup/shutdown behavior
|
||||
changes, and no dependency edge from `extension-schema` to runtime or
|
||||
storage implementation crates.
|
||||
- Verification: extension schema check/tests, formatting, migration/layer
|
||||
guards, diff hygiene, Rust risk scan, branch freshness check, and
|
||||
pre-commit quality gate.
|
||||
|
||||
## Next PRs
|
||||
|
||||
1. `contract`: continue larger extension contract coverage for disabled and
|
||||
unsupported runtime capability snapshots.
|
||||
2. `pure-move`: continue larger lifecycle hook slices for optional runtime
|
||||
1. `pure-move`: continue larger lifecycle hook slices for optional runtime
|
||||
sidecars while preserving startup and shutdown ordering.
|
||||
2. `contract`: continue extension contract coverage for future diagnostics and
|
||||
profiler handoff surfaces after runtime owners are stable.
|
||||
|
||||
## Pre-Push Review Log
|
||||
|
||||
| Expert | Status | Notes |
|
||||
|---|---|---|
|
||||
| Quality/architecture | passed | X-012 keeps the profiler schema in `rustfs-extension-schema`, uses explicit backend/status/redaction/provenance DTOs, and adds no new dependency edges or runtime abstractions. |
|
||||
| Migration preservation | passed | Runtime profiling, admin pprof routes, exporters, storage paths, telemetry, sidecar startup, and plugin execution are untouched; the new contract only describes capabilities. |
|
||||
| Testing/verification | passed | Extension schema tests cover enabled, disabled, unsupported, unknown, missing-field, redaction, provenance, and credential-field rejection; focused checks, guards, formatting, diff hygiene, and final pre-commit passed. |
|
||||
| Quality/architecture | passed | X-013 keeps the capability snapshot contract in `rustfs-extension-schema`, reuses explicit profiler backend/runtime DTOs, and adds no runtime dependency edges. |
|
||||
| Migration preservation | passed | Runtime profiling, admin pprof routes, exporters, storage paths, telemetry, sidecar startup, and plugin execution are untouched; validation only rejects unsafe snapshot declarations. |
|
||||
| Testing/verification | passed | Extension schema tests cover enabled, disabled, unsupported, snapshot JSON shape, disabled external runtime policy, and startup-fatal rejection; focused checks, guards, formatting, diff hygiene, and final pre-commit passed. |
|
||||
|
||||
## Verification Notes
|
||||
|
||||
@@ -2030,6 +2046,17 @@ Passed before push:
|
||||
- `make pre-commit`: passed.
|
||||
- Three-expert review: passed.
|
||||
|
||||
- Issue #660 X-013 current slice:
|
||||
- `cargo test -p rustfs-extension-schema`: passed.
|
||||
- `cargo check -p rustfs-extension-schema`: passed.
|
||||
- `./scripts/check_architecture_migration_rules.sh`: passed.
|
||||
- `./scripts/check_layer_dependencies.sh`: passed.
|
||||
- `cargo fmt --all --check`: passed.
|
||||
- `git diff --check`: passed.
|
||||
- Rust risk scan on changed Rust files: passed.
|
||||
- `make pre-commit`: passed.
|
||||
- Three-expert review: passed.
|
||||
|
||||
- Issue #660 R-020 current slice:
|
||||
- `cargo test -p rustfs --lib startup_profiling -- --nocapture`: passed.
|
||||
- `cargo check -p rustfs --lib`: passed.
|
||||
|
||||
@@ -75,9 +75,10 @@ Future sidecars for profiling, eBPF, or NUMA must preserve these invariants:
|
||||
|
||||
`X-013`:
|
||||
|
||||
- Add extension tests for disabled, unsupported, and enabled capability
|
||||
snapshots.
|
||||
- Verify no startup fatal boundary is added for optional profiling sidecars.
|
||||
- Add the extension capability snapshot contract for disabled, unsupported, and
|
||||
enabled profiler backends.
|
||||
- Verify optional profiler sidecar and Wasm runtimes stay disabled by default
|
||||
and cannot declare a startup fatal boundary.
|
||||
|
||||
`R-017`:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user