k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/common/executors/promql_executor.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package executors
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"time"
    25  
    26  	"github.com/prometheus/common/model"
    27  	"github.com/prometheus/prometheus/promql"
    28  	"github.com/prometheus/prometheus/rules"
    29  	"gopkg.in/yaml.v2"
    30  )
    31  
    32  const (
    33  	pathToPrometheusRules = "$GOPATH/src/k8s.io/perf-tests/clusterloader2/pkg/prometheus/manifests/prometheus-rules.yaml"
    34  )
    35  
    36  func toModelSample(s promql.Sample) *model.Sample {
    37  	ls := make(model.Metric)
    38  	for _, l := range s.Metric {
    39  		ls[model.LabelName(l.Name)] = model.LabelValue(l.Value)
    40  	}
    41  
    42  	return &model.Sample{
    43  		Value:     model.SampleValue(s.Point.V),
    44  		Timestamp: model.Time(s.Point.T),
    45  		Metric:    ls,
    46  	}
    47  }
    48  
    49  type series struct {
    50  	Series string `yaml:"series"`
    51  	Values string `yaml:"values"`
    52  }
    53  
    54  type testSeries struct {
    55  	InputSeries []series `yaml:"input_series"`
    56  	Interval    string   `yaml:"interval"`
    57  }
    58  
    59  func (t *testSeries) seriesLoadingString() string {
    60  	result := fmt.Sprintf("load %v\n", t.Interval)
    61  	for _, is := range t.InputSeries {
    62  		result += fmt.Sprintf("  %v %v\n", is.Series, is.Values)
    63  	}
    64  	return result
    65  }
    66  
    67  func loadFromFile(filename string) (*testSeries, error) {
    68  	b, err := ioutil.ReadFile(filename)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	res := new(testSeries)
    74  	err = yaml.Unmarshal(b, res)
    75  
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	return res, nil
    80  }
    81  
    82  // PromqlExecutor executes queries against local prometheus query engine
    83  type PromqlExecutor struct {
    84  	ll       promql.LazyLoader
    85  	interval time.Duration
    86  }
    87  
    88  // NewPromqlExecutor creates a new executor with time series and rules loaded from file
    89  // Samples loaded from test file starts at time.Time.UTC(0,0)
    90  func NewPromqlExecutor(timeSeriesFile string) (*PromqlExecutor, error) {
    91  	//Load time series from file
    92  	f, err := loadFromFile(timeSeriesFile)
    93  	if err != nil {
    94  		return nil, fmt.Errorf("could not parse time series file: %v", err)
    95  	}
    96  
    97  	interval, err := time.ParseDuration(f.Interval)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("could not parse interval from time series file: %v", err)
   100  	}
   101  
   102  	ll, err := promql.NewLazyLoader(nil, f.seriesLoadingString())
   103  	if err != nil {
   104  		return nil, fmt.Errorf("could not initialize lazy loader: %v", err)
   105  	}
   106  
   107  	//Load rule groups
   108  	opts := &rules.ManagerOptions{
   109  		QueryFunc:  rules.EngineQueryFunc(ll.QueryEngine(), ll.Storage()),
   110  		Appendable: ll.Storage(),
   111  		Context:    context.Background(),
   112  		NotifyFunc: func(ctx context.Context, expr string, alerts ...*rules.Alert) {},
   113  		Logger:     nil,
   114  	}
   115  	m := rules.NewManager(opts)
   116  
   117  	rulesFile, err := createRulesFile(os.ExpandEnv(pathToPrometheusRules))
   118  	if err != nil {
   119  		return nil, fmt.Errorf("could not create rules file: %v", err)
   120  	}
   121  	defer os.Remove(rulesFile.Name())
   122  	groupsMap, ers := m.LoadGroups(interval, nil, rulesFile.Name())
   123  	if ers != nil {
   124  		return nil, fmt.Errorf("could not load rules file: %v", ers)
   125  	}
   126  
   127  	//Load data into ll
   128  	ll.WithSamplesTill(time.Now(), func(e error) {
   129  		if err != nil {
   130  			err = e
   131  		}
   132  	})
   133  	if err != nil {
   134  		return nil, fmt.Errorf("could not load samples into lazyloader: %v", err)
   135  	}
   136  
   137  	// Evaluate rules after data was loaded
   138  	// This assumes that no one will try to load time series longer than 6 hours
   139  	maxt := time.Unix(0, 0).UTC().Add(time.Duration(6*60) * time.Minute)
   140  	for ts := time.Unix(0, 0).UTC(); ts.Before(maxt) || ts.Equal(maxt); ts = ts.Add(interval) {
   141  		for _, g := range groupsMap {
   142  			g.Eval(context.Background(), ts)
   143  		}
   144  	}
   145  
   146  	return &PromqlExecutor{ll: *ll, interval: interval}, nil
   147  }
   148  
   149  // Query queries prometheus mock engine with data loaded from file
   150  // The start date for all queries is time.Time.UTC(0,0)
   151  // Currently only queries that result in scalar are supported
   152  func (p *PromqlExecutor) Query(query string, queryTime time.Time) ([]*model.Sample, error) {
   153  	qe := p.ll.QueryEngine()
   154  	q, err := qe.NewInstantQuery(p.ll.Queryable(), query, queryTime)
   155  	if err != nil {
   156  		fmt.Println(err)
   157  		return nil, err
   158  	}
   159  	defer q.Close()
   160  	res := q.Exec(p.ll.Context())
   161  
   162  	switch v := res.Value.(type) {
   163  	case promql.Vector:
   164  		res := make([]*model.Sample, 0, len(v))
   165  		for _, s := range v {
   166  			res = append(res, toModelSample(s))
   167  		}
   168  		return res, nil
   169  	case promql.Scalar:
   170  		return nil, fmt.Errorf("query result is a scalar, expected vector. query: %s", query)
   171  	default:
   172  		return nil, fmt.Errorf("query result is not a vector. query: %s", query)
   173  	}
   174  }
   175  
   176  func (p *PromqlExecutor) Close() {
   177  	p.ll.Close()
   178  }