github.com/go-graphite/carbonapi@v0.17.0/expr/functions/timeShift/function.go (about) 1 package timeShift 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 8 "github.com/lomik/zapwriter" 9 "github.com/spf13/viper" 10 "go.uber.org/zap" 11 12 "github.com/go-graphite/carbonapi/expr/helper" 13 "github.com/go-graphite/carbonapi/expr/interfaces" 14 "github.com/go-graphite/carbonapi/expr/types" 15 "github.com/go-graphite/carbonapi/pkg/parser" 16 ) 17 18 type timeShift struct { 19 config timeShiftConfig 20 } 21 22 func GetOrder() interfaces.Order { 23 return interfaces.Any 24 } 25 26 type timeShiftConfig struct { 27 ResetEndDefaultValue *bool 28 } 29 30 func New(configFile string) []interfaces.FunctionMetadata { 31 logger := zapwriter.Logger("functionInit").With(zap.String("function", "timeShift")) 32 res := make([]interfaces.FunctionMetadata, 0) 33 f := &timeShift{} 34 functions := []string{"timeShift"} 35 for _, n := range functions { 36 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 37 } 38 39 cfg := timeShiftConfig{} 40 v := viper.New() 41 v.SetConfigFile(configFile) 42 err := v.ReadInConfig() 43 if err != nil { 44 logger.Info("failed to read config file, using default", 45 zap.Error(err), 46 ) 47 } else { 48 err = v.Unmarshal(&cfg) 49 if err != nil { 50 logger.Fatal("failed to parse config", 51 zap.Error(err), 52 ) 53 return nil 54 } 55 56 f.config = cfg 57 } 58 59 if cfg.ResetEndDefaultValue == nil { 60 // TODO(civil): Change default value in 0.15 61 v := false 62 f.config.ResetEndDefaultValue = &v 63 logger.Warn("timeShift function in graphite-web have a default value for resetEnd set to true." + 64 "carbonapi currently forces this to be false. This behavior will change in next major release (0.15)" + 65 "to be compatible with graphite-web. Please change your dashboards to explicitly pass resetEnd parameter" + 66 "or create a config file for this function that sets it to false." + 67 "Please see https://github.com/go-graphite/carbonapi/blob/main/doc/configuration.md#example-for-timeshift") 68 } 69 70 return res 71 } 72 73 // timeShift(seriesList, timeShift, resetEnd=True) 74 func (f *timeShift) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 75 // FIXME(civil): support alignDst 76 if e.ArgsLen() < 2 { 77 return nil, parser.ErrMissingArgument 78 } 79 80 offs, err := e.GetIntervalArg(1, -1) 81 if err != nil { 82 return nil, err 83 } 84 offsStr := strconv.Itoa(int(offs)) 85 86 resetEnd, err := e.GetBoolArgDefault(2, *f.config.ResetEndDefaultValue) 87 if err != nil { 88 return nil, err 89 } 90 resetEndStr := strconv.FormatBool(resetEnd) 91 92 arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from+int64(offs), until+int64(offs), values) 93 if err != nil { 94 return nil, err 95 } 96 results := make([]*types.MetricData, len(arg)) 97 98 for n, a := range arg { 99 r := a.CopyLink() 100 r.Name = "timeShift(" + a.Name + ",'" + offsStr + "'," + resetEndStr + ")" 101 r.StartTime = a.StartTime - int64(offs) 102 r.StopTime = a.StopTime - int64(offs) 103 if resetEnd && r.StopTime > until { 104 r.StopTime = until 105 } 106 length := int((r.StopTime - r.StartTime) / r.StepTime) 107 if length < 0 { 108 continue 109 } 110 r.Values = r.Values[:length] 111 112 r.Tags["timeshift"] = fmt.Sprintf("%d", offs) 113 results[n] = r 114 115 } 116 117 return results, nil 118 } 119 120 // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web 121 func (f *timeShift) Description() map[string]types.FunctionDescription { 122 return map[string]types.FunctionDescription{ 123 "timeShift": { 124 Description: "Takes one metric or a wildcard seriesList, followed by a quoted string with the\nlength of time (See ``from / until`` in the render\\_api_ for examples of time formats).\n\nDraws the selected metrics shifted in time. If no sign is given, a minus sign ( - ) is\nimplied which will shift the metric back in time. If a plus sign ( + ) is given, the\nmetric will be shifted forward in time.\n\nWill reset the end date range automatically to the end of the base stat unless\nresetEnd is False. Example case is when you timeshift to last week and have the graph\ndate range set to include a time in the future, will limit this timeshift to pretend\nending at the current time. If resetEnd is False, will instead draw full range including\nfuture time.\n\nBecause time is shifted by a fixed number of seconds, comparing a time period with DST to\na time period without DST, and vice-versa, will result in an apparent misalignment. For\nexample, 8am might be overlaid with 7am. To compensate for this, use the alignDST option.\n\nUseful for comparing a metric against itself at a past periods or correcting data\nstored at an offset.\n\nExample:\n\n.. code-block:: none\n\n &target=timeShift(Sales.widgets.largeBlue,\"7d\")\n &target=timeShift(Sales.widgets.largeBlue,\"-7d\")\n &target=timeShift(Sales.widgets.largeBlue,\"+1h\")", 125 Function: "timeShift(seriesList, timeShift, resetEnd=True, alignDST=False)", 126 Group: "Transform", 127 Module: "graphite.render.functions", 128 Name: "timeShift", 129 Params: []types.FunctionParam{ 130 { 131 Name: "seriesList", 132 Required: true, 133 Type: types.SeriesList, 134 }, 135 { 136 Name: "timeShift", 137 Required: true, 138 Suggestions: types.NewSuggestions( 139 "1h", 140 "6h", 141 "12h", 142 "1d", 143 "2d", 144 "7d", 145 "14d", 146 "30d", 147 ), 148 Type: types.Interval, 149 }, 150 { 151 Default: types.NewSuggestion(*f.config.ResetEndDefaultValue), 152 Name: "resetEnd", 153 Type: types.Boolean, 154 }, 155 /* 156 { 157 Default: types.NewSuggestion(false), 158 Name: "alignDst", 159 Type: types.Boolean, 160 }, 161 */ 162 }, 163 NameChange: true, // name changed 164 ValuesChange: true, // values changed 165 }, 166 } 167 }