github.com/m3db/m3@v1.5.0/src/query/graphite/native/summarize.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package native
    22  
    23  import (
    24  	"fmt"
    25  	"math"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/query/graphite/common"
    30  	"github.com/m3db/m3/src/query/graphite/ts"
    31  	"github.com/m3db/m3/src/x/errors"
    32  )
    33  
    34  // summarize summarizes each series into interval buckets of a certain size.
    35  func summarize(
    36  	ctx *common.Context,
    37  	series singlePathSpec,
    38  	intervalS, fname string,
    39  	alignToFrom bool,
    40  ) (ts.SeriesList, error) {
    41  	if fname == "" {
    42  		fname = "sum"
    43  	}
    44  
    45  	safeAggFn, ok := common.SafeAggregationFns[fname]
    46  	if !ok {
    47  		return ts.NewSeriesList(), errors.NewInvalidParamsError(fmt.Errorf(
    48  			"aggregate function not supported: %s", fname))
    49  	}
    50  
    51  	interval, err := common.ParseInterval(intervalS)
    52  	if err != nil || interval <= 0 {
    53  		err := errors.NewInvalidParamsError(fmt.Errorf(
    54  			"invalid interval %s: %v", interval, err))
    55  		return ts.NewSeriesList(), err
    56  	}
    57  
    58  	alignString := ""
    59  	if alignToFrom {
    60  		alignString = ", true"
    61  	}
    62  
    63  	results := make([]*ts.Series, len(series.Values))
    64  	for i, series := range series.Values {
    65  		name := fmt.Sprintf("summarize(%s, %q, %q%s)", series.Name(), intervalS, fname, alignString)
    66  		results[i] = summarizeTimeSeries(ctx, name, series, interval, safeAggFn, alignToFrom)
    67  	}
    68  
    69  	r := ts.SeriesList(series)
    70  	r.Values = results
    71  	return r, nil
    72  }
    73  
    74  type summarizeBucket struct {
    75  	vals []float64
    76  }
    77  
    78  func summarizeTimeSeries(
    79  	ctx *common.Context,
    80  	newName string,
    81  	series *ts.Series,
    82  	interval time.Duration,
    83  	safeAggFn common.SafeAggregationFn,
    84  	alignToFrom bool,
    85  ) *ts.Series {
    86  	var (
    87  		startTimeInSecs = int(series.StartTime().Unix())
    88  		intervalInSecs  = int(interval / time.Second)
    89  		intervalInMsecs = intervalInSecs * 1000
    90  		buckets         = make(map[int]*summarizeBucket)
    91  	)
    92  
    93  	for i := 0; i < series.Len(); i++ {
    94  		timestamp, n := int(series.StartTimeForStep(i).Unix()), series.ValueAt(i)
    95  		if math.IsNaN(n) {
    96  			continue
    97  		}
    98  
    99  		bucketInterval := timestamp - (timestamp % intervalInSecs)
   100  		if alignToFrom {
   101  			bucketInterval = (timestamp - startTimeInSecs) / intervalInSecs
   102  		}
   103  
   104  		if bucket, exists := buckets[bucketInterval]; exists {
   105  			bucket.vals = append(bucket.vals, n)
   106  		} else {
   107  			buckets[bucketInterval] = &summarizeBucket{[]float64{n}}
   108  		}
   109  	}
   110  
   111  	var (
   112  		newStart = series.StartTime()
   113  		newEnd   = series.EndTime()
   114  	)
   115  
   116  	if !alignToFrom {
   117  		newStartInSecs, newEndInSecs := newStart.Unix(), newEnd.Unix()
   118  		newStart = time.Unix(newStartInSecs-newStartInSecs%int64(intervalInSecs), 0)
   119  		newEnd = time.Unix(newEndInSecs-newEndInSecs%int64(intervalInSecs)+int64(intervalInSecs), 0)
   120  	}
   121  
   122  	var (
   123  		numSteps  = ts.NumSteps(newStart, newEnd, intervalInMsecs)
   124  		newValues = ts.NewValues(ctx, intervalInMsecs, numSteps)
   125  	)
   126  
   127  	for timestamp, i := newStart, 0; i < newValues.Len(); timestamp, i = timestamp.Add(interval), i+1 {
   128  		timestampInSecs := int(timestamp.Unix())
   129  		var bucketInterval int
   130  		if alignToFrom {
   131  			bucketInterval = (timestampInSecs - startTimeInSecs) / intervalInSecs
   132  		} else {
   133  			bucketInterval = timestampInSecs - (timestampInSecs % intervalInSecs)
   134  		}
   135  
   136  		bucket, bucketExists := buckets[bucketInterval]
   137  		if bucketExists {
   138  			safeValue, _, safe := safeAggFn(bucket.vals)
   139  			if safe {
   140  				newValues.SetValueAt(i, safeValue)
   141  			}
   142  		}
   143  	}
   144  	return ts.NewSeries(ctx, newName, newStart, newValues)
   145  }
   146  
   147  // smartSummarize is an alias of summarize with alignToFrom set to true
   148  func smartSummarize(
   149  	ctx *common.Context,
   150  	series singlePathSpec,
   151  	interval, fname string,
   152  ) (ts.SeriesList, error) {
   153  	alignToFrom := true
   154  
   155  	seriesList, err := summarize(ctx, series, interval, fname, alignToFrom)
   156  	if err != nil {
   157  		return ts.NewSeriesList(), err
   158  	}
   159  
   160  	results := seriesList.Values
   161  	for i, series := range seriesList.Values {
   162  		oldName := series.Name()
   163  		newName := strings.Replace(oldName, "summarize", "smartSummarize", 1)
   164  		newName = strings.Replace(newName, ", true", "", 1)
   165  		results[i] = series.RenamedTo(newName)
   166  	}
   167  
   168  	// Retain whether sort was applied or not and metadata.
   169  	r := ts.SeriesList(series)
   170  	r.Values = results
   171  	return r, nil
   172  }
   173  
   174  // specificationFunc determines the output series specification given a series list.
   175  type specificationFunc func(ts.SeriesList) string
   176  
   177  func sumSpecificationFunc(series ts.SeriesList) string {
   178  	return wrapPathExpr("sumSeries", series)
   179  }
   180  
   181  func averageSpecificationFunc(series ts.SeriesList) string {
   182  	return wrapPathExpr("averageSeries", series)
   183  }
   184  
   185  func multiplyWithWildcardsSpecificationFunc(series ts.SeriesList) string {
   186  	return wrapPathExpr("multiplySeriesWithWildcards", series)
   187  }