blob: 3f9fbb1e72199a785776b59981b5879e86051d13 [file] [log] [blame]
MarkDacek733b77c2023-05-09 18:21:36 +00001// Copyright 2023 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 build
16
17// This file contains functionality to parse bazel profile data into
18// a bazel_metrics proto, defined in build/soong/ui/metrics/bazel_metrics_proto
19// These metrics are later uploaded in upload.go
20
21import (
22 "bufio"
23 "os"
24 "strconv"
25 "strings"
26
27 "android/soong/shared"
28 "google.golang.org/protobuf/proto"
29
30 bazel_metrics_proto "android/soong/ui/metrics/bazel_metrics_proto"
31)
32
33func parseTimingToNanos(str string) int64 {
34 millisString := removeDecimalPoint(str)
35 timingMillis, _ := strconv.ParseInt(millisString, 10, 64)
36 return timingMillis * 1000000
37}
38
39func parsePercentageToTenThousandths(str string) int32 {
40 percentageString := removeDecimalPoint(str)
41 //remove the % at the end of the string
42 percentage := strings.ReplaceAll(percentageString, "%", "")
43 percentagePortion, _ := strconv.ParseInt(percentage, 10, 32)
44 return int32(percentagePortion)
45}
46
47func removeDecimalPoint(numString string) string {
48 // The format is always 0.425 or 10.425
49 return strings.ReplaceAll(numString, ".", "")
50}
51
52func parseTotal(line string) int64 {
53 words := strings.Fields(line)
54 timing := words[3]
55 return parseTimingToNanos(timing)
56}
57
58func parsePhaseTiming(line string) bazel_metrics_proto.PhaseTiming {
59 words := strings.Fields(line)
60 getPhaseNameAndTimingAndPercentage := func([]string) (string, int64, int32) {
61 // Sample lines include:
62 // Total launch phase time 0.011 s 2.59%
63 // Total target pattern evaluation phase time 0.011 s 2.59%
64 var beginning int
65 var end int
66 for ind, word := range words {
67 if word == "Total" {
68 beginning = ind + 1
69 } else if beginning > 0 && word == "phase" {
70 end = ind
71 break
72 }
73 }
74 phaseName := strings.Join(words[beginning:end], " ")
75
76 // end is now "phase" - advance by 2 for timing and 4 for percentage
77 percentageString := words[end+4]
78 timingString := words[end+2]
79 timing := parseTimingToNanos(timingString)
80 percentagePortion := parsePercentageToTenThousandths(percentageString)
81 return phaseName, timing, percentagePortion
82 }
83
84 phaseName, timing, portion := getPhaseNameAndTimingAndPercentage(words)
85 phaseTiming := bazel_metrics_proto.PhaseTiming{}
86 phaseTiming.DurationNanos = &timing
87 phaseTiming.PortionOfBuildTime = &portion
88
89 phaseTiming.PhaseName = &phaseName
90 return phaseTiming
91}
92
93// This method takes a file created by bazel's --analyze-profile mode and
94// writes bazel metrics data to the provided filepath.
95func ProcessBazelMetrics(bazelProfileFile string, bazelMetricsFile string, ctx Context, config Config) {
96 if bazelProfileFile == "" {
97 return
98 }
99
100 readBazelProto := func(filepath string) bazel_metrics_proto.BazelMetrics {
101 //serialize the proto, write it
102 bazelMetrics := bazel_metrics_proto.BazelMetrics{}
103
104 file, err := os.ReadFile(filepath)
105 if err != nil {
106 ctx.Fatalln("Error reading metrics file\n", err)
107 }
108
109 scanner := bufio.NewScanner(strings.NewReader(string(file)))
110 scanner.Split(bufio.ScanLines)
111
112 var phaseTimings []*bazel_metrics_proto.PhaseTiming
113 for scanner.Scan() {
114 line := scanner.Text()
115 if strings.HasPrefix(line, "Total run time") {
116 total := parseTotal(line)
117 bazelMetrics.Total = &total
118 } else if strings.HasPrefix(line, "Total") {
119 phaseTiming := parsePhaseTiming(line)
120 phaseTimings = append(phaseTimings, &phaseTiming)
121 }
122 }
123 bazelMetrics.PhaseTimings = phaseTimings
MarkDacek396491e2023-06-14 19:41:18 +0000124 bazelMetrics.BesId = proto.String(config.besId)
MarkDacek733b77c2023-05-09 18:21:36 +0000125
126 return bazelMetrics
127 }
128
129 if _, err := os.Stat(bazelProfileFile); err != nil {
130 // We can assume bazel didn't run if the profile doesn't exist
131 return
132 }
133 bazelProto := readBazelProto(bazelProfileFile)
134 bazelProto.ExitCode = proto.Int32(config.bazelExitCode)
135 shared.Save(&bazelProto, bazelMetricsFile)
136}