@@ -2,8 +2,10 @@ package server
22
33import (
44 "encoding/json"
5+ "maps"
56 "math"
67 "net/http"
8+ "sync"
79 "time"
810
911 "github.com/smartcontractkit/chainlink-confidential-compute/types"
@@ -16,6 +18,7 @@ const durationBucketSeconds = 0.01
1618// ResponseEmitter collects metrics to be included in the response payload
1719// instead of emitting them via OTel.
1820type ResponseEmitter struct {
21+ mu sync.RWMutex
1922 metrics []types.MetricEvent
2023 // Captured at construction so WriteErrorResponse can report the total request
2124 // duration on error, not just on the success path.
@@ -33,9 +36,13 @@ func NewResponseEmitter() *ResponseEmitter {
3336}
3437
3538func (e * ResponseEmitter ) Emit (event string , details map [string ]any ) {
39+ details = roundDurations (maps .Clone (details ))
40+
41+ e .mu .Lock ()
42+ defer e .mu .Unlock ()
3643 e .metrics = append (e .metrics , types.MetricEvent {
3744 Event : event ,
38- Details : roundDurations ( details ) ,
45+ Details : details ,
3946 })
4047}
4148
@@ -59,16 +66,29 @@ func roundDurations(details map[string]any) map[string]any {
5966// Duplicate event names collapse to the last occurrence; use GetMetricEvents
6067// when per-occurrence counts matter (e.g. repeated capability calls).
6168func (e * ResponseEmitter ) GetMetrics () map [string ]any {
62- result := make (map [string ]any )
63- for _ , m := range e .metrics {
64- result [m .Event ] = m .Details
65- }
66- return result
69+ metrics , _ := e .Snapshot ()
70+ return metrics
6771}
6872
6973// GetMetricEvents returns every collected event in order, preserving duplicates.
7074func (e * ResponseEmitter ) GetMetricEvents () []types.MetricEvent {
71- return e .metrics
75+ _ , events := e .Snapshot ()
76+ return events
77+ }
78+
79+ // Snapshot returns consistent map and ordered representations of all events.
80+ func (e * ResponseEmitter ) Snapshot () (map [string ]any , []types.MetricEvent ) {
81+ e .mu .RLock ()
82+ defer e .mu .RUnlock ()
83+
84+ metrics := make (map [string ]any )
85+ events := make ([]types.MetricEvent , len (e .metrics ))
86+ for i , event := range e .metrics {
87+ details := maps .Clone (event .Details )
88+ events [i ] = types.MetricEvent {Event : event .Event , Details : details }
89+ metrics [event .Event ] = details
90+ }
91+ return metrics , events
7292}
7393
7494// WriteErrorResponse writes a JSON error response that includes the accumulated
@@ -87,10 +107,11 @@ func (e *ResponseEmitter) WriteErrorResponse(w http.ResponseWriter, msg string,
87107
88108 w .Header ().Set ("Content-Type" , "application/json" )
89109 w .WriteHeader (statusCode )
110+ metrics , metricEvents := e .Snapshot ()
90111 resp := types.EnclaveErrorResponse {
91112 Error : msg ,
92- Metrics : e . GetMetrics () ,
93- MetricEvents : e . GetMetricEvents () ,
113+ Metrics : metrics ,
114+ MetricEvents : metricEvents ,
94115 }
95116 _ = json .NewEncoder (w ).Encode (resp )
96117}
0 commit comments