diff --git a/README.md b/README.md index 5ed7b14..950cfe0 100644 --- a/README.md +++ b/README.md @@ -268,13 +268,15 @@ expose internal queues or mutable scheduler state. `snapshot(flowHandle)` returns an `Optional` for the exact active registration. It reports current queued/running counts and exact cumulative -accepted, dispatched, and cancelled supplied-cost units. Current queued cost is -available as `acceptedCost - dispatchedCost - cancelledCost` through -`queuedCost()`. `runningSuppliedCost()` reports the committed in-flight -supplied cost directly, while `completedSuppliedCost()` derives the cumulative -completed supplied cost as dispatched minus running. These are caller-supplied -service estimates, not actual execution time. Cost values use `BigInteger`, so -valid `long` costs do not overflow observability counters. +accepted, dispatched, and cancelled supplied-cost units. Current queued supplied +cost is available as `acceptedSuppliedCost - dispatchedSuppliedCost - +cancelledSuppliedCost` through `queuedSuppliedCost()`. +`runningSuppliedCost()` reports the committed in-flight supplied cost directly, +while `completedSuppliedCost()` derives the cumulative completed supplied cost +as dispatched supplied cost minus running supplied cost. All six cost accessors +describe caller-supplied service estimates, not actual execution time. Cost +values use `BigInteger`, so valid `long` costs do not overflow observability +counters. The per-flow snapshot is captured atomically with lifecycle transitions. A foreign, stale, or closed handle returns an empty optional. The library does diff --git a/docs/FORMAL_SPEC.md b/docs/FORMAL_SPEC.md index 4c56a05..6c92f1d 100644 --- a/docs/FORMAL_SPEC.md +++ b/docs/FORMAL_SPEC.md @@ -680,25 +680,25 @@ registration, the operation returns an immutable `FlowSnapshot`: ```text queuedJobs, runningJobs, -acceptedCost, dispatchedCost, cancelledCost, -runningSuppliedCost, completedSuppliedCost +acceptedSuppliedCost, dispatchedSuppliedCost, cancelledSuppliedCost, +queuedSuppliedCost, runningSuppliedCost, completedSuppliedCost ``` Job counts describe current state. Cost values are exact non-negative integers. Cumulative totals cover the lifetime of this registration, while gauges describe current state: -- `acceptedCost` increases only on successful enqueue; -- `dispatchedCost` increases for each job in a successful dispatch batch; -- `cancelledCost` increases only on successful queued cancel; -- current `queuedCost = acceptedCost - dispatchedCost - cancelledCost`; +- `acceptedSuppliedCost` increases only on successful enqueue; +- `dispatchedSuppliedCost` increases for each job in a successful dispatch batch; +- `cancelledSuppliedCost` increases only on successful queued cancel; +- current `queuedSuppliedCost = acceptedSuppliedCost - dispatchedSuppliedCost - cancelledSuppliedCost`; - `runningSuppliedCost` increases on dispatch and decreases on completion; -- `completedSuppliedCost = dispatchedCost - runningSuppliedCost`. +- `completedSuppliedCost = dispatchedSuppliedCost - runningSuppliedCost`. -`runningSuppliedCost` and `completedSuppliedCost` describe caller-supplied -service estimates, not actual execution time. Completion does not change the -cumulative `dispatchedCost`, which includes running and completed jobs. Failed -and no-op outcomes do not change the snapshot. Cost values do **not** overflow: +All six cost values describe caller-supplied service estimates, not actual +execution time or resource service. Completion does not change the cumulative +`dispatchedSuppliedCost`, which includes running and completed jobs. Failed and +no-op outcomes do not change the snapshot. Cost values do **not** overflow: exact integers represent them, and the global never-reused job sequence bounds each value by `Long.MAX_VALUE * Long.MAX_VALUE`. diff --git a/sfqd-core/src/main/java/io/github/pzhin/sfqd/FlowSnapshot.java b/sfqd-core/src/main/java/io/github/pzhin/sfqd/FlowSnapshot.java index bf429b4..60b7869 100644 --- a/sfqd-core/src/main/java/io/github/pzhin/sfqd/FlowSnapshot.java +++ b/sfqd-core/src/main/java/io/github/pzhin/sfqd/FlowSnapshot.java @@ -3,27 +3,31 @@ import java.math.BigInteger; import java.util.Objects; -/** Immutable observation of one registered flow created only by a scheduler implementation. */ +/** + * Immutable observation of one registered flow created only by a scheduler implementation. + * + *

Every cost value is a caller-supplied scheduling cost, not elapsed time or actual resource service. + */ public final class FlowSnapshot { private final int queuedJobs; private final int runningJobs; - private final BigInteger acceptedCost; - private final BigInteger dispatchedCost; - private final BigInteger cancelledCost; + private final BigInteger acceptedSuppliedCost; + private final BigInteger dispatchedSuppliedCost; + private final BigInteger cancelledSuppliedCost; private final BigInteger runningSuppliedCost; FlowSnapshot( int queuedJobs, int runningJobs, - BigInteger acceptedCost, - BigInteger dispatchedCost, - BigInteger cancelledCost, + BigInteger acceptedSuppliedCost, + BigInteger dispatchedSuppliedCost, + BigInteger cancelledSuppliedCost, BigInteger runningSuppliedCost) { this.queuedJobs = queuedJobs; this.runningJobs = runningJobs; - this.acceptedCost = Objects.requireNonNull(acceptedCost, "acceptedCost"); - this.dispatchedCost = Objects.requireNonNull(dispatchedCost, "dispatchedCost"); - this.cancelledCost = Objects.requireNonNull(cancelledCost, "cancelledCost"); + this.acceptedSuppliedCost = Objects.requireNonNull(acceptedSuppliedCost, "acceptedSuppliedCost"); + this.dispatchedSuppliedCost = Objects.requireNonNull(dispatchedSuppliedCost, "dispatchedSuppliedCost"); + this.cancelledSuppliedCost = Objects.requireNonNull(cancelledSuppliedCost, "cancelledSuppliedCost"); this.runningSuppliedCost = Objects.requireNonNull(runningSuppliedCost, "runningSuppliedCost"); } @@ -48,28 +52,28 @@ public int runningJobs() { /** * Returns the exact cumulative supplied cost of successfully accepted jobs for the flow registration. * - * @return cumulative accepted cost units + * @return cumulative accepted supplied-cost units */ - public BigInteger acceptedCost() { - return acceptedCost; + public BigInteger acceptedSuppliedCost() { + return acceptedSuppliedCost; } /** * Returns the exact cumulative supplied cost of jobs dispatched for the flow registration. * - * @return cumulative dispatched cost units + * @return cumulative dispatched supplied-cost units */ - public BigInteger dispatchedCost() { - return dispatchedCost; + public BigInteger dispatchedSuppliedCost() { + return dispatchedSuppliedCost; } /** * Returns the exact cumulative supplied cost of successfully cancelled jobs for the flow registration. * - * @return cumulative cancelled cost units + * @return cumulative cancelled supplied-cost units */ - public BigInteger cancelledCost() { - return cancelledCost; + public BigInteger cancelledSuppliedCost() { + return cancelledSuppliedCost; } /** @@ -91,19 +95,19 @@ public BigInteger runningSuppliedCost() { * @return cumulative completed supplied cost units */ public BigInteger completedSuppliedCost() { - return dispatchedCost.subtract(runningSuppliedCost); + return dispatchedSuppliedCost.subtract(runningSuppliedCost); } /** - * Returns the exact total cost of jobs currently queued for the flow. + * Returns the exact supplied cost of jobs currently queued for the flow. * *

This is derived from cumulative lifecycle costs as accepted minus dispatched minus cancelled. Completion * does not affect it because dispatched cost includes both running and completed jobs. * - * @return current queued cost units + * @return current queued supplied-cost units */ - public BigInteger queuedCost() { - return acceptedCost.subtract(dispatchedCost).subtract(cancelledCost); + public BigInteger queuedSuppliedCost() { + return acceptedSuppliedCost.subtract(dispatchedSuppliedCost).subtract(cancelledSuppliedCost); } @Override @@ -116,25 +120,26 @@ public boolean equals(Object other) { } return queuedJobs == snapshot.queuedJobs && runningJobs == snapshot.runningJobs - && acceptedCost.equals(snapshot.acceptedCost) - && dispatchedCost.equals(snapshot.dispatchedCost) - && cancelledCost.equals(snapshot.cancelledCost) + && acceptedSuppliedCost.equals(snapshot.acceptedSuppliedCost) + && dispatchedSuppliedCost.equals(snapshot.dispatchedSuppliedCost) + && cancelledSuppliedCost.equals(snapshot.cancelledSuppliedCost) && runningSuppliedCost.equals(snapshot.runningSuppliedCost); } @Override public int hashCode() { return Objects.hash( - queuedJobs, runningJobs, acceptedCost, dispatchedCost, cancelledCost, runningSuppliedCost); + queuedJobs, runningJobs, acceptedSuppliedCost, dispatchedSuppliedCost, + cancelledSuppliedCost, runningSuppliedCost); } @Override public String toString() { return "FlowSnapshot[queuedJobs=" + queuedJobs + ", runningJobs=" + runningJobs - + ", acceptedCost=" + acceptedCost - + ", dispatchedCost=" + dispatchedCost - + ", cancelledCost=" + cancelledCost + + ", acceptedSuppliedCost=" + acceptedSuppliedCost + + ", dispatchedSuppliedCost=" + dispatchedSuppliedCost + + ", cancelledSuppliedCost=" + cancelledSuppliedCost + ", runningSuppliedCost=" + runningSuppliedCost + ']'; } } diff --git a/sfqd-core/src/test/java/io/github/pzhin/sfqd/ApiVocabularyTest.java b/sfqd-core/src/test/java/io/github/pzhin/sfqd/ApiVocabularyTest.java index c68c6a8..72aa06d 100644 --- a/sfqd-core/src/test/java/io/github/pzhin/sfqd/ApiVocabularyTest.java +++ b/sfqd-core/src/test/java/io/github/pzhin/sfqd/ApiVocabularyTest.java @@ -138,14 +138,17 @@ void valueOutcomesAndSnapshotCarryOnlySpecifiedData() { 3, 4, BigInteger.TEN, BigInteger.valueOf(6L), BigInteger.ONE, BigInteger.valueOf(2L)); assertEquals(3, flowSnapshot.queuedJobs()); assertEquals(4, flowSnapshot.runningJobs()); - assertEquals(BigInteger.TEN, flowSnapshot.acceptedCost()); - assertEquals(BigInteger.valueOf(6L), flowSnapshot.dispatchedCost()); - assertEquals(BigInteger.ONE, flowSnapshot.cancelledCost()); - assertEquals(BigInteger.valueOf(3L), flowSnapshot.queuedCost()); + assertEquals(BigInteger.TEN, flowSnapshot.acceptedSuppliedCost()); + assertEquals(BigInteger.valueOf(6L), flowSnapshot.dispatchedSuppliedCost()); + assertEquals(BigInteger.ONE, flowSnapshot.cancelledSuppliedCost()); + assertEquals(BigInteger.valueOf(3L), flowSnapshot.queuedSuppliedCost()); assertEquals(BigInteger.valueOf(2L), flowSnapshot.runningSuppliedCost()); assertEquals(BigInteger.valueOf(4L), flowSnapshot.completedSuppliedCost()); - assertTrue(flowSnapshot.toString().contains("acceptedCost=10")); + assertTrue(flowSnapshot.toString().contains("acceptedSuppliedCost=10")); assertTrue(flowSnapshot.toString().contains("runningSuppliedCost=2")); + assertFalse(Arrays.stream(FlowSnapshot.class.getMethods()) + .map(Method::getName) + .anyMatch(List.of("acceptedCost", "dispatchedCost", "cancelledCost", "queuedCost")::contains)); assertEquals(flowSnapshot, new FlowSnapshot( 3, 4, BigInteger.TEN, BigInteger.valueOf(6L), BigInteger.ONE, BigInteger.valueOf(2L))); assertEquals(flowSnapshot.hashCode(), new FlowSnapshot( diff --git a/sfqd-core/src/test/java/io/github/pzhin/sfqd/FlowSnapshotTest.java b/sfqd-core/src/test/java/io/github/pzhin/sfqd/FlowSnapshotTest.java index 575c7e1..8cab853 100644 --- a/sfqd-core/src/test/java/io/github/pzhin/sfqd/FlowSnapshotTest.java +++ b/sfqd-core/src/test/java/io/github/pzhin/sfqd/FlowSnapshotTest.java @@ -22,10 +22,10 @@ void reportsExactRegistrationLifetimeCostsAndCurrentCounts() { FlowSnapshot queued = scheduler.snapshot(flow).orElseThrow(); assertEquals(2, queued.queuedJobs()); assertEquals(0, queued.runningJobs()); - assertEquals(twiceMaximum, queued.acceptedCost()); - assertEquals(BigInteger.ZERO, queued.dispatchedCost()); - assertEquals(BigInteger.ZERO, queued.cancelledCost()); - assertEquals(twiceMaximum, queued.queuedCost()); + assertEquals(twiceMaximum, queued.acceptedSuppliedCost()); + assertEquals(BigInteger.ZERO, queued.dispatchedSuppliedCost()); + assertEquals(BigInteger.ZERO, queued.cancelledSuppliedCost()); + assertEquals(twiceMaximum, queued.queuedSuppliedCost()); assertEquals(BigInteger.ZERO, queued.runningSuppliedCost()); assertEquals(BigInteger.ZERO, queued.completedSuppliedCost()); @@ -40,9 +40,9 @@ void reportsExactRegistrationLifetimeCostsAndCurrentCounts() { FlowSnapshot afterCancellation = scheduler.snapshot(flow).orElseThrow(); assertEquals(0, afterCancellation.queuedJobs()); assertEquals(1, afterCancellation.runningJobs()); - assertEquals(BigInteger.valueOf(Long.MAX_VALUE), afterCancellation.dispatchedCost()); - assertEquals(BigInteger.valueOf(Long.MAX_VALUE), afterCancellation.cancelledCost()); - assertEquals(BigInteger.ZERO, afterCancellation.queuedCost()); + assertEquals(BigInteger.valueOf(Long.MAX_VALUE), afterCancellation.dispatchedSuppliedCost()); + assertEquals(BigInteger.valueOf(Long.MAX_VALUE), afterCancellation.cancelledSuppliedCost()); + assertEquals(BigInteger.ZERO, afterCancellation.queuedSuppliedCost()); assertEquals(BigInteger.valueOf(Long.MAX_VALUE), afterCancellation.runningSuppliedCost()); assertEquals(BigInteger.ZERO, afterCancellation.completedSuppliedCost()); @@ -97,12 +97,13 @@ void rejectsNullAndDoesNotExposeForeignOrReplacedRegistrations() { } private static FlowSnapshot snapshot( - int queuedJobs, int runningJobs, long acceptedCost, long dispatchedCost, long cancelledCost) { + int queuedJobs, int runningJobs, long acceptedSuppliedCost, long dispatchedSuppliedCost, + long cancelledSuppliedCost) { return new FlowSnapshot( queuedJobs, runningJobs, - BigInteger.valueOf(acceptedCost), - BigInteger.valueOf(dispatchedCost), - BigInteger.valueOf(cancelledCost), + BigInteger.valueOf(acceptedSuppliedCost), + BigInteger.valueOf(dispatchedSuppliedCost), + BigInteger.valueOf(cancelledSuppliedCost), BigInteger.ZERO); }