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