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

     1  package asPercent
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/go-graphite/carbonapi/expr/interfaces"
    10  	"github.com/go-graphite/carbonapi/expr/metadata"
    11  	"github.com/go-graphite/carbonapi/expr/types"
    12  	"github.com/go-graphite/carbonapi/pkg/parser"
    13  	th "github.com/go-graphite/carbonapi/tests"
    14  )
    15  
    16  var (
    17  	md []interfaces.FunctionMetadata = New("")
    18  )
    19  
    20  func init() {
    21  	for _, m := range md {
    22  		metadata.RegisterFunction(m.Name, m.F)
    23  	}
    24  }
    25  
    26  func TestAsPercent(t *testing.T) {
    27  	now32 := int64(time.Now().Unix())
    28  	NaN := math.NaN()
    29  
    30  	tests := []th.EvalTestItem{
    31  		{
    32  			"asPercent(metric1,metric2)",
    33  			map[parser.MetricRequest][]*types.MetricData{
    34  				{Metric: "metric1", From: 0, Until: 1}: {types.MakeMetricData("metric1", []float64{1, NaN, NaN, 3, 4, 12}, 1, now32)},
    35  				{Metric: "metric2", From: 0, Until: 1}: {types.MakeMetricData("metric2", []float64{2, NaN, 3, NaN, 0, 6}, 1, now32)},
    36  			},
    37  			[]*types.MetricData{types.MakeMetricData("asPercent(metric1,metric2)",
    38  				[]float64{50, NaN, NaN, NaN, NaN, 200}, 1, now32)},
    39  		},
    40  		{
    41  			"asPercent(metric1,'5')", // Ensure quoted numeric values are handled
    42  			map[parser.MetricRequest][]*types.MetricData{
    43  				{Metric: "metric1", From: 0, Until: 1}: {types.MakeMetricData("metric1", []float64{1, NaN, NaN, 3, 4, 12}, 1, now32)},
    44  			},
    45  			[]*types.MetricData{types.MakeMetricData("asPercent(metric1,5)",
    46  				[]float64{20, NaN, NaN, 60, 80, 240}, 1, now32)},
    47  		},
    48  		{
    49  			"asPercent(metricA*,metricB*)",
    50  			map[parser.MetricRequest][]*types.MetricData{
    51  				{Metric: "metricA*", From: 0, Until: 1}: {
    52  					types.MakeMetricData("metricA1", []float64{1, 20, 10}, 1, now32),
    53  					types.MakeMetricData("metricA2", []float64{1, 10, 20}, 1, now32),
    54  				},
    55  				{Metric: "metricB*", From: 0, Until: 1}: {
    56  					types.MakeMetricData("metricB1", []float64{4, 4, 8}, 1, now32),
    57  					types.MakeMetricData("metricB2", []float64{4, 16, 2}, 1, now32),
    58  				},
    59  			},
    60  			[]*types.MetricData{types.MakeMetricData("asPercent(metricA1,metricB1)",
    61  				[]float64{25, 500, 125}, 1, now32),
    62  				types.MakeMetricData("asPercent(metricA2,metricB2)",
    63  					[]float64{25, 62.5, 1000}, 1, now32)},
    64  		},
    65  		{
    66  			"asPercent(Server{1,2}.memory.used,Server{1,3}.memory.total)",
    67  			map[parser.MetricRequest][]*types.MetricData{
    68  				{Metric: "Server{1,2}.memory.used", From: 0, Until: 1}: {
    69  					types.MakeMetricData("Server1.memory.used", []float64{1, 20, 10}, 1, now32),
    70  					types.MakeMetricData("Server2.memory.used", []float64{1, 10, 20}, 1, now32),
    71  				},
    72  				{Metric: "Server{1,3}.memory.total", From: 0, Until: 1}: {
    73  					types.MakeMetricData("Server1.memory.total", []float64{4, 4, 8}, 1, now32),
    74  					types.MakeMetricData("Server3.memory.total", []float64{4, 16, 2}, 1, now32),
    75  				},
    76  			},
    77  			[]*types.MetricData{
    78  				types.MakeMetricData("asPercent(Server1.memory.used,Server1.memory.total)", []float64{25, 500, 125}, 1, now32),
    79  				types.MakeMetricData("asPercent(Server2.memory.used,Server3.memory.total)", []float64{25, 62.5, 1000}, 1, now32),
    80  			},
    81  		},
    82  		{
    83  			"asPercent(metricC*,metricD*)", // This test is to verify that passing in metrics with different number of values and different step values for the series and the totalSeries does not throw an error
    84  			map[parser.MetricRequest][]*types.MetricData{
    85  				{Metric: "metricC*", From: 0, Until: 1}: {
    86  					types.MakeMetricData("metricC1", []float64{1, 20, 10, 15, 5}, 1, now32), // Test that error isn't thrown when seriesList has more values than totalSeries
    87  					types.MakeMetricData("metricC2", []float64{1, 10, 20, 15, 5}, 1, now32),
    88  				},
    89  				{Metric: "metricD*", From: 0, Until: 1}: {
    90  					types.MakeMetricData("metricD1", []float64{4, 4, 8}, 2, now32),
    91  					types.MakeMetricData("metricD2", []float64{4, 16, 2}, 2, now32),
    92  				},
    93  			},
    94  			[]*types.MetricData{types.MakeMetricData("asPercent(metricC1,metricD1)",
    95  				[]float64{25, 500, 125, math.NaN(), math.NaN(), math.NaN()}, 1, now32),
    96  				types.MakeMetricData("asPercent(metricC2,metricD2)",
    97  					[]float64{25, 62.5, 1000, math.NaN(), math.NaN(), math.NaN()}, 1, now32)},
    98  		},
    99  		{
   100  			"asPercent(metricE*,metricF*)", // This test is to verify that passing in metrics with different lengths and different number of values and different step values for the series and the totalSeries does not throw an error
   101  			map[parser.MetricRequest][]*types.MetricData{
   102  				{Metric: "metricE*", From: 0, Until: 1}: {
   103  					types.MakeMetricData("metricE1", []float64{1, 20, 10, 15, 5}, 1, now32), // Test that error isn't thrown when seriesList has more values than totalSeries
   104  					types.MakeMetricData("metricE2", []float64{1, 10, 20, 15, 5}, 1, now32),
   105  					types.MakeMetricData("metricE3", []float64{1, 10, 20, 15, 5}, 1, now32),
   106  				},
   107  				{Metric: "metricF*", From: 0, Until: 1}: {
   108  					types.MakeMetricData("metricF1", []float64{4, 4, 8}, 2, now32),
   109  					types.MakeMetricData("metricF2", []float64{4, 16, 2}, 2, now32),
   110  				},
   111  			},
   112  			[]*types.MetricData{types.MakeMetricData("asPercent(metricE1,metricF1)",
   113  				[]float64{25, 500, 125, math.NaN(), math.NaN(), math.NaN()}, 1, now32),
   114  				types.MakeMetricData("asPercent(metricE2,metricF2)",
   115  					[]float64{25, 62.5, 1000, math.NaN(), math.NaN(), math.NaN()}, 1, now32),
   116  				types.MakeMetricData("asPercent(metricE3,MISSING)",
   117  					[]float64{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()}, 1, now32)},
   118  		},
   119  
   120  		// Extend tests
   121  		{
   122  			"asPercent(metric*)",
   123  			map[parser.MetricRequest][]*types.MetricData{
   124  				{Metric: "metric*", From: 0, Until: 1}: {
   125  					types.MakeMetricData("metric1", []float64{1, NaN, NaN, 3, 4, 14}, 1, now32),
   126  					types.MakeMetricData("metric2", []float64{4, NaN, 3, NaN, 0, 6}, 1, now32),
   127  				},
   128  			},
   129  			[]*types.MetricData{
   130  				types.MakeMetricData("asPercent(metric1)", []float64{20, NaN, NaN, 100, 100, 70}, 1, now32),
   131  				types.MakeMetricData("asPercent(metric2)", []float64{80, NaN, 100, NaN, 0, 30}, 1, now32),
   132  			},
   133  		},
   134  		{
   135  			"asPercent(Server{1,2}.memory.used,Server{1,2}.memory.total)",
   136  			map[parser.MetricRequest][]*types.MetricData{
   137  				{Metric: "Server{1,2}.memory.used", From: 0, Until: 1}: {
   138  					types.MakeMetricData("Server1.memory.used", []float64{1, 20, 10}, 1, now32),
   139  					types.MakeMetricData("Server2.memory.used", []float64{1, 11, 20}, 1, now32),
   140  				},
   141  				{Metric: "Server{1,2}.memory.total", From: 0, Until: 1}: {
   142  					types.MakeMetricData("Server2.memory.total", []float64{4, 2, 2}, 1, now32),
   143  					types.MakeMetricData("Server1.memory.total", []float64{4, 4, 8}, 1, now32),
   144  				},
   145  			},
   146  			[]*types.MetricData{
   147  				types.MakeMetricData("asPercent(Server1.memory.used,Server1.memory.total)", []float64{25, 500, 125}, 1, now32),
   148  				types.MakeMetricData("asPercent(Server2.memory.used,Server2.memory.total)", []float64{25, 550, 1000}, 1, now32),
   149  			},
   150  		},
   151  		{
   152  			"asPercent(Server{1,2}.memory.used,Server{1,2,3}.memory.total)",
   153  			map[parser.MetricRequest][]*types.MetricData{
   154  				{Metric: "Server{1,2}.memory.used", From: 0, Until: 1}: {
   155  					types.MakeMetricData("Server1.memory.used", []float64{1, 20, 15}, 1, now32),
   156  					types.MakeMetricData("Server2.memory.used", []float64{1, 11, 20}, 1, now32),
   157  				},
   158  				{Metric: "Server{1,2,3}.memory.total", From: 0, Until: 1}: {
   159  					types.MakeMetricData("Server1.memory.total", []float64{4, 40, 25}, 1, now32),
   160  					types.MakeMetricData("Server2.memory.total", []float64{4, 20, 40}, 1, now32),
   161  					types.MakeMetricData("Server3.memory.total", []float64{4, 20, 40}, 1, now32),
   162  				},
   163  			},
   164  			[]*types.MetricData{
   165  				types.MakeMetricData("asPercent(Server1.memory.used,Server1.memory.total)", []float64{25, 50, 60}, 1, now32),
   166  				types.MakeMetricData("asPercent(Server2.memory.used,Server2.memory.total)", []float64{25, 55, 50}, 1, now32),
   167  				types.MakeMetricData("asPercent(MISSING,Server3.memory.total)", []float64{NaN, NaN, NaN}, 1, now32),
   168  			},
   169  		},
   170  		{
   171  			"asPercent(Server{1,2,3}.memory.used,Server{1,2}.memory.total)",
   172  			map[parser.MetricRequest][]*types.MetricData{
   173  				{Metric: "Server{1,2,3}.memory.used", From: 0, Until: 1}: {
   174  					types.MakeMetricData("Server1.memory.used", []float64{1, 20, 15}, 1, now32),
   175  					types.MakeMetricData("Server2.memory.used", []float64{1, 11, 20}, 1, now32),
   176  					types.MakeMetricData("Server3.memory.used", []float64{1, 11, 20}, 1, now32),
   177  				},
   178  				{Metric: "Server{1,2}.memory.total", From: 0, Until: 1}: {
   179  					types.MakeMetricData("Server1.memory.total", []float64{4, 40, 25}, 1, now32),
   180  					types.MakeMetricData("Server2.memory.total", []float64{4, 20, 40}, 1, now32),
   181  				},
   182  			},
   183  			[]*types.MetricData{
   184  				types.MakeMetricData("asPercent(Server1.memory.used,Server1.memory.total)", []float64{25, 50, 60}, 1, now32),
   185  				types.MakeMetricData("asPercent(Server2.memory.used,Server2.memory.total)", []float64{25, 55, 50}, 1, now32),
   186  				types.MakeMetricData("asPercent(Server3.memory.used,MISSING)", []float64{NaN, NaN, NaN}, 1, now32),
   187  			},
   188  		},
   189  		// tagged series
   190  		{
   191  			"asPercent(seriesByTag('name=metric', 'tag=A*'),metricB*)",
   192  			map[parser.MetricRequest][]*types.MetricData{
   193  				{Metric: "seriesByTag('name=metric', 'tag=A*')", From: 0, Until: 1}: {
   194  					types.MakeMetricData("metric;tag=A1", []float64{1, 20, 10}, 1, now32),
   195  					types.MakeMetricData("metric;tag=A2", []float64{1, 10, 20}, 1, now32),
   196  				},
   197  				{Metric: "metricB*", From: 0, Until: 1}: {
   198  					types.MakeMetricData("metricB1", []float64{4, 4, 8}, 1, now32),
   199  					types.MakeMetricData("metricB2", []float64{4, 16, 2}, 1, now32),
   200  				},
   201  			},
   202  			[]*types.MetricData{
   203  				types.MakeMetricData("asPercent(metric;tag=A1,metricB1)", []float64{25, 500, 125}, 1, now32).SetTag("tag", "A1"),
   204  				types.MakeMetricData("asPercent(metric;tag=A2,metricB2)", []float64{25, 62.5, 1000}, 1, now32).SetTag("tag", "A2"),
   205  			},
   206  		},
   207  	}
   208  
   209  	for _, tt := range tests {
   210  		testName := tt.Target
   211  		t.Run(testName, func(t *testing.T) {
   212  			eval := th.EvaluatorFromFunc(md[0].F)
   213  			th.TestEvalExpr(t, eval, &tt)
   214  		})
   215  	}
   216  }
   217  
   218  func TestAsPercentAlignment(t *testing.T) {
   219  	now32 := int64(time.Now().Unix())
   220  	NaN := math.NaN()
   221  	testAlignments := []th.EvalTestItem{
   222  		{
   223  			"asPercent(Server{1,2}.aligned.memory.used,Server{1,3}.aligned.memory.total)",
   224  			map[parser.MetricRequest][]*types.MetricData{
   225  				{Metric: "Server{1,2}.aligned.memory.used", From: 0, Until: 1}: {
   226  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20}, 1, now32),
   227  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 1, 10, 20}, 1, now32-1),
   228  				},
   229  				{Metric: "Server{1,3}.aligned.memory.total", From: 0, Until: 1}: {
   230  					types.MakeMetricData("Server1.aligned.memory.total", []float64{1, 4, 4, 8}, 1, now32-1),
   231  					types.MakeMetricData("Server3.aligned.memory.total", []float64{4, 16, 2, 10}, 1, now32),
   232  				},
   233  			},
   234  			[]*types.MetricData{
   235  				types.MakeMetricData("asPercent(Server1.aligned.memory.used,Server1.aligned.memory.total)", []float64{NaN, 25, 500, 125, NaN}, 1, now32-1),
   236  				types.MakeMetricData("asPercent(Server2.aligned.memory.used,Server3.aligned.memory.total)", []float64{NaN, 25, 62.5, 1000, NaN}, 1, now32-1),
   237  			},
   238  		},
   239  		{
   240  			"asPercent(Server{1,2}.aligned.memory.used,Server3.aligned.memory.total)",
   241  			map[parser.MetricRequest][]*types.MetricData{
   242  				{Metric: "Server{1,2}.aligned.memory.used", From: 0, Until: 1}: {
   243  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20}, 1, now32),
   244  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 2, 10, 20}, 1, now32-1),
   245  				},
   246  				{Metric: "Server3.aligned.memory.total", From: 0, Until: 1}: {
   247  					types.MakeMetricData("Server3.aligned.memory.total", []float64{4, 16, 2, 10, 40}, 1, now32-1),
   248  				},
   249  			},
   250  			[]*types.MetricData{
   251  				types.MakeMetricData("asPercent(Server1.aligned.memory.used,Server3.aligned.memory.total)", []float64{NaN, 6.25, 1000, 100, 50}, 1, now32-1),
   252  				types.MakeMetricData("asPercent(Server2.aligned.memory.used,Server3.aligned.memory.total)", []float64{0, 12.5, 500, 200, NaN}, 1, now32-1),
   253  			},
   254  		},
   255  		{
   256  			"asPercent(Server{1,2}.aligned.memory.used,100)",
   257  			map[parser.MetricRequest][]*types.MetricData{
   258  				{Metric: "Server{1,2}.aligned.memory.used", From: 0, Until: 1}: {
   259  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20}, 1, now32),
   260  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 1, 10, 20}, 1, now32-1),
   261  				},
   262  			},
   263  			[]*types.MetricData{
   264  				types.MakeMetricData("asPercent(Server1.aligned.memory.used,100)", []float64{NaN, 1, 20, 10, 20}, 1, now32-1),
   265  				types.MakeMetricData("asPercent(Server2.aligned.memory.used,100)", []float64{0, 1, 10, 20, NaN}, 1, now32-1),
   266  			},
   267  		},
   268  	}
   269  
   270  	for _, tt := range testAlignments {
   271  		testName := tt.Target
   272  		t.Run(testName, func(t *testing.T) {
   273  			eval := th.EvaluatorFromFunc(md[0].F)
   274  			th.TestEvalExpr(t, eval, &tt)
   275  		})
   276  	}
   277  }
   278  
   279  func TestAsPercentGroup(t *testing.T) {
   280  	now32 := int64(time.Now().Unix())
   281  	NaN := math.NaN()
   282  
   283  	tests := []th.EvalTestItem{
   284  		{
   285  			"asPercent(Server{1,2}.memory.used,Server{1,3}.memory.total,0)",
   286  			map[parser.MetricRequest][]*types.MetricData{
   287  				{Metric: "Server{1,2}.memory.used", From: 0, Until: 1}: {
   288  					types.MakeMetricData("Server1.memory.used", []float64{1, 20, 10}, 1, now32),
   289  					types.MakeMetricData("Server2.memory.used", []float64{1, 10, 20}, 1, now32),
   290  				},
   291  				{Metric: "Server{1,3}.memory.total", From: 0, Until: 1}: {
   292  					types.MakeMetricData("Server1.memory.total", []float64{4, 4, 8}, 1, now32),
   293  					types.MakeMetricData("Server3.memory.total", []float64{4, 16, 2}, 1, now32),
   294  				},
   295  			},
   296  			[]*types.MetricData{
   297  				types.MakeMetricData("asPercent(Server1.memory.used,Server1.memory.total)", []float64{25, 500, 125}, 1, now32),
   298  				types.MakeMetricData("asPercent(Server2.memory.used,MISSING)", []float64{NaN, NaN, NaN}, 1, now32),
   299  				types.MakeMetricData("asPercent(MISSING,Server3.memory.total)", []float64{NaN, NaN, NaN}, 1, now32),
   300  			},
   301  		},
   302  
   303  		// Broken tests with current
   304  		{
   305  			"asPercent(Server{1,2}.memory.{used,free},Server{1,3}.memory.total,0)",
   306  			map[parser.MetricRequest][]*types.MetricData{
   307  				{Metric: "Server{1,2}.memory.{used,free}", From: 0, Until: 1}: {
   308  					types.MakeMetricData("Server1.memory.used", []float64{1, 20, 10}, 1, now32),
   309  					types.MakeMetricData("Server1.memory.free", []float64{1, 20, 10}, 1, now32),
   310  					types.MakeMetricData("Server2.memory.used", []float64{1, 10, 20}, 1, now32),
   311  					types.MakeMetricData("Server2.memory.free", []float64{1, 20, 10}, 1, now32),
   312  				},
   313  				{Metric: "Server{1,3}.memory.total", From: 0, Until: 1}: {
   314  					types.MakeMetricData("Server1.memory.total", []float64{4, 4, 8}, 1, now32),
   315  					types.MakeMetricData("Server3.memory.total", []float64{4, 16, 2}, 1, now32),
   316  				},
   317  			},
   318  			[]*types.MetricData{
   319  				types.MakeMetricData("asPercent(Server1.memory.free,Server1.memory.total)", []float64{25, 500, 125}, 1, now32),
   320  				types.MakeMetricData("asPercent(Server1.memory.used,Server1.memory.total)", []float64{25, 500, 125}, 1, now32),
   321  				types.MakeMetricData("asPercent(Server2.memory.free,MISSING)", []float64{NaN, NaN, NaN}, 1, now32),
   322  				types.MakeMetricData("asPercent(Server2.memory.used,MISSING)", []float64{NaN, NaN, NaN}, 1, now32),
   323  				types.MakeMetricData("asPercent(MISSING,Server3.memory.total)", []float64{NaN, NaN, NaN}, 1, now32),
   324  			},
   325  		},
   326  		{
   327  			"asPercent(Server{1,2}.memory.{used,free},None,0)",
   328  			map[parser.MetricRequest][]*types.MetricData{
   329  				{Metric: "Server{1,2}.memory.{used,free}", From: 0, Until: 1}: {
   330  					types.MakeMetricData("Server1.memory.used", []float64{2, 1, NaN}, 1, now32),
   331  					types.MakeMetricData("Server1.memory.free", []float64{3, NaN, 8}, 1, now32),
   332  					types.MakeMetricData("Server2.memory.used", []float64{4, NaN, 2}, 1, now32),
   333  				},
   334  			},
   335  			[]*types.MetricData{
   336  				types.MakeMetricData("asPercent(Server1.memory.free,None)", []float64{60, NaN, 100}, 1, now32),
   337  				types.MakeMetricData("asPercent(Server1.memory.used,None)", []float64{40, 100, NaN}, 1, now32),
   338  				types.MakeMetricData("asPercent(Server2.memory.used,None)", []float64{100, NaN, 100}, 1, now32),
   339  			},
   340  		},
   341  	}
   342  
   343  	for _, tt := range tests {
   344  		testName := tt.Target
   345  		t.Run(testName, func(t *testing.T) {
   346  			eval := th.EvaluatorFromFunc(md[0].F)
   347  			th.TestEvalExpr(t, eval, &tt)
   348  		})
   349  	}
   350  }
   351  
   352  func BenchmarkAsPercent(b *testing.B) {
   353  	NaN := math.NaN()
   354  	benchmarks := []struct {
   355  		target string
   356  		M      map[parser.MetricRequest][]*types.MetricData
   357  	}{
   358  		{
   359  			target: "asPercent(metric*)",
   360  			M: map[parser.MetricRequest][]*types.MetricData{
   361  				{Metric: "metric*", From: 0, Until: 1}: {
   362  					types.MakeMetricData("metric1", []float64{1, NaN, NaN, 3, 4, 14}, 1, 1),
   363  					types.MakeMetricData("metric2", []float64{4, NaN, 3, NaN, 0, 6}, 1, 1),
   364  				},
   365  			},
   366  		},
   367  		{
   368  			target: "asPercent(metric1,metric2)",
   369  			M: map[parser.MetricRequest][]*types.MetricData{
   370  				{Metric: "metric1", From: 0, Until: 1}: {types.MakeMetricData("metric1", []float64{1, NaN, NaN, 3, 4, 12}, 1, 1)},
   371  				{Metric: "metric2", From: 0, Until: 1}: {types.MakeMetricData("metric2", []float64{2, NaN, 3, NaN, 0, 6}, 1, 1)},
   372  			},
   373  		},
   374  		{
   375  			target: "asPercent(Server{1,2}.memory.used,Server{1,2,3}.memory.total)",
   376  			M: map[parser.MetricRequest][]*types.MetricData{
   377  				{Metric: "Server{1,2}.memory.used", From: 0, Until: 1}: {
   378  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20}, 1, 2),
   379  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 1, 10, 20}, 1, 1),
   380  				},
   381  				{Metric: "Server{1,2,3}.memory.total", From: 0, Until: 1}: {
   382  					types.MakeMetricData("Server1.aligned.memory.total", []float64{1, 4, 4, 8}, 1, 1),
   383  					types.MakeMetricData("Server2.aligned.memory.total", []float64{4, 16, 2, 10}, 1, 2),
   384  					types.MakeMetricData("Server3.aligned.memory.total", []float64{4, 16, 2, 10}, 1, 2),
   385  				},
   386  			},
   387  		},
   388  		{
   389  			target: "asPercent(Server{1,2,3}.memory.used,Server{1,2}.memory.total)",
   390  			M: map[parser.MetricRequest][]*types.MetricData{
   391  				{Metric: "Server{1,2,3}.memory.used", From: 0, Until: 1}: {
   392  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20}, 1, 2),
   393  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 1, 10, 20}, 1, 1),
   394  					types.MakeMetricData("Server3.aligned.memory.used", []float64{0, 1, 10, 20}, 1, 1),
   395  				},
   396  				{Metric: "Server{1,2}.memory.total", From: 0, Until: 1}: {
   397  					types.MakeMetricData("Server1.aligned.memory.total", []float64{1, 4, 4, 8}, 1, 1),
   398  					types.MakeMetricData("Server2.aligned.memory.total", []float64{4, 16, 2, 10}, 1, 2),
   399  				},
   400  			},
   401  		},
   402  		{
   403  			target: "asPercent(Server{1,2}.memory.{used,free},Server{1,2}.memory.total)",
   404  			M: map[parser.MetricRequest][]*types.MetricData{
   405  				{Metric: "Server{1,2}.memory.{used,free}", From: 0, Until: 1}: {
   406  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20}, 1, 2),
   407  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 1, 10, 20}, 1, 1),
   408  					types.MakeMetricData("Server1.aligned.memory.free", []float64{1, 20, 10, 20}, 1, 2),
   409  					types.MakeMetricData("Server2.aligned.memory.free", []float64{0, 1, 10, 20}, 1, 1),
   410  				},
   411  				{Metric: "Server{1,2}.memory.total", From: 0, Until: 1}: {
   412  					types.MakeMetricData("Server1.aligned.memory.total", []float64{1, 4, 4, 8}, 1, 1),
   413  					types.MakeMetricData("Server2.aligned.memory.total", []float64{4, 16, 2, 10}, 1, 2),
   414  				},
   415  			},
   416  		},
   417  		{
   418  			target: "asPercent(Server{1,2}.aligned.memory.used,Server{1,3}.aligned.memory.total)",
   419  			M: map[parser.MetricRequest][]*types.MetricData{
   420  				{Metric: "Server{1,2}.aligned.memory.used", From: 0, Until: 1}: {
   421  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20}, 1, 2),
   422  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 1, 10, 20}, 1, 1),
   423  				},
   424  				{Metric: "Server{1,3}.aligned.memory.total", From: 0, Until: 1}: {
   425  					types.MakeMetricData("Server1.aligned.memory.total", []float64{1, 4, 4, 8}, 1, 1),
   426  					types.MakeMetricData("Server3.aligned.memory.total", []float64{4, 16, 2, 10}, 1, 2),
   427  				},
   428  			},
   429  		},
   430  		{
   431  			target: "asPercent(Server{1,2}.aligned.memory.used,Server3.aligned.memory.total)",
   432  			M: map[parser.MetricRequest][]*types.MetricData{
   433  				{Metric: "Server{1,2}.aligned.memory.used", From: 0, Until: 1}: {
   434  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20}, 1, 2),
   435  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 2, 10, 20}, 1, 1),
   436  				},
   437  				{Metric: "Server3.aligned.memory.total", From: 0, Until: 1}: {
   438  					types.MakeMetricData("Server3.aligned.memory.total", []float64{4, 16, 2, 10, 40}, 1, 1),
   439  				},
   440  			},
   441  		},
   442  		{
   443  			target: `asPercent(Server{1,2}.aligned.memory.used,100)`,
   444  			M: map[parser.MetricRequest][]*types.MetricData{
   445  				{Metric: "Server{1,2}.aligned.memory.used", From: 0, Until: 1}: {
   446  					types.MakeMetricData("Server1.aligned.memory.used", []float64{1, 20, 10, 20, 1, 20, 10, 20, 1, 20, 10, 20}, 1, 2),
   447  					types.MakeMetricData("Server2.aligned.memory.used", []float64{0, 1, 10, 20, 0, 1, 10, 20, 0, 1, 10, 20}, 1, 1),
   448  				},
   449  			},
   450  		},
   451  	}
   452  
   453  	eval := th.EvaluatorFromFunc(md[0].F)
   454  
   455  	for _, bm := range benchmarks {
   456  		b.Run(bm.target, func(b *testing.B) {
   457  			exp, _, err := parser.ParseExpr(bm.target)
   458  			if err != nil {
   459  				b.Fatalf("failed to parse %s: %+v", bm.target, err)
   460  			}
   461  
   462  			b.ResetTimer()
   463  
   464  			for i := 0; i < b.N; i++ {
   465  				g, err := eval.Eval(context.Background(), exp, 0, 1, bm.M)
   466  				if err != nil {
   467  					b.Fatalf("failed to eval %s: %+v", bm.target, err)
   468  				}
   469  				_ = g
   470  			}
   471  		})
   472  	}
   473  }
   474  
   475  func BenchmarkAsPercentGroup(b *testing.B) {
   476  	NaN := math.NaN()
   477  	benchmarks := []struct {
   478  		target string
   479  		M      map[parser.MetricRequest][]*types.MetricData
   480  	}{
   481  		{
   482  			target: "asPercent(Server{1,2}.memory.used,Server{1,3}.memory.total,0)",
   483  			M: map[parser.MetricRequest][]*types.MetricData{
   484  				{Metric: "Server{1,2}.memory.used", From: 0, Until: 1}: {
   485  					types.MakeMetricData("Server1.memory.used", []float64{1, 20, 10}, 1, 1),
   486  					types.MakeMetricData("Server2.memory.used", []float64{1, 10, 20}, 1, 1),
   487  				},
   488  				{Metric: "Server{1,3}.memory.total", From: 0, Until: 1}: {
   489  					types.MakeMetricData("Server1.memory.total", []float64{4, 4, 8}, 1, 1),
   490  					types.MakeMetricData("Server3.memory.total", []float64{4, 16, 2}, 1, 1),
   491  				},
   492  			},
   493  		},
   494  		{
   495  			target: "asPercent(Server{1,2}.memory.{used,free},None,0)",
   496  			M: map[parser.MetricRequest][]*types.MetricData{
   497  				{Metric: "Server{1,2}.memory.{used,free}", From: 0, Until: 1}: {
   498  					types.MakeMetricData("Server1.memory.used", []float64{2, 1, NaN}, 1, 1),
   499  					types.MakeMetricData("Server1.memory.free", []float64{3, NaN, 8}, 1, 1),
   500  					types.MakeMetricData("Server2.memory.used", []float64{4, NaN, 2}, 1, 1),
   501  				},
   502  			},
   503  		},
   504  	}
   505  
   506  	eval := th.EvaluatorFromFunc(md[0].F)
   507  
   508  	for _, bm := range benchmarks {
   509  		b.Run(bm.target, func(b *testing.B) {
   510  			exp, _, err := parser.ParseExpr(bm.target)
   511  			if err != nil {
   512  				b.Fatalf("failed to parse %s: %+v", bm.target, err)
   513  			}
   514  
   515  			b.ResetTimer()
   516  
   517  			for i := 0; i < b.N; i++ {
   518  				g, err := eval.Eval(context.Background(), exp, 0, 1, bm.M)
   519  				if err != nil {
   520  					b.Fatalf("failed to eval %s: %+v", bm.target, err)
   521  				}
   522  				_ = g
   523  			}
   524  		})
   525  	}
   526  }