go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/tsmon/monitor/http.go (about) 1 // Copyright 2016 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 monitor 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "io" 22 "net/http" 23 "net/url" 24 25 "github.com/golang/protobuf/jsonpb" 26 27 "go.chromium.org/luci/common/clock" 28 "go.chromium.org/luci/common/lhttp" 29 "go.chromium.org/luci/common/logging" 30 pb "go.chromium.org/luci/common/tsmon/ts_mon_proto" 31 "go.chromium.org/luci/common/tsmon/types" 32 ) 33 34 var ( 35 // ProdxmonScopes is the list of oauth2 scopes needed on the http client 36 // given to NewHTTPMonitor. 37 ProdxmonScopes = []string{"https://www.googleapis.com/auth/prodxmon"} 38 ) 39 40 type httpMonitor struct { 41 client *http.Client 42 endpoint *url.URL 43 } 44 45 // NewHTTPMonitor creates a new Monitor object that sends metric to an HTTP 46 // (or HTTPS) endpoint. The http client should be authenticated as required. 47 func NewHTTPMonitor(ctx context.Context, client *http.Client, endpoint *url.URL) (Monitor, error) { 48 return &httpMonitor{ 49 client: client, 50 endpoint: endpoint, 51 }, nil 52 } 53 54 func (m *httpMonitor) ChunkSize() int { 55 return 500 56 } 57 58 func (m *httpMonitor) Send(ctx context.Context, cells []types.Cell) (err error) { 59 startTime := clock.Now(ctx) 60 defer func() { 61 if err == nil { 62 logging.Debugf(ctx, "tsmon: sent %d cells in %s", len(cells), clock.Now(ctx).Sub(startTime)) 63 } else { 64 logging.Warningf(ctx, "tsmon: failed to send %d cells - %s", len(cells), err) 65 } 66 }() 67 68 // Don't waste time on serialization if we are already too late. 69 if ctx.Err() != nil { 70 return ctx.Err() 71 } 72 73 // Serialize the tsmon cells into protobufs. 74 req := &pb.Request{ 75 Payload: &pb.MetricsPayload{ 76 MetricsCollection: SerializeCells(cells, startTime), 77 }, 78 } 79 80 // JSON encode the request. 81 encoded := bytes.Buffer{} 82 marshaller := jsonpb.Marshaler{} 83 if err := marshaller.Marshal(&encoded, req); err != nil { 84 return err 85 } 86 87 // Make the request. 88 status, err := lhttp.NewRequest(ctx, m.client, nil, func() (*http.Request, error) { 89 req, err := http.NewRequest("POST", m.endpoint.String(), bytes.NewReader(encoded.Bytes())) 90 if err != nil { 91 return nil, err 92 } 93 req.Header.Set("Content-Type", "application/json") 94 return req, nil 95 }, func(resp *http.Response) error { 96 return resp.Body.Close() 97 }, func(resp *http.Response, oErr error) error { 98 if resp != nil { 99 body, err := io.ReadAll(resp.Body) 100 if err != nil { 101 logging.WithError(err).Warningf(ctx, "Failed to read error response body") 102 } else { 103 logging.Warningf( 104 ctx, "Monitoring push failed.\nResponse body: %s\nRequest body: %s", 105 body, encoded.Bytes()) 106 } 107 resp.Body.Close() 108 } 109 // On HTTP 429 response (Too many requests) oErr is marked as transient and 110 // returning it causes a retry. We don't want to do that. HTTP 429 is 111 // received if timestamps in the request body indicate that the sampling 112 // period is smaller than the configured retention period. Resending the 113 // exact same body with exact same timestamps won't help. Return a fatal 114 // error instead. 115 if resp != nil && resp.StatusCode == 429 { 116 return fmt.Errorf("giving up on HTTP 429 status") 117 } 118 return oErr 119 })() 120 if err != nil { 121 return err 122 } 123 if status != http.StatusOK { 124 return fmt.Errorf("bad response status %d from endpoint %s", status, m.endpoint) 125 } 126 127 return nil 128 } 129 130 func (m *httpMonitor) Close() error { 131 return nil 132 }