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 }