Repository navigation
Description
Need
bmc.sensors().execute() and bmc.frus().execute() return only after every sensor reading or FRU inventory has been read. On a large BMC that takes a while: hundreds of Get Sensor Reading round trips (each bridged sensor goes through Send Message, and a silent satellite controller costs a full timeout), and FRU reads of a few hundred bytes each in chunks of 16 to 255 bytes (#102). Until the operation ends, the caller sees nothing:
- MetricsHub cannot start processing the first sensors while the last ones are being read, and the operation deadline must cover the whole walk;
ipmicli sensors/ipmicli fruprint nothing until the end, then everything at once;- a caller that only needs the first few matching sensors (
limit(n),findFirst()) still pays for all of them.
winrm-java solved the same problem with a streaming terminal next to the blocking one (WqlRequest.stream(), RemoteDirectoryListing.stream()). ipmi-java should offer the same, with the same contract, so the two MetricsHub libraries feel alike.
Proposed API
try (Stream<Sensor> sensors = bmc.sensors().types(SensorType.Temperature).stream()) {
sensors.forEach(sensor -> process(sensor));
}
try (Stream<Fru> frus = bmc.frus().stream()) {
frus.filter(fru -> fru.serialNumber() != null).forEach(this::inventory);
}SensorsRequest.stream()andFrusRequest.stream(): a lazy, sequential, orderedStream(Java 8StreamSupportover aSpliterator, as in winrm-java). EachSensor/Fruis yielded as soon as it is read, in the same order asexecute(). The selection (types(),names(),ids()) applies as it does today: an unselected sensor costs no round trip.execute()stays and keeps its result type; it can becomestream().collect(...)internally so both paths share one implementation (asRemoteDirectoryListing.execute()does).- The stream must be closed (same contract as
Files.lines()and winrm-java): try-with-resources. Closing early stops the walk; no further command is sent. - The SDR records skipped by the walk (
SensorList.skippedRecords()/FruList.skippedRecords()) must stay reachable: the SDR repository is walked before the first element, so they are known whenstream()returns. Options: anonSkippedRecord(Consumer<SkippedRecord>)on the request (like winrm-java'sRemoteDirectoryListing.onInaccessible()), or a small result type holding both the stream and the skipped records. Pick one;SkippedRecordmust not be lost silently (AGENTS.md: the library never logs, the caller gets the problems). - Per-sensor and per-FRU problems are unchanged: they stay in
Sensor.problem()/Fru.problems()and do not end the stream. A failure that ends the walk (session lost, BMC stops answering) is thrown from the stream's operation (forEach,tryAdvance...), as in winrm-java;stream()itself throws when the session cannot be opened or the SDR repository cannot be read.
Points to settle in the implementation
Things in IpmiClient that a streaming terminal does not fit as-is:
- Single worker thread and deadline. Every operation goes through
run(): a task submitted to the client's single worker, awaited withfuture.get(deadline). A stream is consumed on the caller's thread, element by element, so the deadline cannot be the duration of the whole operation (the caller's processing time would count). Follow winrm-java: the deadline becomes an inactivity timeout, the longest wait for the next element (or for the SDR walk before the first one). Document that difference in the Javadoc ofstream()and oftimeout(Duration). - Serialization. While a stream is open, it owns the session: other operations on the same client (and the keep-alive) must wait until the stream is closed or exhausted, or be interleaved safely between two elements. winrm-java blocks them ("other operations on the same client block until it is closed or exhausted"); say which one ipmi-java does, and make sure an abandoned, unclosed stream cannot block the client forever (at least
IpmiClient.close()must still work). - Replay after a lost session.
perform()reruns the whole task once when the session is lost (replayable). A stream that has already emitted elements must not replay from the start (duplicates). Either resume after the last element emitted (re-open the session, keep the SDR records already walked, continue with the next sensor/FRU), or end the stream with theIpmiConnectionException/IpmiTimeoutException. Resuming is better for MetricsHub; document the choice. - Silent satellite controllers.
readSensors()keeps asilentOwnersset across the walk so that a controller that did not answer is not waited for again: it must live in the stream's state, not be rebuilt per element. - SDR cache. The walk uses
repository()(shared withfrus()while the cache is current): unchanged, the walk happens before the first element.
CLI
ipmicli sensors and ipmicli fru should use the stream, so that each row (and each --json line) is printed as it is read. The table output needs its column widths before the first row: either keep collecting for the table and stream only --json / JSON Lines output, or switch the table to fixed widths. Golden files must stay byte-identical for the collected output.
Out of scope / later
- SEL:
bmc.sel()could get the samestream()(entries are read one Get SEL Entry at a time), but SEL reservation handling (Expose SEL (System Event Log) reading in the client API #103) makes it a separate change. File it separately if wanted. - No reactive/async API (
CompletableFuture,Flow): a plainjava.util.stream.Stream, Java 8, like winrm-java.
Done when
SensorsRequest.stream()andFrusRequest.stream()exist, with Javadoc stating the close contract, the inactivity timeout, which exceptions come fromstream()and which from the stream's operations, and how skipped SDR records are reported.execute()returns the same results as today (fake-BMC tests unchanged).- Unit tests against the fake BMC: elements arrive before the walk ends; early
close()sends no more commands;limit(n)reads only n sensors; a session lost mid-stream behaves as documented; an unselected sensor costs no command. - README.md,
src/site/markdown(sensors and FRU pages) andupgrading.mddescribe the new terminal.
Activity
Add the streaming terminals sensors().stream() and frus().stream()
Merge pull request #238 from MetricsHub/235-streaming-terminal-for-se…