k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/util/latency_metric.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  	"fmt"
    21  	"math"
    22  	"strconv"
    23  	"time"
    24  
    25  	"github.com/prometheus/common/model"
    26  )
    27  
    28  // LatencyMetric represent 50th, 90th and 99th duration quantiles.
    29  type LatencyMetric struct {
    30  	Perc50 time.Duration `json:"Perc50"`
    31  	Perc90 time.Duration `json:"Perc90"`
    32  	Perc99 time.Duration `json:"Perc99"`
    33  }
    34  
    35  // SetQuantile set quantile value.
    36  // Only 0.5, 0.9 and 0.99 quantiles are supported.
    37  func (metric *LatencyMetric) SetQuantile(quantile float64, latency time.Duration) {
    38  	switch quantile {
    39  	case 0.5:
    40  		metric.Perc50 = latency
    41  	case 0.9:
    42  		metric.Perc90 = latency
    43  	case 0.99:
    44  		metric.Perc99 = latency
    45  	}
    46  }
    47  
    48  // VerifyThreshold verifies latency metric against given percentile thresholds.
    49  func (metric *LatencyMetric) VerifyThreshold(threshold time.Duration) error {
    50  	if metric.Perc50 > threshold {
    51  		return fmt.Errorf("too high latency 50th percentile: got %v expected: %v", metric.Perc50, threshold)
    52  	}
    53  	if metric.Perc90 > threshold {
    54  		return fmt.Errorf("too high latency 90th percentile: got %v expected: %v", metric.Perc90, threshold)
    55  	}
    56  	if metric.Perc99 > threshold {
    57  		return fmt.Errorf("too high latency 99th percentile: got %v expected: %v", metric.Perc99, threshold)
    58  	}
    59  	return nil
    60  }
    61  
    62  // ToPerfData converts latency metric to PerfData.
    63  func (metric *LatencyMetric) ToPerfData(name string) DataItem {
    64  	return DataItem{
    65  		Data: map[string]float64{
    66  			"Perc50": float64(metric.Perc50) / float64(time.Millisecond),
    67  			"Perc90": float64(metric.Perc90) / float64(time.Millisecond),
    68  			"Perc99": float64(metric.Perc99) / float64(time.Millisecond),
    69  		},
    70  		Unit: "ms",
    71  		Labels: map[string]string{
    72  			"Metric": name,
    73  		},
    74  	}
    75  }
    76  
    77  func (metric LatencyMetric) String() string {
    78  	return fmt.Sprintf("perc50: %v, perc90: %v, perc99: %v", metric.Perc50, metric.Perc90, metric.Perc99)
    79  }
    80  
    81  // LatencyData is an interface for latance data structure.
    82  type LatencyData interface {
    83  	GetLatency() time.Duration
    84  }
    85  
    86  // LatencySlice is a sortable latency array.
    87  type LatencySlice []LatencyData
    88  
    89  func (l LatencySlice) Len() int           { return len(l) }
    90  func (l LatencySlice) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
    91  func (l LatencySlice) Less(i, j int) bool { return l[i].GetLatency() < l[j].GetLatency() }
    92  
    93  // NewLatencyMetric converts latency data array to latency metric.
    94  func NewLatencyMetric(latencies []LatencyData) LatencyMetric {
    95  	length := len(latencies)
    96  	if length == 0 {
    97  		// Ideally we can return LatencyMetric with some NaN/incorrect values,
    98  		// but 0 is the best we can get for time.Duration type.
    99  		return LatencyMetric{Perc50: 0, Perc90: 0, Perc99: 0}
   100  	}
   101  	perc50 := latencies[int(math.Ceil(float64(length*50)/100))-1].GetLatency()
   102  	perc90 := latencies[int(math.Ceil(float64(length*90)/100))-1].GetLatency()
   103  	perc99 := latencies[int(math.Ceil(float64(length*99)/100))-1].GetLatency()
   104  	return LatencyMetric{Perc50: perc50, Perc90: perc90, Perc99: perc99}
   105  }
   106  
   107  // NewLatencyMetricPrometheus tries to parse latency data from results of Prometheus query.
   108  func NewLatencyMetricPrometheus(samples []*model.Sample) (*LatencyMetric, error) {
   109  	var latencyMetric LatencyMetric
   110  	for _, sample := range samples {
   111  		val, ok := sample.Metric["quantile"]
   112  		if !ok {
   113  			return nil, fmt.Errorf("quantile missing in sample %v", sample)
   114  		}
   115  
   116  		quantile, err := strconv.ParseFloat(string(val), 64)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		latency := time.Duration(float64(sample.Value) * float64(time.Second))
   121  		latencyMetric.SetQuantile(quantile, latency)
   122  	}
   123  	return &latencyMetric, nil
   124  }