k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/util/phase_latency.go (about) 1 /* 2 Copyright 2019 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 "sort" 22 "sync" 23 "time" 24 25 "k8s.io/klog/v2" 26 ) 27 28 // Transition describe transition between two phases. 29 type Transition struct { 30 From string 31 To string 32 Threshold time.Duration 33 } 34 35 // ObjectTransitionTimes stores beginning time of each phase. 36 // It can calculate transition latency between phases. 37 // ObjectTransitionTimes is thread-safe. 38 type ObjectTransitionTimes struct { 39 name string 40 lock sync.Mutex 41 // times is a map: object key->phase->time. 42 times map[string]map[string]time.Time 43 } 44 45 // NewObjectTransitionTimes creates new ObjectTransitionTimes instance. 46 func NewObjectTransitionTimes(name string) *ObjectTransitionTimes { 47 return &ObjectTransitionTimes{ 48 name: name, 49 times: make(map[string]map[string]time.Time), 50 } 51 } 52 53 // Set sets time of given phase for given key. 54 func (o *ObjectTransitionTimes) Set(key, phase string, t time.Time) { 55 o.lock.Lock() 56 defer o.lock.Unlock() 57 if _, exists := o.times[key]; !exists { 58 o.times[key] = make(map[string]time.Time) 59 } 60 o.times[key][phase] = t 61 } 62 63 // Get returns time of given phase for given key. 64 func (o *ObjectTransitionTimes) Get(key, phase string) (time.Time, bool) { 65 o.lock.Lock() 66 defer o.lock.Unlock() 67 if entry, exists := o.times[key]; exists { 68 val, ok := entry[phase] 69 return val, ok 70 } 71 return time.Time{}, false 72 } 73 74 // Count returns number of key having given phase entry. 75 func (o *ObjectTransitionTimes) Count(phase string) int { 76 o.lock.Lock() 77 defer o.lock.Unlock() 78 count := 0 79 for _, entry := range o.times { 80 if _, exists := entry[phase]; exists { 81 count++ 82 } 83 } 84 return count 85 } 86 87 // KeyFilterFunc is a function that for a given key returns whether 88 // its corresponding entry should be included in the metric. 89 type KeyFilterFunc func(string) bool 90 91 // MatchAll implements KeyFilterFunc and matches every element. 92 func MatchAll(_ string) bool { return true } 93 94 // CalculateTransitionsLatency returns a latency map for given transitions. 95 func (o *ObjectTransitionTimes) CalculateTransitionsLatency(t map[string]Transition, filter KeyFilterFunc) map[string]*LatencyMetric { 96 o.lock.Lock() 97 defer o.lock.Unlock() 98 metric := make(map[string]*LatencyMetric) 99 for name, transition := range t { 100 lag := make([]LatencyData, 0, len(o.times)) 101 for key, transitionTimes := range o.times { 102 if !filter(key) { 103 klog.V(4).Infof("%s: filter doesn match key %s", o.name, key) 104 continue 105 } 106 fromPhaseTime, exists := transitionTimes[transition.From] 107 if !exists { 108 klog.V(4).Infof("%s: failed to find %v time for %v", o.name, transition.From, key) 109 continue 110 } 111 toPhaseTime, exists := transitionTimes[transition.To] 112 if !exists { 113 klog.V(4).Infof("%s: failed to find %v time for %v", o.name, transition.To, key) 114 continue 115 } 116 latencyTime := toPhaseTime.Sub(fromPhaseTime) 117 // latencyTime should be always larger than zero, however, in some cases, it might be a 118 // negative value due to the precision of timestamp can only get to the level of second, 119 // the microsecond and nanosecond have been discarded purposely in kubelet, this is 120 // because apiserver does not support RFC339NANO. 121 if latencyTime < 0 { 122 latencyTime = 0 123 } 124 lag = append(lag, latencyData{key: key, latency: latencyTime}) 125 } 126 127 sort.Sort(LatencySlice(lag)) 128 o.printLatencies(lag, fmt.Sprintf("worst %s latencies", name), transition.Threshold) 129 lagMetric := NewLatencyMetric(lag) 130 metric[name] = &lagMetric 131 } 132 return metric 133 } 134 135 func (o *ObjectTransitionTimes) printLatencies(latencies []LatencyData, header string, threshold time.Duration) { 136 metrics := NewLatencyMetric(latencies) 137 index := len(latencies) - 100 138 if index < 0 { 139 index = 0 140 } 141 klog.V(2).Infof("%s: %d %s: %v", o.name, len(latencies)-index, header, latencies[index:]) 142 var thresholdString string 143 if threshold != time.Duration(0) { 144 thresholdString = fmt.Sprintf("; threshold %v", threshold) 145 } 146 klog.V(0).Infof("%s: perc50: %v, perc90: %v, perc99: %v%s", o.name, metrics.Perc50, metrics.Perc90, metrics.Perc99, thresholdString) 147 } 148 149 type latencyData struct { 150 key string 151 latency time.Duration 152 } 153 154 func (l latencyData) GetLatency() time.Duration { 155 return l.latency 156 } 157 158 func (l latencyData) String() string { 159 return fmt.Sprintf("{%s %v}", l.key, l.latency) 160 } 161 162 // LatencyMapToPerfData converts latency map into PerfData. 163 func LatencyMapToPerfData(latency map[string]*LatencyMetric) *PerfData { 164 perfData := &PerfData{Version: "1.0"} 165 for name, l := range latency { 166 perfData.DataItems = append(perfData.DataItems, l.ToPerfData(name)) 167 } 168 return perfData 169 }