platform_build_soong/ui/metrics/event.go
Liz Kammer f2a80c6396 Add non-zero exit + error message to soong metrics
Improves some error messages that would have given little/no information

Bug: 254650145
Test: relevant unit tests
Test: Induce errors and check metrics proto
Change-Id: Ife6116af74af6e62c2f8ae8774e53c28178fb8d0
2022-10-24 10:51:56 -04:00

159 lines
5.1 KiB
Go

// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package metrics
// This file contains the functionality to represent a build event in respect
// to the metric system. A build event corresponds to a block of scoped code
// that contains a "Begin()" and immediately followed by "defer End()" trace.
// When defined, the duration of the scoped code is measure along with other
// performance measurements such as memory.
//
// As explained in the metrics package, the metrics system is a stacked based
// system since the collected metrics is considered to be topline metrics.
// The steps of the build system in the UI layer is sequential. Hence, the
// functionality defined below follows the stack data structure operations.
import (
"os"
"syscall"
"time"
soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
"google.golang.org/protobuf/proto"
)
// _now wraps the time.Now() function. _now is declared for unit testing purpose.
var _now = func() time.Time {
return time.Now()
}
// event holds the performance metrics data of a single build event.
type event struct {
// The event name (mostly used for grouping a set of events)
name string
// The description of the event (used to uniquely identify an event
// for metrics analysis).
desc string
nonZeroExitCode bool
errorMsg *string
// The time that the event started to occur.
start time.Time
// The list of process resource information that was executed.
procResInfo []*soong_metrics_proto.ProcessResourceInfo
}
// newEvent returns an event with start populated with the now time.
func newEvent(name, desc string) *event {
return &event{
name: name,
desc: desc,
start: _now(),
}
}
func (e event) perfInfo() soong_metrics_proto.PerfInfo {
realTime := uint64(_now().Sub(e.start).Nanoseconds())
perfInfo := soong_metrics_proto.PerfInfo{
Description: proto.String(e.desc),
Name: proto.String(e.name),
StartTime: proto.Uint64(uint64(e.start.UnixNano())),
RealTime: proto.Uint64(realTime),
ProcessesResourceInfo: e.procResInfo,
NonZeroExit: proto.Bool(e.nonZeroExitCode),
}
if m := e.errorMsg; m != nil {
perfInfo.ErrorMessage = proto.String(*m)
}
return perfInfo
}
// EventTracer is an array of events that provides functionality to trace a
// block of code on time and performance. The End call expects the Begin is
// invoked, otherwise panic is raised.
type EventTracer []*event
// empty returns true if there are no pending events.
func (t *EventTracer) empty() bool {
return len(*t) == 0
}
// lastIndex returns the index of the last element of events.
func (t *EventTracer) lastIndex() int {
return len(*t) - 1
}
// peek returns the active build event.
func (t *EventTracer) peek() *event {
if t.empty() {
return nil
}
return (*t)[t.lastIndex()]
}
// push adds the active build event in the stack.
func (t *EventTracer) push(e *event) {
*t = append(*t, e)
}
// pop removes the active event from the stack since the event has completed.
// A panic is raised if there are no pending events.
func (t *EventTracer) pop() *event {
if t.empty() {
panic("Internal error: No pending events")
}
e := (*t)[t.lastIndex()]
*t = (*t)[:t.lastIndex()]
return e
}
// AddProcResInfo adds information on an executed process such as max resident
// set memory and the number of voluntary context switches.
func (t *EventTracer) AddProcResInfo(name string, state *os.ProcessState) {
if t.empty() {
return
}
rusage := state.SysUsage().(*syscall.Rusage)
e := t.peek()
e.procResInfo = append(e.procResInfo, &soong_metrics_proto.ProcessResourceInfo{
Name: proto.String(name),
UserTimeMicros: proto.Uint64(uint64(rusage.Utime.Usec)),
SystemTimeMicros: proto.Uint64(uint64(rusage.Stime.Usec)),
MinorPageFaults: proto.Uint64(uint64(rusage.Minflt)),
MajorPageFaults: proto.Uint64(uint64(rusage.Majflt)),
// ru_inblock and ru_oublock are measured in blocks of 512 bytes.
IoInputKb: proto.Uint64(uint64(rusage.Inblock / 2)),
IoOutputKb: proto.Uint64(uint64(rusage.Oublock / 2)),
VoluntaryContextSwitches: proto.Uint64(uint64(rusage.Nvcsw)),
InvoluntaryContextSwitches: proto.Uint64(uint64(rusage.Nivcsw)),
})
}
// Begin starts tracing the event.
func (t *EventTracer) Begin(name, desc string) {
t.push(newEvent(name, desc))
}
// End performs post calculations such as duration of the event, aggregates
// the collected performance information into PerfInfo protobuf message.
func (t *EventTracer) End() soong_metrics_proto.PerfInfo {
return t.pop().perfInfo()
}