1*1fa6dee9SAndroid Build Coastguard Worker// Copyright 2022 Google Inc. All Rights Reserved. 2*1fa6dee9SAndroid Build Coastguard Worker// 3*1fa6dee9SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*1fa6dee9SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*1fa6dee9SAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*1fa6dee9SAndroid Build Coastguard Worker// 7*1fa6dee9SAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*1fa6dee9SAndroid Build Coastguard Worker// 9*1fa6dee9SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*1fa6dee9SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*1fa6dee9SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*1fa6dee9SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*1fa6dee9SAndroid Build Coastguard Worker// limitations under the License. 14*1fa6dee9SAndroid Build Coastguard Worker 15*1fa6dee9SAndroid Build Coastguard Workerpackage metrics 16*1fa6dee9SAndroid Build Coastguard Worker 17*1fa6dee9SAndroid Build Coastguard Workerimport ( 18*1fa6dee9SAndroid Build Coastguard Worker "fmt" 19*1fa6dee9SAndroid Build Coastguard Worker "strings" 20*1fa6dee9SAndroid Build Coastguard Worker "time" 21*1fa6dee9SAndroid Build Coastguard Worker) 22*1fa6dee9SAndroid Build Coastguard Worker 23*1fa6dee9SAndroid Build Coastguard Worker// EventHandler tracks nested events and their start/stop times in a single 24*1fa6dee9SAndroid Build Coastguard Worker// thread. 25*1fa6dee9SAndroid Build Coastguard Workertype EventHandler struct { 26*1fa6dee9SAndroid Build Coastguard Worker completedEvents []Event 27*1fa6dee9SAndroid Build Coastguard Worker 28*1fa6dee9SAndroid Build Coastguard Worker // These fields handle event scoping. When starting a new event, a new entry 29*1fa6dee9SAndroid Build Coastguard Worker // is pushed onto these fields. When ending an event, these fields are popped. 30*1fa6dee9SAndroid Build Coastguard Worker scopeIds []string 31*1fa6dee9SAndroid Build Coastguard Worker scopeStartTimes []time.Time 32*1fa6dee9SAndroid Build Coastguard Worker} 33*1fa6dee9SAndroid Build Coastguard Worker 34*1fa6dee9SAndroid Build Coastguard Worker// _now simply delegates to time.Now() function. _now is declared for unit testing purpose. 35*1fa6dee9SAndroid Build Coastguard Workervar _now = time.Now 36*1fa6dee9SAndroid Build Coastguard Worker 37*1fa6dee9SAndroid Build Coastguard Worker// Event holds the performance metrics data of a single build event. 38*1fa6dee9SAndroid Build Coastguard Workertype Event struct { 39*1fa6dee9SAndroid Build Coastguard Worker // A unique human-readable identifier / "name" for the build event. Event 40*1fa6dee9SAndroid Build Coastguard Worker // names use period-delimited scoping. For example, if an event alpha starts, 41*1fa6dee9SAndroid Build Coastguard Worker // then an event bravo starts, then an event charlie starts and ends, the 42*1fa6dee9SAndroid Build Coastguard Worker // unique identifier for charlie will be 'alpha.bravo.charlie'. 43*1fa6dee9SAndroid Build Coastguard Worker Id string 44*1fa6dee9SAndroid Build Coastguard Worker 45*1fa6dee9SAndroid Build Coastguard Worker Start time.Time 46*1fa6dee9SAndroid Build Coastguard Worker end time.Time 47*1fa6dee9SAndroid Build Coastguard Worker} 48*1fa6dee9SAndroid Build Coastguard Worker 49*1fa6dee9SAndroid Build Coastguard Worker// RuntimeNanoseconds returns the number of nanoseconds between the start 50*1fa6dee9SAndroid Build Coastguard Worker// and end times of the event. 51*1fa6dee9SAndroid Build Coastguard Workerfunc (e Event) RuntimeNanoseconds() uint64 { 52*1fa6dee9SAndroid Build Coastguard Worker return uint64(e.end.Sub(e.Start).Nanoseconds()) 53*1fa6dee9SAndroid Build Coastguard Worker} 54*1fa6dee9SAndroid Build Coastguard Worker 55*1fa6dee9SAndroid Build Coastguard Worker// Begin logs the start of an event. This must be followed by a corresponding 56*1fa6dee9SAndroid Build Coastguard Worker// call to End (though other events may begin and end before this event ends). 57*1fa6dee9SAndroid Build Coastguard Worker// Events within the same scope must have unique names. 58*1fa6dee9SAndroid Build Coastguard Workerfunc (h *EventHandler) Begin(name string) { 59*1fa6dee9SAndroid Build Coastguard Worker if strings.ContainsRune(name, '.') { 60*1fa6dee9SAndroid Build Coastguard Worker panic(fmt.Sprintf("illegal event name (avoid dot): %s", name)) 61*1fa6dee9SAndroid Build Coastguard Worker } 62*1fa6dee9SAndroid Build Coastguard Worker h.scopeIds = append(h.scopeIds, name) 63*1fa6dee9SAndroid Build Coastguard Worker h.scopeStartTimes = append(h.scopeStartTimes, _now()) 64*1fa6dee9SAndroid Build Coastguard Worker} 65*1fa6dee9SAndroid Build Coastguard Worker 66*1fa6dee9SAndroid Build Coastguard Worker// Do wraps a function with calls to Begin() and End(). 67*1fa6dee9SAndroid Build Coastguard Workerfunc (h *EventHandler) Do(name string, f func()) { 68*1fa6dee9SAndroid Build Coastguard Worker h.Begin(name) 69*1fa6dee9SAndroid Build Coastguard Worker defer h.End(name) 70*1fa6dee9SAndroid Build Coastguard Worker f() 71*1fa6dee9SAndroid Build Coastguard Worker} 72*1fa6dee9SAndroid Build Coastguard Worker 73*1fa6dee9SAndroid Build Coastguard Worker// End logs the end of an event. All events nested within this event must have 74*1fa6dee9SAndroid Build Coastguard Worker// themselves been marked completed. 75*1fa6dee9SAndroid Build Coastguard Workerfunc (h *EventHandler) End(name string) { 76*1fa6dee9SAndroid Build Coastguard Worker if len(h.scopeIds) == 0 || name != h.scopeIds[len(h.scopeIds)-1] { 77*1fa6dee9SAndroid Build Coastguard Worker panic(fmt.Errorf("unexpected scope end '%s'. Current scope: (%s)", 78*1fa6dee9SAndroid Build Coastguard Worker name, h.scopeIds)) 79*1fa6dee9SAndroid Build Coastguard Worker } 80*1fa6dee9SAndroid Build Coastguard Worker event := Event{ 81*1fa6dee9SAndroid Build Coastguard Worker // The event Id is formed from the period-delimited scope names of all 82*1fa6dee9SAndroid Build Coastguard Worker // active events (e.g. `alpha.beta.charlie`). See Event.Id documentation 83*1fa6dee9SAndroid Build Coastguard Worker // for more detail. 84*1fa6dee9SAndroid Build Coastguard Worker Id: strings.Join(h.scopeIds, "."), 85*1fa6dee9SAndroid Build Coastguard Worker Start: h.scopeStartTimes[len(h.scopeStartTimes)-1], 86*1fa6dee9SAndroid Build Coastguard Worker end: _now(), 87*1fa6dee9SAndroid Build Coastguard Worker } 88*1fa6dee9SAndroid Build Coastguard Worker h.completedEvents = append(h.completedEvents, event) 89*1fa6dee9SAndroid Build Coastguard Worker h.scopeIds = h.scopeIds[:len(h.scopeIds)-1] 90*1fa6dee9SAndroid Build Coastguard Worker h.scopeStartTimes = h.scopeStartTimes[:len(h.scopeStartTimes)-1] 91*1fa6dee9SAndroid Build Coastguard Worker} 92*1fa6dee9SAndroid Build Coastguard Worker 93*1fa6dee9SAndroid Build Coastguard Worker// CompletedEvents returns all events which have been completed, after 94*1fa6dee9SAndroid Build Coastguard Worker// validation. 95*1fa6dee9SAndroid Build Coastguard Worker// It is an error to call this method if there are still ongoing events, or 96*1fa6dee9SAndroid Build Coastguard Worker// if two events were completed with the same scope and name. 97*1fa6dee9SAndroid Build Coastguard Workerfunc (h *EventHandler) CompletedEvents() []Event { 98*1fa6dee9SAndroid Build Coastguard Worker if len(h.scopeIds) > 0 { 99*1fa6dee9SAndroid Build Coastguard Worker panic(fmt.Errorf( 100*1fa6dee9SAndroid Build Coastguard Worker "retrieving events before all events have been closed. Current scope: (%s)", 101*1fa6dee9SAndroid Build Coastguard Worker h.scopeIds)) 102*1fa6dee9SAndroid Build Coastguard Worker } 103*1fa6dee9SAndroid Build Coastguard Worker // Validate no two events have the same full id. 104*1fa6dee9SAndroid Build Coastguard Worker ids := map[string]struct{}{} 105*1fa6dee9SAndroid Build Coastguard Worker for _, event := range h.completedEvents { 106*1fa6dee9SAndroid Build Coastguard Worker if _, containsId := ids[event.Id]; containsId { 107*1fa6dee9SAndroid Build Coastguard Worker panic(fmt.Errorf("duplicate event registered: %s", event.Id)) 108*1fa6dee9SAndroid Build Coastguard Worker } 109*1fa6dee9SAndroid Build Coastguard Worker ids[event.Id] = struct{}{} 110*1fa6dee9SAndroid Build Coastguard Worker } 111*1fa6dee9SAndroid Build Coastguard Worker return h.completedEvents 112*1fa6dee9SAndroid Build Coastguard Worker} 113