blob: 35712727a18a1ace9a046213ee43faae204d34f5 [file] [log] [blame]
Colin Crossb72c9092020-02-10 11:23:49 -08001// Copyright 2020 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
Colin Cross46b0c752023-10-27 14:56:12 -070018 "bytes"
Colin Crossb72c9092020-02-10 11:23:49 -080019 "io/ioutil"
Colin Cross46b0c752023-10-27 14:56:12 -070020 "os"
Colin Crossb72c9092020-02-10 11:23:49 -080021 "runtime"
MarkDacekff851b82022-04-21 18:33:17 +000022 "sort"
Colin Cross46b0c752023-10-27 14:56:12 -070023 "strconv"
24 "time"
Colin Crossb72c9092020-02-10 11:23:49 -080025
Chris Parsons715b08f2022-03-22 19:23:40 -040026 "github.com/google/blueprint/metrics"
Dan Willemsen4591b642021-05-24 14:24:12 -070027 "google.golang.org/protobuf/proto"
Colin Crossb72c9092020-02-10 11:23:49 -080028
29 soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
30)
31
32var soongMetricsOnceKey = NewOnceKey("soong metrics")
33
Colin Cross46b0c752023-10-27 14:56:12 -070034type soongMetrics struct {
35 modules int
36 variants int
37 perfCollector perfCollector
Colin Crossb72c9092020-02-10 11:23:49 -080038}
39
Colin Cross46b0c752023-10-27 14:56:12 -070040type perfCollector struct {
41 events []*soong_metrics_proto.PerfCounters
42 stop chan<- bool
43}
44
45func getSoongMetrics(config Config) *soongMetrics {
46 return config.Once(soongMetricsOnceKey, func() interface{} {
47 return &soongMetrics{}
48 }).(*soongMetrics)
Colin Crossb72c9092020-02-10 11:23:49 -080049}
50
51func init() {
LaMont Jones0c10e4d2023-05-16 00:58:37 +000052 RegisterParallelSingletonType("soong_metrics", soongMetricsSingletonFactory)
Colin Crossb72c9092020-02-10 11:23:49 -080053}
54
55func soongMetricsSingletonFactory() Singleton { return soongMetricsSingleton{} }
56
57type soongMetricsSingleton struct{}
58
59func (soongMetricsSingleton) GenerateBuildActions(ctx SingletonContext) {
Colin Cross46b0c752023-10-27 14:56:12 -070060 metrics := getSoongMetrics(ctx.Config())
Colin Crossb72c9092020-02-10 11:23:49 -080061 ctx.VisitAllModules(func(m Module) {
62 if ctx.PrimaryModule(m) == m {
Colin Cross46b0c752023-10-27 14:56:12 -070063 metrics.modules++
Colin Crossb72c9092020-02-10 11:23:49 -080064 }
Colin Cross46b0c752023-10-27 14:56:12 -070065 metrics.variants++
Colin Crossb72c9092020-02-10 11:23:49 -080066 })
67}
68
Paul Duffin780a1852022-11-05 10:17:12 +000069func collectMetrics(config Config, eventHandler *metrics.EventHandler) *soong_metrics_proto.SoongBuildMetrics {
Colin Crossb72c9092020-02-10 11:23:49 -080070 metrics := &soong_metrics_proto.SoongBuildMetrics{}
71
Colin Cross46b0c752023-10-27 14:56:12 -070072 soongMetrics := getSoongMetrics(config)
73 if soongMetrics.modules > 0 {
74 metrics.Modules = proto.Uint32(uint32(soongMetrics.modules))
75 metrics.Variants = proto.Uint32(uint32(soongMetrics.variants))
Joe Onorato2e5e4012022-06-07 17:16:08 -070076 }
Colin Crossb72c9092020-02-10 11:23:49 -080077
Colin Cross46b0c752023-10-27 14:56:12 -070078 soongMetrics.perfCollector.stop <- true
79 metrics.PerfCounters = soongMetrics.perfCollector.events
80
Colin Crossb72c9092020-02-10 11:23:49 -080081 memStats := runtime.MemStats{}
82 runtime.ReadMemStats(&memStats)
83 metrics.MaxHeapSize = proto.Uint64(memStats.HeapSys)
84 metrics.TotalAllocCount = proto.Uint64(memStats.Mallocs)
85 metrics.TotalAllocSize = proto.Uint64(memStats.TotalAlloc)
86
Chris Parsons715b08f2022-03-22 19:23:40 -040087 for _, event := range eventHandler.CompletedEvents() {
88 perfInfo := soong_metrics_proto.PerfInfo{
89 Description: proto.String(event.Id),
90 Name: proto.String("soong_build"),
91 StartTime: proto.Uint64(uint64(event.Start.UnixNano())),
92 RealTime: proto.Uint64(event.RuntimeNanoseconds()),
93 }
94 metrics.Events = append(metrics.Events, &perfInfo)
95 }
MarkDacekff851b82022-04-21 18:33:17 +000096 mixedBuildsInfo := soong_metrics_proto.MixedBuildsInfo{}
97 mixedBuildEnabledModules := make([]string, 0, len(config.mixedBuildEnabledModules))
98 for module, _ := range config.mixedBuildEnabledModules {
99 mixedBuildEnabledModules = append(mixedBuildEnabledModules, module)
100 }
101
102 mixedBuildDisabledModules := make([]string, 0, len(config.mixedBuildDisabledModules))
103 for module, _ := range config.mixedBuildDisabledModules {
104 mixedBuildDisabledModules = append(mixedBuildDisabledModules, module)
105 }
106 // Sorted for deterministic output.
107 sort.Strings(mixedBuildEnabledModules)
108 sort.Strings(mixedBuildDisabledModules)
109
110 mixedBuildsInfo.MixedBuildEnabledModules = mixedBuildEnabledModules
111 mixedBuildsInfo.MixedBuildDisabledModules = mixedBuildDisabledModules
112 metrics.MixedBuildsInfo = &mixedBuildsInfo
Chris Parsons715b08f2022-03-22 19:23:40 -0400113
Colin Crossb72c9092020-02-10 11:23:49 -0800114 return metrics
115}
116
Colin Cross46b0c752023-10-27 14:56:12 -0700117func StartBackgroundMetrics(config Config) {
118 perfCollector := &getSoongMetrics(config).perfCollector
119 stop := make(chan bool)
120 perfCollector.stop = stop
121
122 previousTime := time.Now()
123 previousCpuTime := readCpuTime()
124
125 ticker := time.NewTicker(time.Second)
126
127 go func() {
128 for {
129 select {
130 case <-stop:
131 ticker.Stop()
132 return
133 case <-ticker.C:
134 // carry on
135 }
136
137 currentTime := time.Now()
138
139 var memStats runtime.MemStats
140 runtime.ReadMemStats(&memStats)
141
142 currentCpuTime := readCpuTime()
143
144 interval := currentTime.Sub(previousTime)
145 intervalCpuTime := currentCpuTime - previousCpuTime
146 intervalCpuPercent := intervalCpuTime * 100 / interval
147
148 // heapAlloc is the memory that has been allocated on the heap but not yet GC'd. It may be referenced,
149 // or unrefenced but not yet GC'd.
150 heapAlloc := memStats.HeapAlloc
151 // heapUnused is the memory that was previously used by the heap, but is currently not used. It does not
152 // count memory that was used and then returned to the OS.
153 heapUnused := memStats.HeapIdle - memStats.HeapReleased
154 // heapOverhead is the memory used by the allocator and GC
155 heapOverhead := memStats.MSpanSys + memStats.MCacheSys + memStats.GCSys
156 // otherMem is the memory used outside of the heap.
157 otherMem := memStats.Sys - memStats.HeapSys - heapOverhead
158
159 perfCollector.events = append(perfCollector.events, &soong_metrics_proto.PerfCounters{
160 Time: proto.Uint64(uint64(currentTime.UnixNano())),
161 Groups: []*soong_metrics_proto.PerfCounterGroup{
162 {
163 Name: proto.String("cpu"),
164 Counters: []*soong_metrics_proto.PerfCounter{
165 {Name: proto.String("cpu_percent"), Value: proto.Int64(int64(intervalCpuPercent))},
166 },
167 }, {
168 Name: proto.String("memory"),
169 Counters: []*soong_metrics_proto.PerfCounter{
170 {Name: proto.String("heap_alloc"), Value: proto.Int64(int64(heapAlloc))},
171 {Name: proto.String("heap_unused"), Value: proto.Int64(int64(heapUnused))},
172 {Name: proto.String("heap_overhead"), Value: proto.Int64(int64(heapOverhead))},
173 {Name: proto.String("other"), Value: proto.Int64(int64(otherMem))},
174 },
175 },
176 },
177 })
178
179 previousTime = currentTime
180 previousCpuTime = currentCpuTime
181 }
182 }()
183}
184
185func readCpuTime() time.Duration {
186 if runtime.GOOS != "linux" {
187 return 0
188 }
189
190 stat, err := os.ReadFile("/proc/self/stat")
191 if err != nil {
192 return 0
193 }
194
195 endOfComm := bytes.LastIndexByte(stat, ')')
196 if endOfComm < 0 || endOfComm > len(stat)-2 {
197 return 0
198 }
199
200 stat = stat[endOfComm+2:]
201
202 statFields := bytes.Split(stat, []byte{' '})
203 // This should come from sysconf(_SC_CLK_TCK), but there's no way to call that from Go. Assume it's 100,
204 // which is the value for all platforms we support.
205 const HZ = 100
206 const MS_PER_HZ = 1e3 / HZ * time.Millisecond
207
208 const STAT_UTIME_FIELD = 14 - 2
209 const STAT_STIME_FIELD = 15 - 2
210 if len(statFields) < STAT_STIME_FIELD {
211 return 0
212 }
213 userCpuTicks, err := strconv.ParseUint(string(statFields[STAT_UTIME_FIELD]), 10, 64)
214 if err != nil {
215 return 0
216 }
217 kernelCpuTicks, _ := strconv.ParseUint(string(statFields[STAT_STIME_FIELD]), 10, 64)
218 if err != nil {
219 return 0
220 }
221 return time.Duration(userCpuTicks+kernelCpuTicks) * MS_PER_HZ
222}
223
Paul Duffin780a1852022-11-05 10:17:12 +0000224func WriteMetrics(config Config, eventHandler *metrics.EventHandler, metricsFile string) error {
Chris Parsons715b08f2022-03-22 19:23:40 -0400225 metrics := collectMetrics(config, eventHandler)
Colin Crossb72c9092020-02-10 11:23:49 -0800226
227 buf, err := proto.Marshal(metrics)
228 if err != nil {
229 return err
230 }
231 err = ioutil.WriteFile(absolutePath(metricsFile), buf, 0666)
232 if err != nil {
233 return err
234 }
235
236 return nil
237}