Repository navigation
demos: add actor telemetry continuity reference demo (#503) - #750
Sanskar Singh Bhardwaj (sanskar-singh-2403) wants to merge 1 commit into
Conversation
| return res, nil | ||
| } | ||
|
|
||
| func actorName() string { |
There was a problem hiding this comment.
ATE_ACTOR_NAME isn't set anywhere in the repo, and os.Hostname() returns "runsc" for every actor — it's hardcoded in the OCI spec (cmd/atelet/oci.go:271) and applied via the UTS namespace. So this always returns runsc.
The documented way is /run/ate/actor-id (docs/api-guide.md:108), with a working example in internal/e2e/fixtures/probe/main.go:32. api-guide.md:111 also warns specifically against the env-var approach, since env is frozen in the golden snapshot.
There was a problem hiding this comment.
Good catch, confirmed. os.Hostname() returns runsc for every actor (hardcoded at cmd/atelet/oci.go:268) and ATE_ACTOR_NAME is set nowhere, so this always returned runsc. Switched to reading /run/ate/actor-id fresh per the guidance in docs/api-guide.md, mirroring internal/e2e/fixtures/probe/main.go.
| ctx := context.Background() | ||
| slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) | ||
|
|
||
| res, err := newResource(ctx) |
There was a problem hiding this comment.
This resource is built in main(), so it's captured by the golden snapshot and identical for every actor of the template: even reading /run/ate/actor-id here would give you the golden actor's name.
There was a problem hiding this comment.
Right, building it in main() freezes it into the golden snapshot. Moved actor identity out of the resource entirely and now read it fresh in the request handler, so it reflects the resumed actor rather than the golden one. Updated the test to cover the fresh-read and the missing-file fallback.
| requests, err := meter.Int64Counter( | ||
| "demo.actor.requests", | ||
| metric.WithDescription("Requests handled by the actor-telemetry demo, by actor."), | ||
| ) |
There was a problem hiding this comment.
I just filed #761 : the counter might have the merge and conflate problem when actors run on different worker pods during its lifecycle.
There was a problem hiding this comment.
got it that is the merge/conflate concern across worker pods and is orthogonal to this demo's fix; happy to track the counter-aggregation semantics there.
| ctx, span := tracer.Start(r.Context(), "handle-request") | ||
| defer span.End() | ||
|
|
||
| requests.Add(ctx, 1, metric.WithAttributes( |
There was a problem hiding this comment.
ate.dev/actor_name on the metric datapoint is unbounded cardinality. The north star of this project is to have billions of actors.
On the span below it's fine, since a span is an event rather than a series.
For a reference demo I'd keep actor identity on the span and use template name on the metric.
There was a problem hiding this comment.
Agreed, per-actor labels on the metric are unbounded cardinality at billions of actors. Changed the counter to label with the bounded ate.dev/template and kept ate.dev/actor_name on the span only, since a span is an event rather than a series. README updated to match.
521a7f0 to
5f87ad3
Compare
| func getCurrentIP() string { | ||
| addrs, err := net.InterfaceAddrs() | ||
| if err != nil { | ||
| return "x.x.x.x" | ||
| } | ||
| for _, addr := range addrs { | ||
| if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { | ||
| if ipnet.IP.To4() != nil { | ||
| return ipnet.IP.String() | ||
| } | ||
| } | ||
| } | ||
| return "y.y.y.y" | ||
| } |
There was a problem hiding this comment.
This is the os.Hostname() problem again. I'd either drop it, or keep it and relabel it to something like fake-static IP.
There was a problem hiding this comment.
Removed getCurrentIP entirely. Per #761 every actor egresses behind one constant interior IP inside the gVisor netns, so it could never identify the worker pod. The response now reports the actor name (read fresh from the identity file) instead.
| ```bash | ||
| curl -X POST -H "Host: my-telemetry-1.demo.actors.resources.substrate.ate.dev" http://localhost:8000 | ||
| ``` | ||
| Each response reports the worker pod that served it. The metric |
There was a problem hiding this comment.
This is not correct, see https://github.com/agent-substrate/substrate/pull/750/changes#r3728711006
There was a problem hiding this comment.
Fixed. Reworded to "reports the actor it was handled for" now that the IP is gone.
| sdktrace.WithBatcher(exp, sdktrace.WithBatchTimeout(*pushInterval)), | ||
| ) | ||
| otel.SetTracerProvider(tp) | ||
| otel.SetTextMapPropagator(propagation.TraceContext{}) |
There was a problem hiding this comment.
You set the propagator but never extract, so tracer always starts a root span and the actor's traces never join the router's. See #427
You can also use otelhttp.NewHandler to do avoid extraction and setting attrs manually.
There was a problem hiding this comment.
Good catch. Wrapped the mux in otelhttp.NewHandler(mux, "/") so the incoming W3C trace context is extracted and the per-request span joins the router's trace instead of starting a detached root. Follows the same pattern as the atenet router and glutton.
| // Metric: label with the bounded template name only. Per-actor labels | ||
| // are unbounded cardinality at this project's scale (see templateName). | ||
| requests.Add(ctx, 1, metric.WithAttributes( | ||
| attribute.String("ate.dev/template", templateName), |
There was a problem hiding this comment.
ate.dev/ is the k8s label and what we log to sdtout. Span and metric attributes use the ate.* namespace. So these should be ate.actor.template.name here and ate.actor.name below.
Please also update this in README.
There was a problem hiding this comment.
Done. Switched to the dotted ate.* namespace: ate.actor.template.name on the metric and ate.actor.name on the span, matching internal/ateattr. Updated the README to match.
| // OTLP every push-interval. The long interval is the whole point: it is what | ||
| // leaves telemetry buffered when a suspend arrives before the timer fires. | ||
| func initMeterProvider(ctx context.Context, res *resource.Resource) (*sdkmetric.MeterProvider, error) { | ||
| exp, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithInsecure()) |
There was a problem hiding this comment.
Dropped the hardcoded WithInsecure() from both exporters. The demo endpoint is plaintext http://, which the SDK already infers from the scheme, so nothing regresses while the SDK's env-driven TLS config is now respected.
| meter := mp.Meter(serviceName) | ||
| requests, err := meter.Int64Counter( | ||
| "demo.actor.requests", | ||
| metric.WithDescription("Requests handled by the actor-telemetry demo, by actor."), |
There was a problem hiding this comment.
| metric.WithDescription("Requests handled by the actor-telemetry demo, by actor."), | |
| metric.WithDescription("Requests handled by the actor-telemetry demo, by template."), |
There was a problem hiding this comment.
done
| } | ||
|
|
||
| meter := mp.Meter(serviceName) | ||
| requests, err := meter.Int64Counter( |
There was a problem hiding this comment.
Nit: please add metric.WithUnit("{request}")
There was a problem hiding this comment.
done
| # listening, so telemetry would be flushed into the void. This mirrors the | ||
| # endpoint the ate-system components use on Kind (see | ||
| # manifests/ate-install/kind/kustomization.yaml). | ||
| - name: OTEL_EXPORTER_OTLP_ENDPOINT |
There was a problem hiding this comment.
Da Huang (@git286), I think it's a good opportunity to set delta temporarily here as well. See my reasoning in #761 (comment)
There was a problem hiding this comment.
Set OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta in the template. Agreed it fits this suspend/resume workload: with cumulative, a restore from the golden snapshot carries the golden actor's accumulated value and reset detection cannot see the restart, so delta avoids the inflate/merge. Tracking the broader identity fix in #761.
| res, err := resource.New(ctx, | ||
| resource.WithTelemetrySDK(), | ||
| resource.WithSchemaURL(semconv.SchemaURL), | ||
| resource.WithAttributes( |
There was a problem hiding this comment.
Please add service.instance.id
There was a problem hiding this comment.
Added semconv.ServiceInstanceID, generated once and shared by the meter and tracer resources, mirroring internal/serverboot.
…#503) Add a reference demo for actor telemetry continuity across suspend/resume, the follow-up work requested in agent-substrate#503. The demo is a small Go HTTP server that emits OpenTelemetry metrics and trace spans over OTLP on every request. It reproduces the agent-substrate#503 loss: an OTel push exporter batches telemetry in memory and flushes on a timer, so when the push interval (default 60s) is longer than the active window before idle suspension, telemetry is still buffered in the SDK when the actor is suspended. With snapshotsConfig.onPause: Data (the mode the template uses) Substrate drops process RAM on pause, so that buffered telemetry is lost. The mitigation is isolated behind a flushTelemetry(ctx) seam that ForceFlushes both the MeterProvider and TracerProvider. Today it is called from the self-suspend path via an idle watcher; when the PreSuspend hook (agent-substrate#450) lands, flushTelemetry does not change, only its call-site moves into the PreSuspend handler. This keeps agent-substrate#450 a call-site move rather than a rewrite. The ActorTemplate sets OTEL_EXPORTER_OTLP_ENDPOINT to the in-cluster collector, since the SDK default (localhost:4317) has nothing listening inside the actor sandbox netns. Includes install-demo-actor-telemetry.sh wired into install-ate.sh, a rendered-and-validated template, unit tests for the idle-flush watcher, and a README documenting the reproduce/verify flow against Jaeger and Prometheus on Kind.
5f87ad3 to
46a8ac3
Compare
Add a reference demo for actor telemetry continuity across suspend/resume, the
follow-up work requested in #503.
The demo is a small Go HTTP server that emits OpenTelemetry metrics and trace
spans over OTLP on every request. It reproduces the #503 loss: an OTel push
exporter batches telemetry in memory and flushes on a timer, so when the push
interval (default 60s) is longer than the active window before idle suspension,
telemetry is still buffered in the SDK when the actor is suspended. With
snapshotsConfig.onPause: Data(the mode the template uses) Substrate dropsprocess RAM on pause, so that buffered telemetry is lost.
The mitigation is isolated behind a
flushTelemetry(ctx)seam that ForceFlushesboth the MeterProvider and TracerProvider. Today it is called from the
self-suspend path via an idle watcher; when the PreSuspend hook (#450) lands,
flushTelemetrydoes not change, only its call-site moves into the PreSuspendhandler. This keeps #450 a call-site move rather than a rewrite.
The ActorTemplate sets
OTEL_EXPORTER_OTLP_ENDPOINTto the in-clustercollector, since the SDK default (
localhost:4317) has nothing listening insidethe actor sandbox netns.
Includes
install-demo-actor-telemetry.shwired intoinstall-ate.sh, arendered-and-validated template, unit tests for the idle-flush watcher, and a
README documenting the reproduce/verify flow against Jaeger and Prometheus on
Kind.
Scope
This covers the "reference demo for actor telemetry continuity" action item in
#503 only. The
docs/observability.mdupdate is tracked separately, and thePreSuspend/PostResumealignment is #450.Part of #503
Testing
gofmt,go vet,go build, andgo test ./demos/actor-telemetry/all pass(4/4 unit tests on the idle-flush watcher).
Template renders via the install script's
sedsubstitution and parses asvalid Kubernetes manifests.
End-to-end suspend/resume verification against a live Kind cluster with
ate-system is described in the README.
Tests pass
Appropriate changes to documentation are included in the PR