tensorfs 0.3.60__tar.gz → 0.3.61__tar.gz
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.
- {tensorfs-0.3.60 → tensorfs-0.3.61}/Cargo.lock +2 -2
- {tensorfs-0.3.60 → tensorfs-0.3.61}/Cargo.toml +1 -1
- {tensorfs-0.3.60 → tensorfs-0.3.61}/PKG-INFO +1 -1
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/fetch.rs +32 -1
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/main.rs +3 -1
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/catalog.rs +183 -4
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/fingerprint.rs +24 -1
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/source.rs +96 -4
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/stats.rs +21 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/store.rs +72 -41
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/mod.rs +4 -1
- tensorfs-0.3.61/crates/tensorfs-core/src/transport/pace.rs +236 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/pull.rs +177 -100
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/speculate.rs +11 -3
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/tests.rs +254 -1
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-py/src/lib.rs +3 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-py/src/store.rs +19 -12
- {tensorfs-0.3.60 → tensorfs-0.3.61}/pyproject.toml +1 -1
- {tensorfs-0.3.60 → tensorfs-0.3.61}/python/tensorfs/_ext.pyi +5 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/asset_update.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/companion_tests.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/custody.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/repo_cache/identity.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/memory.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/additive_fields.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fd_headroom.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/qwen_image21_profile.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_download.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/transfer_memory.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/trust_index.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/crates/tensorfs-py/src/ingest.rs +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/python/tensorfs/_derived_channel.py +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/python/tensorfs/_source_channel.py +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/python/tensorfs/_tensors.py +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/python/tensorfs/derived.py +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.60 → tensorfs-0.3.61}/python/tensorfs/py.typed +0 -0
|
@@ -439,7 +439,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
439
439
|
|
|
440
440
|
[[package]]
|
|
441
441
|
name = "tensorfs-core"
|
|
442
|
-
version = "0.3.
|
|
442
|
+
version = "0.3.61"
|
|
443
443
|
dependencies = [
|
|
444
444
|
"fs2",
|
|
445
445
|
"libc",
|
|
@@ -454,7 +454,7 @@ dependencies = [
|
|
|
454
454
|
|
|
455
455
|
[[package]]
|
|
456
456
|
name = "tensorfs-py"
|
|
457
|
-
version = "0.3.
|
|
457
|
+
version = "0.3.61"
|
|
458
458
|
dependencies = [
|
|
459
459
|
"pyo3",
|
|
460
460
|
"tensorfs-core",
|
|
@@ -273,7 +273,8 @@ fn pull_with_budget(
|
|
|
273
273
|
let mut request = PullRequest::new(&store, base, refspec, &credential, &policy);
|
|
274
274
|
request.lane = flag(flags, "lane").unwrap_or("");
|
|
275
275
|
request.session = flag(flags, "session").unwrap_or("");
|
|
276
|
-
request.streams = crate::flag_num(flags, "streams", transport::
|
|
276
|
+
request.streams = crate::flag_num(flags, "streams", transport::PULL_STREAMS);
|
|
277
|
+
request.streams_start = crate::flag_num(flags, "streams-start", transport::STREAMS);
|
|
277
278
|
request.deadline = deadline_of(flags);
|
|
278
279
|
request.download_budget = budget;
|
|
279
280
|
// THE WALK REPORTS WHILE IT WALKS.
|
|
@@ -300,7 +301,11 @@ fn pull_with_budget(
|
|
|
300
301
|
let observe = |_object: &ObjectRef, moved: u64, door: ObjectSource| progress.saw(moved, door);
|
|
301
302
|
request.on_plan = Some(&planned);
|
|
302
303
|
request.on_object = Some(&observe);
|
|
304
|
+
let before = tensorfs_core::stats::snapshot();
|
|
305
|
+
let started = std::time::Instant::now();
|
|
303
306
|
let report = ok!(transport::pull(&request));
|
|
307
|
+
let seconds = started.elapsed().as_secs_f64();
|
|
308
|
+
let after = tensorfs_core::stats::snapshot();
|
|
304
309
|
progress.settle();
|
|
305
310
|
if !tensorfs_core::repo_cache::finish_optional_backfills() {
|
|
306
311
|
eprintln!("repo cache replication incomplete; local fetch remains complete");
|
|
@@ -313,6 +318,32 @@ fn pull_with_budget(
|
|
|
313
318
|
quoted(&report.model), quoted(&report.manifest), quoted(&report.scope),
|
|
314
319
|
report.cache_writes.bytes(), report.bytes_cached, report.bytes_total,
|
|
315
320
|
);
|
|
321
|
+
// What each stream level bought, so a slow pull says whether more streams helped
|
|
322
|
+
// (one GET was the limit) or not (the link, disk or origin was).
|
|
323
|
+
let levels: Vec<String> = report
|
|
324
|
+
.levels
|
|
325
|
+
.iter()
|
|
326
|
+
.map(|level| {
|
|
327
|
+
format!(
|
|
328
|
+
"[{},{}]",
|
|
329
|
+
level.streams,
|
|
330
|
+
level.bytes_per_second.round() as u64
|
|
331
|
+
)
|
|
332
|
+
})
|
|
333
|
+
.collect();
|
|
334
|
+
// And what admitting them cost: a pull whose streams stop growing while its batches
|
|
335
|
+
// take most of the wall time is held by the disk, not the link.
|
|
336
|
+
eprintln!(
|
|
337
|
+
"{{\"event\":\"pull.streams\",\"model\":{},\"seconds\":{:.3},\"bytes_moved\":{},\"streams\":{},\"levels\":[{}],\"admission\":{{\"objects\":{},\"batches\":{},\"seconds\":{:.3}}}}}",
|
|
338
|
+
quoted(&report.model),
|
|
339
|
+
seconds,
|
|
340
|
+
report.bytes_moved,
|
|
341
|
+
report.streams,
|
|
342
|
+
levels.join(","),
|
|
343
|
+
after.admitted_objects - before.admitted_objects,
|
|
344
|
+
after.admission_batches - before.admission_batches,
|
|
345
|
+
(after.admission_nanos - before.admission_nanos) as f64 / 1e9,
|
|
346
|
+
);
|
|
316
347
|
println!(
|
|
317
348
|
"model {}@{} lane {}",
|
|
318
349
|
report.model, report.release, report.lane
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
//! The ONE transport (tfs-048/tfs-049/tfs-050). The credential is `--credential-file` or
|
|
43
43
|
//! TFS_CREDENTIAL (`bearer <token>`, or empty for anonymous), never bare argv; a
|
|
44
44
|
//! URL of `-` arrives on stdin. Flags: --hub <base> --lane --session --streams <n>
|
|
45
|
-
//! --timeout <s> --allow-hosts a,b,.c --allow-local --max-redirects <n>.
|
|
45
|
+
//! --streams-start <n> --timeout <s> --allow-hosts a,b,.c --allow-local --max-redirects <n>.
|
|
46
|
+
//! `--streams` is the most objects in flight (default 128, memory-bounded); the walk starts
|
|
47
|
+
//! at `--streams-start` (default 16) and doubles while measured goodput follows.
|
|
46
48
|
//! `tfs fetch` reads and backfills the Store's bound repo cache between the local Store
|
|
47
49
|
//! and the origin. It is bound by `tfs store ensure --repo-cache` and never by a flag here.
|
|
48
50
|
//!
|
|
@@ -903,8 +903,9 @@ impl Catalog {
|
|
|
903
903
|
hold_on(&self.connection()?, operation, key, length)
|
|
904
904
|
}
|
|
905
905
|
|
|
906
|
-
///
|
|
907
|
-
///
|
|
906
|
+
/// A HELD admission: the hold is written before the object is published, so an
|
|
907
|
+
/// operation that cannot hold it publishes nothing. Unheld admissions, which is every
|
|
908
|
+
/// pull, take `record_grouped` instead.
|
|
908
909
|
pub(crate) fn admit_blob(
|
|
909
910
|
&self,
|
|
910
911
|
operation: Option<&str>,
|
|
@@ -914,7 +915,7 @@ impl Catalog {
|
|
|
914
915
|
// One admission at a time per catalog in this process, holding the write lock from
|
|
915
916
|
// before publication: streams landing objects together queue here instead of in
|
|
916
917
|
// SQLite's busy timeout, where a lost race refused bytes already verified.
|
|
917
|
-
let turn =
|
|
918
|
+
let turn = held_admissions(&self.path);
|
|
918
919
|
let _turn = turn.lock().unwrap_or_else(|error| error.into_inner());
|
|
919
920
|
let mut connection = self.connection()?;
|
|
920
921
|
let transaction = connection
|
|
@@ -941,6 +942,117 @@ impl Catalog {
|
|
|
941
942
|
Ok(verified.is_some())
|
|
942
943
|
}
|
|
943
944
|
|
|
945
|
+
/// The object's existing hold and verification become durable together. Filesystem
|
|
946
|
+
/// publication has already happened, on the caller's thread, and precedes this record,
|
|
947
|
+
/// which is written with FULL synchronization.
|
|
948
|
+
///
|
|
949
|
+
/// Records queue per catalog in this process, and whichever arrives while no batch is
|
|
950
|
+
/// committing leads the next one: one immediate transaction and one commit make every
|
|
951
|
+
/// waiting record durable. One object alone costs what it always did. Before this,
|
|
952
|
+
/// directory creation, link, directory fsync and commit ran for one object at a time
|
|
953
|
+
/// under one lock, several fsyncs each, and on a disk busy flushing those same objects'
|
|
954
|
+
/// bodies that queue, not the network, set a pull's rate.
|
|
955
|
+
pub(crate) fn record_grouped(&self, recording: Recording) -> Result<()> {
|
|
956
|
+
let group = admission_group(&self.path);
|
|
957
|
+
let mut state = group
|
|
958
|
+
.state
|
|
959
|
+
.lock()
|
|
960
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
961
|
+
let ticket = state.next;
|
|
962
|
+
state.next += 1;
|
|
963
|
+
state.queue.push((ticket, recording));
|
|
964
|
+
loop {
|
|
965
|
+
if let Some(result) = state.done.remove(&ticket) {
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
if state.leading {
|
|
969
|
+
state = group
|
|
970
|
+
.turned
|
|
971
|
+
.wait(state)
|
|
972
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
state.leading = true;
|
|
976
|
+
let batch = std::mem::take(&mut state.queue);
|
|
977
|
+
let mut lead = Lead {
|
|
978
|
+
group: &group,
|
|
979
|
+
tickets: batch.iter().map(|(ticket, _)| *ticket).collect(),
|
|
980
|
+
results: Vec::new(),
|
|
981
|
+
};
|
|
982
|
+
drop(state);
|
|
983
|
+
let started = std::time::Instant::now();
|
|
984
|
+
let objects = batch.len();
|
|
985
|
+
lead.results = self.record_batch(batch);
|
|
986
|
+
crate::stats::admission_batch(objects, started.elapsed());
|
|
987
|
+
drop(lead);
|
|
988
|
+
state = group
|
|
989
|
+
.state
|
|
990
|
+
.lock()
|
|
991
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
fn record_batch(&self, batch: Vec<(u64, Recording)>) -> Vec<(u64, Result<()>)> {
|
|
996
|
+
let fail_all = |batch: Vec<(u64, Recording)>, error: Refusal| {
|
|
997
|
+
batch
|
|
998
|
+
.into_iter()
|
|
999
|
+
.map(|(ticket, _)| (ticket, Err(error.clone())))
|
|
1000
|
+
.collect::<Vec<_>>()
|
|
1001
|
+
};
|
|
1002
|
+
let mut connection = match self.connection() {
|
|
1003
|
+
Ok(connection) => connection,
|
|
1004
|
+
Err(error) => return fail_all(batch, error),
|
|
1005
|
+
};
|
|
1006
|
+
let mut transaction =
|
|
1007
|
+
match connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate) {
|
|
1008
|
+
Ok(transaction) => transaction,
|
|
1009
|
+
Err(error) => return fail_all(batch, sql("begin blob admission", error)),
|
|
1010
|
+
};
|
|
1011
|
+
let mut results = Vec::with_capacity(batch.len());
|
|
1012
|
+
let mut written = Vec::with_capacity(batch.len());
|
|
1013
|
+
// Each object's row sits behind its own savepoint: one refused row refuses that
|
|
1014
|
+
// object, as it did when every object had a transaction of its own.
|
|
1015
|
+
for (ticket, recording) in batch {
|
|
1016
|
+
let outcome = transaction
|
|
1017
|
+
.savepoint()
|
|
1018
|
+
.map_err(|error| sql("begin object admission", error))
|
|
1019
|
+
.and_then(|savepoint| {
|
|
1020
|
+
if let Some(row) = &recording.record {
|
|
1021
|
+
write_verification_on(&savepoint, row)?;
|
|
1022
|
+
}
|
|
1023
|
+
savepoint
|
|
1024
|
+
.commit()
|
|
1025
|
+
.map_err(|error| sql("record object admission", error))
|
|
1026
|
+
});
|
|
1027
|
+
match outcome {
|
|
1028
|
+
Ok(()) => written.push((ticket, recording.record)),
|
|
1029
|
+
Err(error) => results.push((ticket, Err(error))),
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
match transaction
|
|
1033
|
+
.commit()
|
|
1034
|
+
.map_err(|error| sql("commit blob admission", error))
|
|
1035
|
+
{
|
|
1036
|
+
Ok(()) => {
|
|
1037
|
+
let trust = Trust::live(&self.path);
|
|
1038
|
+
for (ticket, record) in written {
|
|
1039
|
+
if let (Some(row), Some(trust)) = (&record, &trust) {
|
|
1040
|
+
trust.remember(row);
|
|
1041
|
+
}
|
|
1042
|
+
results.push((ticket, Ok(())));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
Err(error) => {
|
|
1046
|
+
results.extend(
|
|
1047
|
+
written
|
|
1048
|
+
.into_iter()
|
|
1049
|
+
.map(|(ticket, _)| (ticket, Err(error.clone()))),
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
results
|
|
1054
|
+
}
|
|
1055
|
+
|
|
944
1056
|
pub fn held_keys(&self) -> Result<Vec<crate::storage::HeldKey>> {
|
|
945
1057
|
let connection = self.connection()?;
|
|
946
1058
|
let mut statement = connection
|
|
@@ -1084,7 +1196,7 @@ impl Catalog {
|
|
|
1084
1196
|
}
|
|
1085
1197
|
}
|
|
1086
1198
|
|
|
1087
|
-
fn
|
|
1199
|
+
fn held_admissions(path: &Path) -> Arc<Mutex<()>> {
|
|
1088
1200
|
static TURNS: OnceLock<Mutex<HashMap<PathBuf, Arc<Mutex<()>>>>> = OnceLock::new();
|
|
1089
1201
|
TURNS
|
|
1090
1202
|
.get_or_init(Default::default)
|
|
@@ -1095,6 +1207,73 @@ fn admissions(path: &Path) -> Arc<Mutex<()>> {
|
|
|
1095
1207
|
.clone()
|
|
1096
1208
|
}
|
|
1097
1209
|
|
|
1210
|
+
/// One published object's catalog rows, handed to the admission group.
|
|
1211
|
+
pub(crate) struct Recording {
|
|
1212
|
+
/// The verification row, when this call installed the object.
|
|
1213
|
+
pub record: Option<crate::store::VRecord>,
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
#[derive(Default)]
|
|
1217
|
+
struct AdmissionGroup {
|
|
1218
|
+
state: Mutex<GroupState>,
|
|
1219
|
+
turned: std::sync::Condvar,
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
#[derive(Default)]
|
|
1223
|
+
struct GroupState {
|
|
1224
|
+
next: u64,
|
|
1225
|
+
leading: bool,
|
|
1226
|
+
queue: Vec<(u64, Recording)>,
|
|
1227
|
+
done: HashMap<u64, Result<()>>,
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
fn admission_group(path: &Path) -> Arc<AdmissionGroup> {
|
|
1231
|
+
static GROUPS: OnceLock<Mutex<HashMap<PathBuf, Arc<AdmissionGroup>>>> = OnceLock::new();
|
|
1232
|
+
GROUPS
|
|
1233
|
+
.get_or_init(Default::default)
|
|
1234
|
+
.lock()
|
|
1235
|
+
.unwrap_or_else(|error| error.into_inner())
|
|
1236
|
+
.entry(path.to_path_buf())
|
|
1237
|
+
.or_default()
|
|
1238
|
+
.clone()
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/// A leader hands its batch's results back however it leaves, so a panic inside one batch
|
|
1242
|
+
/// cannot strand the admissions queued behind it.
|
|
1243
|
+
struct Lead<'a> {
|
|
1244
|
+
group: &'a AdmissionGroup,
|
|
1245
|
+
tickets: Vec<u64>,
|
|
1246
|
+
results: Vec<(u64, Result<()>)>,
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
impl Drop for Lead<'_> {
|
|
1250
|
+
fn drop(&mut self) {
|
|
1251
|
+
let mut state = self
|
|
1252
|
+
.group
|
|
1253
|
+
.state
|
|
1254
|
+
.lock()
|
|
1255
|
+
.unwrap_or_else(|error| error.into_inner());
|
|
1256
|
+
state.leading = false;
|
|
1257
|
+
let mut answered = std::collections::HashSet::new();
|
|
1258
|
+
for (ticket, result) in self.results.drain(..) {
|
|
1259
|
+
answered.insert(ticket);
|
|
1260
|
+
state.done.insert(ticket, result);
|
|
1261
|
+
}
|
|
1262
|
+
for ticket in &self.tickets {
|
|
1263
|
+
if !answered.contains(ticket) {
|
|
1264
|
+
state.done.insert(
|
|
1265
|
+
*ticket,
|
|
1266
|
+
Err(Refusal {
|
|
1267
|
+
code: Code::IO_FAILED,
|
|
1268
|
+
detail: "the admission batch holding this object did not finish".into(),
|
|
1269
|
+
}),
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
self.group.turned.notify_all();
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1098
1277
|
fn hold_on(connection: &Connection, operation: &str, key: &str, length: u64) -> Result<()> {
|
|
1099
1278
|
let kind = if key.starts_with("blobs/") {
|
|
1100
1279
|
"blob"
|
|
@@ -262,6 +262,21 @@ impl FingerprintRegistry {
|
|
|
262
262
|
crate::canon::write(&self.to_value())
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
/// Extend this registry with a newer reviewed one: its entries and profiles replace
|
|
266
|
+
/// these under the same key and add the rest. Every entry keeps its own provenance.
|
|
267
|
+
pub fn extend(&mut self, newer: FingerprintRegistry) {
|
|
268
|
+
for entry in newer.entries {
|
|
269
|
+
self.merge(entry);
|
|
270
|
+
}
|
|
271
|
+
for profile in newer.source_profiles {
|
|
272
|
+
self.merge_source_profile(profile);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// Key set and component classify. An entry banked from the exact same tensor schema is
|
|
277
|
+
/// preferred; otherwise any entry for this key set and component serves when they agree
|
|
278
|
+
/// on dialect and converter, and conversion validates the actual shapes and dtypes (an
|
|
279
|
+
/// fp16 upload of a model banked in bf16 classifies).
|
|
265
280
|
pub fn classify(&self, fp: &Fingerprint) -> Verdict {
|
|
266
281
|
let keyset: Vec<&Banked> = self
|
|
267
282
|
.entries
|
|
@@ -281,7 +296,7 @@ impl FingerprintRegistry {
|
|
|
281
296
|
banked_as: keyset.iter().map(|e| name_of(e)).collect(),
|
|
282
297
|
};
|
|
283
298
|
}
|
|
284
|
-
let
|
|
299
|
+
let exact: Vec<&Banked> = component
|
|
285
300
|
.iter()
|
|
286
301
|
.copied()
|
|
287
302
|
.filter(|e| {
|
|
@@ -289,6 +304,14 @@ impl FingerprintRegistry {
|
|
|
289
304
|
&& e.logical_keys as usize == fp.logical_keys
|
|
290
305
|
})
|
|
291
306
|
.collect();
|
|
307
|
+
let hits = if exact.is_empty() {
|
|
308
|
+
let mut verdicts = component.clone();
|
|
309
|
+
verdicts.sort_by(|a, b| (&a.dialect, &a.converter).cmp(&(&b.dialect, &b.converter)));
|
|
310
|
+
verdicts.dedup_by(|a, b| (&a.dialect, &a.converter) == (&b.dialect, &b.converter));
|
|
311
|
+
verdicts
|
|
312
|
+
} else {
|
|
313
|
+
exact
|
|
314
|
+
};
|
|
292
315
|
match hits.len() {
|
|
293
316
|
0 => Verdict::Unregistered,
|
|
294
317
|
1 => Verdict::Authorized(Box::new(hits[0].clone())),
|
|
@@ -242,6 +242,9 @@ fn profile_assignments(
|
|
|
242
242
|
) -> Vec<Vec<SourceAssignment>> {
|
|
243
243
|
let mut candidates = Vec::new();
|
|
244
244
|
for component in &profile.components {
|
|
245
|
+
// Carriers banked from this exact tensor schema first; otherwise any carrier with
|
|
246
|
+
// the reviewed key set, whose shapes and dtypes the conversion itself validates.
|
|
247
|
+
let mut exact = Vec::new();
|
|
245
248
|
let mut hits = Vec::new();
|
|
246
249
|
for (variant_index, variant) in component.variants.iter().enumerate() {
|
|
247
250
|
for entry in registry.entries.iter().filter(|entry| {
|
|
@@ -277,15 +280,21 @@ fn profile_assignments(
|
|
|
277
280
|
Ok(observed) => observed,
|
|
278
281
|
Err(_) => continue,
|
|
279
282
|
};
|
|
280
|
-
if observed.keyset_digest
|
|
281
|
-
|
|
283
|
+
if observed.keyset_digest != variant.keyset_digest {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
let hit = (index, projected, variant_index);
|
|
287
|
+
if observed.tensor_schema_digest == entry.tensor_schema_digest
|
|
282
288
|
&& observed.logical_keys as u64 == entry.logical_keys
|
|
283
289
|
{
|
|
284
|
-
|
|
290
|
+
exact.push(hit);
|
|
291
|
+
} else if !hits.contains(&hit) {
|
|
292
|
+
hits.push(hit);
|
|
285
293
|
}
|
|
286
294
|
}
|
|
287
295
|
}
|
|
288
296
|
}
|
|
297
|
+
let hits = if exact.is_empty() { hits } else { exact };
|
|
289
298
|
if hits.is_empty() {
|
|
290
299
|
return Vec::new();
|
|
291
300
|
}
|
|
@@ -303,6 +312,58 @@ fn profile_assignments(
|
|
|
303
312
|
solutions
|
|
304
313
|
}
|
|
305
314
|
|
|
315
|
+
/// Where a Store keeps a reviewed registry newer than the built-in one: shipped as a pinned
|
|
316
|
+
/// file or served by the Hub, and bound with [`bind_registry`]. It only extends the built-in
|
|
317
|
+
/// registry, so a TensorFS older than a model can still ingest it.
|
|
318
|
+
pub const REGISTRY_BINDING: &str = "fingerprint-registry.json";
|
|
319
|
+
|
|
320
|
+
/// Bind (or with `None`, clear) the Store's reviewed registry. The data is validated here,
|
|
321
|
+
/// provenance included, so a malformed file is a caller's mistake at bind time.
|
|
322
|
+
pub fn bind_registry(store: &Store, bytes: Option<&[u8]>) -> Result<()> {
|
|
323
|
+
let path = store.root().join(REGISTRY_BINDING);
|
|
324
|
+
let Some(bytes) = bytes else {
|
|
325
|
+
return match fs::remove_file(&path) {
|
|
326
|
+
Ok(()) => Ok(()),
|
|
327
|
+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
|
|
328
|
+
Err(error) => refuse(Code::IO_FAILED, format!("clear registry binding: {error}")),
|
|
329
|
+
};
|
|
330
|
+
};
|
|
331
|
+
FingerprintRegistry::parse(bytes)?;
|
|
332
|
+
let temporary = store.root().join("tmp").join(format!(
|
|
333
|
+
"{REGISTRY_BINDING}.{}",
|
|
334
|
+
crate::meta::now_nanos_unique()
|
|
335
|
+
));
|
|
336
|
+
let written = fs::write(&temporary, bytes)
|
|
337
|
+
.and_then(|()| fs::File::open(&temporary)?.sync_all())
|
|
338
|
+
.and_then(|()| fs::rename(&temporary, &path));
|
|
339
|
+
if let Err(error) = written {
|
|
340
|
+
let _ = fs::remove_file(&temporary);
|
|
341
|
+
return refuse(Code::IO_FAILED, format!("bind registry: {error}"));
|
|
342
|
+
}
|
|
343
|
+
Ok(())
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/// The registry an ingest on this Store classifies with: the built-in one, extended by the
|
|
347
|
+
/// Store's bound registry, then by a caller-`supplied` one. A bound file that no longer
|
|
348
|
+
/// parses is skipped (it can only add); supplied data that does not parse refuses.
|
|
349
|
+
pub fn effective_registry(store: &Store, supplied: Option<&[u8]>) -> Result<(String, Vec<u8>)> {
|
|
350
|
+
let mut registry = FingerprintRegistry::parse(BUILTIN_REGISTRY_BYTES)?;
|
|
351
|
+
let mut locator = BUILTIN_REGISTRY.to_string();
|
|
352
|
+
let bound = fs::read(store.root().join(REGISTRY_BINDING))
|
|
353
|
+
.ok()
|
|
354
|
+
.filter(|bytes| bytes.len() <= crate::limits::DOC_MAX_BYTES)
|
|
355
|
+
.and_then(|bytes| FingerprintRegistry::parse(&bytes).ok());
|
|
356
|
+
if let Some(bound) = bound {
|
|
357
|
+
registry.extend(bound);
|
|
358
|
+
locator.push_str("+store");
|
|
359
|
+
}
|
|
360
|
+
if let Some(bytes) = supplied {
|
|
361
|
+
registry.extend(FingerprintRegistry::parse(bytes)?);
|
|
362
|
+
locator.push_str("+supplied");
|
|
363
|
+
}
|
|
364
|
+
Ok((locator, registry.to_bytes()))
|
|
365
|
+
}
|
|
366
|
+
|
|
306
367
|
pub fn read_header_only(path: &Path) -> Result<(SourceHeader, Vec<u8>)> {
|
|
307
368
|
let json = fs::read(path).map_err(|error| Refusal {
|
|
308
369
|
code: Code::IO_FAILED,
|
|
@@ -1658,7 +1719,8 @@ pub fn prepare_model_source(
|
|
|
1658
1719
|
store: &Store,
|
|
1659
1720
|
request: &PrepareModelSource,
|
|
1660
1721
|
) -> Result<PreparedModelSources> {
|
|
1661
|
-
|
|
1722
|
+
let (locator, bytes) = effective_registry(store, None)?;
|
|
1723
|
+
prepare_model_source_with_registry(store, request, &locator, &bytes)
|
|
1662
1724
|
}
|
|
1663
1725
|
|
|
1664
1726
|
/// One member of an exact provider selection: its pinned identity and the header a ranged
|
|
@@ -2308,6 +2370,36 @@ mod tests {
|
|
|
2308
2370
|
let _ = fs::remove_dir_all(root);
|
|
2309
2371
|
}
|
|
2310
2372
|
|
|
2373
|
+
#[test]
|
|
2374
|
+
fn a_bound_registry_ingests_models_the_build_never_banked_at_any_precision() {
|
|
2375
|
+
let root = temporary("bound-registry");
|
|
2376
|
+
let (store, registry, mut request) = fixture(&root, false);
|
|
2377
|
+
// Banked from F32 carriers; the first carrier is now re-uploaded as F16.
|
|
2378
|
+
let first_path = request.roster[0].path.clone();
|
|
2379
|
+
let header = "{\"weight\":{\"data_offsets\":[0,2],\"dtype\":\"F16\",\"shape\":[1]}}";
|
|
2380
|
+
let mut bytes = (header.len() as u64).to_le_bytes().to_vec();
|
|
2381
|
+
bytes.extend_from_slice(header.as_bytes());
|
|
2382
|
+
bytes.extend_from_slice(&[0x00, 0x3c]);
|
|
2383
|
+
fs::write(&first_path, &bytes).unwrap();
|
|
2384
|
+
request.roster[0].object_id = ObjectRef::of(&bytes).id();
|
|
2385
|
+
request.roster[0].length = bytes.len() as u64;
|
|
2386
|
+
request.roster[0].header = header_prefix(&bytes);
|
|
2387
|
+
|
|
2388
|
+
// Unknown to the compiled-in registry until the reviewed data is bound.
|
|
2389
|
+
assert_eq!(
|
|
2390
|
+
prepare_model_source(&store, &request).unwrap_err().code,
|
|
2391
|
+
Code::UNREGISTERED_FINGERPRINT
|
|
2392
|
+
);
|
|
2393
|
+
bind_registry(&store, Some(®istry)).unwrap();
|
|
2394
|
+
let prepared = prepare_model_source(&store, &request).unwrap();
|
|
2395
|
+
assert!(prepared.complete);
|
|
2396
|
+
assert_eq!(prepared.sources.len(), 2);
|
|
2397
|
+
// Registry data without provenance is not reviewed data.
|
|
2398
|
+
assert!(bind_registry(&store, Some(b"{\"entries\":[{\"component\":\"x\"}]}")).is_err());
|
|
2399
|
+
bind_registry(&store, None).unwrap();
|
|
2400
|
+
let _ = fs::remove_dir_all(root);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2311
2403
|
#[test]
|
|
2312
2404
|
#[ignore = "subprocess for source journal SIGKILL proof"]
|
|
2313
2405
|
fn source_tail_snapshot_worker() {
|
|
@@ -6,6 +6,9 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
|
|
6
6
|
static CATALOG_OPENS: AtomicU64 = AtomicU64::new(0);
|
|
7
7
|
static PRESENCE_PASSES: AtomicU64 = AtomicU64::new(0);
|
|
8
8
|
static REPO_CACHE_VERIFIED_READS: AtomicU64 = AtomicU64::new(0);
|
|
9
|
+
static ADMISSION_BATCHES: AtomicU64 = AtomicU64::new(0);
|
|
10
|
+
static ADMITTED_OBJECTS: AtomicU64 = AtomicU64::new(0);
|
|
11
|
+
static ADMISSION_NANOS: AtomicU64 = AtomicU64::new(0);
|
|
9
12
|
|
|
10
13
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
11
14
|
pub struct Stats {
|
|
@@ -15,6 +18,12 @@ pub struct Stats {
|
|
|
15
18
|
pub presence_passes: u64,
|
|
16
19
|
/// Repository-cache entries read and hashed.
|
|
17
20
|
pub repo_cache_verified_reads: u64,
|
|
21
|
+
/// Blob admission batches committed (one transaction and commit each).
|
|
22
|
+
pub admission_batches: u64,
|
|
23
|
+
/// Blobs those batches published and recorded.
|
|
24
|
+
pub admitted_objects: u64,
|
|
25
|
+
/// Wall time those batches spent linking, fsyncing and committing.
|
|
26
|
+
pub admission_nanos: u64,
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
pub fn snapshot() -> Stats {
|
|
@@ -22,6 +31,9 @@ pub fn snapshot() -> Stats {
|
|
|
22
31
|
catalog_opens: CATALOG_OPENS.load(Ordering::Relaxed),
|
|
23
32
|
presence_passes: PRESENCE_PASSES.load(Ordering::Relaxed),
|
|
24
33
|
repo_cache_verified_reads: REPO_CACHE_VERIFIED_READS.load(Ordering::Relaxed),
|
|
34
|
+
admission_batches: ADMISSION_BATCHES.load(Ordering::Relaxed),
|
|
35
|
+
admitted_objects: ADMITTED_OBJECTS.load(Ordering::Relaxed),
|
|
36
|
+
admission_nanos: ADMISSION_NANOS.load(Ordering::Relaxed),
|
|
25
37
|
}
|
|
26
38
|
}
|
|
27
39
|
|
|
@@ -48,3 +60,12 @@ pub(crate) fn presence_pass() {
|
|
|
48
60
|
pub(crate) fn repo_cache_verified_read() {
|
|
49
61
|
REPO_CACHE_VERIFIED_READS.fetch_add(1, Ordering::Relaxed);
|
|
50
62
|
}
|
|
63
|
+
|
|
64
|
+
pub(crate) fn admission_batch(objects: usize, spent: std::time::Duration) {
|
|
65
|
+
ADMISSION_BATCHES.fetch_add(1, Ordering::Relaxed);
|
|
66
|
+
ADMITTED_OBJECTS.fetch_add(objects as u64, Ordering::Relaxed);
|
|
67
|
+
ADMISSION_NANOS.fetch_add(
|
|
68
|
+
u64::try_from(spent.as_nanos()).unwrap_or(u64::MAX),
|
|
69
|
+
Ordering::Relaxed,
|
|
70
|
+
);
|
|
71
|
+
}
|