github.com/go-graphite/carbonapi@v0.17.0/expr/functions/highestLowest/function.go (about)

     1  package highestLowest
     2  
     3  import (
     4  	"container/heap"
     5  	"context"
     6  	"fmt"
     7  	"math"
     8  	"strings"
     9  
    10  	"github.com/go-graphite/carbonapi/expr/consolidations"
    11  	"github.com/go-graphite/carbonapi/expr/helper"
    12  	"github.com/go-graphite/carbonapi/expr/interfaces"
    13  	"github.com/go-graphite/carbonapi/expr/types"
    14  	"github.com/go-graphite/carbonapi/pkg/parser"
    15  )
    16  
    17  type highest struct{}
    18  
    19  func GetOrder() interfaces.Order {
    20  	return interfaces.Any
    21  }
    22  
    23  func New(configFile string) []interfaces.FunctionMetadata {
    24  	res := make([]interfaces.FunctionMetadata, 0)
    25  	f := &highest{}
    26  	functions := []string{"highestAverage", "highestCurrent", "highestMax", "highestMin", "highest", "lowestMax", "lowestMin", "lowestAverage", "lowestCurrent", "lowest"}
    27  	for _, n := range functions {
    28  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    29  	}
    30  	return res
    31  }
    32  
    33  // highestAverage(seriesList, n) , highestCurrent(seriesList, n), highestMax(seriesList, n)
    34  func (f *highest) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    35  	arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	n := 1
    41  	if e.ArgsLen() > 1 && e.Target() != "highest" && e.Target() != "lowest" {
    42  		n, err = e.GetIntArg(1)
    43  		if err != nil {
    44  			return nil, err
    45  		}
    46  	}
    47  
    48  	// we have fewer arguments than we want result series
    49  	if len(arg) < n {
    50  		return arg, nil
    51  	}
    52  
    53  	var mh types.MetricHeap
    54  
    55  	var compute func([]float64) float64
    56  
    57  	isHighest := strings.HasPrefix(e.Target(), "highest")
    58  	switch e.Target() {
    59  	case "highest", "lowest":
    60  		consolidation := "average"
    61  		switch e.ArgsLen() {
    62  		case 2:
    63  			n, err = e.GetIntArg(1)
    64  			if err != nil {
    65  				// We need to support case where only function specified
    66  				n = 1
    67  				consolidation, err = e.GetStringArgDefault(1, "average")
    68  				if err != nil {
    69  					return nil, err
    70  				}
    71  			}
    72  		case 3:
    73  			n, err = e.GetIntArg(1)
    74  
    75  			if err != nil {
    76  				return nil, err
    77  			}
    78  			consolidation, err = e.GetStringArgDefault(2, "average")
    79  
    80  			if err != nil {
    81  				return nil, err
    82  			}
    83  		}
    84  		var ok bool
    85  		compute, ok = consolidations.ConsolidationToFunc[consolidation]
    86  		if !ok {
    87  			return nil, fmt.Errorf("unsupported consolidation function %v", consolidation)
    88  		}
    89  	case "highestMax", "lowestMax":
    90  		compute = consolidations.MaxValue
    91  	case "highestAverage", "lowestAverage":
    92  		compute = consolidations.AvgValue
    93  	case "highestCurrent", "lowestCurrent":
    94  		compute = consolidations.CurrentValue
    95  	case "highestMin", "lowestMin":
    96  		compute = consolidations.MinValue
    97  	default:
    98  		return nil, fmt.Errorf("unsupported function %v", e.Target())
    99  	}
   100  
   101  	var results []*types.MetricData
   102  
   103  	if n <= 0 {
   104  		return results, nil
   105  	}
   106  
   107  	if isHighest {
   108  		for i, a := range arg {
   109  			m := compute(a.Values)
   110  			if math.IsNaN(m) {
   111  				continue
   112  			}
   113  
   114  			if len(mh) < n {
   115  				heap.Push(&mh, types.MetricHeapElement{Idx: i, Val: m})
   116  				continue
   117  			}
   118  			// m is bigger than smallest max found so far
   119  			if mh[0].Val < m {
   120  				mh[0].Val = m
   121  				mh[0].Idx = i
   122  				heap.Fix(&mh, 0)
   123  			}
   124  		}
   125  
   126  		results = make([]*types.MetricData, len(mh))
   127  
   128  		// results should be ordered ascending
   129  		for len(mh) > 0 {
   130  			v := heap.Pop(&mh).(types.MetricHeapElement)
   131  			results[len(mh)] = arg[v.Idx]
   132  		}
   133  	} else {
   134  		for i, a := range arg {
   135  			m := compute(a.Values)
   136  			heap.Push(&mh, types.MetricHeapElement{Idx: i, Val: m})
   137  		}
   138  
   139  		results = make([]*types.MetricData, n)
   140  
   141  		for i := 0; i < n; i++ {
   142  			v := heap.Pop(&mh).(types.MetricHeapElement)
   143  			results[i] = arg[v.Idx]
   144  		}
   145  	}
   146  
   147  	return results, nil
   148  }
   149  
   150  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   151  func (f *highest) Description() map[string]types.FunctionDescription {
   152  	return map[string]types.FunctionDescription{
   153  		"highest": {
   154  			Name:        "highest",
   155  			Function:    "highest(seriesList, n=1, func='average')",
   156  			Description: "Takes one metric or a wildcard seriesList followed by an integer N and an aggregation function.\nOut of all metrics passed, draws only the N metrics with the highest aggregated value over the\ntime period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=highest(server*.instance*.threads.busy,5,'max')\n\nDraws the 5 servers with the highest number of busy threads.",
   157  			Module:      "graphite.render.functions",
   158  			Group:       "Filter Series",
   159  			Params: []types.FunctionParam{
   160  				{
   161  					Name:     "seriesList",
   162  					Type:     types.SeriesList,
   163  					Required: true,
   164  				},
   165  				{
   166  					Name:     "n",
   167  					Type:     types.Integer,
   168  					Required: true,
   169  				},
   170  				{
   171  					Name: "func",
   172  					Type: types.String,
   173  					Default: &types.Suggestion{
   174  						Type:  types.SString,
   175  						Value: "average",
   176  					},
   177  					Options: types.StringsToSuggestionList(consolidations.AvailableConsolidationFuncs()),
   178  				},
   179  			},
   180  			SeriesChange: true, // function aggregate metrics or change series items count
   181  		},
   182  		"highestAverage": {
   183  			Description: "Takes one metric or a wildcard seriesList followed by an integer N.\nOut of all metrics passed, draws only the top N metrics with the highest\naverage value for the time period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=highestAverage(server*.instance*.threads.busy,5)\n\nDraws the top 5 servers with the highest average value.\n\nThis is an alias for :py:func:`highest <highest>` with aggregation ``average``.",
   184  			Function:    "highestAverage(seriesList, n)",
   185  			Group:       "Filter Series",
   186  			Module:      "graphite.render.functions",
   187  			Name:        "highestAverage",
   188  			Params: []types.FunctionParam{
   189  				{
   190  					Name:     "seriesList",
   191  					Required: true,
   192  					Type:     types.SeriesList,
   193  				},
   194  				{
   195  					Name:     "n",
   196  					Required: true,
   197  					Type:     types.Integer,
   198  				},
   199  			},
   200  			SeriesChange: true, // function aggregate metrics or change series items count
   201  		},
   202  		"highestCurrent": {
   203  			Description: "Takes one metric or a wildcard seriesList followed by an integer N.\nOut of all metrics passed, draws only the N metrics with the highest value\nat the end of the time period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=highestCurrent(server*.instance*.threads.busy,5)\n\nDraws the 5 servers with the highest busy threads.\n\nThis is an alias for :py:func:`highest <highest>` with aggregation ``current``.",
   204  			Function:    "highestCurrent(seriesList, n)",
   205  			Group:       "Filter Series",
   206  			Module:      "graphite.render.functions",
   207  			Name:        "highestCurrent",
   208  			Params: []types.FunctionParam{
   209  				{
   210  					Name:     "seriesList",
   211  					Required: true,
   212  					Type:     types.SeriesList,
   213  				},
   214  				{
   215  					Name:     "n",
   216  					Required: true,
   217  					Type:     types.Integer,
   218  				},
   219  			},
   220  			SeriesChange: true, // function aggregate metrics or change series items count
   221  		},
   222  		"highestMax": {
   223  			Description: "Takes one metric or a wildcard seriesList followed by an integer N.\n\nOut of all metrics passed, draws only the N metrics with the highest maximum\nvalue in the time period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=highestMax(server*.instance*.threads.busy,5)\n\nDraws the top 5 servers who have had the most busy threads during the time\nperiod specified.\n\nThis is an alias for :py:func:`highest <highest>` with aggregation ``max``.",
   224  			Function:    "highestMax(seriesList, n)",
   225  			Group:       "Filter Series",
   226  			Module:      "graphite.render.functions",
   227  			Name:        "highestMax",
   228  			Params: []types.FunctionParam{
   229  				{
   230  					Name:     "seriesList",
   231  					Required: true,
   232  					Type:     types.SeriesList,
   233  				},
   234  				{
   235  					Name:     "n",
   236  					Required: true,
   237  					Type:     types.Integer,
   238  				},
   239  			},
   240  			SeriesChange: true, // function aggregate metrics or change series items count
   241  		},
   242  		"highestMin": {
   243  			Description: "Takes one metric or a wildcard seriesList followed by an integer N.\n\nOut of all metrics passed, draws only the N metrics with the highest minimum\nvalue in the time period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=highestMin(server*.instance*.threads.busy,5)\n\nDraws the top 5 servers who have had the highest minimum of all the values during the time\nperiod specified.\n\nThis is an alias for :py:func:`highest <highest>` with aggregation ``min``.",
   244  			Function:    "highestMin(seriesList, n)",
   245  			Group:       "Filter Series",
   246  			Module:      "graphite.render.functions",
   247  			Name:        "highestMin",
   248  			Params: []types.FunctionParam{
   249  				{
   250  					Name:     "seriesList",
   251  					Required: true,
   252  					Type:     types.SeriesList,
   253  				},
   254  				{
   255  					Name:     "n",
   256  					Required: true,
   257  					Type:     types.Integer,
   258  				},
   259  			},
   260  			SeriesChange: true, // function aggregate metrics or change series items count
   261  		},
   262  		"lowest": {
   263  			Name:        "lowest",
   264  			Function:    "lowest(seriesList, n=1, func='average')",
   265  			Description: "Takes one metric or a wildcard seriesList followed by an integer N and an aggregation function.\nOut of all metrics passed, draws only the N metrics with the lowest aggregated value over the\ntime period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=lowest(server*.instance*.threads.busy,5,'min')\n\nDraws the 5 servers with the lowest number of busy threads.",
   266  			Module:      "graphite.render.functions",
   267  			Group:       "Filter Series",
   268  			Params: []types.FunctionParam{
   269  				{
   270  					Name:     "seriesList",
   271  					Type:     types.SeriesList,
   272  					Required: true,
   273  				},
   274  				{
   275  					Name:     "n",
   276  					Type:     types.Integer,
   277  					Required: true,
   278  				},
   279  				{
   280  					Name: "func",
   281  					Type: types.String,
   282  					Default: &types.Suggestion{
   283  						Type:  types.SString,
   284  						Value: "average",
   285  					},
   286  					Options: types.StringsToSuggestionList(consolidations.AvailableConsolidationFuncs()),
   287  				},
   288  			},
   289  			SeriesChange: true, // function aggregate metrics or change series items count
   290  		},
   291  		"lowestCurrent": {
   292  			Description: "Takes one metric or a wildcard seriesList followed by an integer N.\nOut of all metrics passed, draws only the N metrics with the lowest value at\nthe end of the time period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=lowestCurrent(server*.instance*.threads.busy,5)\n\nDraws the 5 servers with the least busy threads right now.\n\nThis is an alias for :py:func:`lowest <lowest>` with aggregation ``current``.",
   293  			Function:    "lowestCurrent(seriesList, n)",
   294  			Group:       "Filter Series",
   295  			Module:      "graphite.render.functions",
   296  			Name:        "lowestCurrent",
   297  			Params: []types.FunctionParam{
   298  				{
   299  					Name:     "seriesList",
   300  					Required: true,
   301  					Type:     types.SeriesList,
   302  				},
   303  				{
   304  					Name:     "n",
   305  					Required: true,
   306  					Type:     types.Integer,
   307  				},
   308  			},
   309  			SeriesChange: true, // function aggregate metrics or change series items count
   310  		},
   311  		"lowestAverage": {
   312  			Description: "Takes one metric or a wildcard seriesList followed by an integer N.\nOut of all metrics passed, draws only the bottom N metrics with the lowest\naverage value for the time period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=lowestAverage(server*.instance*.threads.busy,5)\n\nDraws the bottom 5 servers with the lowest average value.\n\nThis is an alias for :py:func:`lowest <lowest>` with aggregation ``average``.",
   313  			Function:    "lowestAverage(seriesList, n)",
   314  			Group:       "Filter Series",
   315  			Module:      "graphite.render.functions",
   316  			Name:        "lowestAverage",
   317  			Params: []types.FunctionParam{
   318  				{
   319  					Name:     "seriesList",
   320  					Required: true,
   321  					Type:     types.SeriesList,
   322  				},
   323  				{
   324  					Name:     "n",
   325  					Required: true,
   326  					Type:     types.Integer,
   327  				},
   328  			},
   329  			SeriesChange: true, // function aggregate metrics or change series items count
   330  		},
   331  		"lowestMax": {
   332  			Description: "Takes one metric or a wildcard seriesList followed by an integer N.\nOut of all metrics passed, draws only the bottom N metrics with the lowest\nmaximum value for the time period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=lowestMax(server*.instance*.threads.busy,5)\n\nDraws the bottom 5 servers with the lowest maximum value.\n\nThis is an alias for :py:func:`lowest <lowest>` with aggregation ``max``.",
   333  			Function:    "lowestMax(seriesList, n)",
   334  			Group:       "Filter Series",
   335  			Module:      "graphite.render.functions",
   336  			Name:        "lowestMax",
   337  			Params: []types.FunctionParam{
   338  				{
   339  					Name:     "seriesList",
   340  					Required: true,
   341  					Type:     types.SeriesList,
   342  				},
   343  				{
   344  					Name:     "n",
   345  					Required: true,
   346  					Type:     types.Integer,
   347  				},
   348  			},
   349  			SeriesChange: true, // function aggregate metrics or change series items count
   350  		},
   351  		"lowestMin": {
   352  			Description: "Takes one metric or a wildcard seriesList followed by an integer N.\nOut of all metrics passed, draws only the bottom N metrics with the lowest\nminimum value for the time period specified.\n\nExample:\n\n.. code-block:: none\n\n  &target=lowestMin(server*.instance*.threads.busy,5)\n\nDraws the bottom 5 servers with the lowest minimum value.\n\nThis is an alias for :py:func:`lowest <lowest>` with aggregation ``min``.",
   353  			Function:    "lowestMin(seriesList, n)",
   354  			Group:       "Filter Series",
   355  			Module:      "graphite.render.functions",
   356  			Name:        "lowestMin",
   357  			Params: []types.FunctionParam{
   358  				{
   359  					Name:     "seriesList",
   360  					Required: true,
   361  					Type:     types.SeriesList,
   362  				},
   363  				{
   364  					Name:     "n",
   365  					Required: true,
   366  					Type:     types.Integer,
   367  				},
   368  			},
   369  			SeriesChange: true, // function aggregate metrics or change series items count
   370  		},
   371  	}
   372  }