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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,13 +268,15 @@ expose internal queues or mutable scheduler state.

`snapshot(flowHandle)` returns an `Optional<FlowSnapshot>` 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
Expand Down
22 changes: 11 additions & 11 deletions docs/FORMAL_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
67 changes: 36 additions & 31 deletions sfqd-core/src/main/java/io/github/pzhin/sfqd/FlowSnapshot.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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");
}

Expand All @@ -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;
}

/**
Expand All @@ -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.
*
* <p>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
Expand All @@ -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 + ']';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
23 changes: 12 additions & 11 deletions sfqd-core/src/test/java/io/github/pzhin/sfqd/FlowSnapshotTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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());

Expand Down Expand Up @@ -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);
}

Expand Down