diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index b9b0a346..5360c9e8 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -92,6 +92,14 @@ type ApiService struct { monitorMu sync.Mutex lifecycleCtx context.Context lifecycleCancel context.CancelFunc + + // Reader for durable S2 telemetry storage. Nil when S2 is not configured. + telemetryReader *events.S2Reader +} + +// s2Enabled reports whether durable S2 telemetry storage is configured. +func (s *ApiService) s2Enabled() bool { + return s.telemetryReader != nil } var _ oapi.StrictServerInterface = (*ApiService)(nil) @@ -105,6 +113,7 @@ func New( telemetrySession *telemetry.TelemetrySession, eventStream *events.EventStream, displayNum int, + telemetryReader *events.S2Reader, ) (*ApiService, error) { switch { case recordManager == nil: @@ -140,6 +149,7 @@ func New( cdpMonitor: mon, lifecycleCtx: ctx, lifecycleCancel: cancel, + telemetryReader: telemetryReader, }, nil } diff --git a/server/cmd/api/api/api_test.go b/server/cmd/api/api/api_test.go index 4f2c2165..42c00717 100644 --- a/server/cmd/api/api/api_test.go +++ b/server/cmd/api/api/api_test.go @@ -321,7 +321,7 @@ func newTelemetrySession(t *testing.T) (*telemetry.TelemetrySession, *events.Eve func newSvc(t *testing.T, mgr recorder.RecordManager) (*ApiService, error) { t.Helper() ts, es := newTelemetrySession(t) - return New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0) + return New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0, nil) } func TestApiService_PatchChromiumFlags(t *testing.T) { diff --git a/server/cmd/api/api/display_test.go b/server/cmd/api/api/display_test.go index 154b6359..c37700c3 100644 --- a/server/cmd/api/api/display_test.go +++ b/server/cmd/api/api/display_test.go @@ -36,7 +36,7 @@ func testFFmpegFactory(t *testing.T, tempDir string) recorder.FFmpegRecorderFact func newTestServiceWithFactory(t *testing.T, mgr recorder.RecordManager, factory recorder.FFmpegRecorderFactory) *ApiService { t.Helper() ts, es := newTelemetrySession(t) - svc, err := New(mgr, factory, newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0) + svc, err := New(mgr, factory, newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0, nil) require.NoError(t, err) return svc } diff --git a/server/cmd/api/api/events.go b/server/cmd/api/api/events.go index 27dfc50a..16f1a7fd 100644 --- a/server/cmd/api/api/events.go +++ b/server/cmd/api/api/events.go @@ -12,6 +12,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/events" + "github.com/kernel/kernel-images/server/lib/logger" oapi "github.com/kernel/kernel-images/server/lib/oapi" ) @@ -136,6 +137,123 @@ func (s *ApiService) StreamTelemetryEvents(ctx context.Context, req oapi.StreamT return oapi.StreamTelemetryEvents200TexteventStreamResponse{Body: pr, Headers: headers}, nil } +const ( + // defaultReadWindow bounds a read that supplies no since/until. + defaultReadWindow = 5 * time.Minute + // defaultPageSize / maxPageSize bound how many records one page reads. + defaultPageSize = 100 + maxPageSize = 1000 +) + +// ReadTelemetryEvents handles GET /telemetry/events. +// Reads one page of archived telemetry envelopes for this browser from durable +// S2 storage in ascending sequence order, applying the category filter. The +// X-Has-More / X-Next-Offset response headers carry the pagination cursor. +// Returns an empty list when S2 storage is not configured. +func (s *ApiService) ReadTelemetryEvents(ctx context.Context, req oapi.ReadTelemetryEventsRequestObject) (oapi.ReadTelemetryEventsResponseObject, error) { + log := logger.FromContext(ctx) + + if !s.s2Enabled() { + return readTelemetryEventsOKResponse{}, nil + } + + result, err := s.telemetryReader.Read(ctx, buildReadOptions(req.Params), log) + if err != nil { + log.Error("failed to read telemetry events from S2", "err", err) + return oapi.ReadTelemetryEvents500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to read telemetry events"}}, nil + } + + // has_more / next cursor track the raw stream position, independent of the + // category filter, so a filtered page may come back empty while more remain. + envs := filterByCategory(result.Envelopes, req.Params.Category) + + return readTelemetryEventsOKResponse{envs: envs, nextSeqNum: result.NextSeqNum, hasMore: result.HasMore}, nil +} + +// buildReadOptions maps query params to a bounded, paginated read. offset is the +// S2 sequence cursor and takes precedence over since as the start position (the +// two are mutually exclusive starts); until bounds the window end; the page is +// bounded to limit records. +func buildReadOptions(p oapi.ReadTelemetryEventsParams) events.ReadOptions { + var opts events.ReadOptions + + switch { + case p.Offset != nil && *p.Offset >= 0: + seq := uint64(*p.Offset) + opts.SeqNum = &seq + case p.Since != nil: + ts := uint64(*p.Since) + opts.Timestamp = &ts + default: + ts := uint64(time.Now().Add(-defaultReadWindow).UnixMilli()) + opts.Timestamp = &ts + } + + if p.Until != nil { + until := uint64(*p.Until) + opts.Until = &until + } + + count := uint64(pageSize(p.Limit)) + opts.Count = &count + return opts +} + +// pageSize clamps a requested limit into [1, maxPageSize], defaulting when unset. +func pageSize(limit *int) int { + switch { + case limit == nil: + return defaultPageSize + case *limit < 1: + return 1 + case *limit > maxPageSize: + return maxPageSize + default: + return *limit + } +} + +func filterByCategory(envs []events.Envelope, cats *[]oapi.TelemetryEventCategory) []events.Envelope { + if cats == nil || len(*cats) == 0 { + return envs + } + want := make(map[oapi.TelemetryEventCategory]struct{}, len(*cats)) + for _, c := range *cats { + want[c] = struct{}{} + } + out := make([]events.Envelope, 0, len(envs)) + for _, e := range envs { + if _, ok := want[e.Event.Category]; ok { + out = append(out, e) + } + } + return out +} + +// readTelemetryEventsOKResponse serializes a page of events.Envelope directly, +// matching the SSE stream and publish endpoints. The pagination cursor rides in +// the X-Has-More / X-Next-Offset headers (X-Next-Offset only when there is more), +// following the offset_pagination convention used by the list endpoints. +type readTelemetryEventsOKResponse struct { + envs []events.Envelope + nextSeqNum uint64 + hasMore bool +} + +func (r readTelemetryEventsOKResponse) VisitReadTelemetryEventsResponse(w http.ResponseWriter) error { + w.Header().Set("X-Has-More", strconv.FormatBool(r.hasMore)) + if r.hasMore { + w.Header().Set("X-Next-Offset", strconv.FormatUint(r.nextSeqNum, 10)) + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + envs := r.envs + if envs == nil { + envs = []events.Envelope{} + } + return json.NewEncoder(w).Encode(envs) +} + // publishTelemetryEventOKResponse serializes events.Envelope directly so the response // is identical in shape to the SSE stream frames. type publishTelemetryEventOKResponse struct{ env events.Envelope } diff --git a/server/cmd/api/api/events_test.go b/server/cmd/api/api/events_test.go index 36ab935f..90f0a939 100644 --- a/server/cmd/api/api/events_test.go +++ b/server/cmd/api/api/events_test.go @@ -4,6 +4,8 @@ import ( "bufio" "context" "encoding/json" + "net/http" + "net/http/httptest" "strconv" "strings" "testing" @@ -296,3 +298,74 @@ func TestStreamResumeAfterLastEventIDUnchanged(t *testing.T) { id := streamFirstID(t, svc, oapi.StreamTelemetryEventsParams{LastEventID: ptrOf("5")}) assert.Equal(t, uint64(6), id, "Last-Event-ID without replay must behave as before and resume after seq 5") } + +func TestReadTelemetryEventsS2Disabled(t *testing.T) { + t.Parallel() + svc := newTestService(t, newMockRecordManager()) // s2 creds empty -> disabled + + resp, err := svc.ReadTelemetryEvents(context.Background(), oapi.ReadTelemetryEventsRequestObject{}) + require.NoError(t, err) + ok, isOK := resp.(readTelemetryEventsOKResponse) + require.True(t, isOK, "expected 200 response when S2 is disabled") + + rec := httptest.NewRecorder() + require.NoError(t, ok.VisitReadTelemetryEventsResponse(rec)) + assert.Equal(t, http.StatusOK, rec.Code) + // Empty result must serialize as [] not null, or the Python SDK chokes. + assert.JSONEq(t, `[]`, rec.Body.String()) + assert.Equal(t, "false", rec.Header().Get("X-Has-More")) + assert.Empty(t, rec.Header().Get("X-Next-Offset"), "no cursor when there is no more") +} + +func TestFilterByCategory(t *testing.T) { + t.Parallel() + mk := func(c oapi.TelemetryEventCategory) events.Envelope { + return events.Envelope{Event: events.Event{Category: c}} + } + envs := []events.Envelope{mk(events.Console), mk(events.Network), mk(events.Console)} + + assert.Len(t, filterByCategory(envs, nil), 3, "nil filter keeps everything") + + cats := []oapi.TelemetryEventCategory{events.Console} + assert.Len(t, filterByCategory(envs, &cats), 2) +} + +func TestPageSize(t *testing.T) { + t.Parallel() + assert.Equal(t, defaultPageSize, pageSize(nil), "unset defaults") + + ptr := func(n int) *int { return &n } + assert.Equal(t, 50, pageSize(ptr(50))) + assert.Equal(t, 1, pageSize(ptr(0)), "clamped up to 1") + assert.Equal(t, 1, pageSize(ptr(-5)), "clamped up to 1") + assert.Equal(t, maxPageSize, pageSize(ptr(5000)), "clamped down to max") +} + +func TestBuildReadOptions(t *testing.T) { + t.Parallel() + + // No params: defaults the start to ~defaultReadWindow ago, no end bound, default page. + opts := buildReadOptions(oapi.ReadTelemetryEventsParams{}) + require.NotNil(t, opts.Timestamp) + assert.Nil(t, opts.SeqNum) + assert.Nil(t, opts.Until) + require.NotNil(t, opts.Count) + assert.Equal(t, uint64(defaultPageSize), *opts.Count) + + // since/until/limit map through; limit bounds the page. + since, until, limit := int64(1000), int64(2000), 25 + opts = buildReadOptions(oapi.ReadTelemetryEventsParams{Since: &since, Until: &until, Limit: &limit}) + require.NotNil(t, opts.Timestamp) + assert.Equal(t, uint64(1000), *opts.Timestamp) + require.NotNil(t, opts.Until) + assert.Equal(t, uint64(2000), *opts.Until) + require.NotNil(t, opts.Count) + assert.Equal(t, uint64(25), *opts.Count) + + // offset is the cursor and takes precedence over since (SeqNum start, no Timestamp). + offset := int64(4213) + opts = buildReadOptions(oapi.ReadTelemetryEventsParams{Offset: &offset, Since: &since}) + require.NotNil(t, opts.SeqNum) + assert.Equal(t, uint64(4213), *opts.SeqNum) + assert.Nil(t, opts.Timestamp, "since is ignored when offset is set") +} diff --git a/server/cmd/api/api/middleware.go b/server/cmd/api/api/middleware.go index a558199a..14508fde 100644 --- a/server/cmd/api/api/middleware.go +++ b/server/cmd/api/api/middleware.go @@ -35,6 +35,15 @@ func DisableTelemetryMiddleware() { telemetryMiddlewareEnabled.Store(false) } // TelemetryMiddlewareEnabled reports the current state. func TelemetryMiddlewareEnabled() bool { return telemetryMiddlewareEnabled.Load() } +// telemetryReadOps are the telemetry-observer endpoints that must not emit their +// own api_call event: doing so would append to the very stream they read, a +// feedback loop that (e.g. at page size 1) prevents a paginated read from ever +// catching the tail. +var telemetryReadOps = map[string]struct{}{ + "ReadTelemetryEvents": {}, + "StreamTelemetryEvents": {}, +} + // TelemetryHTTPMiddleware emits a BrowserApiCallEvent per documented operation, // capturing the final status and wall-clock duration. publish is wired to // TelemetrySession.Publish; the middleware ignores the returns. @@ -55,6 +64,9 @@ func TelemetryHTTPMiddleware(publish func(events.Event) (events.Envelope, bool)) if tc.operationID == "" { return } + if _, skip := telemetryReadOps[tc.operationID]; skip { + return + } data, _ := json.Marshal(oapi.BrowserApiCallEventData{ RequestId: chiMiddleware.GetReqID(ctx), OperationId: tc.operationID, diff --git a/server/cmd/api/api/telemetry_test.go b/server/cmd/api/api/telemetry_test.go index 258b6987..7b6a0b52 100644 --- a/server/cmd/api/api/telemetry_test.go +++ b/server/cmd/api/api/telemetry_test.go @@ -358,7 +358,7 @@ func (m *mockRecordManager) StopAll(_ context.Context) error func newTestService(t *testing.T, mgr recorder.RecordManager) *ApiService { t.Helper() ts, es := newTelemetrySession(t) - svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0) + svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t), ts, es, 0, nil) require.NoError(t, err) svc.cdpMonitor = &stubCdpMonitor{} return svc diff --git a/server/cmd/api/main.go b/server/cmd/api/main.go index b3500a52..822fcf12 100644 --- a/server/cmd/api/main.go +++ b/server/cmd/api/main.go @@ -118,7 +118,10 @@ func main() { var s2Writer *events.S2StorageWriter if config.S2Basin != "" && config.S2AccessToken != "" && config.S2Stream != "" { slogger.Info("S2 storage enabled", "basin", config.S2Basin, "stream", config.S2Stream) - s2Writer = events.NewS2StorageWriter(eventStream, config.S2Basin, config.S2AccessToken, config.S2Stream, events.S2Config{}, slogger) + s2Writer = events.NewS2StorageWriter(eventStream, config.S2Basin, config.S2AccessToken, config.S2Stream, events.S2Config{ + BatcherLinger: config.S2BatcherLinger, + BatcherMaxRecords: config.S2BatcherMaxRecords, + }, slogger) if err := s2Writer.Start(ctx); err != nil { slogger.Error("failed to start S2 storage writer", "err", err) os.Exit(1) @@ -134,6 +137,7 @@ func main() { telemetrySession, eventStream, config.DisplayNum, + events.NewS2Reader(config.S2Basin, config.S2AccessToken, config.S2Stream), ) if err != nil { slogger.Error("failed to create api service", "err", err) diff --git a/server/cmd/config/config.go b/server/cmd/config/config.go index aae38b9e..1caa3cd7 100644 --- a/server/cmd/config/config.go +++ b/server/cmd/config/config.go @@ -47,6 +47,9 @@ type Config struct { S2Basin string `envconfig:"S2_BASIN" default:""` S2AccessToken string `envconfig:"S2_ACCESS_TOKEN" default:""` S2Stream string `envconfig:"S2_STREAM" default:""` + // S2 batcher tuning for the append path. + S2BatcherLinger time.Duration `envconfig:"S2_BATCHER_LINGER" default:"100ms"` + S2BatcherMaxRecords int `envconfig:"S2_BATCHER_MAX_RECORDS" default:"50"` } // LogValue implements slog.LogValuer, redacting secret fields. @@ -73,6 +76,8 @@ func (c *Config) LogValue() slog.Value { slog.String("s2_basin", c.S2Basin), slog.String("s2_access_token", s2AccessToken), slog.String("s2_stream", c.S2Stream), + slog.Duration("s2_batcher_linger", c.S2BatcherLinger), + slog.Int("s2_batcher_max_records", c.S2BatcherMaxRecords), ) } diff --git a/server/cmd/config/config_test.go b/server/cmd/config/config_test.go index 27dd8b3d..447e7ac9 100644 --- a/server/cmd/config/config_test.go +++ b/server/cmd/config/config_test.go @@ -31,6 +31,8 @@ func TestLoad(t *testing.T) { ChromeDriverProxyPort: 9224, ChromeDriverUpstreamAddr: "127.0.0.1:9225", DevToolsProxyAddr: "127.0.0.1:9222", + S2BatcherLinger: 100 * time.Millisecond, + S2BatcherMaxRecords: 50, }, }, { @@ -48,6 +50,8 @@ func TestLoad(t *testing.T) { "SCALE_TO_ZERO_COOLDOWN": "5s", "CHROMEDRIVER_PROXY_PORT": "5432", "CHROMEDRIVER_UPSTREAM_ADDR": "127.0.0.1:9999", + "S2_BATCHER_LINGER": "250ms", + "S2_BATCHER_MAX_RECORDS": "100", }, wantCfg: &Config{ Port: 12345, @@ -63,6 +67,8 @@ func TestLoad(t *testing.T) { ChromeDriverProxyPort: 5432, ChromeDriverUpstreamAddr: "127.0.0.1:9999", DevToolsProxyAddr: "127.0.0.1:9876", + S2BatcherLinger: 250 * time.Millisecond, + S2BatcherMaxRecords: 100, }, }, { @@ -85,6 +91,8 @@ func TestLoad(t *testing.T) { ChromeDriverProxyPort: 9224, ChromeDriverUpstreamAddr: "127.0.0.1:9225", DevToolsProxyAddr: "10.0.0.1:1234", + S2BatcherLinger: 100 * time.Millisecond, + S2BatcherMaxRecords: 50, }, }, { diff --git a/server/e2e/e2e_telemetry_events_read_test.go b/server/e2e/e2e_telemetry_events_read_test.go new file mode 100644 index 00000000..2aff7095 --- /dev/null +++ b/server/e2e/e2e_telemetry_events_read_test.go @@ -0,0 +1,183 @@ +package e2e + +import ( + "context" + "fmt" + "net/http" + "os" + "os/exec" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + instanceoapi "github.com/kernel/kernel-images/server/lib/oapi" +) + +// startTelemetryReadContainer boots a headless container wired to S2 on a +// per-test stream (so tests sharing the S2_STREAM env don't pollute each other) +// and starts a telemetry session. Skips when S2 creds or docker are absent. +func startTelemetryReadContainer(t *testing.T, ctx context.Context) *instanceoapi.ClientWithResponses { + t.Helper() + basin := os.Getenv("S2_BASIN") + accessToken := os.Getenv("S2_ACCESS_TOKEN") + stream := os.Getenv("S2_STREAM") + if basin == "" || accessToken == "" || stream == "" { + t.Skip("S2_BASIN, S2_ACCESS_TOKEN, and S2_STREAM must be set to run this test") + } + if _, err := exec.LookPath("docker"); err != nil { + t.Skipf("docker not available: %v", err) + } + + c := NewTestContainer(t, headlessImage) + require.NoError(t, c.Start(ctx, ContainerConfig{ + Env: map[string]string{ + "S2_BASIN": basin, + "S2_ACCESS_TOKEN": accessToken, + "S2_STREAM": fmt.Sprintf("%s-%s", stream, t.Name()), + }, + }), "failed to start container") + t.Cleanup(func() { c.Stop(context.Background()) }) + + require.NoError(t, c.WaitReady(ctx), "api not ready") + + client, err := c.APIClient() + require.NoError(t, err) + + startResp, err := client.PutTelemetryWithResponse(ctx, instanceoapi.PutTelemetryJSONRequestBody{}) + require.NoError(t, err) + require.Equal(t, http.StatusCreated, startResp.StatusCode(), "put telemetry: %s", string(startResp.Body)) + return client +} + +// TestReadTelemetryEvents publishes a known set of events and reads them back +// through GET /telemetry/events against a real S2 stream, exercising the full +// archive read path rather than the in-memory ring buffer. +// +// Skips automatically when S2_BASIN, S2_ACCESS_TOKEN, or S2_STREAM are unset. +func TestReadTelemetryEvents(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + + client := startTelemetryReadContainer(t, ctx) + + // Publish a deterministic set of events across two enabled categories. + const systemCount, connectionCount = 3, 2 + for i := 0; i < systemCount; i++ { + publishEvent(t, ctx, client, "test.system", instanceoapi.PublishEventRequestCategorySystem) + } + for i := 0; i < connectionCount; i++ { + publishEvent(t, ctx, client, "test.connection", instanceoapi.PublishEventRequestCategoryConnection) + } + + // Give the storage writer time to flush to S2 (batcher linger + network). + time.Sleep(2 * time.Second) + + // Bound every read tightly: a correct handler caps the S2 read, so these + // return promptly. A hang here means the read is unbounded. + readCtx, readCancel := context.WithTimeout(ctx, 10*time.Second) + defer readCancel() + + // Full read returns at least everything we published. + all, _, _ := readEventsPage(t, readCtx, client, &instanceoapi.ReadTelemetryEventsParams{}) + assert.GreaterOrEqual(t, len(all), systemCount+connectionCount) + + // Category filter returns only the requested category. + systemCat := []instanceoapi.TelemetryEventCategory{instanceoapi.TelemetryEventCategorySystem} + systemOnly, _, _ := readEventsPage(t, readCtx, client, &instanceoapi.ReadTelemetryEventsParams{Category: &systemCat}) + assert.GreaterOrEqual(t, len(systemOnly), systemCount) + for _, e := range systemOnly { + require.NotNil(t, e.Event.Category) + assert.Equal(t, instanceoapi.TelemetryEventCategorySystem, *e.Event.Category) + } + + // An empty window returns [] (not null) with no cursor. + pastSince, pastUntil := int64(1), int64(2) + empty, err := client.ReadTelemetryEventsWithResponse(readCtx, &instanceoapi.ReadTelemetryEventsParams{Since: &pastSince, Until: &pastUntil}) + require.NoError(t, err) + require.NotNil(t, empty.JSON200) + assert.Empty(t, *empty.JSON200) + assert.JSONEq(t, `[]`, string(empty.Body), "empty result must be [] not null") + assert.Equal(t, "false", empty.HTTPResponse.Header.Get("X-Has-More")) +} + +// TestReadTelemetryEventsPagination publishes more events than the page size and +// walks every page via the X-Next-Offset cursor, asserting the full set comes +// back exactly once in ascending order. +func TestReadTelemetryEventsPagination(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + + client := startTelemetryReadContainer(t, ctx) + + const published = 5 + for i := 0; i < published; i++ { + publishEvent(t, ctx, client, "test.system", instanceoapi.PublishEventRequestCategorySystem) + } + time.Sleep(2 * time.Second) + + readCtx, readCancel := context.WithTimeout(ctx, 20*time.Second) + defer readCancel() + + const pageLimit = 2 + var collected []instanceoapi.TelemetryEnvelope + var offset *int64 + pages := 0 + for { + pages++ + require.LessOrEqual(t, pages, 50, "pagination did not terminate") + limit := pageLimit + page, hasMore, next := readEventsPage(t, readCtx, client, &instanceoapi.ReadTelemetryEventsParams{Limit: &limit, Offset: offset}) + require.LessOrEqual(t, len(page), pageLimit, "a page must not exceed the limit") + collected = append(collected, page...) + if !hasMore { + break + } + offset = &next + } + + require.GreaterOrEqual(t, pages, 3, "5 events at limit 2 should span multiple pages") + require.GreaterOrEqual(t, len(collected), published) + // Strictly ascending seqs prove the cursor neither skips nor re-reads across + // page boundaries. + for i := 1; i < len(collected); i++ { + assert.Greater(t, collected[i].Seq, collected[i-1].Seq, "events must be strictly ascending with no dupes across pages") + } + + // Reads must be side-effect-free: reading telemetry must not emit an api_call + // event back into the stream, or pagination could never catch the tail. Two + // full reads must return the same count. + first, _, _ := readEventsPage(t, readCtx, client, &instanceoapi.ReadTelemetryEventsParams{}) + second, _, _ := readEventsPage(t, readCtx, client, &instanceoapi.ReadTelemetryEventsParams{}) + assert.Equal(t, len(first), len(second), "a read must not append to the stream it reads") +} + +// readEventsPage calls the endpoint and returns the page plus its cursor state. +func readEventsPage(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses, params *instanceoapi.ReadTelemetryEventsParams) (page []instanceoapi.TelemetryEnvelope, hasMore bool, next int64) { + t.Helper() + resp, err := client.ReadTelemetryEventsWithResponse(ctx, params) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode(), "read events: %s", string(resp.Body)) + require.NotNil(t, resp.JSON200) + + hasMore = resp.HTTPResponse.Header.Get("X-Has-More") == "true" + if hasMore { + nextStr := resp.HTTPResponse.Header.Get("X-Next-Offset") + require.NotEmpty(t, nextStr, "X-Next-Offset must be set when X-Has-More is true") + next, err = strconv.ParseInt(nextStr, 10, 64) + require.NoError(t, err) + } + return *resp.JSON200, hasMore, next +} + +func publishEvent(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses, eventType string, category instanceoapi.PublishEventRequestCategory) { + t.Helper() + resp, err := client.PublishTelemetryEventWithResponse(ctx, instanceoapi.PublishTelemetryEventJSONRequestBody{ + Type: eventType, + Category: &category, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode(), "publish %s: %s", eventType, string(resp.Body)) +} diff --git a/server/lib/events/s2storage.go b/server/lib/events/s2storage.go index d10b51f5..07caa39d 100644 --- a/server/lib/events/s2storage.go +++ b/server/lib/events/s2storage.go @@ -226,3 +226,91 @@ func (w *S2StorageWriter) Stop(ctx context.Context) error { } return w.storage.Close(ctx) } + +// ReadOptions bounds a one-shot read. SeqNum and Timestamp set the start +// position; Count and Until bound the end. +type ReadOptions struct { + SeqNum *uint64 + Timestamp *uint64 + Count *uint64 + Until *uint64 +} + +// S2Reader reads archived telemetry envelopes from a single S2 stream. +type S2Reader struct { + basin string + accessToken string + streamName string +} + +// NewS2Reader returns a reader for streamName, or nil when any connection value +// is empty (durable storage not configured). +func NewS2Reader(basin, accessToken, streamName string) *S2Reader { + if basin == "" || accessToken == "" || streamName == "" { + return nil + } + return &S2Reader{basin: basin, accessToken: accessToken, streamName: streamName} +} + +// S2ReadResult is one page of a paginated read. NextSeqNum is the S2 sequence +// number to resume from on the next page; it is meaningful only when HasMore. +type S2ReadResult struct { + Envelopes []Envelope + NextSeqNum uint64 + HasMore bool +} + +// Read returns one page of telemetry envelopes from the bounded range. It opens +// a fresh client per call and surfaces S2 errors to the caller. +func (r *S2Reader) Read(ctx context.Context, opts ReadOptions, log *slog.Logger) (*S2ReadResult, error) { + readOpts := &s2.ReadOptions{ + Clamp: s2.Bool(true), + IgnoreCommandRecords: true, + SeqNum: opts.SeqNum, + Timestamp: opts.Timestamp, + Count: opts.Count, + Until: opts.Until, + } + // An unbounded read blocks waiting for new records; cap it at the tail. + if readOpts.Count == nil && readOpts.Until == nil { + readOpts.Until = s2.Uint64(uint64(time.Now().UnixMilli())) + } + + client := s2.New(r.accessToken, nil) + stream := client.Basin(r.basin).Stream(s2.StreamName(r.streamName)) + + session, err := stream.ReadSession(ctx, readOpts) + if err != nil { + return nil, fmt.Errorf("s2storage: open read session: %w", err) + } + defer session.Close() + + envelopes := make([]Envelope, 0) + for session.Next() { + rec := session.Record() + var env Envelope + if err := json.Unmarshal(rec.Body, &env); err != nil { + return nil, fmt.Errorf("s2storage: unmarshal envelope seqnum=%d: %w", rec.SeqNum, err) + } + envelopes = append(envelopes, env) + } + if err := session.Err(); err != nil { + return nil, fmt.Errorf("s2storage: read session: %w", err) + } + + // Both positions ride along in the read batches, so there's no extra + // round-trip. More records remain whenever the resume position hasn't caught + // up to the stream tail. This holds regardless of whether the page stopped on + // the count, byte, or time bound, so it never under-reports as a "full page" + // heuristic would. + result := &S2ReadResult{Envelopes: envelopes} + next := session.NextReadPosition() + tail := session.LastObservedTail() + if next != nil && tail != nil && next.SeqNum < tail.SeqNum { + result.NextSeqNum = next.SeqNum + result.HasMore = true + } + + log.DebugContext(ctx, "s2storage: read complete", "stream", r.streamName, "records", len(envelopes), "has_more", result.HasMore) + return result, nil +} diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 901aa0a9..f0db145d 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -3850,6 +3850,24 @@ type DownloadRecordingParams struct { Id *string `form:"id,omitempty" json:"id,omitempty"` } +// ReadTelemetryEventsParams defines parameters for ReadTelemetryEvents. +type ReadTelemetryEventsParams struct { + // Offset Pagination cursor: pass the `X-Next-Offset` value from the previous response to fetch the next page. This is a stream position and takes precedence over `since`; it is not an event's `seq` field — do not derive it from the response body. + Offset *int64 `form:"offset,omitempty" json:"offset,omitempty"` + + // Since Start of the time window, unix milliseconds. Defaults to 5 minutes ago. Ignored when `offset` is set. + Since *int64 `form:"since,omitempty" json:"since,omitempty"` + + // Until End of the time window (exclusive), unix milliseconds. + Until *int64 `form:"until,omitempty" json:"until,omitempty"` + + // Limit Maximum number of events per page. Defaults to 100. + Limit *int `form:"limit,omitempty" json:"limit,omitempty"` + + // Category Restrict results to these event categories. Repeat the parameter for multiple values. + Category *[]TelemetryEventCategory `form:"category,omitempty" json:"category,omitempty"` +} + // StreamTelemetryEventsParams defines parameters for StreamTelemetryEvents. type StreamTelemetryEventsParams struct { // Replay Pass `all` to start from the oldest retained event. Ring buffer caps at 1024; older events are evicted and surface as a first `id` greater than 1. @@ -5191,6 +5209,9 @@ type ClientInterface interface { PutTelemetry(ctx context.Context, body PutTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ReadTelemetryEvents request + ReadTelemetryEvents(ctx context.Context, params *ReadTelemetryEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // PublishTelemetryEventWithBody request with any body PublishTelemetryEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -6196,6 +6217,18 @@ func (c *Client) PutTelemetry(ctx context.Context, body PutTelemetryJSONRequestB return c.Client.Do(req) } +func (c *Client) ReadTelemetryEvents(ctx context.Context, params *ReadTelemetryEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewReadTelemetryEventsRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) PublishTelemetryEventWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewPublishTelemetryEventRequestWithBody(c.Server, contentType, body) if err != nil { @@ -8342,6 +8375,119 @@ func NewPutTelemetryRequestWithBody(server string, contentType string, body io.R return req, nil } +// NewReadTelemetryEventsRequest generates requests for ReadTelemetryEvents +func NewReadTelemetryEventsRequest(server string, params *ReadTelemetryEventsParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/telemetry/events") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Offset != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "offset", *params.Offset, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: "int64"}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Since != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "since", *params.Since, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: "int64"}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Until != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "until", *params.Until, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: "int64"}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "limit", *params.Limit, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Category != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "category", *params.Category, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewPublishTelemetryEventRequest calls the generic PublishTelemetryEvent builder with application/json body func NewPublishTelemetryEventRequest(server string, body PublishTelemetryEventJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -8708,6 +8854,9 @@ type ClientWithResponsesInterface interface { PutTelemetryWithResponse(ctx context.Context, body PutTelemetryJSONRequestBody, reqEditors ...RequestEditorFn) (*PutTelemetryResponse, error) + // ReadTelemetryEventsWithResponse request + ReadTelemetryEventsWithResponse(ctx context.Context, params *ReadTelemetryEventsParams, reqEditors ...RequestEditorFn) (*ReadTelemetryEventsResponse, error) + // PublishTelemetryEventWithBodyWithResponse request with any body PublishTelemetryEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishTelemetryEventResponse, error) @@ -9979,6 +10128,29 @@ func (r PutTelemetryResponse) StatusCode() int { return 0 } +type ReadTelemetryEventsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]TelemetryEnvelope + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r ReadTelemetryEventsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ReadTelemetryEventsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type PublishTelemetryEventResponse struct { Body []byte HTTPResponse *http.Response @@ -10740,6 +10912,15 @@ func (c *ClientWithResponses) PutTelemetryWithResponse(ctx context.Context, body return ParsePutTelemetryResponse(rsp) } +// ReadTelemetryEventsWithResponse request returning *ReadTelemetryEventsResponse +func (c *ClientWithResponses) ReadTelemetryEventsWithResponse(ctx context.Context, params *ReadTelemetryEventsParams, reqEditors ...RequestEditorFn) (*ReadTelemetryEventsResponse, error) { + rsp, err := c.ReadTelemetryEvents(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseReadTelemetryEventsResponse(rsp) +} + // PublishTelemetryEventWithBodyWithResponse request with arbitrary body returning *PublishTelemetryEventResponse func (c *ClientWithResponses) PublishTelemetryEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PublishTelemetryEventResponse, error) { rsp, err := c.PublishTelemetryEventWithBody(ctx, contentType, body, reqEditors...) @@ -12795,6 +12976,39 @@ func ParsePutTelemetryResponse(rsp *http.Response) (*PutTelemetryResponse, error return response, nil } +// ParseReadTelemetryEventsResponse parses an HTTP response from a ReadTelemetryEventsWithResponse call +func ParseReadTelemetryEventsResponse(rsp *http.Response) (*ReadTelemetryEventsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ReadTelemetryEventsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []TelemetryEnvelope + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParsePublishTelemetryEventResponse parses an HTTP response from a PublishTelemetryEventWithResponse call func ParsePublishTelemetryEventResponse(rsp *http.Response) (*PublishTelemetryEventResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -13005,6 +13219,9 @@ type ServerInterface interface { // Set telemetry configuration // (PUT /telemetry) PutTelemetry(w http.ResponseWriter, r *http.Request) + // Read archived telemetry events from durable storage + // (GET /telemetry/events) + ReadTelemetryEvents(w http.ResponseWriter, r *http.Request, params ReadTelemetryEventsParams) // Publish an event into the telemetry stream // (POST /telemetry/events) PublishTelemetryEvent(w http.ResponseWriter, r *http.Request) @@ -13335,6 +13552,12 @@ func (_ Unimplemented) PutTelemetry(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +// Read archived telemetry events from durable storage +// (GET /telemetry/events) +func (_ Unimplemented) ReadTelemetryEvents(w http.ResponseWriter, r *http.Request, params ReadTelemetryEventsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // Publish an event into the telemetry stream // (POST /telemetry/events) func (_ Unimplemented) PublishTelemetryEvent(w http.ResponseWriter, r *http.Request) { @@ -14368,6 +14591,65 @@ func (siw *ServerInterfaceWrapper) PutTelemetry(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r) } +// ReadTelemetryEvents operation middleware +func (siw *ServerInterfaceWrapper) ReadTelemetryEvents(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params ReadTelemetryEventsParams + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "offset", r.URL.Query(), ¶ms.Offset, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "offset", Err: err}) + return + } + + // ------------- Optional query parameter "since" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "since", r.URL.Query(), ¶ms.Since, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "since", Err: err}) + return + } + + // ------------- Optional query parameter "until" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "until", r.URL.Query(), ¶ms.Until, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "until", Err: err}) + return + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "limit", r.URL.Query(), ¶ms.Limit, runtime.BindQueryParameterOptions{Type: "integer", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "limit", Err: err}) + return + } + + // ------------- Optional query parameter "category" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "category", r.URL.Query(), ¶ms.Category, runtime.BindQueryParameterOptions{Type: "array", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "category", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReadTelemetryEvents(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // PublishTelemetryEvent operation middleware func (siw *ServerInterfaceWrapper) PublishTelemetryEvent(w http.ResponseWriter, r *http.Request) { @@ -14702,6 +14984,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Put(options.BaseURL+"/telemetry", wrapper.PutTelemetry) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/telemetry/events", wrapper.ReadTelemetryEvents) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/telemetry/events", wrapper.PublishTelemetryEvent) }) @@ -16931,6 +17216,42 @@ func (response PutTelemetry500JSONResponse) VisitPutTelemetryResponse(w http.Res return json.NewEncoder(w).Encode(response) } +type ReadTelemetryEventsRequestObject struct { + Params ReadTelemetryEventsParams +} + +type ReadTelemetryEventsResponseObject interface { + VisitReadTelemetryEventsResponse(w http.ResponseWriter) error +} + +type ReadTelemetryEvents200ResponseHeaders struct { + XHasMore bool + XNextOffset int64 +} + +type ReadTelemetryEvents200JSONResponse struct { + Body []TelemetryEnvelope + Headers ReadTelemetryEvents200ResponseHeaders +} + +func (response ReadTelemetryEvents200JSONResponse) VisitReadTelemetryEventsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-Has-More", fmt.Sprint(response.Headers.XHasMore)) + w.Header().Set("X-Next-Offset", fmt.Sprint(response.Headers.XNextOffset)) + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response.Body) +} + +type ReadTelemetryEvents500JSONResponse struct{ InternalErrorJSONResponse } + +func (response ReadTelemetryEvents500JSONResponse) VisitReadTelemetryEventsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type PublishTelemetryEventRequestObject struct { Body *PublishTelemetryEventJSONRequestBody } @@ -17183,6 +17504,9 @@ type StrictServerInterface interface { // Set telemetry configuration // (PUT /telemetry) PutTelemetry(ctx context.Context, request PutTelemetryRequestObject) (PutTelemetryResponseObject, error) + // Read archived telemetry events from durable storage + // (GET /telemetry/events) + ReadTelemetryEvents(ctx context.Context, request ReadTelemetryEventsRequestObject) (ReadTelemetryEventsResponseObject, error) // Publish an event into the telemetry stream // (POST /telemetry/events) PublishTelemetryEvent(ctx context.Context, request PublishTelemetryEventRequestObject) (PublishTelemetryEventResponseObject, error) @@ -18784,6 +19108,32 @@ func (sh *strictHandler) PutTelemetry(w http.ResponseWriter, r *http.Request) { } } +// ReadTelemetryEvents operation middleware +func (sh *strictHandler) ReadTelemetryEvents(w http.ResponseWriter, r *http.Request, params ReadTelemetryEventsParams) { + var request ReadTelemetryEventsRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReadTelemetryEvents(ctx, request.(ReadTelemetryEventsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReadTelemetryEvents") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReadTelemetryEventsResponseObject); ok { + if err := validResponse.VisitReadTelemetryEventsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // PublishTelemetryEvent operation middleware func (sh *strictHandler) PublishTelemetryEvent(w http.ResponseWriter, r *http.Request) { var request PublishTelemetryEventRequestObject @@ -18844,366 +19194,375 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AQA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index fcbc991a..301a987d 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1379,6 +1379,83 @@ paths: description: Event accepted but filtered by the active telemetry config; not published. "400": $ref: "#/components/responses/BadRequestError" + get: + summary: Read archived telemetry events from durable storage + description: > + Reads a page of telemetry event envelopes for this browser from durable + S2 storage, so events remain available after they age out of the live + SSE ring buffer. Events are returned in ascending sequence order. To page + through results, pass the `X-Next-Offset` value from the previous + response as `offset` and repeat while `X-Has-More` is true. Returns an + empty list when durable storage is not configured. + operationId: readTelemetryEvents + parameters: + - in: query + name: offset + required: false + description: > + Pagination cursor: pass the `X-Next-Offset` value from the previous + response to fetch the next page. This is a stream position and takes + precedence over `since`; it is not an event's `seq` field — do not + derive it from the response body. + schema: + type: integer + format: int64 + minimum: 0 + - in: query + name: since + required: false + description: Start of the time window, unix milliseconds. Defaults to 5 minutes ago. Ignored when `offset` is set. + schema: + type: integer + format: int64 + - in: query + name: until + required: false + description: End of the time window (exclusive), unix milliseconds. + schema: + type: integer + format: int64 + - in: query + name: limit + required: false + description: Maximum number of events per page. Defaults to 100. + schema: + type: integer + minimum: 1 + maximum: 1000 + default: 100 + - in: query + name: category + required: false + description: Restrict results to these event categories. Repeat the parameter for multiple values. + schema: + type: array + items: + $ref: "#/components/schemas/TelemetryEventCategory" + style: form + explode: true + responses: + "200": + description: A page of telemetry events from durable storage in ascending sequence order. + headers: + X-Has-More: + description: Whether more events are available beyond this page. + schema: + type: boolean + X-Next-Offset: + description: Cursor to pass as `offset` for the next page. Present only when X-Has-More is true. + schema: + type: integer + format: int64 + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TelemetryEnvelope" + "500": + $ref: "#/components/responses/InternalError" /telemetry/stream: get: summary: Stream telemetry events as Server-Sent Events @@ -1478,19 +1555,7 @@ components: type: string description: Event type identifier. category: - type: string - description: Event category. - enum: - - console - - network - - page - - interaction - - control - - connection - - system - - screenshot - - captcha - - monitor + $ref: "#/components/schemas/TelemetryEventCategory" source: $ref: "#/components/schemas/BrowserEventSource" data: @@ -1501,6 +1566,20 @@ components: truncated: type: boolean description: Set by the server when the data field was truncated to fit the size limit. + TelemetryEventCategory: + type: string + description: Event category. + enum: + - console + - network + - page + - interaction + - control + - connection + - system + - screenshot + - captcha + - monitor BrowserEventSource: type: object description: Provenance metadata identifying which producer emitted the event.