k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/util/prometheus.go (about) 1 /* 2 Copyright 2018 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 util 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io" 23 "math" 24 "strings" 25 "time" 26 27 "github.com/prometheus/common/expfmt" 28 "github.com/prometheus/common/model" 29 "k8s.io/apimachinery/pkg/util/wait" 30 "k8s.io/klog/v2" 31 prom "k8s.io/perf-tests/clusterloader2/pkg/prometheus/clients" 32 ) 33 34 const ( 35 queryTimeout = 15 * time.Minute 36 queryInterval = 30 * time.Second 37 ) 38 39 // ExtractMetricSamples unpacks metric blob into prometheus model structures. 40 func ExtractMetricSamples(metricsBlob string) ([]*model.Sample, error) { 41 dec := expfmt.NewDecoder(strings.NewReader(metricsBlob), expfmt.FmtText) 42 decoder := expfmt.SampleDecoder{ 43 Dec: dec, 44 Opts: &expfmt.DecodeOptions{}, 45 } 46 47 var samples []*model.Sample 48 for { 49 var v model.Vector 50 if err := decoder.Decode(&v); err != nil { 51 if err == io.EOF { 52 // Expected loop termination condition. 53 return samples, nil 54 } 55 return nil, err 56 } 57 samples = append(samples, v...) 58 } 59 } 60 61 // ExtractMetricSamples2 unpacks metric blob into prometheus model structures. 62 func ExtractMetricSamples2(response []byte) ([]*model.Sample, error) { 63 var pqr promQueryResponse 64 if err := json.Unmarshal(response, &pqr); err != nil { 65 return nil, err 66 } 67 if pqr.Status != "success" { 68 return nil, fmt.Errorf("non-success response status: %v", pqr.Status) 69 } 70 vector, ok := pqr.Data.v.(model.Vector) 71 if !ok { 72 return nil, fmt.Errorf("incorrect response type: %v", pqr.Data.v.Type()) 73 } 74 return []*model.Sample(vector), nil 75 } 76 77 // promQueryResponse stores the response from the Prometheus server. 78 // This struct follows the format described in the Prometheus documentation: 79 // https://prometheus.io/docs/prometheus/latest/querying/api/#format-overview. 80 type promQueryResponse struct { 81 Status string `json:"status"` 82 Data promResponseData `json:"data"` 83 ErrorType string `json:"errorType"` 84 Error string `json:"error"` 85 Warnings []string `json:"warnings"` 86 } 87 88 type promResponseData struct { 89 v model.Value 90 } 91 92 // NewQueryExecutor creates instance of PrometheusQueryExecutor. 93 func NewQueryExecutor(pc prom.Client) *PrometheusQueryExecutor { 94 return &PrometheusQueryExecutor{client: pc} 95 } 96 97 // PrometheusQueryExecutor executes queries against Prometheus. 98 type PrometheusQueryExecutor struct { 99 client prom.Client 100 } 101 102 // Query executes given prometheus query at given point in time. 103 func (e *PrometheusQueryExecutor) Query(query string, queryTime time.Time) ([]*model.Sample, error) { 104 if queryTime.IsZero() { 105 return nil, fmt.Errorf("query time can't be zero") 106 } 107 108 var body []byte 109 var queryErr error 110 111 klog.V(2).Infof("Executing %q at %v", query, queryTime.Format(time.RFC3339)) 112 if err := wait.PollImmediate(queryInterval, queryTimeout, func() (bool, error) { 113 body, queryErr = e.client.Query(query, queryTime) 114 if queryErr != nil { 115 return false, nil 116 } 117 return true, nil 118 }); err != nil { 119 if queryErr != nil { 120 resp := "(empty)" 121 if body != nil { 122 resp = string(body) 123 } 124 return nil, fmt.Errorf("query error: %v [body: %s]", queryErr, resp) 125 } 126 return nil, fmt.Errorf("error: %v", err) 127 } 128 129 samples, err := ExtractMetricSamples2(body) 130 if err != nil { 131 return nil, fmt.Errorf("extracting error: %v", err) 132 } 133 134 var resultSamples []*model.Sample 135 for _, sample := range samples { 136 if !math.IsNaN(float64(sample.Value)) { 137 resultSamples = append(resultSamples, sample) 138 } 139 } 140 klog.V(4).Infof("Got %d samples", len(resultSamples)) 141 return resultSamples, nil 142 } 143 144 // UnmarshalJSON unmarshals json into promResponseData structure. 145 func (qr *promResponseData) UnmarshalJSON(b []byte) error { 146 v := struct { 147 Type model.ValueType `json:"resultType"` 148 Result json.RawMessage `json:"result"` 149 }{} 150 151 err := json.Unmarshal(b, &v) 152 if err != nil { 153 return err 154 } 155 156 switch v.Type { 157 case model.ValScalar: 158 var sv model.Scalar 159 err = json.Unmarshal(v.Result, &sv) 160 qr.v = &sv 161 case model.ValVector: 162 var vv model.Vector 163 err = json.Unmarshal(v.Result, &vv) 164 qr.v = vv 165 case model.ValMatrix: 166 var mv model.Matrix 167 err = json.Unmarshal(v.Result, &mv) 168 qr.v = mv 169 default: 170 err = fmt.Errorf("unexpected value type %q", v.Type) 171 } 172 return err 173 } 174 175 // ToPrometheusTime returns prometheus string representation of given time. 176 func ToPrometheusTime(t time.Duration) string { 177 return fmt.Sprintf("%ds", int64(t)/int64(time.Second)) 178 }