diff options
Diffstat (limited to 'android/metrics.go')
| -rw-r--r-- | android/metrics.go | 168 |
1 files changed, 132 insertions, 36 deletions
diff --git a/android/metrics.go b/android/metrics.go index 3d41a1d6c..6834b1bde 100644 --- a/android/metrics.go +++ b/android/metrics.go @@ -15,9 +15,12 @@ package android import ( + "bytes" "io/ioutil" + "os" "runtime" - "sort" + "strconv" + "time" "github.com/google/blueprint/metrics" "google.golang.org/protobuf/proto" @@ -27,22 +30,25 @@ import ( var soongMetricsOnceKey = NewOnceKey("soong metrics") -type SoongMetrics struct { - Modules int - Variants int +type soongMetrics struct { + modules int + variants int + perfCollector perfCollector } -func readSoongMetrics(config Config) (SoongMetrics, bool) { - soongMetrics, ok := config.Peek(soongMetricsOnceKey) - if ok { - return soongMetrics.(SoongMetrics), true - } else { - return SoongMetrics{}, false - } +type perfCollector struct { + events []*soong_metrics_proto.PerfCounters + stop chan<- bool +} + +func getSoongMetrics(config Config) *soongMetrics { + return config.Once(soongMetricsOnceKey, func() interface{} { + return &soongMetrics{} + }).(*soongMetrics) } func init() { - RegisterSingletonType("soong_metrics", soongMetricsSingletonFactory) + RegisterParallelSingletonType("soong_metrics", soongMetricsSingletonFactory) } func soongMetricsSingletonFactory() Singleton { return soongMetricsSingleton{} } @@ -50,27 +56,27 @@ func soongMetricsSingletonFactory() Singleton { return soongMetricsSingleton{} } type soongMetricsSingleton struct{} func (soongMetricsSingleton) GenerateBuildActions(ctx SingletonContext) { - metrics := SoongMetrics{} + metrics := getSoongMetrics(ctx.Config()) ctx.VisitAllModules(func(m Module) { if ctx.PrimaryModule(m) == m { - metrics.Modules++ + metrics.modules++ } - metrics.Variants++ - }) - ctx.Config().Once(soongMetricsOnceKey, func() interface{} { - return metrics + metrics.variants++ }) } func collectMetrics(config Config, eventHandler *metrics.EventHandler) *soong_metrics_proto.SoongBuildMetrics { metrics := &soong_metrics_proto.SoongBuildMetrics{} - soongMetrics, ok := readSoongMetrics(config) - if ok { - metrics.Modules = proto.Uint32(uint32(soongMetrics.Modules)) - metrics.Variants = proto.Uint32(uint32(soongMetrics.Variants)) + soongMetrics := getSoongMetrics(config) + if soongMetrics.modules > 0 { + metrics.Modules = proto.Uint32(uint32(soongMetrics.modules)) + metrics.Variants = proto.Uint32(uint32(soongMetrics.variants)) } + soongMetrics.perfCollector.stop <- true + metrics.PerfCounters = soongMetrics.perfCollector.events + memStats := runtime.MemStats{} runtime.ReadMemStats(&memStats) metrics.MaxHeapSize = proto.Uint64(memStats.HeapSys) @@ -86,25 +92,115 @@ func collectMetrics(config Config, eventHandler *metrics.EventHandler) *soong_me } metrics.Events = append(metrics.Events, &perfInfo) } - mixedBuildsInfo := soong_metrics_proto.MixedBuildsInfo{} - mixedBuildEnabledModules := make([]string, 0, len(config.mixedBuildEnabledModules)) - for module, _ := range config.mixedBuildEnabledModules { - mixedBuildEnabledModules = append(mixedBuildEnabledModules, module) + + return metrics +} + +func StartBackgroundMetrics(config Config) { + perfCollector := &getSoongMetrics(config).perfCollector + stop := make(chan bool) + perfCollector.stop = stop + + previousTime := time.Now() + previousCpuTime := readCpuTime() + + ticker := time.NewTicker(time.Second) + + go func() { + for { + select { + case <-stop: + ticker.Stop() + return + case <-ticker.C: + // carry on + } + + currentTime := time.Now() + + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + + currentCpuTime := readCpuTime() + + interval := currentTime.Sub(previousTime) + intervalCpuTime := currentCpuTime - previousCpuTime + intervalCpuPercent := intervalCpuTime * 100 / interval + + // heapAlloc is the memory that has been allocated on the heap but not yet GC'd. It may be referenced, + // or unrefenced but not yet GC'd. + heapAlloc := memStats.HeapAlloc + // heapUnused is the memory that was previously used by the heap, but is currently not used. It does not + // count memory that was used and then returned to the OS. + heapUnused := memStats.HeapIdle - memStats.HeapReleased + // heapOverhead is the memory used by the allocator and GC + heapOverhead := memStats.MSpanSys + memStats.MCacheSys + memStats.GCSys + // otherMem is the memory used outside of the heap. + otherMem := memStats.Sys - memStats.HeapSys - heapOverhead + + perfCollector.events = append(perfCollector.events, &soong_metrics_proto.PerfCounters{ + Time: proto.Uint64(uint64(currentTime.UnixNano())), + Groups: []*soong_metrics_proto.PerfCounterGroup{ + { + Name: proto.String("cpu"), + Counters: []*soong_metrics_proto.PerfCounter{ + {Name: proto.String("cpu_percent"), Value: proto.Int64(int64(intervalCpuPercent))}, + }, + }, { + Name: proto.String("memory"), + Counters: []*soong_metrics_proto.PerfCounter{ + {Name: proto.String("heap_alloc"), Value: proto.Int64(int64(heapAlloc))}, + {Name: proto.String("heap_unused"), Value: proto.Int64(int64(heapUnused))}, + {Name: proto.String("heap_overhead"), Value: proto.Int64(int64(heapOverhead))}, + {Name: proto.String("other"), Value: proto.Int64(int64(otherMem))}, + }, + }, + }, + }) + + previousTime = currentTime + previousCpuTime = currentCpuTime + } + }() +} + +func readCpuTime() time.Duration { + if runtime.GOOS != "linux" { + return 0 + } + + stat, err := os.ReadFile("/proc/self/stat") + if err != nil { + return 0 } - mixedBuildDisabledModules := make([]string, 0, len(config.mixedBuildDisabledModules)) - for module, _ := range config.mixedBuildDisabledModules { - mixedBuildDisabledModules = append(mixedBuildDisabledModules, module) + endOfComm := bytes.LastIndexByte(stat, ')') + if endOfComm < 0 || endOfComm > len(stat)-2 { + return 0 } - // Sorted for deterministic output. - sort.Strings(mixedBuildEnabledModules) - sort.Strings(mixedBuildDisabledModules) - mixedBuildsInfo.MixedBuildEnabledModules = mixedBuildEnabledModules - mixedBuildsInfo.MixedBuildDisabledModules = mixedBuildDisabledModules - metrics.MixedBuildsInfo = &mixedBuildsInfo + stat = stat[endOfComm+2:] - return metrics + statFields := bytes.Split(stat, []byte{' '}) + // This should come from sysconf(_SC_CLK_TCK), but there's no way to call that from Go. Assume it's 100, + // which is the value for all platforms we support. + const HZ = 100 + const MS_PER_HZ = 1e3 / HZ * time.Millisecond + + const STAT_UTIME_FIELD = 14 - 2 + const STAT_STIME_FIELD = 15 - 2 + if len(statFields) < STAT_STIME_FIELD { + return 0 + } + userCpuTicks, err := strconv.ParseUint(string(statFields[STAT_UTIME_FIELD]), 10, 64) + if err != nil { + return 0 + } + kernelCpuTicks, _ := strconv.ParseUint(string(statFields[STAT_STIME_FIELD]), 10, 64) + if err != nil { + return 0 + } + return time.Duration(userCpuTicks+kernelCpuTicks) * MS_PER_HZ } func WriteMetrics(config Config, eventHandler *metrics.EventHandler, metricsFile string) error { |