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  }