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  }