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

     1  package cactiStyle
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  	"strings"
     8  
     9  	"github.com/dustin/go-humanize"
    10  	"github.com/go-graphite/carbonapi/expr/helper"
    11  	"github.com/go-graphite/carbonapi/expr/interfaces"
    12  	"github.com/go-graphite/carbonapi/expr/types"
    13  	"github.com/go-graphite/carbonapi/pkg/parser"
    14  )
    15  
    16  type cactiStyle struct{}
    17  
    18  func GetOrder() interfaces.Order {
    19  	return interfaces.Any
    20  }
    21  
    22  func New(configFile string) []interfaces.FunctionMetadata {
    23  	res := make([]interfaces.FunctionMetadata, 0)
    24  	f := &cactiStyle{}
    25  	functions := []string{"cactiStyle"}
    26  	for _, n := range functions {
    27  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    28  	}
    29  	return res
    30  }
    31  
    32  // cactiStyle(seriesList, system=None, units=None)
    33  func (f *cactiStyle) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    34  	// Get the series data
    35  	original, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	// Get the arguments
    41  	system, err := e.GetStringNamedOrPosArgDefault("system", 1, "")
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	unit, err := e.GetStringNamedOrPosArgDefault("units", 2, "")
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	// Deal with each of the series
    51  	metrics := make([]*types.MetricData, len(original))
    52  	for n, a := range original {
    53  		// Calculate min, max, current
    54  		//
    55  		// This saves calling helper.SummarizeValues 3 times and looping over
    56  		// the metrics 3 times
    57  		//
    58  		// For min:
    59  		// Ignoring any absent values and inf (if we have a value)
    60  		// Using helper.SummarizeValues("min", ...) results in incorrect values, when absent
    61  		// values are present
    62  		//
    63  		minVal := math.Inf(1)
    64  		currentVal := math.Inf(-1)
    65  		maxVal := math.Inf(-1)
    66  		for _, av := range a.Values {
    67  			if !math.IsNaN(av) {
    68  				minVal = math.Min(minVal, av)
    69  				maxVal = math.Max(maxVal, av)
    70  				currentVal = av
    71  			}
    72  		}
    73  
    74  		// Format the output correctly
    75  		min := ""
    76  		max := ""
    77  		current := ""
    78  		if system == "si" {
    79  			mv, mf := humanize.ComputeSI(minVal)
    80  			xv, xf := humanize.ComputeSI(maxVal)
    81  			cv, cf := humanize.ComputeSI(currentVal)
    82  
    83  			min = fmt.Sprintf("%.2f%s", mv, mf)
    84  			max = fmt.Sprintf("%.2f%s", xv, xf)
    85  			current = fmt.Sprintf("%.2f%s", cv, cf)
    86  
    87  		} else if system == "" {
    88  			min = fmt.Sprintf("%.0f", minVal)
    89  			max = fmt.Sprintf("%.0f", maxVal)
    90  			current = fmt.Sprintf("%.0f", currentVal)
    91  
    92  		} else {
    93  			return nil, fmt.Errorf("%s is not supported for system", system)
    94  		}
    95  
    96  		// Append the unit if specified
    97  		if len(unit) > 0 {
    98  			min = fmt.Sprintf("%s %s", min, unit)
    99  			max = fmt.Sprintf("%s %s", max, unit)
   100  			current = fmt.Sprintf("%s %s", current, unit)
   101  		}
   102  
   103  		r := a.CopyLinkTags()
   104  		labels := map[string]string{
   105  			"current": "Current:" + current,
   106  			"min":     "Min:" + min,
   107  			"max":     "Max:" + max,
   108  		}
   109  
   110  		maxLength := len(labels["current"])
   111  		if len(labels["min"]) > maxLength {
   112  			maxLength = len(labels["min"])
   113  		}
   114  		if len(labels["max"]) > maxLength {
   115  			maxLength = len(labels["max"])
   116  		}
   117  
   118  		for k, v := range labels {
   119  			tmpBB := strings.Builder{}
   120  			for i := 0; i < maxLength-len(v); i++ {
   121  				tmpBB.WriteRune(' ')
   122  			}
   123  			tmpBB.WriteString(v)
   124  			labels[k] = tmpBB.String()
   125  		}
   126  
   127  		r.Name = a.Name + " " + labels["current"] + labels["max"] + labels["min"]
   128  		metrics[n] = r
   129  	}
   130  
   131  	return metrics, nil
   132  }
   133  
   134  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   135  func (f *cactiStyle) Description() map[string]types.FunctionDescription {
   136  	return map[string]types.FunctionDescription{
   137  		"cactiStyle": {
   138  			Description: "Takes a series list and modifies the aliases to provide column aligned\noutput with Current, Max, and Min values in the style of cacti. Optionally\ntakes a \"system\" value to apply unit formatting in the same style as the\nY-axis, or a \"unit\" string to append an arbitrary unit suffix.\n\n.. code-block:: none\n\n  &target=cactiStyle(ganglia.*.net.bytes_out,\"si\")\n  &target=cactiStyle(ganglia.*.net.bytes_out,\"si\",\"b\")\n\nA possible value for ``system`` is ``si``, which would express your values in\nmultiples of a thousand. A second option is to use ``binary`` which will\ninstead express your values in multiples of 1024 (useful for network devices).\n\nColumn alignment of the Current, Max, Min values works under two conditions:\nyou use a monospace font such as terminus and use a single cactiStyle call, as\nseparate cactiStyle calls are not aware of each other. In case you have\ndifferent targets for which you would like to have cactiStyle to line up, you\ncan use ``group()`` to combine them before applying cactiStyle, such as:\n\n.. code-block:: none\n\n  &target=cactiStyle(group(metricA,metricB))",
   139  			Function:    "cactiStyle(seriesList, system=None, units=None)",
   140  			Group:       "Special",
   141  			Module:      "graphite.render.functions",
   142  			Name:        "cactiStyle",
   143  			Params: []types.FunctionParam{
   144  				{
   145  					Name:     "seriesList",
   146  					Required: true,
   147  					Type:     types.SeriesList,
   148  				},
   149  				{
   150  					Name: "system",
   151  					Options: types.StringsToSuggestionList([]string{
   152  						"si",
   153  						"binary",
   154  					}),
   155  					Type: types.String,
   156  				},
   157  				{
   158  					Name: "units",
   159  					Type: types.String,
   160  				},
   161  			},
   162  		},
   163  	}
   164  }