xref: /aosp_15_r20/build/blueprint/metrics/event_handler.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
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