github.com/go-graphite/carbonapi@v0.17.0/expr/functions/aliasByRedis/function.go (about) 1 package aliasByRedis 2 3 import ( 4 "context" 5 "strings" 6 "time" 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 "github.com/gomodule/redigo/redis" 18 ) 19 20 func prepareMetric(metric string) (string, []string) { 21 parts := strings.Split(metric, ".") 22 return parts[len(parts)-1], parts 23 } 24 25 func redisGetHash(name, key string, c redis.Conn, timeout time.Duration) (string, error) { 26 v, err := redis.DoWithTimeout(c, timeout, "HGET", key, name) 27 return redis.String(v, err) 28 } 29 30 type Database struct { 31 Enabled bool 32 MaxIdleConnections *int 33 IdleTimeout *time.Duration 34 PingInterval *time.Duration 35 Address *string 36 DatabaseNumber *int 37 Username *string 38 Password *string 39 ConnectTimeout *time.Duration 40 QueryTimeout *time.Duration 41 KeepAliveInterval *time.Duration 42 UseTLS *bool 43 TLSSkipVerify *bool 44 } 45 46 type aliasByRedis struct { 47 address string 48 dialOptions []redis.DialOption 49 queryTimeout time.Duration 50 pool *redis.Pool 51 pingInterval *time.Duration 52 } 53 54 func GetOrder() interfaces.Order { 55 return interfaces.Any 56 } 57 58 func New(configFile string) []interfaces.FunctionMetadata { 59 logger := zapwriter.Logger("functionInit").With(zap.String("function", "aliasByRedis")) 60 if configFile == "" { 61 logger.Debug("no config file specified", 62 zap.String("message", "this function requrires config file to work properly"), 63 ) 64 return nil 65 } 66 v := viper.New() 67 v.SetConfigFile(configFile) 68 err := v.ReadInConfig() 69 if err != nil { 70 logger.Error("failed to read config file", 71 zap.Error(err), 72 ) 73 return nil 74 } 75 76 cfg := Database{} 77 78 err = v.Unmarshal(&cfg) 79 if err != nil { 80 logger.Error("failed to parse config", 81 zap.Error(err), 82 ) 83 return nil 84 } 85 86 logger.Info("will use configuration", 87 zap.Any("config", cfg), 88 ) 89 90 if !cfg.Enabled { 91 logger.Warn("aliasByRedis config found but aliasByRedis is disabled") 92 return nil 93 } 94 95 f := &aliasByRedis{ 96 address: "127.0.0.1:6379", 97 dialOptions: make([]redis.DialOption, 0), 98 queryTimeout: 250 * time.Millisecond, 99 } 100 101 if cfg.Address != nil { 102 f.address = *cfg.Address 103 } 104 105 if cfg.DatabaseNumber != nil { 106 f.dialOptions = append(f.dialOptions, redis.DialDatabase(*cfg.DatabaseNumber)) 107 } 108 109 if cfg.Username != nil { 110 f.dialOptions = append(f.dialOptions, redis.DialUsername(*cfg.Username)) 111 } 112 113 if cfg.Password != nil { 114 f.dialOptions = append(f.dialOptions, redis.DialPassword(*cfg.Password)) 115 } 116 117 if cfg.KeepAliveInterval != nil { 118 f.dialOptions = append(f.dialOptions, redis.DialKeepAlive(*cfg.KeepAliveInterval)) 119 } 120 121 if cfg.ConnectTimeout != nil { 122 f.dialOptions = append(f.dialOptions, redis.DialConnectTimeout(*cfg.ConnectTimeout)) 123 } 124 125 if cfg.UseTLS != nil { 126 f.dialOptions = append(f.dialOptions, redis.DialUseTLS(*cfg.UseTLS)) 127 } 128 129 if cfg.TLSSkipVerify != nil { 130 f.dialOptions = append(f.dialOptions, redis.DialTLSSkipVerify(*cfg.TLSSkipVerify)) 131 } 132 133 f.pingInterval = cfg.PingInterval 134 135 maxIdle := 1 136 if cfg.MaxIdleConnections != nil && *cfg.MaxIdleConnections > 1 { 137 maxIdle = *cfg.MaxIdleConnections 138 } 139 140 idleTimeout := 60 * time.Second 141 if cfg.IdleTimeout != nil { 142 idleTimeout = *cfg.IdleTimeout 143 } 144 145 f.pool = &redis.Pool{ 146 MaxIdle: maxIdle, 147 IdleTimeout: idleTimeout, 148 Dial: func() (redis.Conn, error) { 149 return redis.Dial("tcp", f.address, f.dialOptions...) 150 }, 151 TestOnBorrow: func(c redis.Conn, t time.Time) error { 152 if f.pingInterval == nil || time.Since(t) < *f.pingInterval { 153 return nil 154 } 155 _, err := c.Do("PING") 156 return err 157 }, 158 } 159 160 res := make([]interfaces.FunctionMetadata, 0) 161 for _, n := range []string{"aliasByRedis"} { 162 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 163 } 164 return res 165 } 166 167 func (f *aliasByRedis) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 168 args, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 169 if err != nil { 170 return nil, err 171 } 172 173 redisHashName, err := e.GetStringArg(1) 174 if err != nil { 175 return nil, err 176 } 177 178 keepPath, err := e.GetBoolArgDefault(2, false) 179 if err != nil { 180 return nil, err 181 } 182 _ = keepPath 183 184 redisConnection, err := f.pool.GetContext(ctx) 185 if err != nil { 186 return nil, err 187 } 188 defer redisConnection.Close() 189 190 results := make([]*types.MetricData, len(args)) 191 192 for i, a := range args { 193 var r *types.MetricData 194 name, nodes := prepareMetric(a.Tags["name"]) 195 redisName, err := redisGetHash(name, redisHashName, redisConnection, f.queryTimeout) 196 if err == nil { 197 if keepPath { 198 nodes[len(nodes)-1] = redisName 199 r = a.CopyName(strings.Join(nodes, ".")) 200 } else { 201 r = a.CopyName(redisName) 202 } 203 } else { 204 r = a.CopyLink() 205 } 206 results[i] = r 207 } 208 209 return results, nil 210 } 211 212 func (f *aliasByRedis) Description() map[string]types.FunctionDescription { 213 return map[string]types.FunctionDescription{ 214 "aliasByHash": { 215 Description: "Takes a seriesList, extracts first part of a metric name and use it as a field name for HGET redis query. Key name is specified by argument.\n\n.. code-block:: none\n\n &target=aliasByRedis(some.metric, \"redis_key_name\")", 216 Function: "aliasByRedis(seriesList, keyName[, keepPath])", 217 Group: "Alias", 218 Module: "graphite.render.functions", 219 Name: "aliasByRedis", 220 Params: []types.FunctionParam{ 221 { 222 Name: "seriesList", 223 Required: true, 224 Type: types.SeriesList, 225 }, 226 { 227 Name: "redisKey", 228 Required: true, 229 Type: types.String, 230 }, 231 { 232 Name: "keepPath", 233 Type: types.Boolean, 234 Default: &types.Suggestion{ 235 Value: false, 236 Type: types.SBool, 237 }, 238 }, 239 }, 240 NameChange: true, // name changed 241 TagsChange: true, // name tag changed 242 }, 243 } 244 }