github.com/go-graphite/carbonapi@v0.17.0/zipper/protocols/v2/protobuf_group.go (about) 1 package v2 2 3 import ( 4 "context" 5 "encoding/json" 6 "math" 7 "net/http" 8 "net/url" 9 "strconv" 10 11 "github.com/ansel1/merry" 12 13 protov2 "github.com/go-graphite/protocol/carbonapi_v2_pb" 14 protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb" 15 16 "github.com/go-graphite/carbonapi/limiter" 17 utilctx "github.com/go-graphite/carbonapi/util/ctx" 18 "github.com/go-graphite/carbonapi/zipper/helper" 19 "github.com/go-graphite/carbonapi/zipper/httpHeaders" 20 "github.com/go-graphite/carbonapi/zipper/metadata" 21 "github.com/go-graphite/carbonapi/zipper/types" 22 23 "go.uber.org/zap" 24 ) 25 26 const ( 27 format = "protobuf" 28 ) 29 30 func init() { 31 aliases := []string{"carbonapi_v2_pb", "proto_v2_pb", "v2_pb", "pb", "pb3", "protobuf", "protobuf3"} 32 metadata.Metadata.Lock() 33 for _, name := range aliases { 34 metadata.Metadata.SupportedProtocols[name] = struct{}{} 35 metadata.Metadata.ProtocolInits[name] = New 36 metadata.Metadata.ProtocolInitsWithLimiter[name] = NewWithLimiter 37 } 38 defer metadata.Metadata.Unlock() 39 } 40 41 // RoundRobin is used to connect to backends inside clientGroups, implements BackendServer interface 42 type ClientProtoV2Group struct { 43 groupName string 44 servers []string 45 46 client *http.Client 47 48 limiter limiter.ServerLimiter 49 logger *zap.Logger 50 timeout types.Timeouts 51 maxTries int 52 maxMetricsPerRequest int 53 54 httpQuery *helper.HttpQuery 55 } 56 57 func (c *ClientProtoV2Group) Children() []types.BackendServer { 58 return []types.BackendServer{c} 59 } 60 61 func NewWithLimiter(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool, l limiter.ServerLimiter) (types.BackendServer, merry.Error) { 62 logger = logger.With(zap.String("type", "protoV2Group"), zap.String("name", config.GroupName)) 63 64 httpClient := helper.GetHTTPClient(logger, config) 65 66 httpLimiter := limiter.NewServerLimiter(config.Servers, *config.ConcurrencyLimit) 67 httpQuery := helper.NewHttpQuery(config.GroupName, config.Servers, *config.MaxTries, httpLimiter, httpClient, httpHeaders.ContentTypeCarbonAPIv2PB) 68 69 c := &ClientProtoV2Group{ 70 groupName: config.GroupName, 71 servers: config.Servers, 72 timeout: *config.Timeouts, 73 maxTries: *config.MaxTries, 74 maxMetricsPerRequest: *config.MaxBatchSize, 75 76 client: httpClient, 77 limiter: l, 78 logger: logger, 79 80 httpQuery: httpQuery, 81 } 82 return c, nil 83 } 84 85 func New(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool) (types.BackendServer, merry.Error) { 86 if config.ConcurrencyLimit == nil { 87 return nil, types.ErrConcurrencyLimitNotSet 88 } 89 if len(config.Servers) == 0 { 90 return nil, types.ErrNoServersSpecified 91 } 92 limiter := limiter.NewServerLimiter(config.Servers, *config.ConcurrencyLimit) 93 94 return NewWithLimiter(logger, config, tldCacheDisabled, requireSuccessAll, limiter) 95 } 96 97 func (c ClientProtoV2Group) MaxMetricsPerRequest() int { 98 return c.maxMetricsPerRequest 99 } 100 101 func (c ClientProtoV2Group) Name() string { 102 return c.groupName 103 } 104 105 func (c ClientProtoV2Group) Backends() []string { 106 return c.servers 107 } 108 109 type queryBatch struct { 110 pathExpression string 111 from int64 112 until int64 113 } 114 115 func (c *ClientProtoV2Group) Fetch(ctx context.Context, request *protov3.MultiFetchRequest) (*protov3.MultiFetchResponse, *types.Stats, merry.Error) { 116 logger := c.logger.With(zap.String("type", "fetch"), zap.String("request", request.String()), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 117 stats := &types.Stats{} 118 rewrite, _ := url.Parse("http://127.0.0.1/render/") 119 120 batches := make(map[queryBatch][]string) 121 for _, m := range request.Metrics { 122 b := queryBatch{ 123 pathExpression: m.PathExpression, 124 from: m.StartTime, 125 until: m.StopTime, 126 } 127 128 batches[b] = append(batches[b], m.Name) 129 } 130 131 var r protov3.MultiFetchResponse 132 var e merry.Error 133 for batch, targets := range batches { 134 v := url.Values{ 135 "target": targets, 136 "format": []string{format}, 137 "from": []string{strconv.Itoa(int(batch.from))}, 138 "until": []string{strconv.Itoa(int(batch.until))}, 139 } 140 rewrite.RawQuery = v.Encode() 141 stats.RenderRequests += 1 142 res, err := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil) 143 if err != nil { 144 stats.RenderErrors += 1 145 if merry.Is(err, types.ErrTimeoutExceeded) { 146 stats.Timeouts += 1 147 stats.RenderTimeouts += 1 148 } 149 if e == nil { 150 e = err 151 } else { 152 e = e.WithCause(err) 153 } 154 continue 155 } 156 157 var metrics protov2.MultiFetchResponse 158 marshalErr := metrics.Unmarshal(res.Response) 159 if marshalErr != nil { 160 stats.RenderErrors += 1 161 if e == nil { 162 e = types.ErrUnmarshalFailed.WithCause(marshalErr) 163 } else { 164 e = e.WithCause(marshalErr) 165 } 166 continue 167 } 168 169 for _, m := range metrics.Metrics { 170 for i, v := range m.IsAbsent { 171 if v { 172 m.Values[i] = math.NaN() 173 } 174 } 175 r.Metrics = append(r.Metrics, protov3.FetchResponse{ 176 Name: m.Name, 177 PathExpression: batch.pathExpression, 178 ConsolidationFunc: "Average", 179 StopTime: int64(m.StopTime), 180 StartTime: int64(m.StartTime), 181 StepTime: int64(m.StepTime), 182 Values: m.Values, 183 XFilesFactor: 0.0, 184 RequestStartTime: batch.from, 185 RequestStopTime: batch.until, 186 }) 187 } 188 } 189 190 if e != nil { 191 logger.Warn("errors occurred while getting results", 192 zap.Any("errors", e), 193 ) 194 return &r, stats, e 195 } 196 return &r, stats, nil 197 } 198 199 func (c *ClientProtoV2Group) Find(ctx context.Context, request *protov3.MultiGlobRequest) (*protov3.MultiGlobResponse, *types.Stats, merry.Error) { 200 logger := c.logger.With(zap.String("type", "find"), zap.Strings("request", request.Metrics), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 201 stats := &types.Stats{} 202 rewrite, _ := url.Parse("http://127.0.0.1/metrics/find/") 203 204 var r protov3.MultiGlobResponse 205 r.Metrics = make([]protov3.GlobResponse, 0) 206 var e merry.Error 207 for _, query := range request.Metrics { 208 logger.Debug("will do query", 209 zap.String("query", query), 210 ) 211 v := url.Values{ 212 "query": []string{query}, 213 "format": []string{format}, 214 } 215 rewrite.RawQuery = v.Encode() 216 stats.FindRequests += 1 217 res, err := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil) 218 if err != nil { 219 stats.FindErrors += 1 220 if merry.Is(err, types.ErrTimeoutExceeded) { 221 stats.Timeouts += 1 222 stats.FindTimeouts += 1 223 } 224 if e == nil { 225 e = err 226 } else { 227 e = e.WithCause(err) 228 } 229 continue 230 } 231 var globs protov2.GlobResponse 232 marshalErr := globs.Unmarshal(res.Response) 233 if marshalErr != nil { 234 stats.FindErrors += 1 235 if e == nil { 236 e = types.ErrUnmarshalFailed.WithCause(marshalErr) 237 } else { 238 e = e.WithCause(marshalErr) 239 } 240 continue 241 } 242 stats.Servers = append(stats.Servers, res.Server) 243 matches := make([]protov3.GlobMatch, 0, len(globs.Matches)) 244 for _, m := range globs.Matches { 245 matches = append(matches, protov3.GlobMatch{ 246 Path: m.Path, 247 IsLeaf: m.IsLeaf, 248 }) 249 } 250 if len(matches) != 0 { 251 r.Metrics = append(r.Metrics, protov3.GlobResponse{ 252 Name: globs.Name, 253 Matches: matches, 254 }) 255 } 256 } 257 258 if e != nil { 259 logger.Warn("errors occurred while getting results", 260 zap.Any("errors", e), 261 ) 262 return nil, stats, e 263 } 264 return &r, stats, nil 265 } 266 267 func (c *ClientProtoV2Group) Info(ctx context.Context, request *protov3.MultiMetricsInfoRequest) (*protov3.ZipperInfoResponse, *types.Stats, merry.Error) { 268 logger := c.logger.With(zap.String("type", "info"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 269 stats := &types.Stats{} 270 rewrite, _ := url.Parse("http://127.0.0.1/info/") 271 272 var r protov3.ZipperInfoResponse 273 var e merry.Error 274 r.Info = make(map[string]protov3.MultiMetricsInfoResponse) 275 data := protov3.MultiMetricsInfoResponse{} 276 server := c.groupName 277 if len(c.servers) == 1 { 278 server = c.servers[0] 279 } 280 281 for _, query := range request.Names { 282 v := url.Values{ 283 "target": []string{query}, 284 "format": []string{format}, 285 } 286 rewrite.RawQuery = v.Encode() 287 stats.InfoRequests += 1 288 res, err := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil) 289 if err != nil { 290 stats.InfoErrors += 1 291 if merry.Is(err, types.ErrTimeoutExceeded) { 292 stats.Timeouts += 1 293 stats.InfoTimeouts += 1 294 } 295 if e == nil { 296 e = err 297 } else { 298 e = e.WithCause(err) 299 } 300 continue 301 } 302 303 var info protov2.InfoResponse 304 marshalErr := info.Unmarshal(res.Response) 305 if marshalErr != nil { 306 stats.InfoErrors += 1 307 if e == nil { 308 e = types.ErrUnmarshalFailed.WithCause(marshalErr) 309 } else { 310 e = e.WithCause(marshalErr) 311 } 312 continue 313 } 314 stats.Servers = append(stats.Servers, res.Server) 315 316 if info.AggregationMethod == "" { 317 info.AggregationMethod = "average" 318 } 319 infoV3 := protov3.MetricsInfoResponse{ 320 Name: info.Name, 321 ConsolidationFunc: info.AggregationMethod, 322 XFilesFactor: info.XFilesFactor, 323 MaxRetention: int64(info.MaxRetention), 324 } 325 326 for _, r := range info.Retentions { 327 newR := protov3.Retention{ 328 SecondsPerPoint: int64(r.SecondsPerPoint), 329 NumberOfPoints: int64(r.NumberOfPoints), 330 } 331 infoV3.Retentions = append(infoV3.Retentions, newR) 332 } 333 334 data.Metrics = append(data.Metrics, infoV3) 335 } 336 r.Info[server] = data 337 338 if e != nil { 339 logger.Warn("errors occurred while getting results", 340 zap.Any("errors", e), 341 ) 342 return &r, stats, e 343 } 344 345 logger.Debug("got client response", 346 zap.Any("response", r), 347 ) 348 349 return &r, stats, nil 350 } 351 352 func (c *ClientProtoV2Group) doTagQuery(ctx context.Context, isTagName bool, query string, limit int64) ([]string, merry.Error) { 353 logger := c.logger 354 var rewrite *url.URL 355 if isTagName { 356 logger = logger.With(zap.String("type", "tagName"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 357 rewrite, _ = url.Parse("http://127.0.0.1/tags/autoComplete/tags") 358 } else { 359 logger = logger.With(zap.String("type", "tagValues"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx))) 360 rewrite, _ = url.Parse("http://127.0.0.1/tags/autoComplete/values") 361 } 362 363 var r []string 364 365 rewrite.RawQuery = query 366 res, e := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil) 367 if e != nil { 368 return r, e 369 } 370 371 err := json.Unmarshal(res.Response, &r) 372 if err != nil { 373 return r, merry.Wrap(err) 374 } 375 376 logger.Debug("got client response", 377 zap.Strings("response", r), 378 ) 379 380 return r, nil 381 } 382 383 func (c *ClientProtoV2Group) TagNames(ctx context.Context, query string, limit int64) ([]string, merry.Error) { 384 return c.doTagQuery(ctx, true, query, limit) 385 } 386 387 func (c *ClientProtoV2Group) TagValues(ctx context.Context, query string, limit int64) ([]string, merry.Error) { 388 return c.doTagQuery(ctx, false, query, limit) 389 } 390 391 func (c *ClientProtoV2Group) List(ctx context.Context) (*protov3.ListMetricsResponse, *types.Stats, merry.Error) { 392 return nil, nil, types.ErrNotImplementedYet 393 } 394 func (c *ClientProtoV2Group) Stats(ctx context.Context) (*protov3.MetricDetailsResponse, *types.Stats, merry.Error) { 395 return nil, nil, types.ErrNotImplementedYet 396 } 397 398 func (c *ClientProtoV2Group) ProbeTLDs(ctx context.Context) ([]string, merry.Error) { 399 logger := c.logger.With(zap.String("function", "prober")) 400 req := &protov3.MultiGlobRequest{ 401 Metrics: []string{"*"}, 402 } 403 404 logger.Debug("doing request", 405 zap.Strings("request", req.Metrics), 406 ) 407 408 res, _, err := c.Find(ctx, req) 409 if err != nil { 410 return nil, err 411 } 412 413 var tlds []string 414 for _, m := range res.Metrics { 415 for _, v := range m.Matches { 416 tlds = append(tlds, v.Path) 417 } 418 } 419 420 logger.Debug("will return data", 421 zap.Strings("tlds", tlds), 422 ) 423 424 return tlds, nil 425 }