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 }