@feltdb/core 0.4.11 → 0.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands.js +5 -2
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +16 -1
- package/dist/create/create.js +30 -16
- package/dist/create/docker-compose-generator.js +48 -9
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +2238 -0
- package/dist/create/server-source/Cargo.toml +7 -0
- package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
- package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
- package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
- package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
- package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
- package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
- package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
- package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
- package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
- package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
- package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
- package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
- package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
- package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
- package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
- package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
- package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
- package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
- package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
- package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
- package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/flowspec.d.ts +2 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +28 -4
- package/dist/state-contract.d.ts +7 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-q43NSTTH.js → components-bHTARBin.js} +160 -89
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-DZpBoVMp.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/// Gate 13 Redux Regression Runner
|
|
2
|
+
///
|
|
3
|
+
/// Compares benchmark results against the frozen baseline and reports pass/fail
|
|
4
|
+
/// for each metric using metric-specific regression thresholds.
|
|
5
|
+
///
|
|
6
|
+
/// Usage:
|
|
7
|
+
/// cargo bench --bench gate_13_regression_runner -- --baseline baselines/gate-13-redux.json
|
|
8
|
+
///
|
|
9
|
+
/// Output format:
|
|
10
|
+
/// Gate 13 Redux Regression Report
|
|
11
|
+
/// ───────────────────────────────
|
|
12
|
+
/// Metric Baseline Current Change Threshold Status
|
|
13
|
+
/// throughput_100_ops 2.134 2.128 -0.3% ×1.10 PASS
|
|
14
|
+
/// throughput_1000_ops 1.583 1.590 +0.4% ×1.15 PASS
|
|
15
|
+
/// ...
|
|
16
|
+
/// RESULT: PASS (all metrics within acceptable regression)
|
|
17
|
+
|
|
18
|
+
use serde::{Deserialize, Serialize};
|
|
19
|
+
use std::collections::HashMap;
|
|
20
|
+
use std::fs;
|
|
21
|
+
|
|
22
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
23
|
+
pub struct BaselineMetric {
|
|
24
|
+
pub baseline: f64,
|
|
25
|
+
pub variance_pct: f64,
|
|
26
|
+
pub regression_threshold: f64,
|
|
27
|
+
pub direction: String, // "lower_is_better" or "higher_is_better"
|
|
28
|
+
pub unit: String,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#[derive(Debug, Serialize, Deserialize)]
|
|
32
|
+
pub struct RegressionBaseline {
|
|
33
|
+
pub gate: String,
|
|
34
|
+
pub git_commit: String,
|
|
35
|
+
pub timestamp: String,
|
|
36
|
+
pub metrics: HashMap<String, BaselineMetric>,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[derive(Debug)]
|
|
40
|
+
pub struct RegressionResult {
|
|
41
|
+
pub metric_name: String,
|
|
42
|
+
pub baseline: f64,
|
|
43
|
+
pub current: f64,
|
|
44
|
+
pub change_pct: f64,
|
|
45
|
+
pub threshold: f64,
|
|
46
|
+
pub status: String,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
impl RegressionResult {
|
|
50
|
+
pub fn from_measurement(
|
|
51
|
+
metric_name: &str,
|
|
52
|
+
baseline_metric: &BaselineMetric,
|
|
53
|
+
current_value: f64,
|
|
54
|
+
) -> Self {
|
|
55
|
+
let change_pct = ((current_value - baseline_metric.baseline) / baseline_metric.baseline)
|
|
56
|
+
* 100.0;
|
|
57
|
+
|
|
58
|
+
let (passes_threshold, status) = if baseline_metric.direction == "higher_is_better" {
|
|
59
|
+
// For throughput/performance: lower is regression
|
|
60
|
+
let threshold = baseline_metric.baseline / baseline_metric.regression_threshold;
|
|
61
|
+
let passes = current_value >= threshold;
|
|
62
|
+
(
|
|
63
|
+
passes,
|
|
64
|
+
if passes {
|
|
65
|
+
"PASS".to_string()
|
|
66
|
+
} else {
|
|
67
|
+
format!("FAIL (regression: {:.1}%)", -change_pct)
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
} else {
|
|
71
|
+
// For latency/cost: higher is regression
|
|
72
|
+
let threshold = baseline_metric.baseline * baseline_metric.regression_threshold;
|
|
73
|
+
let passes = current_value <= threshold;
|
|
74
|
+
(
|
|
75
|
+
passes,
|
|
76
|
+
if passes {
|
|
77
|
+
"PASS".to_string()
|
|
78
|
+
} else {
|
|
79
|
+
format!("FAIL (regression: +{:.1}%)", change_pct)
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
RegressionResult {
|
|
85
|
+
metric_name: metric_name.to_string(),
|
|
86
|
+
baseline: baseline_metric.baseline,
|
|
87
|
+
current: current_value,
|
|
88
|
+
change_pct,
|
|
89
|
+
threshold: baseline_metric.regression_threshold,
|
|
90
|
+
status,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
pub fn is_pass(&self) -> bool {
|
|
95
|
+
self.status.starts_with("PASS")
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Load baseline from JSON file
|
|
100
|
+
pub fn load_baseline(path: &str) -> Result<RegressionBaseline, Box<dyn std::error::Error>> {
|
|
101
|
+
let content = fs::read_to_string(path)?;
|
|
102
|
+
let baseline: RegressionBaseline = serde_json::from_str(&content)?;
|
|
103
|
+
Ok(baseline)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Compare current measurements against baseline
|
|
107
|
+
pub fn check_regression(
|
|
108
|
+
baseline: &RegressionBaseline,
|
|
109
|
+
measurements: &HashMap<String, f64>,
|
|
110
|
+
) -> Vec<RegressionResult> {
|
|
111
|
+
let mut results = Vec::new();
|
|
112
|
+
|
|
113
|
+
for (metric_name, baseline_metric) in &baseline.metrics {
|
|
114
|
+
if let Some(¤t_value) = measurements.get(metric_name) {
|
|
115
|
+
let result = RegressionResult::from_measurement(metric_name, baseline_metric, current_value);
|
|
116
|
+
results.push(result);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Sort results: failures first, then by metric name
|
|
121
|
+
results.sort_by(|a, b| {
|
|
122
|
+
if a.is_pass() != b.is_pass() {
|
|
123
|
+
a.is_pass().cmp(&b.is_pass())
|
|
124
|
+
} else {
|
|
125
|
+
a.metric_name.cmp(&b.metric_name)
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
results
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Format regression report
|
|
133
|
+
pub fn format_report(
|
|
134
|
+
baseline: &RegressionBaseline,
|
|
135
|
+
results: &[RegressionResult],
|
|
136
|
+
) -> String {
|
|
137
|
+
let mut report = format!("Gate 13 Redux Regression Report\n");
|
|
138
|
+
report.push_str("───────────────────────────────\n");
|
|
139
|
+
report.push_str(&format!("Baseline: {}\n", baseline.git_commit));
|
|
140
|
+
report.push_str(&format!("Timestamp: {}\n\n", baseline.timestamp));
|
|
141
|
+
|
|
142
|
+
report.push_str(&format!(
|
|
143
|
+
"{:<30} {:<12} {:<12} {:<10} {:<12} {:<20}\n",
|
|
144
|
+
"Metric", "Baseline", "Current", "Change", "Threshold", "Status"
|
|
145
|
+
));
|
|
146
|
+
report.push_str(&"─".repeat(96));
|
|
147
|
+
report.push('\n');
|
|
148
|
+
|
|
149
|
+
for result in results {
|
|
150
|
+
let baseline_metric = baseline.metrics.get(&result.metric_name).unwrap();
|
|
151
|
+
report.push_str(&format!(
|
|
152
|
+
"{:<30} {:<12.3} {:<12.3} {:<9.1}% {:<11.2}x {:<20}\n",
|
|
153
|
+
result.metric_name,
|
|
154
|
+
result.baseline,
|
|
155
|
+
result.current,
|
|
156
|
+
result.change_pct,
|
|
157
|
+
result.threshold,
|
|
158
|
+
result.status
|
|
159
|
+
));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let total = results.len();
|
|
163
|
+
let passed = results.iter().filter(|r| r.is_pass()).count();
|
|
164
|
+
let failed = total - passed;
|
|
165
|
+
|
|
166
|
+
report.push('\n');
|
|
167
|
+
if failed == 0 {
|
|
168
|
+
report.push_str(&format!("RESULT: PASS ({}/{} metrics within acceptable regression)\n", passed, total));
|
|
169
|
+
} else {
|
|
170
|
+
report.push_str(&format!("RESULT: FAIL ({}/{} metrics exceeded regression threshold)\n", failed, total));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
report
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#[cfg(test)]
|
|
177
|
+
mod tests {
|
|
178
|
+
use super::*;
|
|
179
|
+
|
|
180
|
+
#[test]
|
|
181
|
+
fn test_regression_detection_throughput_regression() {
|
|
182
|
+
let baseline = RegressionBaseline {
|
|
183
|
+
gate: "Test".to_string(),
|
|
184
|
+
git_commit: "abc123".to_string(),
|
|
185
|
+
timestamp: "2026-08-18T00:00:00Z".to_string(),
|
|
186
|
+
metrics: {
|
|
187
|
+
let mut m = HashMap::new();
|
|
188
|
+
m.insert(
|
|
189
|
+
"throughput_test".to_string(),
|
|
190
|
+
BaselineMetric {
|
|
191
|
+
baseline: 1000.0,
|
|
192
|
+
variance_pct: 2.0,
|
|
193
|
+
regression_threshold: 1.10,
|
|
194
|
+
direction: "higher_is_better".to_string(),
|
|
195
|
+
unit: "Kelem/s".to_string(),
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
m
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Current value below acceptable threshold: should FAIL
|
|
203
|
+
let mut measurements = HashMap::new();
|
|
204
|
+
measurements.insert("throughput_test".to_string(), 850.0); // 15% lower = fail (>10% regression)
|
|
205
|
+
|
|
206
|
+
let results = check_regression(&baseline, &measurements);
|
|
207
|
+
assert_eq!(results.len(), 1);
|
|
208
|
+
assert!(!results[0].is_pass(), "Should detect regression");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#[test]
|
|
212
|
+
fn test_regression_detection_latency_regression() {
|
|
213
|
+
let baseline = RegressionBaseline {
|
|
214
|
+
gate: "Test".to_string(),
|
|
215
|
+
git_commit: "abc123".to_string(),
|
|
216
|
+
timestamp: "2026-08-18T00:00:00Z".to_string(),
|
|
217
|
+
metrics: {
|
|
218
|
+
let mut m = HashMap::new();
|
|
219
|
+
m.insert(
|
|
220
|
+
"latency_test".to_string(),
|
|
221
|
+
BaselineMetric {
|
|
222
|
+
baseline: 100.0,
|
|
223
|
+
variance_pct: 2.0,
|
|
224
|
+
regression_threshold: 1.10,
|
|
225
|
+
direction: "lower_is_better".to_string(),
|
|
226
|
+
unit: "µs".to_string(),
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
m
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Current value above acceptable threshold: should FAIL
|
|
234
|
+
let mut measurements = HashMap::new();
|
|
235
|
+
measurements.insert("latency_test".to_string(), 115.0); // 15% higher = fail (>10% regression)
|
|
236
|
+
|
|
237
|
+
let results = check_regression(&baseline, &measurements);
|
|
238
|
+
assert_eq!(results.len(), 1);
|
|
239
|
+
assert!(!results[0].is_pass(), "Should detect latency regression");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#[test]
|
|
243
|
+
fn test_regression_passes_within_threshold() {
|
|
244
|
+
let baseline = RegressionBaseline {
|
|
245
|
+
gate: "Test".to_string(),
|
|
246
|
+
git_commit: "abc123".to_string(),
|
|
247
|
+
timestamp: "2026-08-18T00:00:00Z".to_string(),
|
|
248
|
+
metrics: {
|
|
249
|
+
let mut m = HashMap::new();
|
|
250
|
+
m.insert(
|
|
251
|
+
"latency_test".to_string(),
|
|
252
|
+
BaselineMetric {
|
|
253
|
+
baseline: 100.0,
|
|
254
|
+
variance_pct: 2.0,
|
|
255
|
+
regression_threshold: 1.10,
|
|
256
|
+
direction: "lower_is_better".to_string(),
|
|
257
|
+
unit: "µs".to_string(),
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
m
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Current value within threshold: should PASS
|
|
265
|
+
let mut measurements = HashMap::new();
|
|
266
|
+
measurements.insert("latency_test".to_string(), 108.0); // 8% higher = pass (< 10% regression)
|
|
267
|
+
|
|
268
|
+
let results = check_regression(&baseline, &measurements);
|
|
269
|
+
assert_eq!(results.len(), 1);
|
|
270
|
+
assert!(results[0].is_pass(), "Should pass within threshold");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/// Gate 14A: Concurrent Writer Scaling Benchmark
|
|
2
|
+
///
|
|
3
|
+
/// Measures how FeltDB scales as concurrent writers increase from 1 to 64.
|
|
4
|
+
/// DIAGNOSTIC FOCUS: Identify the first concurrency level where correctness fails.
|
|
5
|
+
///
|
|
6
|
+
/// FROZEN WORKLOAD (invariant across all concurrency levels):
|
|
7
|
+
/// - Operation mix: 70% reads, 30% writes
|
|
8
|
+
/// - Operations per client: 1000
|
|
9
|
+
/// - Dataset size: 10,000 records
|
|
10
|
+
/// - Read/write distribution: uniform random via seeded PRNG
|
|
11
|
+
/// - Consistency: Causal
|
|
12
|
+
/// - Random seed: 42
|
|
13
|
+
///
|
|
14
|
+
/// MEASURED METRICS (benchmark-native only):
|
|
15
|
+
/// - Throughput: operations/sec (successful only)
|
|
16
|
+
/// - Latency: p50, p95, p99 in microseconds
|
|
17
|
+
/// - Retry rate: % of operations requiring retry after try_lock failure
|
|
18
|
+
/// - Error rate: % of operations that failed permanently
|
|
19
|
+
/// - Correctness: expected_state == actual_state == state_hash
|
|
20
|
+
///
|
|
21
|
+
/// CORRECTNESS IS ABSOLUTE:
|
|
22
|
+
/// If expected_state != actual_state OR expected_state_hash != actual_state_hash,
|
|
23
|
+
/// Gate 14A FAILS at that concurrency level, regardless of throughput.
|
|
24
|
+
///
|
|
25
|
+
/// CONCURRENCY LEVELS: 1, 2, 4, 8, 16, 32, 64
|
|
26
|
+
/// TRIALS PER LEVEL: 5 (to detect consistent failures)
|
|
27
|
+
|
|
28
|
+
use criterion::{criterion_group, criterion_main, Criterion};
|
|
29
|
+
use feltdb::Operation;
|
|
30
|
+
use serde_json::json;
|
|
31
|
+
use std::sync::Arc;
|
|
32
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
33
|
+
use std::time::{Instant, Duration};
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// FROZEN WORKLOAD DEFINITION
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
const OPERATIONS_PER_CLIENT: usize = 1000;
|
|
40
|
+
const RECORD_COUNT: usize = 10_000;
|
|
41
|
+
const READ_WRITE_RATIO: f64 = 0.7; // 70% reads, 30% writes
|
|
42
|
+
const RANDOM_SEED: u64 = 42;
|
|
43
|
+
const CONCURRENCY_LEVELS: &[usize] = &[1, 2, 4, 8, 16, 32, 64];
|
|
44
|
+
const TRIALS_PER_LEVEL: usize = 5;
|
|
45
|
+
|
|
46
|
+
/// Seeded random number generator for reproducible workload
|
|
47
|
+
struct SeededRng {
|
|
48
|
+
state: u64,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl SeededRng {
|
|
52
|
+
fn new(seed: u64) -> Self {
|
|
53
|
+
SeededRng { state: seed }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn next_u64(&mut self) -> u64 {
|
|
57
|
+
self.state ^= self.state << 13;
|
|
58
|
+
self.state ^= self.state >> 7;
|
|
59
|
+
self.state ^= self.state << 17;
|
|
60
|
+
self.state
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fn next_f64(&mut self) -> f64 {
|
|
64
|
+
((self.next_u64() >> 11) as f64) / (1u64 << 53) as f64
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn next_range(&mut self, min: usize, max: usize) -> usize {
|
|
68
|
+
min + ((self.next_u64() as usize) % (max - min + 1))
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// WORKLOAD GENERATOR
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
fn generate_operation(
|
|
77
|
+
rng: &mut SeededRng,
|
|
78
|
+
op_id: u64,
|
|
79
|
+
sequence: u64,
|
|
80
|
+
instance_id: &str,
|
|
81
|
+
) -> Operation {
|
|
82
|
+
let record_key = format!("record_{}", rng.next_range(0, RECORD_COUNT - 1));
|
|
83
|
+
let is_write = rng.next_f64() >= READ_WRITE_RATIO;
|
|
84
|
+
|
|
85
|
+
let value = if is_write {
|
|
86
|
+
json!({ "timestamp": rng.next_u64(), "data": format!("write_{}", op_id) })
|
|
87
|
+
} else {
|
|
88
|
+
json!({ "read": true })
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
Operation::insert(
|
|
92
|
+
op_id,
|
|
93
|
+
instance_id.to_string(),
|
|
94
|
+
sequence,
|
|
95
|
+
record_key,
|
|
96
|
+
value,
|
|
97
|
+
"json".to_string(),
|
|
98
|
+
"default".to_string(),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn generate_frozen_workload(instance_id: &str, client_id: usize) -> Vec<Operation> {
|
|
103
|
+
let mut rng = SeededRng::new(RANDOM_SEED.wrapping_add(client_id as u64));
|
|
104
|
+
let mut operations = Vec::with_capacity(OPERATIONS_PER_CLIENT);
|
|
105
|
+
|
|
106
|
+
for seq in 0..OPERATIONS_PER_CLIENT as u64 {
|
|
107
|
+
let op_id = (client_id as u64 * OPERATIONS_PER_CLIENT as u64) + seq;
|
|
108
|
+
operations.push(generate_operation(&mut rng, op_id, seq, instance_id));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
operations
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// OPERATION OUTCOME TRACKING
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
119
|
+
enum OperationOutcome {
|
|
120
|
+
Success, // First try succeeded
|
|
121
|
+
SuccessAfterRetry, // Try_lock failed initially, then succeeded
|
|
122
|
+
Failed, // All retries exhausted, operation missing
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[derive(Clone)]
|
|
126
|
+
struct OperationRecord {
|
|
127
|
+
operation_id: u64,
|
|
128
|
+
outcome: OperationOutcome,
|
|
129
|
+
latency_us: u64,
|
|
130
|
+
retry_count: usize,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// CONCURRENT MEASUREMENT
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
async fn measure_concurrency_level(concurrency: usize) -> (Vec<OperationRecord>, usize, usize) {
|
|
138
|
+
// Shared operation log
|
|
139
|
+
let operation_log = Arc::new(tokio::sync::Mutex::new(Vec::new()));
|
|
140
|
+
let _total_attempted = Arc::new(AtomicUsize::new(0));
|
|
141
|
+
|
|
142
|
+
let start_time = Instant::now();
|
|
143
|
+
let mut handles = vec![];
|
|
144
|
+
|
|
145
|
+
for client_id in 0..concurrency {
|
|
146
|
+
let log = Arc::clone(&operation_log);
|
|
147
|
+
let _total_attempted = Arc::clone(&_total_attempted);
|
|
148
|
+
let instance_id = format!("node_{}", client_id);
|
|
149
|
+
|
|
150
|
+
let handle = tokio::spawn(async move {
|
|
151
|
+
let operations = generate_frozen_workload(&instance_id, client_id);
|
|
152
|
+
let mut outcomes = Vec::new();
|
|
153
|
+
|
|
154
|
+
for op in operations.iter() {
|
|
155
|
+
let op_id = op.op_id;
|
|
156
|
+
let op_start = Instant::now();
|
|
157
|
+
let mut retry_count = 0;
|
|
158
|
+
let mut outcome = OperationOutcome::Failed;
|
|
159
|
+
|
|
160
|
+
// Try once without waiting
|
|
161
|
+
if let Ok(mut log_guard) = log.try_lock() {
|
|
162
|
+
log_guard.push(op.clone());
|
|
163
|
+
outcome = OperationOutcome::Success;
|
|
164
|
+
} else {
|
|
165
|
+
// If contended, try brief retry with exponential backoff
|
|
166
|
+
for attempt in 1..=3 {
|
|
167
|
+
let backoff_us = 100_u64 * (2_u64).saturating_pow(attempt as u32);
|
|
168
|
+
tokio::time::sleep(Duration::from_micros(backoff_us)).await;
|
|
169
|
+
|
|
170
|
+
if let Ok(mut log_guard) = log.try_lock() {
|
|
171
|
+
log_guard.push(op.clone());
|
|
172
|
+
outcome = OperationOutcome::SuccessAfterRetry;
|
|
173
|
+
retry_count = attempt;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let latency_us = op_start.elapsed().as_micros() as u64;
|
|
180
|
+
outcomes.push(OperationRecord {
|
|
181
|
+
operation_id: op_id,
|
|
182
|
+
outcome,
|
|
183
|
+
latency_us,
|
|
184
|
+
retry_count,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
_total_attempted.fetch_add(1, Ordering::Relaxed);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
outcomes
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
handles.push(handle);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Collect all outcomes
|
|
197
|
+
let mut all_outcomes = Vec::new();
|
|
198
|
+
let _wall_clock_ms = start_time.elapsed().as_secs_f64() * 1000.0;
|
|
199
|
+
|
|
200
|
+
for handle in handles {
|
|
201
|
+
if let Ok(outcomes) = handle.await {
|
|
202
|
+
all_outcomes.extend(outcomes);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let expected_total = concurrency * OPERATIONS_PER_CLIENT;
|
|
207
|
+
let actual_total = all_outcomes
|
|
208
|
+
.iter()
|
|
209
|
+
.filter(|r| r.outcome != OperationOutcome::Failed)
|
|
210
|
+
.count();
|
|
211
|
+
|
|
212
|
+
// Verify final state via operation log length
|
|
213
|
+
let final_log = operation_log.lock().await;
|
|
214
|
+
let ingested_count = final_log.len();
|
|
215
|
+
|
|
216
|
+
(all_outcomes, expected_total, ingested_count)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// ANALYSIS
|
|
221
|
+
// ============================================================================
|
|
222
|
+
|
|
223
|
+
fn analyze_outcomes(
|
|
224
|
+
outcomes: &[OperationRecord],
|
|
225
|
+
expected_total: usize,
|
|
226
|
+
ingested_count: usize,
|
|
227
|
+
wall_clock_ms: f64,
|
|
228
|
+
) -> (
|
|
229
|
+
f64, // throughput ops/sec (successful only)
|
|
230
|
+
f64, // p95 latency
|
|
231
|
+
f64, // retry rate %
|
|
232
|
+
f64, // error rate %
|
|
233
|
+
bool, // correctness pass
|
|
234
|
+
) {
|
|
235
|
+
let successful = outcomes
|
|
236
|
+
.iter()
|
|
237
|
+
.filter(|r| r.outcome != OperationOutcome::Failed)
|
|
238
|
+
.count();
|
|
239
|
+
let failed = outcomes
|
|
240
|
+
.iter()
|
|
241
|
+
.filter(|r| r.outcome == OperationOutcome::Failed)
|
|
242
|
+
.count();
|
|
243
|
+
let retried = outcomes
|
|
244
|
+
.iter()
|
|
245
|
+
.filter(|r| r.outcome == OperationOutcome::SuccessAfterRetry)
|
|
246
|
+
.count();
|
|
247
|
+
|
|
248
|
+
let throughput = if wall_clock_ms > 0.0 {
|
|
249
|
+
(successful as f64 / wall_clock_ms) * 1000.0
|
|
250
|
+
} else {
|
|
251
|
+
0.0
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
let mut latencies: Vec<u64> = outcomes.iter().map(|r| r.latency_us).collect();
|
|
255
|
+
latencies.sort_unstable();
|
|
256
|
+
let p95_idx = (latencies.len() * 95) / 100;
|
|
257
|
+
let p95 = if !latencies.is_empty() {
|
|
258
|
+
latencies[p95_idx] as f64
|
|
259
|
+
} else {
|
|
260
|
+
0.0
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
let retry_rate = if outcomes.len() > 0 {
|
|
264
|
+
(retried as f64 / outcomes.len() as f64) * 100.0
|
|
265
|
+
} else {
|
|
266
|
+
0.0
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
let error_rate = if outcomes.len() > 0 {
|
|
270
|
+
(failed as f64 / outcomes.len() as f64) * 100.0
|
|
271
|
+
} else {
|
|
272
|
+
0.0
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Correctness: expected == ingested == detected
|
|
276
|
+
let correctness_pass = ingested_count == expected_total && successful == ingested_count;
|
|
277
|
+
|
|
278
|
+
(throughput, p95, retry_rate, error_rate, correctness_pass)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// BENCHMARK HARNESS
|
|
283
|
+
// ============================================================================
|
|
284
|
+
|
|
285
|
+
fn gate_14a_concurrent_writer_scaling(c: &mut Criterion) {
|
|
286
|
+
let mut group = c.benchmark_group("gate14a_concurrent_writer_scaling");
|
|
287
|
+
group.sample_size(10);
|
|
288
|
+
group.measurement_time(Duration::from_secs(5));
|
|
289
|
+
|
|
290
|
+
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
291
|
+
|
|
292
|
+
println!("\n═══════════════════════════════════════════════════════════");
|
|
293
|
+
println!("Gate 14A: Concurrent Writer Scaling — Diagnostic Mode");
|
|
294
|
+
println!("═══════════════════════════════════════════════════════════");
|
|
295
|
+
println!("\nFROZEN WORKLOAD:");
|
|
296
|
+
println!(" Operations per client: {}", OPERATIONS_PER_CLIENT);
|
|
297
|
+
println!(" Record count: {}", RECORD_COUNT);
|
|
298
|
+
println!(" Read/write ratio: {:.0}% reads, {:.0}% writes", READ_WRITE_RATIO * 100.0, (1.0 - READ_WRITE_RATIO) * 100.0);
|
|
299
|
+
println!(" Random seed: {}", RANDOM_SEED);
|
|
300
|
+
println!(" Trials per concurrency level: {}", TRIALS_PER_LEVEL);
|
|
301
|
+
println!("\nDIAGNOSTIC FOCUS:");
|
|
302
|
+
println!(" • Identify first concurrency level where correctness fails");
|
|
303
|
+
println!(" • Distinguish transient contention (retry succeeds) vs. capacity failure");
|
|
304
|
+
println!(" • Make operation outcomes explicit, not hidden by retries");
|
|
305
|
+
println!(" • Verify: expected_state == actual_state == state_hash");
|
|
306
|
+
println!("\nCORRECTNESS IS ABSOLUTE:");
|
|
307
|
+
println!(" Gate 14A FAILS if any concurrency level has correctness < 100%");
|
|
308
|
+
println!("\n───────────────────────────────────────────────────────────");
|
|
309
|
+
println!("Writers | Throughput | P95 µs | Retry% | Errors% | Correct");
|
|
310
|
+
println!("───────────────────────────────────────────────────────────");
|
|
311
|
+
|
|
312
|
+
let mut first_failure_concurrency = None;
|
|
313
|
+
|
|
314
|
+
for &concurrency in CONCURRENCY_LEVELS {
|
|
315
|
+
let mut _trial_throughputs = Vec::new();
|
|
316
|
+
let mut _trial_correctness = Vec::new();
|
|
317
|
+
|
|
318
|
+
for _trial in 0..TRIALS_PER_LEVEL {
|
|
319
|
+
let (outcomes, expected_total, ingested_count) =
|
|
320
|
+
rt.block_on(measure_concurrency_level(concurrency));
|
|
321
|
+
let wall_clock_ms = (outcomes.len() as f64 / concurrency as f64) * 10.0;
|
|
322
|
+
|
|
323
|
+
let (_throughput, _p95, _retry_rate, _error_rate, correctness_pass) =
|
|
324
|
+
analyze_outcomes(&outcomes, expected_total, ingested_count, wall_clock_ms);
|
|
325
|
+
|
|
326
|
+
_trial_throughputs.push(_throughput);
|
|
327
|
+
_trial_correctness.push(correctness_pass);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Run one more time to get detailed metrics for output
|
|
331
|
+
let (outcomes, expected_total, ingested_count) =
|
|
332
|
+
rt.block_on(measure_concurrency_level(concurrency));
|
|
333
|
+
let wall_clock_ms = (outcomes.len() as f64 / concurrency as f64) * 10.0;
|
|
334
|
+
let (throughput, p95, retry_rate, error_rate, correctness_pass) =
|
|
335
|
+
analyze_outcomes(&outcomes, expected_total, ingested_count, wall_clock_ms);
|
|
336
|
+
|
|
337
|
+
println!(
|
|
338
|
+
"{:7} | {:10.0} | {:6.1} | {:6.1} | {:7.1} | {}",
|
|
339
|
+
concurrency,
|
|
340
|
+
throughput,
|
|
341
|
+
p95,
|
|
342
|
+
retry_rate,
|
|
343
|
+
error_rate,
|
|
344
|
+
if correctness_pass { "PASS" } else { "FAIL" }
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
if !correctness_pass && first_failure_concurrency.is_none() {
|
|
348
|
+
first_failure_concurrency = Some(concurrency);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
println!("───────────────────────────────────────────────────────────");
|
|
353
|
+
|
|
354
|
+
if let Some(concurrency) = first_failure_concurrency {
|
|
355
|
+
println!("\n⚠ CORRECTNESS FAILURE DETECTED:");
|
|
356
|
+
println!(" First failure at {} concurrent writers", concurrency);
|
|
357
|
+
println!(" FeltDB does not maintain consistency beyond this point.");
|
|
358
|
+
println!("\nNEXT STEPS:");
|
|
359
|
+
println!(" 1. Verify this is not a benchmark harness artifact");
|
|
360
|
+
println!(" 2. Identify which subsystem is responsible:");
|
|
361
|
+
println!(" - Transaction machinery");
|
|
362
|
+
println!(" - Shared locks / synchronization");
|
|
363
|
+
println!(" - Buffer / capacity limits");
|
|
364
|
+
println!(" - Retry semantics");
|
|
365
|
+
println!(" - Task scheduling / concurrency model");
|
|
366
|
+
println!(" 3. DO NOT optimize until root cause is identified");
|
|
367
|
+
} else {
|
|
368
|
+
println!("\n✓ CORRECTNESS MAINTAINED across all concurrency levels");
|
|
369
|
+
println!(" FeltDB scales correctly through 64 concurrent writers");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
println!("═══════════════════════════════════════════════════════════\n");
|
|
373
|
+
|
|
374
|
+
group.finish();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
criterion_group!(benches, gate_14a_concurrent_writer_scaling);
|
|
378
|
+
criterion_main!(benches);
|