@@ -50,6 +50,7 @@ const (
5050
5151// CacheKeyEvent records a single cache key lookup result.
5252type CacheKeyEvent struct {
53+ Timestamp time.Time // when the lookup occurred; stamped by Record* if zero
5354 CacheKey string
5455 EntityType string
5556 Kind CacheKeyEventKind
@@ -61,6 +62,7 @@ type CacheKeyEvent struct {
6162
6263// CacheWriteEvent records a single cache write operation.
6364type CacheWriteEvent struct {
65+ Timestamp time.Time // when the write occurred; stamped by RecordWrite if zero
6466 CacheKey string
6567 EntityType string
6668 ByteSize int
@@ -74,6 +76,7 @@ type CacheWriteEvent struct {
7476
7577// FetchTimingEvent records the duration of a subgraph fetch or cache lookup.
7678type FetchTimingEvent struct {
79+ Timestamp time.Time // when the fetch started; stamped by RecordFetchTiming if zero
7780 DataSource string // subgraph name
7881 EntityType string // entity type (empty for root fetches)
7982 DurationMs int64 // time spent on this operation in milliseconds
@@ -87,15 +90,17 @@ type FetchTimingEvent struct {
8790
8891// SubgraphErrorEvent records a subgraph error for analytics.
8992type SubgraphErrorEvent struct {
90- DataSource string // subgraph name
91- EntityType string // entity type (empty for root fetches)
92- Message string // error message (truncated for safety)
93- Code string // error code from errors[0].extensions.code (empty if not present)
93+ Timestamp time.Time // when the error was observed; stamped by RecordError if zero
94+ DataSource string // subgraph name
95+ EntityType string // entity type (empty for root fetches)
96+ Message string // error message (truncated for safety)
97+ Code string // error code from errors[0].extensions.code (empty if not present)
9498}
9599
96100// EntityFieldHash stores an xxhash of a scalar field value on an entity type,
97101// along with the entity's key data and the source of the data.
98102type EntityFieldHash struct {
103+ Timestamp time.Time // when the hash was computed; stamped by HashFieldValue if zero
99104 EntityType string
100105 FieldName string
101106 FieldHash uint64 // xxhash of the non-key field value
@@ -127,6 +132,7 @@ type entitySourceRecord struct {
127132
128133// ShadowComparisonEvent records a comparison between cached and fresh data in shadow mode.
129134type ShadowComparisonEvent struct {
135+ Timestamp time.Time // when the comparison was performed; stamped by RecordShadowComparison if zero
130136 CacheKey string // cache key for correlation
131137 EntityType string // entity type name
132138 IsFresh bool // true if ProvidesData fields match between cached and fresh
@@ -143,14 +149,15 @@ type ShadowComparisonEvent struct {
143149// Recorded during mutation execution by proactively comparing the mutation response
144150// with the L2 cached value for the same entity.
145151type MutationEvent struct {
146- MutationRootField string // e.g., "updateUsername"
147- EntityType string // e.g., "User"
148- EntityCacheKey string // display key e.g. {"__typename":"User","key":{"id":"1234"}}
149- HadCachedValue bool // true if L2 had a cached value for this entity
150- IsStale bool // true if cached value differs from mutation response (always false when HadCachedValue=false)
151- CachedHash uint64 // xxhash of cached ProvidesData fields (0 when HadCachedValue=false)
152- FreshHash uint64 // xxhash of mutation response ProvidesData fields
153- CachedBytes int // 0 when HadCachedValue=false
152+ Timestamp time.Time // when the mutation was observed; stamped by RecordMutationEvent if zero
153+ MutationRootField string // e.g., "updateUsername"
154+ EntityType string // e.g., "User"
155+ EntityCacheKey string // display key e.g. {"__typename":"User","key":{"id":"1234"}}
156+ HadCachedValue bool // true if L2 had a cached value for this entity
157+ IsStale bool // true if cached value differs from mutation response (always false when HadCachedValue=false)
158+ CachedHash uint64 // xxhash of cached ProvidesData fields (0 when HadCachedValue=false)
159+ FreshHash uint64 // xxhash of mutation response ProvidesData fields
160+ CachedBytes int // 0 when HadCachedValue=false
154161 FreshBytes int
155162 Source CacheOperationSource // what triggered this event (query/mutation/subscription)
156163}
@@ -159,24 +166,26 @@ type MutationEvent struct {
159166// Cache errors are non-fatal (the engine falls back to subgraph fetch), but tracking them
160167// in analytics allows operators to detect cache infrastructure issues.
161168type CacheOperationError struct {
162- Operation string // "get", "set", "set_negative", or "delete"
163- CacheName string // named cache instance
164- EntityType string // entity type (empty for root fetches)
165- DataSource string // subgraph name
166- Message string // error message (truncated for safety)
167- ItemCount int // number of keys involved in the failed operation
169+ Timestamp time.Time // when the error occurred; stamped by RecordCacheOperationError if zero
170+ Operation string // "get", "set", "set_negative", or "delete"
171+ CacheName string // named cache instance
172+ EntityType string // entity type (empty for root fetches)
173+ DataSource string // subgraph name
174+ Message string // error message (truncated for safety)
175+ ItemCount int // number of keys involved in the failed operation
168176}
169177
170178// HeaderImpactEvent records a fresh fetch that wrote to L2 cache with header-prefixed keys.
171179// A cross-request consumer can aggregate these events: when the same BaseKey appears with
172180// different HeaderHash values but identical ResponseHash values, the forwarded headers
173181// do not affect the subgraph response, and IncludeSubgraphHeaderPrefix can be disabled.
174182type HeaderImpactEvent struct {
175- BaseKey string // cache key WITHOUT header prefix (stable identity for grouping)
176- HeaderHash uint64 // hash of forwarded headers for this subgraph
177- ResponseHash uint64 // xxhash of the response value bytes written to L2
178- EntityType string // entity type (e.g., "User") or "Query" for root fields
179- DataSource string // subgraph name
183+ Timestamp time.Time // when the header-prefixed write occurred; stamped by RecordHeaderImpactEvent if zero
184+ BaseKey string // cache key WITHOUT header prefix (stable identity for grouping)
185+ HeaderHash uint64 // hash of forwarded headers for this subgraph
186+ ResponseHash uint64 // xxhash of the response value bytes written to L2
187+ EntityType string // entity type (e.g., "User") or "Query" for root fields
188+ DataSource string // subgraph name
180189}
181190
182191// CacheAnalyticsCollector accumulates cache analytics events during request execution.
@@ -265,6 +274,7 @@ func (c *CacheAnalyticsCollector) ResetForReuse() {
265274// RecordL1KeyEvent records an L1 cache key lookup event. Main thread only.
266275func (c * CacheAnalyticsCollector ) RecordL1KeyEvent (kind CacheKeyEventKind , entityType , cacheKey , dataSource string , byteSize int ) {
267276 c .l1KeyEvents = append (c .l1KeyEvents , CacheKeyEvent {
277+ Timestamp : time .Now (),
268278 CacheKey : cacheKey ,
269279 EntityType : entityType ,
270280 Kind : kind ,
@@ -279,6 +289,7 @@ func (c *CacheAnalyticsCollector) RecordL1KeyEvent(kind CacheKeyEventKind, entit
279289// Use MergeL2Events to merge events collected on per-result slices from goroutines.
280290func (c * CacheAnalyticsCollector ) RecordL2KeyEvent (kind CacheKeyEventKind , entityType , cacheKey , dataSource string , byteSize int ) {
281291 c .l2KeyEvents = append (c .l2KeyEvents , CacheKeyEvent {
292+ Timestamp : time .Now (),
282293 CacheKey : cacheKey ,
283294 EntityType : entityType ,
284295 Kind : kind ,
@@ -295,6 +306,9 @@ func (c *CacheAnalyticsCollector) MergeL2Events(events []CacheKeyEvent) {
295306
296307// RecordWrite records a cache write event. Main thread only.
297308func (c * CacheAnalyticsCollector ) RecordWrite (event CacheWriteEvent ) {
309+ if event .Timestamp .IsZero () {
310+ event .Timestamp = time .Now ()
311+ }
298312 c .writeEvents = append (c .writeEvents , event )
299313}
300314
@@ -306,6 +320,7 @@ func (c *CacheAnalyticsCollector) HashFieldValue(entityType, fieldName string, v
306320 hash := c .xxh .Sum64 ()
307321
308322 c .fieldHashes = append (c .fieldHashes , EntityFieldHash {
323+ Timestamp : time .Now (),
309324 EntityType : entityType ,
310325 FieldName : fieldName ,
311326 FieldHash : hash ,
@@ -357,6 +372,9 @@ func (c *CacheAnalyticsCollector) MergeEntitySources(sources []entitySourceRecor
357372// It is exported for external consumers such as cosmo router; this repository
358373// has no production caller. If cosmo no longer uses it, internalize it in the next breaking window.
359374func (c * CacheAnalyticsCollector ) RecordFetchTiming (event FetchTimingEvent ) {
375+ if event .Timestamp .IsZero () {
376+ event .Timestamp = time .Now ()
377+ }
360378 c .fetchTimings = append (c .fetchTimings , event )
361379}
362380
@@ -368,6 +386,9 @@ func (c *CacheAnalyticsCollector) MergeL2FetchTimings(timings []FetchTimingEvent
368386
369387// RecordError records a subgraph error event. Main thread only.
370388func (c * CacheAnalyticsCollector ) RecordError (event SubgraphErrorEvent ) {
389+ if event .Timestamp .IsZero () {
390+ event .Timestamp = time .Now ()
391+ }
371392 c .errorEvents = append (c .errorEvents , event )
372393}
373394
@@ -380,21 +401,33 @@ func (c *CacheAnalyticsCollector) MergeL2Errors(events []SubgraphErrorEvent) {
380401// RecordShadowComparison records a shadow mode comparison between cached and fresh data.
381402// Main thread only.
382403func (c * CacheAnalyticsCollector ) RecordShadowComparison (event ShadowComparisonEvent ) {
404+ if event .Timestamp .IsZero () {
405+ event .Timestamp = time .Now ()
406+ }
383407 c .shadowComparisons = append (c .shadowComparisons , event )
384408}
385409
386410// RecordMutationEvent records a mutation entity impact event. Main thread only.
387411func (c * CacheAnalyticsCollector ) RecordMutationEvent (event MutationEvent ) {
412+ if event .Timestamp .IsZero () {
413+ event .Timestamp = time .Now ()
414+ }
388415 c .mutationEvents = append (c .mutationEvents , event )
389416}
390417
391418// RecordHeaderImpactEvent records a header impact event. Main thread only.
392419func (c * CacheAnalyticsCollector ) RecordHeaderImpactEvent (event HeaderImpactEvent ) {
420+ if event .Timestamp .IsZero () {
421+ event .Timestamp = time .Now ()
422+ }
393423 c .headerImpactEvents = append (c .headerImpactEvents , event )
394424}
395425
396426// RecordCacheOperationError records a cache operation error. Main thread only.
397427func (c * CacheAnalyticsCollector ) RecordCacheOperationError (event CacheOperationError ) {
428+ if event .Timestamp .IsZero () {
429+ event .Timestamp = time .Now ()
430+ }
398431 c .cacheOpErrors = append (c .cacheOpErrors , event )
399432}
400433
0 commit comments