Skip to content

Commit fe7687c

Browse files
phbnfroger2hk
andauthored
Add deduplication attribute to metrics (#318)
* add deduplication attribute to metrics # Conflicts: # internal/ct/handlers.go * Update internal/ct/handlers.go Co-authored-by: Roger Ng <roger2hk@gmail.com> --------- Co-authored-by: Roger Ng <roger2hk@gmail.com>
1 parent 3ce4356 commit fe7687c

File tree

2 files changed

+33
-30
lines changed

2 files changed

+33
-30
lines changed

internal/ct/handlers.go

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ import (
2626
"sync"
2727
"time"
2828

29+
"github.com/transparency-dev/tessera"
2930
"github.com/transparency-dev/tesseract/internal/otel"
3031
"github.com/transparency-dev/tesseract/internal/types/rfc6962"
3132
"github.com/transparency-dev/tesseract/internal/types/tls"
3233
"github.com/transparency-dev/tesseract/internal/x509util"
33-
"github.com/transparency-dev/tessera"
34+
"go.opentelemetry.io/otel/attribute"
3435
"go.opentelemetry.io/otel/metric"
3536
"k8s.io/klog/v2"
3637
)
@@ -69,8 +70,6 @@ var (
6970
// setupMetrics initializes all the exported metrics.
7071
func setupMetrics() {
7172
// TODO(phboneff): add metrics for chain storage.
72-
// TODO(phboneff): add metrics for deduplication.
73-
// TODO(phboneff): break down metrics by whether or not the response has been deduped.
7473
knownLogs = mustCreate(meter.Int64Gauge("tesseract.known_logs",
7574
metric.WithDescription("Set to 1 for known logs")))
7675

@@ -108,25 +107,25 @@ type pathHandlers map[string]appHandler
108107
type appHandler struct {
109108
log *log
110109
opts *HandlerOptions
111-
handler func(context.Context, *HandlerOptions, *log, http.ResponseWriter, *http.Request) (int, error)
110+
handler func(context.Context, *HandlerOptions, *log, http.ResponseWriter, *http.Request) (int, []attribute.KeyValue, error)
112111
name entrypointName
113112
method string // http.MethodGet or http.MethodPost
114113
}
115114

116115
// ServeHTTP for an AppHandler invokes the underlying handler function but
117116
// does additional common error and stats processing.
118117
func (a appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
119-
var statusCode int
120-
121118
originAttr := originKey.String(a.log.origin)
122119
operationAttr := operationKey.String(a.name)
123-
reqCounter.Add(r.Context(), 1, metric.WithAttributes(originAttr, operationAttr))
120+
attrs := []attribute.KeyValue{originAttr, operationAttr}
121+
122+
reqCounter.Add(r.Context(), 1, metric.WithAttributes(attrs...))
124123
startTime := time.Now()
125124
logCtx := a.opts.RequestLog.start(r.Context())
126125
a.opts.RequestLog.origin(logCtx, a.log.origin)
127126
defer func() {
128127
latency := time.Since(startTime).Seconds()
129-
reqDuration.Record(r.Context(), latency, metric.WithAttributes(originAttr, operationAttr, codeKey.Int(statusCode)))
128+
reqDuration.Record(r.Context(), latency, metric.WithAttributes(attrs...))
130129
}()
131130

132131
klog.V(2).Infof("%s: request %v %q => %s", a.log.origin, r.Method, r.URL, a.name)
@@ -153,11 +152,12 @@ func (a appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
153152
ctx, cancel := context.WithTimeout(logCtx, a.opts.Deadline)
154153
defer cancel()
155154

156-
var err error
157-
statusCode, err = a.handler(ctx, a.opts, a.log, w, r)
155+
statusCode, hattrs, err := a.handler(ctx, a.opts, a.log, w, r)
156+
attrs = append(attrs, hattrs...)
157+
attrs = append(attrs, codeKey.Int(statusCode))
158158
a.opts.RequestLog.status(ctx, statusCode)
159159
klog.V(2).Infof("%s: %s <= st=%d", a.log.origin, a.name, statusCode)
160-
rspCounter.Add(r.Context(), 1, metric.WithAttributes(originAttr, operationAttr, codeKey.Int(statusCode)))
160+
rspCounter.Add(r.Context(), 1, metric.WithAttributes(attrs...))
161161
if err != nil {
162162
klog.Warningf("%s: %s handler error: %v", a.log.origin, a.name, err)
163163
a.opts.sendHTTPError(w, statusCode, err)
@@ -240,7 +240,7 @@ func parseBodyAsJSONChain(r *http.Request) (rfc6962.AddChainRequest, error) {
240240

241241
// addChainInternal is called by add-chain and add-pre-chain as the logic involved in
242242
// processing these requests is almost identical
243-
func addChainInternal(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, r *http.Request, isPrecert bool) (int, error) {
243+
func addChainInternal(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, r *http.Request, isPrecert bool) (int, []attribute.KeyValue, error) {
244244
var method entrypointName
245245
if isPrecert {
246246
method = addPreChainName
@@ -251,15 +251,15 @@ func addChainInternal(ctx context.Context, opts *HandlerOptions, log *log, w htt
251251
// Check the contents of the request and convert to slice of certificates.
252252
addChainReq, err := parseBodyAsJSONChain(r)
253253
if err != nil {
254-
return http.StatusBadRequest, fmt.Errorf("%s: failed to parse add-chain body: %s", log.origin, err)
254+
return http.StatusBadRequest, nil, fmt.Errorf("%s: failed to parse add-chain body: %s", log.origin, err)
255255
}
256256
// Log the DERs now because they might not parse as valid X.509.
257257
for _, der := range addChainReq.Chain {
258258
opts.RequestLog.addDERToChain(ctx, der)
259259
}
260260
chain, err := log.chainValidator.Validate(addChainReq, isPrecert)
261261
if err != nil {
262-
return http.StatusBadRequest, fmt.Errorf("failed to verify add-chain contents: %s", err)
262+
return http.StatusBadRequest, nil, fmt.Errorf("failed to verify add-chain contents: %s", err)
263263
}
264264
for _, cert := range chain {
265265
opts.RequestLog.addCertToChain(ctx, cert)
@@ -271,74 +271,76 @@ func addChainInternal(ctx context.Context, opts *HandlerOptions, log *log, w htt
271271

272272
entry, err := x509util.EntryFromChain(chain, isPrecert, timeMillis)
273273
if err != nil {
274-
return http.StatusBadRequest, fmt.Errorf("failed to build MerkleTreeLeaf: %s", err)
274+
return http.StatusBadRequest, nil, fmt.Errorf("failed to build MerkleTreeLeaf: %s", err)
275275
}
276276

277277
if err := log.storage.AddIssuerChain(ctx, chain[1:]); err != nil {
278-
return http.StatusInternalServerError, fmt.Errorf("failed to store issuer chain: %s", err)
278+
return http.StatusInternalServerError, nil, fmt.Errorf("failed to store issuer chain: %s", err)
279279
}
280280

281281
klog.V(2).Infof("%s: %s => storage.Add", log.origin, method)
282282
index, dedupedTimeMillis, err := log.storage.Add(ctx, entry)
283283
if err != nil {
284284
if errors.Is(err, tessera.ErrPushback) {
285285
w.Header().Add("Retry-After", "1")
286-
return http.StatusServiceUnavailable, fmt.Errorf("received pushback from Tessera sequencer: %v", err)
286+
return http.StatusServiceUnavailable, nil, fmt.Errorf("received pushback from Tessera sequencer: %v", err)
287287
}
288-
return http.StatusInternalServerError, fmt.Errorf("couldn't store the leaf: %v", err)
288+
return http.StatusInternalServerError, nil, fmt.Errorf("couldn't store the leaf: %v", err)
289289
}
290+
isDup := dedupedTimeMillis != timeMillis
291+
dedupedAttribute := duplicateKey.Bool(isDup)
290292
entry.Timestamp = dedupedTimeMillis
291293

292294
// Always use the returned leaf as the basis for an SCT.
293295
var loggedLeaf rfc6962.MerkleTreeLeaf
294296
leafValue := entry.MerkleTreeLeaf(index)
295297
if rest, err := tls.Unmarshal(leafValue, &loggedLeaf); err != nil {
296-
return http.StatusInternalServerError, fmt.Errorf("failed to reconstruct MerkleTreeLeaf: %s", err)
298+
return http.StatusInternalServerError, nil, fmt.Errorf("failed to reconstruct MerkleTreeLeaf: %s", err)
297299
} else if len(rest) > 0 {
298-
return http.StatusInternalServerError, fmt.Errorf("extra data (%d bytes) on reconstructing MerkleTreeLeaf", len(rest))
300+
return http.StatusInternalServerError, nil, fmt.Errorf("extra data (%d bytes) on reconstructing MerkleTreeLeaf", len(rest))
299301
}
300302

301303
// As the Log server has definitely got the Merkle tree leaf, we can
302304
// generate an SCT and respond with it.
303305
sct, err := log.signSCT(&loggedLeaf)
304306
if err != nil {
305-
return http.StatusInternalServerError, fmt.Errorf("failed to generate SCT: %s", err)
307+
return http.StatusInternalServerError, nil, fmt.Errorf("failed to generate SCT: %s", err)
306308
}
307309
sctBytes, err := tls.Marshal(*sct)
308310
if err != nil {
309-
return http.StatusInternalServerError, fmt.Errorf("failed to marshall SCT: %s", err)
311+
return http.StatusInternalServerError, nil, fmt.Errorf("failed to marshall SCT: %s", err)
310312
}
311313
// We could possibly fail to issue the SCT after this but it's v. unlikely.
312314
opts.RequestLog.issueSCT(ctx, sctBytes)
313315
err = marshalAndWriteAddChainResponse(sct, w)
314316
if err != nil {
315317
// reason is logged and http status is already set
316-
return http.StatusInternalServerError, fmt.Errorf("failed to write response: %s", err)
318+
return http.StatusInternalServerError, nil, fmt.Errorf("failed to write response: %s", err)
317319
}
318320
klog.V(3).Infof("%s: %s <= SCT", log.origin, method)
319-
if sct.Timestamp == timeMillis {
321+
if !isDup {
320322
lastSCTTimestamp.Record(ctx, otel.Clamp64(sct.Timestamp), metric.WithAttributes(originKey.String(log.origin)))
321323
lastSCTIndex.Record(ctx, otel.Clamp64(index), metric.WithAttributes(originKey.String(log.origin)))
322324
}
323325

324-
return http.StatusOK, nil
326+
return http.StatusOK, []attribute.KeyValue{dedupedAttribute}, nil
325327
}
326328

327-
func addChain(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, r *http.Request) (int, error) {
329+
func addChain(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, r *http.Request) (int, []attribute.KeyValue, error) {
328330
ctx, span := tracer.Start(ctx, "tesseract.addChain")
329331
defer span.End()
330332

331333
return addChainInternal(ctx, opts, log, w, r, false)
332334
}
333335

334-
func addPreChain(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, r *http.Request) (int, error) {
336+
func addPreChain(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, r *http.Request) (int, []attribute.KeyValue, error) {
335337
ctx, span := tracer.Start(ctx, "tesseract.addPreChain")
336338
defer span.End()
337339

338340
return addChainInternal(ctx, opts, log, w, r, true)
339341
}
340342

341-
func getRoots(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, _ *http.Request) (int, error) {
343+
func getRoots(ctx context.Context, opts *HandlerOptions, log *log, w http.ResponseWriter, _ *http.Request) (int, []attribute.KeyValue, error) {
342344
_, span := tracer.Start(ctx, "tesseract.getRoots")
343345
defer span.End()
344346

@@ -355,10 +357,10 @@ func getRoots(ctx context.Context, opts *HandlerOptions, log *log, w http.Respon
355357
err := enc.Encode(jsonMap)
356358
if err != nil {
357359
klog.Warningf("%s: get_roots failed: %v", log.origin, err)
358-
return http.StatusInternalServerError, fmt.Errorf("get-roots failed with: %s", err)
360+
return http.StatusInternalServerError, nil, fmt.Errorf("get-roots failed with: %s", err)
359361
}
360362

361-
return http.StatusOK, nil
363+
return http.StatusOK, nil, nil
362364
}
363365

364366
// marshalAndWriteAddChainResponse is used by add-chain and add-pre-chain to create and write

internal/ct/otel.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var (
3131
codeKey = attribute.Key("http.response.status_code")
3232
operationKey = attribute.Key("tesseract.operation")
3333
originKey = attribute.Key("tesseract.origin")
34+
duplicateKey = attribute.Key("tesseract.duplicate")
3435
)
3536

3637
func mustCreate[T any](t T, err error) T {

0 commit comments

Comments
 (0)