github.com/go-graphite/carbonapi@v0.17.0/expr/types/types.go (about) 1 package types 2 3 import ( 4 "bytes" 5 "errors" 6 "math" 7 "sort" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/go-graphite/carbonapi/expr/consolidations" 13 "github.com/go-graphite/carbonapi/expr/tags" 14 "github.com/go-graphite/carbonapi/expr/types/config" 15 pbv2 "github.com/go-graphite/protocol/carbonapi_v2_pb" 16 pb "github.com/go-graphite/protocol/carbonapi_v3_pb" 17 pickle "github.com/lomik/og-rek" 18 ) 19 20 var ( 21 // ErrWildcardNotAllowed is an eval error returned when a wildcard/glob argument is found where a single series is required. 22 ErrWildcardNotAllowed = errors.New("found wildcard where series expected") 23 // ErrTooManyArguments is an eval error returned when too many arguments are provided. 24 ErrTooManyArguments = errors.New("too many arguments") 25 ) 26 27 // MetricData contains necessary data to represent parsed metric (ready to be send out or drawn) 28 type MetricData struct { 29 pb.FetchResponse 30 31 GraphOptions 32 33 ValuesPerPoint int 34 aggregatedValues []float64 35 Tags map[string]string 36 AggregateFunction func([]float64) float64 `json:"-"` 37 } 38 39 func appendInt2(b []byte, n int64) []byte { 40 if n > 9 { 41 return strconv.AppendInt(b, n, 10) 42 } 43 b = append(b, '0') 44 return strconv.AppendInt(b, n, 10) 45 } 46 47 // MarshalCSV marshals metric data to CSV 48 func MarshalCSV(results []*MetricData) []byte { 49 if len(results) == 0 { 50 return []byte("[]") 51 } 52 n := len(results) * (len(results[0].Name) + len(results[0].PathExpression) + 128*len(results[0].Values) + 128) 53 b := make([]byte, 0, n) 54 55 for _, r := range results { 56 57 step := r.StepTime 58 t := r.StartTime 59 for _, v := range r.Values { 60 b = append(b, '"') 61 b = append(b, r.Name...) 62 b = append(b, `",`...) 63 tm := time.Unix(t, 0).UTC() 64 b = strconv.AppendInt(b, int64(tm.Year()), 10) 65 b = append(b, '-') 66 b = appendInt2(b, int64(tm.Month())) 67 b = append(b, '-') 68 b = appendInt2(b, int64(tm.Day())) 69 b = append(b, ' ') 70 b = appendInt2(b, int64(tm.Hour())) 71 b = append(b, ':') 72 b = appendInt2(b, int64(tm.Minute())) 73 b = append(b, ':') 74 b = appendInt2(b, int64(tm.Second())) 75 b = append(b, ',') 76 if !math.IsNaN(v) { 77 b = strconv.AppendFloat(b, v, 'f', -1, 64) 78 } 79 b = append(b, '\n') 80 t += step 81 } 82 } 83 return b 84 } 85 86 // ConsolidateJSON consolidates values to maxDataPoints size 87 func ConsolidateJSON(maxDataPoints int64, results []*MetricData) { 88 if len(results) == 0 { 89 return 90 } 91 startTime := results[0].StartTime 92 endTime := results[0].StopTime 93 for _, r := range results { 94 t := r.StartTime 95 if startTime > t { 96 startTime = t 97 } 98 t = r.StopTime 99 if endTime < t { 100 endTime = t 101 } 102 } 103 104 timeRange := endTime - startTime 105 106 if timeRange <= 0 { 107 return 108 } 109 110 for _, r := range results { 111 numberOfDataPoints := math.Floor(float64(timeRange) / float64(r.StepTime)) 112 if numberOfDataPoints > float64(maxDataPoints) { 113 valuesPerPoint := math.Ceil(numberOfDataPoints / float64(maxDataPoints)) 114 r.SetValuesPerPoint(int(valuesPerPoint)) 115 } 116 } 117 } 118 119 // MarshalJSON marshals metric data to JSON 120 func MarshalJSON(results []*MetricData, timestampMultiplier int64, noNullPoints bool) []byte { 121 if len(results) == 0 { 122 return []byte("[]") 123 } 124 n := len(results) * (len(results[0].Name) + len(results[0].PathExpression) + 128*len(results[0].Values) + 128) 125 126 b := make([]byte, 0, n) 127 b = append(b, '[') 128 129 var topComma bool 130 for _, r := range results { 131 if r == nil { 132 continue 133 } 134 135 if topComma { 136 b = append(b, ',') 137 } 138 topComma = true 139 140 b = append(b, `{"target":`...) 141 b = strconv.AppendQuoteToASCII(b, r.Name) 142 b = append(b, `,"datapoints":[`...) 143 144 var innerComma bool 145 t := r.AggregatedStartTime() * timestampMultiplier 146 for _, v := range r.AggregatedValues() { 147 if noNullPoints && math.IsNaN(v) { 148 t += r.AggregatedTimeStep() * timestampMultiplier 149 } else { 150 if innerComma { 151 b = append(b, ',') 152 } 153 innerComma = true 154 155 b = append(b, '[') 156 157 if math.IsNaN(v) || math.IsInf(v, 1) || math.IsInf(v, -1) { 158 b = append(b, "null"...) 159 } else { 160 b = strconv.AppendFloat(b, v, 'f', -1, 64) 161 } 162 163 b = append(b, ',') 164 165 b = strconv.AppendInt(b, t, 10) 166 167 b = append(b, ']') 168 169 t += r.AggregatedTimeStep() * timestampMultiplier 170 } 171 } 172 173 b = append(b, `],"tags":{`...) 174 notFirstTag := false 175 responseTags := make([]string, 0, len(r.Tags)) 176 for tag := range r.Tags { 177 responseTags = append(responseTags, tag) 178 } 179 sort.Strings(responseTags) 180 for _, tag := range responseTags { 181 v := r.Tags[tag] 182 if notFirstTag { 183 b = append(b, ',') 184 } 185 b = strconv.AppendQuoteToASCII(b, tag) 186 b = append(b, ':') 187 b = strconv.AppendQuoteToASCII(b, v) 188 notFirstTag = true 189 } 190 191 b = append(b, `}}`...) 192 } 193 194 b = append(b, ']') 195 196 return b 197 } 198 199 // MarshalPickle marshals metric data to pickle format 200 func MarshalPickle(results []*MetricData) []byte { 201 202 var p []map[string]interface{} 203 204 for _, r := range results { 205 values := make([]interface{}, len(r.Values)) 206 for i, v := range r.Values { 207 if math.IsNaN(v) { 208 values[i] = pickle.None{} 209 } else { 210 values[i] = v 211 } 212 213 } 214 p = append(p, map[string]interface{}{ 215 "name": r.Name, 216 "pathExpression": r.PathExpression, 217 "consolidationFunc": r.ConsolidationFunc, 218 "start": r.StartTime, 219 "end": r.StopTime, 220 "step": r.StepTime, 221 "xFilesFactor": r.XFilesFactor, 222 "values": values, 223 }) 224 } 225 226 var buf bytes.Buffer 227 228 penc := pickle.NewEncoder(&buf) 229 _ = penc.Encode(p) 230 231 return buf.Bytes() 232 } 233 234 // MarshalProtobufV3 marshals metric data to protobuf 235 func MarshalProtobufV2(results []*MetricData) ([]byte, error) { 236 response := pbv2.MultiFetchResponse{} 237 for _, metric := range results { 238 fmv3 := metric.FetchResponse 239 v := make([]float64, len(fmv3.Values)) 240 isAbsent := make([]bool, len(fmv3.Values)) 241 for i := range fmv3.Values { 242 if math.IsNaN(fmv3.Values[i]) { 243 v[i] = 0 244 isAbsent[i] = true 245 } else { 246 v[i] = fmv3.Values[i] 247 } 248 } 249 fm := pbv2.FetchResponse{ 250 Name: fmv3.Name, 251 StartTime: int32(fmv3.StartTime), 252 StopTime: int32(fmv3.StopTime), 253 StepTime: int32(fmv3.StepTime), 254 Values: v, 255 IsAbsent: isAbsent, 256 } 257 response.Metrics = append(response.Metrics, fm) 258 } 259 b, err := response.Marshal() 260 if err != nil { 261 return nil, err 262 } 263 264 return b, nil 265 } 266 267 // MarshalProtobufV3 marshals metric data to protobuf 268 func MarshalProtobufV3(results []*MetricData) ([]byte, error) { 269 response := pb.MultiFetchResponse{} 270 for _, metric := range results { 271 response.Metrics = append(response.Metrics, metric.FetchResponse) 272 } 273 b, err := response.Marshal() 274 if err != nil { 275 return nil, err 276 } 277 278 return b, nil 279 } 280 281 // MarshalRaw marshals metric data to graphite's internal format, called 'raw' 282 func MarshalRaw(results []*MetricData) []byte { 283 if len(results) == 0 { 284 return []byte{} 285 } 286 n := len(results) * (len(results[0].Name) + len(results[0].PathExpression) + 128*len(results[0].Values) + 128) 287 b := make([]byte, 0, n) 288 289 for _, r := range results { 290 291 b = append(b, r.Name...) 292 293 b = append(b, ',') 294 b = strconv.AppendInt(b, r.StartTime, 10) 295 b = append(b, ',') 296 b = strconv.AppendInt(b, r.StopTime, 10) 297 b = append(b, ',') 298 b = strconv.AppendInt(b, r.StepTime, 10) 299 b = append(b, '|') 300 301 var comma bool 302 for _, v := range r.Values { 303 if comma { 304 b = append(b, ',') 305 } 306 comma = true 307 if math.IsNaN(v) { 308 b = append(b, "None"...) 309 } else { 310 b = strconv.AppendFloat(b, v, 'f', -1, 64) 311 } 312 } 313 314 b = append(b, '\n') 315 } 316 return b 317 } 318 319 // SetValuesPerPoint sets value per point coefficient. 320 func (r *MetricData) SetValuesPerPoint(v int) { 321 r.ValuesPerPoint = v 322 r.aggregatedValues = nil 323 } 324 325 // AggregatedTimeStep aggregates time step 326 func (r *MetricData) AggregatedTimeStep() int64 { 327 if r.ValuesPerPoint == 1 || r.ValuesPerPoint == 0 { 328 return r.StepTime 329 } 330 331 return r.StepTime * int64(r.ValuesPerPoint) 332 } 333 334 // AggregatedStartTime returns the start time of the aggregated series. 335 // This can be different from the original start time if NudgeStartTimeOnAggregation 336 // or UseBucketsHighestTimestampOnAggregation are enabled. 337 func (r *MetricData) AggregatedStartTime() int64 { 338 start := r.StartTime + r.nudgePointsCount()*r.StepTime 339 if config.Config.UseBucketsHighestTimestampOnAggregation { 340 return start + r.AggregatedTimeStep() - r.StepTime 341 } 342 return start 343 } 344 345 // nudgePointsCount returns the number of points to discard at the beginning of 346 // the series when aggregating. This is done if NudgeStartTimeOnAggregation is 347 // enabled, and has the purpose of assigning timestamps of a series to buckets 348 // consistently across different time ranges. To simplify the aggregation 349 // logic, we discard points at the beginning of the series so that a bucket 350 // starts right at the beginning. This function calculates how many points to 351 // discard. 352 func (r *MetricData) nudgePointsCount() int64 { 353 if !config.Config.NudgeStartTimeOnAggregation { 354 return 0 355 } 356 357 if len(r.Values) <= 2*r.ValuesPerPoint { 358 // There would be less than 2 points after aggregation, removing one would be too impactful. 359 return 0 360 } 361 362 // Suppose r.StartTime=4, r.StepTime=3 and aggTimeStep=6. 363 // - ts: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 ... 364 // - original buckets: -- -- --| | | | | | ... 365 // - aggregated buckets: -- -- --| | | ... 366 367 // We start counting our aggTimeStep buckets at absolute time r.StepTime. 368 // Notice the following: 369 // - ts: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 ... 370 // - bucket #: - - - 1 1 1 1 1 1 2 2 2 2 2 2 3 3 ... 371 // - (ts-step) % aggTimeStep: - - - 0 1 2 3 4 5 0 1 2 3 4 5 0 1 ... 372 373 // Given a timestamp 'ts', we can calculate how far it is from the beginning 374 // of the nearest bucket to the right by doing: 375 // * aggTimeStep - ((ts-r.StepTime) % aggTimeStep) 376 // Using this, we calculate the 'distance' from r.StartTime to the 377 // nearest bucket to the right. If this distance is less than aggTimeStep, 378 // then r.StartTime is not the beginning of a bucket. We need to discard 379 // dist / r.StepTime points (which could be zero if dist < r.StepTime). 380 aggTimeStep := r.AggregatedTimeStep() 381 dist := aggTimeStep - ((r.StartTime - r.StepTime) % aggTimeStep) 382 if dist < aggTimeStep { 383 return dist / r.StepTime 384 } 385 return 0 386 } 387 388 // GetAggregateFunction returns MetricData.AggregateFunction and set it, if it's not yet 389 func (r *MetricData) GetAggregateFunction() func([]float64) float64 { 390 if r.AggregateFunction == nil { 391 var ok bool 392 if r.AggregateFunction, ok = consolidations.ConsolidationToFunc[strings.ToLower(r.ConsolidationFunc)]; !ok { 393 // if consolidation function is not known, we should fall back to average 394 r.AggregateFunction = consolidations.AvgValue 395 } 396 } 397 398 return r.AggregateFunction 399 } 400 401 // AggregatedValues aggregates values (with cache) 402 func (r *MetricData) AggregatedValues() []float64 { 403 if r.aggregatedValues == nil { 404 r.AggregateValues() 405 } 406 return r.aggregatedValues 407 } 408 409 // AggregateValues aggregates values 410 func (r *MetricData) AggregateValues() { 411 if r.ValuesPerPoint == 1 || r.ValuesPerPoint == 0 { 412 r.aggregatedValues = make([]float64, len(r.Values)) 413 copy(r.aggregatedValues, r.Values) 414 return 415 } 416 aggFunc := r.GetAggregateFunction() 417 418 n := len(r.Values)/r.ValuesPerPoint + 1 419 aggV := make([]float64, 0, n) 420 421 nudgeCount := r.nudgePointsCount() 422 v := r.Values[nudgeCount:] 423 424 for len(v) >= r.ValuesPerPoint { 425 val := aggFunc(v[:r.ValuesPerPoint]) 426 aggV = append(aggV, val) 427 v = v[r.ValuesPerPoint:] 428 } 429 430 if len(v) > 0 { 431 val := aggFunc(v) 432 aggV = append(aggV, val) 433 } 434 435 r.aggregatedValues = aggV 436 } 437 438 // Copy returns the copy of r. If includeValues set to true, it copies values as well. 439 func (r *MetricData) Copy(includeValues bool) *MetricData { 440 var values, aggregatedValues []float64 441 values = make([]float64, 0) 442 appliedFunctions := make([]string, 0) 443 aggregatedValues = nil 444 445 if includeValues { 446 values = make([]float64, len(r.Values)) 447 copy(values, r.Values) 448 449 if r.aggregatedValues != nil { 450 aggregatedValues = make([]float64, len(r.aggregatedValues)) 451 copy(aggregatedValues, r.aggregatedValues) 452 } 453 454 appliedFunctions = make([]string, len(r.AppliedFunctions)) 455 copy(appliedFunctions, r.AppliedFunctions) 456 } 457 458 tags := make(map[string]string) 459 for k, v := range r.Tags { 460 tags[k] = v 461 } 462 463 return &MetricData{ 464 FetchResponse: pb.FetchResponse{ 465 Name: r.Name, 466 PathExpression: r.PathExpression, 467 ConsolidationFunc: r.ConsolidationFunc, 468 StartTime: r.StartTime, 469 StopTime: r.StopTime, 470 StepTime: r.StepTime, 471 XFilesFactor: r.XFilesFactor, 472 HighPrecisionTimestamps: r.HighPrecisionTimestamps, 473 Values: values, 474 AppliedFunctions: appliedFunctions, 475 RequestStartTime: r.RequestStartTime, 476 RequestStopTime: r.RequestStopTime, 477 }, 478 GraphOptions: r.GraphOptions, 479 ValuesPerPoint: r.ValuesPerPoint, 480 aggregatedValues: aggregatedValues, 481 Tags: tags, 482 AggregateFunction: r.AggregateFunction, 483 } 484 } 485 486 func CopyLink(tags map[string]string) map[string]string { 487 newTags := make(map[string]string) 488 for k, v := range tags { 489 newTags[k] = v 490 } 491 return newTags 492 } 493 494 // CopyLink returns the copy of MetricData, Values not copied and link from parent. Tags map are copied 495 func (r *MetricData) CopyLink() *MetricData { 496 tags := CopyLink(r.Tags) 497 498 return &MetricData{ 499 FetchResponse: pb.FetchResponse{ 500 Name: r.Name, 501 PathExpression: r.PathExpression, 502 ConsolidationFunc: r.ConsolidationFunc, 503 StartTime: r.StartTime, 504 StopTime: r.StopTime, 505 StepTime: r.StepTime, 506 XFilesFactor: r.XFilesFactor, 507 HighPrecisionTimestamps: r.HighPrecisionTimestamps, 508 Values: r.Values, 509 AppliedFunctions: r.AppliedFunctions, 510 RequestStartTime: r.RequestStartTime, 511 RequestStopTime: r.RequestStopTime, 512 }, 513 GraphOptions: r.GraphOptions, 514 ValuesPerPoint: r.ValuesPerPoint, 515 aggregatedValues: r.aggregatedValues, 516 Tags: tags, 517 AggregateFunction: r.AggregateFunction, 518 } 519 } 520 521 // CopyLinkTags returns the copy of MetricData, Values not copied and link from parent. Tags map set by rereference without copy (so, DON'T change them for prevent naming bugs) 522 func (r *MetricData) CopyLinkTags() *MetricData { 523 return &MetricData{ 524 FetchResponse: pb.FetchResponse{ 525 Name: r.Name, 526 PathExpression: r.PathExpression, 527 ConsolidationFunc: r.ConsolidationFunc, 528 StartTime: r.StartTime, 529 StopTime: r.StopTime, 530 StepTime: r.StepTime, 531 XFilesFactor: r.XFilesFactor, 532 HighPrecisionTimestamps: r.HighPrecisionTimestamps, 533 Values: r.Values, 534 AppliedFunctions: r.AppliedFunctions, 535 RequestStartTime: r.RequestStartTime, 536 RequestStopTime: r.RequestStopTime, 537 }, 538 GraphOptions: r.GraphOptions, 539 ValuesPerPoint: r.ValuesPerPoint, 540 aggregatedValues: r.aggregatedValues, 541 Tags: r.Tags, 542 AggregateFunction: r.AggregateFunction, 543 } 544 } 545 546 // CopyName returns the copy of MetricData, Values not copied and link from parent. If name set, Name and Name tag changed 547 func (r *MetricData) CopyName(name string) *MetricData { 548 res := r.CopyLink() 549 res.Name = name 550 res.Tags["name"] = name 551 552 return res 553 } 554 555 // CopyNameWithDefault returns the copy of MetricData, Values not copied and link from parent. Name is changed, Tags will be reset. 556 // If Name tag not set, it will be set with default value. 557 // Use this function in aggregate function (like sumSeries) 558 func (r *MetricData) CopyNameWithDefault(name, defaultName string) *MetricData { 559 if name == "" { 560 name = defaultName 561 } 562 563 tags := tags.ExtractTags(ExtractName(name)) 564 if _, exist := tags["name"]; !exist { 565 tags["name"] = defaultName 566 } 567 568 return &MetricData{ 569 FetchResponse: pb.FetchResponse{ 570 Name: name, 571 PathExpression: r.PathExpression, 572 ConsolidationFunc: r.ConsolidationFunc, 573 StartTime: r.StartTime, 574 StopTime: r.StopTime, 575 StepTime: r.StepTime, 576 XFilesFactor: r.XFilesFactor, 577 HighPrecisionTimestamps: r.HighPrecisionTimestamps, 578 Values: r.Values, 579 AppliedFunctions: r.AppliedFunctions, 580 RequestStartTime: r.RequestStartTime, 581 RequestStopTime: r.RequestStopTime, 582 }, 583 GraphOptions: r.GraphOptions, 584 ValuesPerPoint: r.ValuesPerPoint, 585 aggregatedValues: r.aggregatedValues, 586 Tags: tags, 587 AggregateFunction: r.AggregateFunction, 588 } 589 } 590 591 // CopyTag returns the copy of MetricData, Values not copied and link from parent. If name set, Name and Name tag changed, Tags will be reset. 592 // WARNING: can provide inconsistence beetween name and tags, if incorectly used 593 func (r *MetricData) CopyTag(name string, tags map[string]string) *MetricData { 594 if name == "" { 595 return r.CopyLink() 596 } 597 598 return &MetricData{ 599 FetchResponse: pb.FetchResponse{ 600 Name: name, 601 PathExpression: r.PathExpression, 602 ConsolidationFunc: r.ConsolidationFunc, 603 StartTime: r.StartTime, 604 StopTime: r.StopTime, 605 StepTime: r.StepTime, 606 XFilesFactor: r.XFilesFactor, 607 HighPrecisionTimestamps: r.HighPrecisionTimestamps, 608 Values: r.Values, 609 AppliedFunctions: r.AppliedFunctions, 610 RequestStartTime: r.RequestStartTime, 611 RequestStopTime: r.RequestStopTime, 612 }, 613 GraphOptions: r.GraphOptions, 614 ValuesPerPoint: r.ValuesPerPoint, 615 aggregatedValues: r.aggregatedValues, 616 Tags: tags, 617 AggregateFunction: r.AggregateFunction, 618 } 619 } 620 621 // CopyNameArg returns the copy of MetricData, Values not copied and link from parent. Name is changed, tags extracted from seriesByTag args, if extractTags is true 622 // For use in functions like aggregate 623 func (r *MetricData) CopyNameArg(name, defaultName string, defaultTags map[string]string, extractTags bool) *MetricData { 624 if name == "" { 625 return r.CopyLink() 626 } 627 628 var tagsExtracted map[string]string 629 nameStiped := ExtractName(name) 630 if strings.HasPrefix(nameStiped, "seriesByTag(") { 631 if extractTags { 632 // from aggregation functions with seriesByTag 633 tagsExtracted = tags.ExtractSeriesByTags(nameStiped, defaultName) 634 } else { 635 tagsExtracted = defaultTags 636 } 637 } else { 638 tagsExtracted = tags.ExtractTags(nameStiped) 639 } 640 641 return &MetricData{ 642 FetchResponse: pb.FetchResponse{ 643 Name: name, 644 PathExpression: r.PathExpression, 645 ConsolidationFunc: r.ConsolidationFunc, 646 StartTime: r.StartTime, 647 StopTime: r.StopTime, 648 StepTime: r.StepTime, 649 XFilesFactor: r.XFilesFactor, 650 HighPrecisionTimestamps: r.HighPrecisionTimestamps, 651 Values: r.Values, 652 AppliedFunctions: r.AppliedFunctions, 653 RequestStartTime: r.RequestStartTime, 654 RequestStopTime: r.RequestStopTime, 655 }, 656 GraphOptions: r.GraphOptions, 657 ValuesPerPoint: r.ValuesPerPoint, 658 aggregatedValues: r.aggregatedValues, 659 Tags: tagsExtracted, 660 AggregateFunction: r.AggregateFunction, 661 } 662 } 663 664 // CopyName returns the copy of MetricData, Values not copied and link from parent. If name set, Name and Name tag changed, Tags wil be reset 665 func (r *MetricData) CopyNameWithVal(name string) *MetricData { 666 if name == "" { 667 return r.Copy(true) 668 } 669 670 values := make([]float64, len(r.Values)) 671 copy(values, r.Values) 672 673 tags := tags.ExtractTags(ExtractName(name)) 674 675 return &MetricData{ 676 FetchResponse: pb.FetchResponse{ 677 Name: name, 678 PathExpression: r.PathExpression, 679 ConsolidationFunc: r.ConsolidationFunc, 680 StartTime: r.StartTime, 681 StopTime: r.StopTime, 682 StepTime: r.StepTime, 683 XFilesFactor: r.XFilesFactor, 684 HighPrecisionTimestamps: r.HighPrecisionTimestamps, 685 Values: values, 686 AppliedFunctions: r.AppliedFunctions, 687 RequestStartTime: r.RequestStartTime, 688 RequestStopTime: r.RequestStopTime, 689 }, 690 GraphOptions: r.GraphOptions, 691 ValuesPerPoint: r.ValuesPerPoint, 692 aggregatedValues: r.aggregatedValues, 693 Tags: tags, 694 AggregateFunction: r.AggregateFunction, 695 } 696 } 697 698 // SetConsolidationFunc set ConsolidationFunc 699 func (r *MetricData) SetConsolidationFunc(f string) *MetricData { 700 r.ConsolidationFunc = f 701 return r 702 } 703 704 // SetXFilesFactor set XFilesFactor 705 func (r *MetricData) SetXFilesFactor(x float32) *MetricData { 706 r.XFilesFactor = x 707 return r 708 } 709 710 // AppendStopTime append to StopTime for simulate broken time series 711 func (r *MetricData) AppendStopTime(step int64) *MetricData { 712 r.StopTime += step 713 return r 714 } 715 716 // FixStopTime fix broken StopTime (less than need for values) 717 func (r *MetricData) FixStopTime() *MetricData { 718 stop := r.StartTime + int64(len(r.Values))*r.StepTime 719 if r.StopTime < stop { 720 r.StopTime = stop 721 } 722 return r 723 } 724 725 // SetNameTag set name tag 726 func (r *MetricData) SetNameTag(name string) *MetricData { 727 r.Tags["name"] = name 728 return r 729 } 730 731 // FixNameTag for safe name tag for future use without metric.ExtractMetric 732 func (r *MetricData) FixNameTag() *MetricData { 733 r.Tags["name"] = ExtractName(r.Tags["name"]) 734 return r 735 } 736 737 // SetTag allow to set custom tag (for tests) 738 func (r *MetricData) SetTag(key, value string) *MetricData { 739 r.Tags[key] = value 740 return r 741 } 742 743 // SetTag allow to set tags 744 func (r *MetricData) SetTags(tags map[string]string) *MetricData { 745 r.Tags = tags 746 return r 747 } 748 749 // SetPathExpression set path expression 750 func (r *MetricData) SetPathExpression(path string) *MetricData { 751 r.PathExpression = path 752 return r 753 } 754 755 // RecalcStopTime recalc StopTime with StartTime and Values length 756 func (r *MetricData) RecalcStopTime() *MetricData { 757 stop := r.StartTime + int64(len(r.Values))*r.StepTime 758 if r.StopTime != stop { 759 r.StopTime = stop 760 } 761 return r 762 } 763 764 // CopyMetricDataSlice returns the slice of metrics that should be changed later. 765 // It allows to avoid a changing of source data, e.g. by AlignMetrics 766 func CopyMetricDataSlice(args []*MetricData) (newData []*MetricData) { 767 newData = make([]*MetricData, len(args)) 768 for i, m := range args { 769 newData[i] = m.Copy(true) 770 } 771 return newData 772 } 773 774 // CopyMetricDataSliceLink returns the copies slice of metrics, Values not copied and link from parent. 775 func CopyMetricDataSliceLink(args []*MetricData) (newData []*MetricData) { 776 newData = make([]*MetricData, len(args)) 777 for i, m := range args { 778 newData[i] = m.CopyLink() 779 } 780 return newData 781 } 782 783 // CopyMetricDataSliceWithName returns the copies slice of metrics with name overwrite, Values not copied and link from parent. Tags will be reset 784 func CopyMetricDataSliceWithName(args []*MetricData, name string) (newData []*MetricData) { 785 newData = make([]*MetricData, len(args)) 786 for i, m := range args { 787 newData[i] = m.CopyName(name) 788 } 789 return newData 790 } 791 792 // CopyMetricDataSliceWithTags returns the copies slice of metrics with name overwrite, Values not copied and link from parent. 793 func CopyMetricDataSliceWithTags(args []*MetricData, name string, tags map[string]string) (newData []*MetricData) { 794 newData = make([]*MetricData, len(args)) 795 for i, m := range args { 796 newData[i] = m.CopyTag(name, tags) 797 } 798 return newData 799 } 800 801 // MakeMetricData creates new metrics data with given metric timeseries 802 func MakeMetricData(name string, values []float64, step, start int64) *MetricData { 803 tags := tags.ExtractTags(ExtractName(name)) 804 return makeMetricDataWithTags(name, values, step, start, tags).FixNameTag() 805 } 806 807 // MakeMetricDataWithTags creates new metrics data with given metric Time Series (with tags) 808 func makeMetricDataWithTags(name string, values []float64, step, start int64, tags map[string]string) *MetricData { 809 stop := start + int64(len(values))*step 810 811 return &MetricData{ 812 FetchResponse: pb.FetchResponse{ 813 Name: name, 814 Values: values, 815 StartTime: start, 816 StepTime: step, 817 StopTime: stop, 818 }, 819 Tags: tags, 820 } 821 }