github.com/go-graphite/carbonapi@v0.17.0/cmd/mockbackend/render.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "log" 8 "math" 9 "math/rand" 10 "net/http" 11 "time" 12 13 "github.com/go-graphite/protocol/carbonapi_v2_pb" 14 "github.com/go-graphite/protocol/carbonapi_v3_pb" 15 ogórek "github.com/lomik/og-rek" 16 "go.uber.org/zap" 17 18 "github.com/go-graphite/carbonapi/zipper/httpHeaders" 19 ) 20 21 func (cfg *listener) renderHandler(wr http.ResponseWriter, req *http.Request) { 22 hdrs := make(map[string][]string) 23 24 for n, v := range req.Header { 25 hdrs[n] = v 26 } 27 28 logger := cfg.logger.With( 29 zap.String("function", "renderHandler"), 30 zap.String("method", req.Method), 31 zap.String("path", req.URL.Path), 32 zap.Any("headers", hdrs), 33 ) 34 35 logger.Info("got request") 36 if cfg.Code != http.StatusOK { 37 wr.WriteHeader(cfg.Code) 38 return 39 } 40 41 format, err := getFormat(req) 42 if err != nil { 43 logger.Error("bad request, failed to parse format") 44 wr.WriteHeader(http.StatusBadRequest) 45 _, _ = wr.Write([]byte(err.Error())) 46 return 47 } 48 49 targets := req.Form["target"] 50 maxDataPoints := int64(0) 51 52 if format == protoV3Format { 53 body, err := io.ReadAll(req.Body) 54 if err != nil { 55 logger.Error("bad request, failed to read request body", 56 zap.Error(err), 57 ) 58 http.Error(wr, "bad request (failed to read request body): "+err.Error(), http.StatusBadRequest) 59 return 60 } 61 62 var pv3Request carbonapi_v3_pb.MultiFetchRequest 63 err = pv3Request.Unmarshal(body) 64 65 if err != nil { 66 logger.Error("bad request, failed to unmarshal request", 67 zap.Error(err), 68 ) 69 http.Error(wr, "bad request (failed to parse format): "+err.Error(), http.StatusBadRequest) 70 return 71 } 72 73 targets = make([]string, len(pv3Request.Metrics)) 74 for i, r := range pv3Request.Metrics { 75 targets[i] = r.PathExpression 76 } 77 maxDataPoints = pv3Request.Metrics[0].MaxDataPoints 78 } 79 80 logger.Info("request details", 81 zap.Strings("target", targets), 82 zap.String("format", format.String()), 83 zap.Int64("maxDataPoints", maxDataPoints), 84 ) 85 86 multiv2 := carbonapi_v2_pb.MultiFetchResponse{ 87 Metrics: []carbonapi_v2_pb.FetchResponse{}, 88 } 89 90 multiv3 := carbonapi_v3_pb.MultiFetchResponse{ 91 Metrics: []carbonapi_v3_pb.FetchResponse{}, 92 } 93 94 httpCode := http.StatusOK 95 for _, target := range targets { 96 if response, ok := cfg.Expressions[target]; ok { 97 if response.ReplyDelayMS > 0 { 98 delay := time.Duration(response.ReplyDelayMS) * time.Millisecond 99 logger.Info("will add extra delay", 100 zap.Duration("delay", delay), 101 ) 102 time.Sleep(delay) 103 } 104 if response.Code > 0 && response.Code != http.StatusOK { 105 httpCode = response.Code 106 } 107 if httpCode == http.StatusOK { 108 for _, m := range response.Data { 109 step := m.Step 110 if step == 0 { 111 step = 1 112 } 113 startTime := m.StartTime 114 if startTime == 0 { 115 startTime = step 116 } 117 isAbsent := make([]bool, 0, len(m.Values)) 118 protov2Values := make([]float64, 0, len(m.Values)) 119 for i := range m.Values { 120 if math.IsNaN(m.Values[i]) { 121 isAbsent = append(isAbsent, true) 122 protov2Values = append(protov2Values, 0.0) 123 } else { 124 isAbsent = append(isAbsent, false) 125 protov2Values = append(protov2Values, m.Values[i]) 126 } 127 } 128 fr2 := carbonapi_v2_pb.FetchResponse{ 129 Name: m.MetricName, 130 StartTime: int32(startTime), 131 StopTime: int32(startTime + step*len(protov2Values)), 132 StepTime: int32(step), 133 Values: protov2Values, 134 IsAbsent: isAbsent, 135 } 136 137 fr3 := carbonapi_v3_pb.FetchResponse{ 138 Name: m.MetricName, 139 PathExpression: target, 140 ConsolidationFunc: "avg", 141 StartTime: int64(startTime), 142 StopTime: int64(startTime + step*len(m.Values)), 143 StepTime: int64(step), 144 XFilesFactor: 0, 145 HighPrecisionTimestamps: false, 146 Values: m.Values, 147 RequestStartTime: 1, 148 RequestStopTime: int64(startTime + step*len(m.Values)), 149 } 150 151 multiv2.Metrics = append(multiv2.Metrics, fr2) 152 multiv3.Metrics = append(multiv3.Metrics, fr3) 153 } 154 } 155 } 156 } 157 158 if httpCode == http.StatusOK { 159 if len(multiv2.Metrics) == 0 { 160 wr.WriteHeader(http.StatusNotFound) 161 _, _ = wr.Write([]byte("Not found")) 162 return 163 } 164 165 if cfg.Listener.ShuffleResults { 166 rand.Shuffle(len(multiv2.Metrics), func(i, j int) { 167 multiv2.Metrics[i], multiv2.Metrics[j] = multiv2.Metrics[j], multiv2.Metrics[i] 168 }) 169 rand.Shuffle(len(multiv3.Metrics), func(i, j int) { 170 multiv3.Metrics[i], multiv3.Metrics[j] = multiv3.Metrics[j], multiv3.Metrics[i] 171 }) 172 } 173 174 contentType, d := cfg.marshalResponse(wr, logger, format, multiv3, multiv2) 175 if d == nil { 176 return 177 } 178 wr.Header().Set("Content-Type", contentType) 179 _, _ = wr.Write(d) 180 } else { 181 wr.WriteHeader(httpCode) 182 _, _ = wr.Write([]byte(http.StatusText(httpCode))) 183 } 184 } 185 186 func (cfg *listener) marshalResponse(wr http.ResponseWriter, logger *zap.Logger, format responseFormat, multiv3 carbonapi_v3_pb.MultiFetchResponse, multiv2 carbonapi_v2_pb.MultiFetchResponse) (string, []byte) { 187 var d []byte 188 var contentType string 189 var err error 190 switch format { 191 case pickleFormat: 192 contentType = httpHeaders.ContentTypePickle 193 if cfg.EmptyBody { 194 break 195 } 196 var response []map[string]interface{} 197 198 for _, metric := range multiv3.GetMetrics() { 199 m := make(map[string]interface{}) 200 m["start"] = metric.StartTime 201 m["step"] = metric.StepTime 202 m["end"] = metric.StopTime 203 m["name"] = metric.Name 204 m["pathExpression"] = metric.PathExpression 205 m["xFilesFactor"] = 0.5 206 m["consolidationFunc"] = "avg" 207 208 mv := make([]interface{}, len(metric.Values)) 209 for i, p := range metric.Values { 210 if math.IsNaN(p) { 211 mv[i] = nil 212 } else { 213 mv[i] = p 214 } 215 } 216 217 m["values"] = mv 218 log.Printf("%+v\n\n", m) 219 response = append(response, m) 220 } 221 222 var buf bytes.Buffer 223 logger.Info("request will be served", 224 zap.String("format", "pickle"), 225 zap.Any("content", response), 226 ) 227 pEnc := ogórek.NewEncoder(&buf) 228 err = pEnc.Encode(response) 229 if err != nil { 230 wr.WriteHeader(http.StatusBadGateway) 231 _, _ = wr.Write([]byte(err.Error())) 232 return "", nil 233 } 234 d = buf.Bytes() 235 case protoV2Format: 236 contentType = httpHeaders.ContentTypeCarbonAPIv2PB 237 if cfg.EmptyBody { 238 break 239 } 240 logger.Info("request will be served", 241 zap.String("format", "protov2"), 242 zap.Any("content", multiv2), 243 ) 244 d, err = multiv2.Marshal() 245 if err != nil { 246 wr.WriteHeader(http.StatusBadGateway) 247 _, _ = wr.Write([]byte(err.Error())) 248 return "", nil 249 } 250 case protoV3Format: 251 contentType = httpHeaders.ContentTypeCarbonAPIv3PB 252 if cfg.EmptyBody { 253 break 254 } 255 logger.Info("request will be served", 256 zap.String("format", "protov3"), 257 zap.Any("content", multiv3), 258 ) 259 d, err = multiv3.Marshal() 260 if err != nil { 261 wr.WriteHeader(http.StatusBadGateway) 262 _, _ = wr.Write([]byte(err.Error())) 263 return "", nil 264 } 265 case jsonFormat: 266 contentType = "application/json" 267 if cfg.EmptyBody { 268 break 269 } 270 logger.Info("request will be served", 271 zap.String("format", "json"), 272 zap.Any("content", multiv2), 273 ) 274 d, err = json.Marshal(multiv2) 275 if err != nil { 276 wr.WriteHeader(http.StatusBadGateway) 277 _, _ = wr.Write([]byte(err.Error())) 278 return "", nil 279 } 280 default: 281 logger.Error("format is not supported", 282 zap.Any("format", format), 283 ) 284 return "", nil 285 } 286 return contentType, d 287 }