Repository navigation
actorevent: define the actor usage event and the activation epoch - #1881
Conversation
366f313 to
1d0a91f
Compare
There was a problem hiding this comment.
Wdyt about taking the event time instead? For a usage sample, that should be when we read it, not when we write it. Then ate.stats.observed_at is not even needed, right?
There was a problem hiding this comment.
Agreed. LogAt takes the time; the step 4 emitter passes the read time. ate.stats.observed_at is gone.
| stability: development | ||
| type: int | ||
| brief: When the measurement was read, as the unix time in nanoseconds. | ||
| note: Logs only. The record timestamp is when the record was written, which can be later. |
There was a problem hiding this comment.
The OTel log model has this in reverse. Timestamp is when the thing happened and ObservedTimestamp is when we saw it. The read time fits Timestamp exactly, so I'd drop this key, see my other comment above.
There was a problem hiding this comment.
Dropped. The read time is the record Timestamp.
| - id: ate.stats.cpu.time | ||
| stability: development | ||
| type: int | ||
| brief: The CPU time used since the activation began, in microseconds. |
There was a problem hiding this comment.
Same name as ate.actor.stats.cpu.time, but that one is in seconds (so are semconv's container.cpu.time and process.cpu.time). Can we emit a double in seconds so a query works the same on both signals?
There was a problem hiding this comment.
Done: double, seconds. The wire stays microseconds; the step 4 emitter converts.
| // NewEmitter writes under scope, one of the scope names above. | ||
| func NewEmitter(lp log.LoggerProvider, scope string) *Emitter { |
There was a problem hiding this comment.
Do we even need a second scope at all? event_name already distinguishes two streams.
There was a problem hiding this comment.
Agreed, one scope. NewEmitter lost the parameter and the docs select by event.name.
There was a problem hiding this comment.
Can we clamp this like addSat in statspoller.go and add a math.MaxUint64 test case?
There was a problem hiding this comment.
Done, with a MaxUint64 case.
| // every source. The cgroup source restarts on its own; the guest-agent | ||
| // source is rebased on its first read after a restore, since the guest's | ||
| // counters survive in guest RAM. An ateom that leaves epoch_unix_nano at zero |
There was a problem hiding this comment.
This is coming later only, right?
There was a problem hiding this comment.
Yes, step 4. Until then an ateom leaves the epoch at zero and sends the raw counter, as the next sentence says.
| - ref: ate.stats.memory.usage | ||
| requirement_level: required |
There was a problem hiding this comment.
How about conditionally_required when the source isn't unspecified, so pending actors don't send fake zeros that skew the avg? Same for memory.peak on old kernels.
There was a problem hiding this comment.
Agreed. The four measurements are conditional on the source not being unspecified, and peak also on the source reporting one. A pending record carries identity, pool, class, source, kind and epoch only. Event gained a Conditional list and the registry test reads both levels.
| - ref: ate.workerpool.namespace | ||
| requirement_level: required |
There was a problem hiding this comment.
Should we match this with:
substrate/docs/metrics/registry/metrics.yaml
Lines 897 to 899 in 54cba01
There was a problem hiding this comment.
I would keep them required here. The instrument's condition exists because atelet resolves the pool from a pod list that can miss. The step 4 emitter gets both from the downward API, metadata.namespace and the ate.dev/worker-pool label, so it always knows them.
| brief: > | ||
| The activation of the actor that a record belongs to, as the unix | ||
| time in nanoseconds when it began. |
There was a problem hiding this comment.
This is basically OTel's cumulative StartTimeUnixNano, right?
There was a problem hiding this comment.
Exactly that. The note now says so.
|
|
||
| // TestEmitterScope pins that records land under the scope the emitter was | ||
| // built with, so the usage stream cannot share the lifecycle scope by accident. | ||
| func TestEmitterScope(t *testing.T) { |
There was a problem hiding this comment.
Nit: if we do this as a table with t.Run it would match the other tests here. But it goes away anyway if we drop the second scope, see my comment above.
There was a problem hiding this comment.
Gone with the second scope.
ate.actor.usage_sampled is the record an ateom will emit per actor: identity, pool, sandbox class, source, kind, the activation epoch, and, once the actor is measurable, four measurements. The measurements are conditional so a pending actor sends no fake zeros; absence means not measured. They share the names and units of the ate.actor.stats.* instruments, so one query serves both signals. The record's timestamp is the read time, which LogAt now takes from the caller. A uint64 attribute above MaxInt64 now clamps rather than wraps, as the metric side does. The epoch identifies one activation of an actor, a Run or Restore, as the unix-nano time it began: the StartTimeUnixNano of a cumulative metric point, carried as an attribute. It is what makes CPU time summable: within an epoch the counter is monotonic from zero, so lifetime CPU is the sum over epochs of each epoch's highest value. Without it, no comparison of consecutive samples finds every boundary, since a restored guest can resume its counter above the last reported value. WorkloadStatsSample carries the epoch too, and its measurement comments now say which figures are per activation, which are absolute, and which are raw. The registry declares the event and its attributes, all logs-only, and bars the epoch from metric labels alongside the other per-actor keys. Nothing emits the event or sets the epoch yet.
1d0a91f to
f30634d
Compare
Step 3 of #1748. Vocabulary only: nothing emits the event or sets the epoch yet.
Event.
ate.actor.usage_sampledinactorevent, sixteen required keys viaateattr: identity, pool, sandbox class, source,ate.stats.kind(periodic, initial, final), the four measurements, observation time, epoch. Measurement names follow theate.actor.stats.*instruments; units are in the registry briefs. Body isActor usage sampled, the accepted stdout shape change.Scope. The usage stream has its own instrumentation scope, the lifecycle scope with
/usageappended, so a consumer can sample or drop it without touching lifecycle records.NewEmittertakes the scope; the package-levelLogkeeps the lifecycle one, so ateapi is unchanged.Epoch.
ate.actor.epochandWorkloadStatsSample.epoch_unix_nano: the unix-nano time an activation began. CPU time is per activation for every source; lifetime is the sum over epochs of each epoch's highest value. Memory usage and working set are absolute; peak is as the source reports it. An ateom that leaves the epoch at zero still sends the raw guest counter. The measurement comments on fields 8 to 11 say all of this.Registry. Event group in
events.yaml, seven attributes inmetrics.yaml,ate.actor.epochin the no-actor-identity forbidden list. The one-record-one-place rule and the collector guide now name both scopes.Left for step 4: the event's code anchor moves to the ateom emit site, atelet's poller comments and the guide's stdout usage section get rewritten, and the controller starts passing
OTEL_LOGS_EXPORTERto worker pods.