go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/artifactcontent/metrics.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package artifactcontent 16 17 import ( 18 "context" 19 "net/http" 20 "time" 21 22 "go.chromium.org/luci/common/clock" 23 "go.chromium.org/luci/common/tsmon/field" 24 "go.chromium.org/luci/common/tsmon/metric" 25 "go.chromium.org/luci/common/tsmon/types" 26 "go.chromium.org/luci/server/router" 27 ) 28 29 const firstBucket = 1024 30 31 var ( 32 // artifactTransferDurations tracks the duration of artifacts transfers 33 // between client and storage, e.g. RBE-CAS. 34 artifactTransferDurations = metric.NewCumulativeDistribution( 35 "resultdb/artifacts/durations", 36 "Durations of artifact transfers between client and storage", 37 &types.MetricMetadata{Units: types.Milliseconds}, 38 nil, 39 field.Int("size_bucket"), 40 field.String("op"), // "upload" or "download". 41 ) 42 // artifactTransferStatus tracks the response statuses of artifact 43 // transfers between client and storage, e.g. RBE-CAS. 44 artifactTransferStatus = metric.NewCounter( 45 "resultdb/artifacts/response_status", 46 "Response statuses sent to clients requesting artifact transfer", 47 &types.MetricMetadata{Units: "operations"}, 48 field.Int("http_status"), 49 field.Int("size_bucket"), 50 field.String("op"), // "upload" or "download". 51 ) 52 ) 53 54 // NewMetricsWriter creates a MetricsWriter to be used after the transfer is 55 // complete to time the operation and write the metrics. 56 // It also replaces the given context's writer with a wrapped writer that keeps 57 // track of the response status. 58 func NewMetricsWriter(c *router.Context) *MetricsWriter { 59 if _, ok := c.Writer.(*statusTrackingWriter); !ok { 60 c.Writer = &statusTrackingWriter{ResponseWriter: c.Writer} 61 } 62 return &MetricsWriter{ 63 statusTracker: c.Writer.(*statusTrackingWriter), 64 startTime: clock.Now(c.Request.Context()), 65 } 66 } 67 68 // MetricsWriter can be used to record artifact transfer metrics, by creating 69 // one out of the router context before the transfer, and calling its .Download 70 // or .Upload methods afterwards. 71 type MetricsWriter struct { 72 statusTracker *statusTrackingWriter 73 startTime time.Time 74 } 75 76 // Upload writes upload metrics. 77 func (mw *MetricsWriter) Upload(ctx context.Context, size int64) { 78 mw.writeRequestMetrics(ctx, size, "upload") 79 } 80 81 // Download writes download metrics. 82 func (mw *MetricsWriter) Download(ctx context.Context, size int64) { 83 mw.writeRequestMetrics(ctx, size, "download") 84 } 85 86 func (mw *MetricsWriter) writeRequestMetrics(ctx context.Context, size int64, op string) { 87 duration := clock.Since(ctx, mw.startTime) 88 sizeB := sizeBucket(size) 89 artifactTransferDurations.Add(ctx, duration.Seconds()*1000, sizeB, op) 90 artifactTransferStatus.Add(ctx, 1, mw.statusTracker.status, sizeB, op) 91 } 92 93 type statusTrackingWriter struct { 94 http.ResponseWriter 95 status int 96 } 97 98 func (w *statusTrackingWriter) WriteHeader(status int) { 99 w.status = status 100 w.ResponseWriter.WriteHeader(status) 101 } 102 103 // sizeBucket returns the high bound of the bucket that the given size falls 104 // into. 105 // I.e. the smallest power of 4 that is greater or equal to the given size, 106 // starting at firstBucket. 107 // E.g. 1024, 4096, 16Ki, 64Ki, 256Ki, 1Mi, ... 108 func sizeBucket(s int64) (b int64) { 109 b = firstBucket 110 // Shift b left 2 bits at a time, until it's greater than or equal to s, or 111 // until shifting it further would cause an overflow. 112 for s > b && b<<2 > 0 { 113 b <<= 2 114 } 115 return b 116 }