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 }