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  }