k8s.io/kubernetes@v1.29.3/test/e2e/framework/timer/timer.go (about) 1 /* 2 Copyright 2017 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 timer 18 19 import ( 20 "time" 21 22 "bytes" 23 "fmt" 24 25 "sync" 26 27 "k8s.io/kubernetes/test/e2e/framework" 28 "k8s.io/kubernetes/test/e2e/perftype" 29 ) 30 31 var now = time.Now 32 33 // Phase represents a phase of a test. Phases can overlap. 34 type Phase struct { 35 sequenceNumber int 36 name string 37 startTime time.Time 38 endTime time.Time 39 } 40 41 func (phase *Phase) ended() bool { 42 return !phase.endTime.IsZero() 43 } 44 45 // End marks the phase as ended, unless it had already been ended before. 46 func (phase *Phase) End() { 47 if !phase.ended() { 48 phase.endTime = now() 49 } 50 } 51 52 func (phase *Phase) label() string { 53 return fmt.Sprintf("%03d-%s", phase.sequenceNumber, phase.name) 54 } 55 56 func (phase *Phase) duration() time.Duration { 57 endTime := phase.endTime 58 if !phase.ended() { 59 endTime = now() 60 } 61 return endTime.Sub(phase.startTime) 62 } 63 64 func (phase *Phase) humanReadable() string { 65 if phase.ended() { 66 return fmt.Sprintf("Phase %s: %v\n", phase.label(), phase.duration()) 67 } 68 return fmt.Sprintf("Phase %s: %v so far\n", phase.label(), phase.duration()) 69 } 70 71 // A TestPhaseTimer groups phases and provides a way to export their measurements as JSON or human-readable text. 72 // It is safe to use concurrently. 73 type TestPhaseTimer struct { 74 lock sync.Mutex 75 phases []*Phase 76 } 77 78 // NewTestPhaseTimer creates a new TestPhaseTimer. 79 func NewTestPhaseTimer() *TestPhaseTimer { 80 return &TestPhaseTimer{} 81 } 82 83 // StartPhase starts a new phase. 84 // sequenceNumber is an integer prepended to phaseName in the output, such that lexicographic sorting 85 // of phases in perfdash reconstructs the order of execution. Unfortunately it needs to be 86 // provided manually, since a simple incrementing counter would have the effect that inserting 87 // a new phase would renumber subsequent phases, breaking the continuity of historical records. 88 func (timer *TestPhaseTimer) StartPhase(sequenceNumber int, phaseName string) *Phase { 89 timer.lock.Lock() 90 defer timer.lock.Unlock() 91 newPhase := &Phase{sequenceNumber: sequenceNumber, name: phaseName, startTime: now()} 92 timer.phases = append(timer.phases, newPhase) 93 return newPhase 94 } 95 96 // SummaryKind returns the summary of test summary. 97 func (timer *TestPhaseTimer) SummaryKind() string { 98 return "TestPhaseTimer" 99 } 100 101 // PrintHumanReadable returns durations of all phases. 102 func (timer *TestPhaseTimer) PrintHumanReadable() string { 103 buf := bytes.Buffer{} 104 timer.lock.Lock() 105 defer timer.lock.Unlock() 106 for _, phase := range timer.phases { 107 buf.WriteString(phase.humanReadable()) 108 } 109 return buf.String() 110 } 111 112 // PrintJSON returns durations of all phases with JSON format. 113 func (timer *TestPhaseTimer) PrintJSON() string { 114 data := perftype.PerfData{ 115 Version: "v1", 116 DataItems: []perftype.DataItem{{ 117 Unit: "s", 118 Labels: map[string]string{"test": "phases"}, 119 Data: make(map[string]float64)}}} 120 timer.lock.Lock() 121 defer timer.lock.Unlock() 122 for _, phase := range timer.phases { 123 data.DataItems[0].Data[phase.label()] = phase.duration().Seconds() 124 if !phase.ended() { 125 data.DataItems[0].Labels["ended"] = "false" 126 } 127 } 128 return framework.PrettyPrintJSON(data) 129 }