github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/integrations/prometheus/ingest/putmetrics.go (about) 1 /* 2 Copyright 2023. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package writer 18 19 import ( 20 "encoding/json" 21 "strconv" 22 23 "github.com/gogo/protobuf/proto" 24 "github.com/golang/snappy" 25 "github.com/prometheus/common/model" 26 "github.com/prometheus/prometheus/prompb" 27 . "github.com/siglens/siglens/pkg/segment/utils" 28 "github.com/siglens/siglens/pkg/segment/writer" 29 "github.com/siglens/siglens/pkg/usageStats" 30 "github.com/siglens/siglens/pkg/utils" 31 log "github.com/sirupsen/logrus" 32 "github.com/valyala/fasthttp" 33 ) 34 35 type PrometheusPutResp struct { 36 Failed uint64 `json:"failed"` 37 Success uint64 `json:"success"` 38 Errors []string `json:"errors,omitempty"` 39 } 40 41 func decodeWriteRequest(compressed []byte) (*prompb.WriteRequest, error) { 42 reqBuf, err := snappy.Decode(nil, compressed) 43 if err != nil { 44 log.Errorf("decodeWriteRequest: Error decompressing request body, err: %v", err) 45 return nil, err 46 } 47 var req prompb.WriteRequest 48 if err := proto.Unmarshal(reqBuf, &req); err != nil { 49 log.Errorf("decodeWriteRequest: Error unmarshalling request body, err: %v", err) 50 return nil, err 51 } 52 return &req, nil 53 } 54 55 func PutMetrics(ctx *fasthttp.RequestCtx) { 56 var processedCount uint64 57 var failedCount uint64 58 var err error 59 version := string(ctx.Request.Header.Peek("X-Prometheus-Remote-Write-Version")) 60 if version != "0.1.0" { 61 log.Errorf("PutMetrics: Unsupported remote write protocol version %v", version) 62 writePrometheusResponse(ctx, processedCount, failedCount, "unsupported remote write protocol", fasthttp.StatusBadRequest) 63 return 64 } 65 cType := string(ctx.Request.Header.ContentType()) 66 if cType != "application/x-protobuf" { 67 log.Errorf("PutMetrics: unknown content type [%s]! %v", cType, err) 68 writePrometheusResponse(ctx, processedCount, failedCount, "unknown content type", fasthttp.StatusBadRequest) 69 return 70 } 71 encoding := string(ctx.Request.Header.ContentEncoding()) 72 if encoding != "snappy" { 73 log.Errorf("PutMetrics: unknown content encoding [%s]! %v", encoding, err) 74 writePrometheusResponse(ctx, processedCount, failedCount, "unknown content encoding", fasthttp.StatusBadRequest) 75 return 76 } 77 78 compressed := ctx.PostBody() 79 processedCount, failedCount, err = HandlePutMetrics(compressed) 80 if err != nil { 81 writePrometheusResponse(ctx, processedCount, failedCount, err.Error(), fasthttp.StatusBadRequest) 82 return 83 } 84 writePrometheusResponse(ctx, processedCount, failedCount, "", fasthttp.StatusOK) 85 } 86 87 func HandlePutMetrics(compressed []byte) (uint64, uint64, error) { 88 var successCount uint64 = 0 89 var failedCount uint64 = 0 90 91 req, err := decodeWriteRequest(compressed) 92 if err != nil { 93 return successCount, failedCount, nil 94 } 95 96 for _, ts := range req.Timeseries { 97 metric := make(model.Metric, len(ts.Labels)) 98 for _, l := range ts.Labels { 99 metric[model.LabelName(l.Name)] = model.LabelValue(l.Value) 100 } 101 102 for _, s := range ts.Samples { 103 var sample model.Sample = model.Sample{ 104 Metric: metric, 105 Value: model.SampleValue(s.Value), 106 Timestamp: model.Time(s.Timestamp), 107 } 108 109 data, err := sample.MarshalJSON() 110 if err != nil { 111 failedCount++ 112 log.Errorf("HandlePutMetrics: failed to marshal data, err: %+v", err) 113 continue 114 } 115 116 var dataJson map[string]interface{} 117 err = json.Unmarshal(data, &dataJson) 118 if err != nil { 119 failedCount++ 120 log.Errorf("HandlePutMetrics: failed to Unmarshal data, err: %+v", err) 121 continue 122 } 123 124 var metricName string 125 tags := "{" 126 for key, val := range dataJson { 127 if key == "metric" { 128 valMap, ok := val.(map[string]interface{}) 129 if ok { 130 for k, v := range valMap { 131 if k == "__name__" { 132 valString, ok := v.(string) 133 if ok { 134 metricName = valString 135 } 136 } 137 valString, ok := v.(string) 138 if ok { 139 tags += `"` + k + `":"` + valString + `",` 140 } 141 } 142 } 143 } 144 } 145 tags += `"metric"` + `:"` + metricName + `",` 146 if tags[len(tags)-1] == ',' { 147 tags = tags[:len(tags)-1] 148 } 149 tags += "}" 150 151 modifiedData := `{"metric":"` + metricName + `","tags":` + tags + `,"timestamp":` + strconv.FormatInt(s.Timestamp, 10) + `,"value":` + strconv.FormatFloat(s.Value, 'f', -1, 64) + `}` 152 153 err = writer.AddTimeSeriesEntryToInMemBuf([]byte(modifiedData), SIGNAL_METRICS_OTSDB, uint64(0)) 154 if err != nil { 155 log.Errorf("HandlePutMetrics: failed to add time series entry %+v", err) 156 failedCount++ 157 } else { 158 successCount++ 159 } 160 } 161 } 162 bytesReceived := uint64(len(compressed)) 163 usageStats.UpdateMetricsStats(bytesReceived, successCount, 0) 164 return successCount, failedCount, nil 165 } 166 167 func writePrometheusResponse(ctx *fasthttp.RequestCtx, processedCount uint64, failedCount uint64, err string, code int) { 168 169 resp := PrometheusPutResp{Success: processedCount, Failed: failedCount} 170 if err != "" { 171 resp.Errors = []string{err} 172 } 173 174 ctx.SetStatusCode(code) 175 ctx.SetContentType(utils.ContentJson) 176 jval, mErr := json.Marshal(resp) 177 if mErr != nil { 178 log.Errorf("writePrometheusResponse: failed to marshal resp %+v", mErr) 179 return 180 } 181 _, mErr = ctx.Write(jval) 182 183 if mErr != nil { 184 log.Errorf("writePrometheusResponse: failed to write jval to http request %+v", mErr) 185 return 186 } 187 }