github.com/go-graphite/carbonapi@v0.17.0/expr/functions/join/function.go (about) 1 package join 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/go-graphite/carbonapi/expr/helper" 9 "github.com/go-graphite/carbonapi/expr/interfaces" 10 "github.com/go-graphite/carbonapi/expr/types" 11 "github.com/go-graphite/carbonapi/pkg/parser" 12 ) 13 14 const ( 15 and = "AND" 16 or = "OR" 17 xor = "XOR" 18 sub = "SUB" 19 ) 20 21 type join struct{} 22 23 func GetOrder() interfaces.Order { 24 return interfaces.Any 25 } 26 27 func New(_ string) []interfaces.FunctionMetadata { 28 return []interfaces.FunctionMetadata{ 29 {F: &join{}, Name: "join"}, 30 } 31 } 32 33 func (f *join) Description() map[string]types.FunctionDescription { 34 return map[string]types.FunctionDescription{ 35 "join": { 36 Description: `Performs set operations on 'seriesA' and 'seriesB'. Following options are available: 37 * AND - returns those metrics from 'seriesA' which are presented in 'seriesB'; 38 * OR - returns all metrics from 'seriesA' and also those metrics from 'seriesB' which aren't presented in 'seriesA'; 39 * XOR - returns only those metrics which are presented in either 'seriesA' or 'seriesB', but not in both; 40 * SUB - returns those metrics from 'seriesA' which aren't presented in 'seriesB'; 41 42 Example: 43 44 .. code-block:: none 45 46 &target=join(some.data.series.aaa, some.other.series.bbb, 'AND')`, 47 Function: "join(seriesA, seriesB)", 48 Group: "Transform", 49 Module: "graphite.render.functions", 50 Name: "join", 51 Params: []types.FunctionParam{ 52 { 53 Name: "seriesA", 54 Required: true, 55 Type: types.SeriesList, 56 }, 57 { 58 Name: "seriesB", 59 Required: true, 60 Type: types.SeriesList, 61 }, 62 { 63 Name: "type", 64 Required: false, 65 Type: types.String, 66 Default: types.NewSuggestion(and), 67 Options: types.StringsToSuggestionList([]string{and, or, xor, sub}), 68 }, 69 }, 70 SeriesChange: true, // function aggregate metrics or change series items count 71 NameChange: true, // name changed 72 TagsChange: true, // name tag changed 73 ValuesChange: true, // values changed 74 }, 75 } 76 } 77 78 func (f *join) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) (results []*types.MetricData, err error) { 79 seriesA, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 80 if err != nil { 81 return nil, err 82 } 83 seriesB, err := helper.GetSeriesArg(ctx, eval, e.Arg(1), from, until, values) 84 if err != nil { 85 return nil, err 86 } 87 joinType, err := e.GetStringNamedOrPosArgDefault("type", 2, and) 88 if err != nil { 89 return nil, err 90 } 91 joinType = strings.ToUpper(joinType) 92 93 switch joinType { 94 case and: 95 return doAnd(seriesA, seriesB), nil 96 case or: 97 return doOr(seriesA, seriesB), nil 98 case xor: 99 return doXor(seriesA, seriesB), nil 100 case sub: 101 return doSub(seriesA, seriesB), nil 102 default: 103 return nil, fmt.Errorf("unknown join type: %s", joinType) 104 } 105 } 106 107 func doAnd(seriesA []*types.MetricData, seriesB []*types.MetricData) (results []*types.MetricData) { 108 metricsB := make(map[string]bool, len(seriesB)) 109 for _, md := range seriesB { 110 metricsB[md.Name] = true 111 } 112 113 results = make([]*types.MetricData, 0, len(seriesA)) 114 for _, md := range seriesA { 115 if metricsB[md.Name] { 116 results = append(results, md) 117 } 118 } 119 return results 120 } 121 122 func doOr(seriesA []*types.MetricData, seriesB []*types.MetricData) (results []*types.MetricData) { 123 metricsA := make(map[string]bool, len(seriesA)) 124 for _, md := range seriesA { 125 metricsA[md.Name] = true 126 } 127 128 results = seriesA 129 for _, md := range seriesB { 130 if !metricsA[md.Name] { 131 results = append(results, md) 132 } 133 } 134 return results 135 } 136 137 func doXor(seriesA []*types.MetricData, seriesB []*types.MetricData) (results []*types.MetricData) { 138 metricsA := make(map[string]bool, len(seriesA)) 139 for _, md := range seriesA { 140 metricsA[md.Name] = true 141 } 142 metricsB := make(map[string]bool, len(seriesB)) 143 for _, md := range seriesB { 144 metricsB[md.Name] = true 145 } 146 147 results = make([]*types.MetricData, 0, len(seriesA)+len(seriesB)) 148 for _, md := range seriesA { 149 if !metricsB[md.Name] { 150 results = append(results, md) 151 } 152 } 153 for _, md := range seriesB { 154 if !metricsA[md.Name] { 155 results = append(results, md) 156 } 157 } 158 return results 159 } 160 161 func doSub(seriesA []*types.MetricData, seriesB []*types.MetricData) (results []*types.MetricData) { 162 metricsB := make(map[string]bool, len(seriesB)) 163 for _, md := range seriesB { 164 metricsB[md.Name] = true 165 } 166 167 results = make([]*types.MetricData, 0, len(seriesA)) 168 for _, md := range seriesA { 169 if !metricsB[md.Name] { 170 results = append(results, md) 171 } 172 } 173 return results 174 }