Repository navigation
Conversation
The per-behavior timings are off by default and run on the duty cycle attribution uses, ten ticks every ten seconds: Stratum's recording is the costly part, and a burst is what makes it affordable. The block is Enabled, BurstTicks and IntervalSeconds, after Attribution in the file. A file an admin already has gets the block from the config upgrade without any code of its own, because the upgrade compares the file with what the config class declares. Two tests read that off the real class: a file from before the block is told it is missing, and a block that predates one of its keys is told which. A reload applies the block like it applies Attribution, so PulseCommands.RestartKeys does not list it, and a test says so. Nothing reads the block yet.
Attribution built its own ModOwners, with every loaded mod's systems in it, inside its live-server constructor. The Stratum timings credit entity behaviors to mods through the same table, and they must not depend on attribution being on, or even on its constructor having succeeded, so the table is now built once by PulseModSystem and handed to attribution, which keeps using it exactly as before. The test-facing constructor takes it as an optional last argument and builds an empty one when none is given, so no existing test changes. Nothing observable moves: the same table is filled the same way, and the walks, the marks and the warnings are the ones they were.
With StratumTimings.Enabled set, Pulse leases Stratum's recording for a burst of ticks, reads the
accumulator's totals at both ends of it, and publishes what the burst added through five counters:
pulse_stratum_behavior_tick_seconds_total{category,behavior,threadsafe,modid},
pulse_stratum_ai_task_tick_seconds_total{category,task},
pulse_stratum_entity_tick_seconds_total{type}, pulse_stratum_entity_ticks_total{type} and
pulse_stratum_timed_ticks_total. They are registered only when the reading contract is bound, so a
server that is not Stratum, or is one Pulse cannot read, serves exactly what it served before.
The burst rides the same duty cycle attribution uses, on its own instance. The lease is requested on
the start tick, the baseline is read on the warm-up tick, and the last sample reads the end, lets go
of the lease and folds. The window is exactly BurstTicks tick cycles, which is what the timed ticks
counter adds, so the sampled seconds can be divided by the ticks they came from. The lease is
released on every way out: the burst ending, a reload dropping it part-way through, Stratum
throwing, and the mod stopping. A cycle restarted with a lease still out would take a second one at
its next burst and never give the first back.
Whatever Stratum throws at run time is caught in the tick listener: the lease is let go of, one
warning is logged, and the feature is off for the rest of the run, whatever a reload says. The
fold is built once and kept across reloads, because the cap on each family only bounds what is
served while the fold remembers which label sets it admitted; a fold made again would admit another
hundred while the aggregator went on serving the old series. A family spilling into other for the
first time is logged once.
The modid of a behavior comes from the same ModOwners attribution reads, which PulseModSystem now
builds and hands to both. The walk that teaches it what the loaded entities mark with runs on the
main thread right before each fold, so it does not depend on attribution being on, which it is not
by default; without it the behaviors whose name is not their registration code (entitycontrolledphysics,
displayname, entityStateTags, skinnableplayer) would all read unattributed. A walk that throws costs
precision in that label, not the feature, and is not tried again.
/pulse reload applies the block through a hook the command's owner calls, and says what happened in
the log: that it runs, or, once per run, why it cannot (a Notification on a server that is not
Stratum, a Warning carrying the binder's reason on one that is but cannot be read). The reload's
reply is what it always was.
The live-server half, the lookup of Stratum's type in the API the server loaded and the reload, is
in StratumTimingsMetrics.Server.cs, which is left out of coverage like the attribution one. The
rest is driven from the primary constructor of the binder's source with fake delegates: the lease
on every path that must release it, the count of timed ticks against the ticks that really went by
between the snapshots, the shared table, the cap across a reload, the give-up paths and every line
that is logged.
The suite only ever boots the game's own server, whose API assembly has no accumulator for Pulse to bind, so this is the one thing a scenario can show about the feature: with the block switched on, in a fixture that asks for a burst of five ticks a second apart, the server boots, the mod logs the one notification saying per-behavior timings need a Stratum server, none of the five families is on the wire, and nothing else moves (the endpoint and the engine's own accounting are served, and attribution, which the fixture leaves off, leaves no trace). A /pulse reload with the block still on adds no second line, because it is a fact about the server and not about the reload, and its reply is the one it always was. What the families look like on a real Stratum is for the Stratum lane to prove. The class takes port 29486, the next one after the range the README and the CI comment document, so the range now ends there.
Five mutations over the class that wires the binder, the fold and the duty cycle to the server, each aimed at something that fails silently: a lease that a reload leaves out, which would have Stratum record for the rest of its run, and the same for the give-up path; a count of timed ticks that is one too long, which reads every per-tick query ten percent low at the default; the walk that teaches the table what a behavior marks with running after the fold instead of before it, which credits a behavior to its mod a burst late; and the families registered on a server that cannot serve them. The live-server half of the class goes on the lists of files left out of coverage and out of the Stryker run, with the files that do the same for attribution, and the README sentence that counts them says six.
The upgrade scenario boots a server on an old pulse.json and reads back what the upgrade wrote. It already checked the Attribution block; it now checks that the StratumTimings block arrived too, off and on attribution's duty cycle, which is the part of "the upgrade adds it by itself" that only a live server can show. The remark on RestartKeys says the block is read by a reload like the Attribution one.
… a give-up The overflow test spilled the entity and AI task families only, so the arm that names the behavior family went unrun. It now spills all three and pins each line, which says which label the rest is lumped under. The give-up test also checks that a reload after the give-up does not claim to have started the feature again: it writes nothing.
Attribution built its table of mod owners inside its constructor, which PulseModSystem guards, so a mod list that could not be read cost attribution and nothing else. Building the table before both features moved that read outside every guard. It is now built by whichever feature asks for it first, inside that feature's own guard, and handed to the other, so there is still one table and a failure still costs the features that read it and not the endpoint. A test starts the mod against an API whose mod loader throws: the two warnings come out, one for each feature, and the endpoint is served. It fails against the version that built the table up front.
The line that says the Stratum timings run called them entity behaviors, and the lines about them failing call them Stratum's entity timings, which also covers the AI tasks and the entity types. It now reads like attribution's own start line and like the warnings: "Pulse reads Stratum's entity timings: bursts of 10 ticks every 10s."
With the admin's timings switch off, which is how a server boots, Stratum clears its accumulator the moment the last lease is released. The fake kept its totals through the release, so a feature that read the end snapshot after letting go of the lease would have passed every test and, on a real Stratum, served only the count of timed ticks. The fake now counts the leases that are out and empties its totals when the last one goes back, however often a lease is released. Only one test noticed, because it read cumulative totals across two bursts: it now reads the same one second twice, since the second burst starts from nothing. The swap is a mutation too: the release put before the last snapshot fails eight tests.
… order The fixture the OTLP upgrade scenario seeds for the base mod is described as complete, and turned off because nothing there is about what it serves. It lacked the StratumTimings block, so the base mod rewrote its file on every boot and logged that it had. It carries the block now, off, on the same duty cycle as the rest. The config test that reads a fresh file asserted the order of its keys, but the file is written by the game's own serializer and the test reads the framework's, which only agrees with it by accident. It asserts what the block holds and under which names, and no order.
A reload that switched the block off logged nothing, and its reply came from attribution alone, so an admin reloading from chat got no confirmation, and on a server that is not Stratum, with the block on, the reply claimed that nothing else in the file differed from what was running. The feature's Apply now returns one sentence for the reply, which goes before the rest, whose "nothing else" is about everything said so far: Stratum entity timings are on: bursts of 10 ticks every 10s. Stratum entity timings are off. Stratum entity timings need a Stratum server; this is not one. Stratum entity timings cannot be read: <the binder's reason>. Stratum entity timings are off for the rest of this run. It says something only when the block is on or was running. A block that is off and was not running, which is every server that never used the feature, gets the reply it always got, word for word, and the scenarios that pin that reply are untouched. The first three are the ones a reload can earn on a Stratum server that works or on one that is not; the last two cover a Stratum Pulse cannot read, and a feature that gave up earlier in the run, which a reload cannot revive. The sentences are pure text in PulseCommands, beside the others, and every literal the documentation site checks the terminal against is still whole in that file. The reload hook of the command is now a function that returns the sentence instead of an action. With the reply carrying it, the line that says the feature reads Stratum's entity timings is logged at boot only. The lines that say why a block cannot be served stay in the log, once per run, whether they are earned at boot or by the first reload that asks for the block. The notification for a server that is not Stratum opened with the name of the setting and was the only Pulse line that did not open with Pulse. It now reads like the others and speaks of Stratum's entity timings. The fallback scenario reads the reply on a vanilla server with the block on, then switches the block off again and reads the default reply, since it never ran. The two mutations whose patterns the changes moved are realigned: the reload's lease, and the registration, which is now confined to the constructor because the reply has an `if (source != null)` of its own.
On a server that is not Stratum, with the block off, a mod list that could not be read added a line of the feature's own, "could not start the Stratum entity timings", to the line attribution has always logged for it. The feature has no use for the table of owners there, so the mod system now hands it a function instead of the table, and the feature calls it only when the binder found a Stratum it can read; anywhere else it takes an empty table it never consults. On a server that works, a mod list that cannot be read is still the feature's to report, through the guard around its constructor. The lookup of the type is split from the binding, so a test can hand a fake type with the contract's shape to the same code that the mod runs against the game's own API: the mod list is asked for once when the type binds, never when it is absent or predates the contract, and an exception it throws comes out of the constructor. The test that starts the mod with a mod loader that throws now expects attribution's line alone, with the block off or on. The comment that explains the shared table said neither feature depends on the other having started. That holds for the table, but /pulse reload, which is how the Stratum block is applied after boot, is registered by attribution's constructor, so when that fails, or another mod already owns /pulse, the block can only be set at boot. The comment says so, and so does the remark on the reload.
The order in Finish is not a matter of taste: with the admin's timings switch off, Stratum empties its accumulator when the last reader lets go, so a snapshot taken after the release reads nothing. The comment and the class remarks say so, and the fake accumulator and a mutation now enforce it.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 6 of #6: Pulse serves Stratum's per-behavior, per-AI-task and per-entity-type tick timings. They come through the reading contract merged as StratumServer/Stratum#365, and step 5 (#137) already put the parser and fold in place. The feature is off by default.
Draft until a Stratum release carries the contract.
StratumTimingsSource.MinimumStratumVersionis still a placeholder, and with the block on, a Stratum without the contract would print it in the log. It gets filled before merging. The README, CHANGELOG, dashboard and docs site come in step 8, the CI lane on a real Stratum in step 7, and no prerelease is cut from dev before step 8.What it does
A new
StratumTimingsblock inpulse.json:{"Enabled": false, "BurstTicks": 10, "IntervalSeconds": 10}, placed afterAttribution.ConfigUpgradewrites it into existing files,/pulse reloadapplies it live, and it needs no restart.Five counters, registered only when the contract binds:
pulse_stratum_behavior_tick_seconds_total{category,behavior,threadsafe,modid}pulse_stratum_ai_task_tick_seconds_total{category,task}pulse_stratum_entity_tick_seconds_total{type}andpulse_stratum_entity_ticks_total{type}pulse_stratum_timed_ticks_total, which is 0 while the feature runs, so a flat zero shows that nothing is being timed.Its own
DutyCycle:pulse_stratum_timed_ticks_totalcounts exactly the tick cycles between the two snapshots. The lease goes back on every other path too, exactly once: a reload that turns the block off or changes the cycle, a give-up, andDispose.One
ModOwnerstable, built inPulseModSystemand shared with attribution. The behaviors are learned before each fold even when attribution is off. Attribution's behaviour, replies and logs do not change. The Stratum feature reads the mod list only on a server where the contract binds, so on vanilla a mod list that cannot be read costs attribution's warning alone, as before.The
/pulse reloadreply gains one sentence when the block is on or was running. Examples:Reloaded pulse.json. Attribution is off. Stratum entity timings are on: bursts of 10 ticks every 10s. Nothing else in the file differs from what the server is running.... Stratum entity timings are off. ...... Stratum entity timings need a Stratum server; this is not one. ...... Stratum entity timings cannot be read: <reason>. ...... Stratum entity timings are off for the rest of this run. ...With the block off and never run, the reply is word for word what it was.
Log lines, all through
Mod.Logger: the start line at boot. Once per run, the line saying a Stratum server is needed, or why this one cannot be read. One warning on a run-time failure, which turns the feature off for the run. One warning if the behavior walk fails. One warning per family that overflows its 100 series. With the block off, nothing is logged and nothing is served.Checked
StratumTimingsSourceto the real contract at Stratumc53419a, and its findings are applied:pulse_stratum_series, and answers a reload with the sentence above.tools/mutation-check.shkills 187 of 187 mutations, in about 7.5 minutes locally.