1#!/usr/bin/env python3
2 #
3 # Copyright (C) 2016 The Android Open Source Project
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #      http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17import argparse
18import json
19import os
20
21# This script parses a profile .html report that was generated by Gradle and generates a machine-readable summary
22
23def main():
24  parser = argparse.ArgumentParser(
25     description = "Parses a performance profile file generated by Gradle"
26  )
27  parser.add_argument("--input-profile", required=True, dest="input_profile")
28  parser.add_argument("--output-summary", required=True, dest="output_summary")
29  args = parser.parse_args()
30  summarize(args.input_profile, args.output_summary)
31
32def summarize(inputProfilePath, outputSummaryPath):
33  # mapping from the key in the Gradle report to the key in the summary that we generate
34  mapping = {"Total Build Time": "task_execution_duration", "Task Execution": "total_cpu", "Configuring Projects": "configuration_duration"}
35  parsedValues = parse(inputProfilePath, mapping.keys())
36  outputValues = dict()
37  for k in mapping:
38    if k in parsedValues:
39      outputValues[mapping[k]] = parsedValues[k]
40    else:
41      raise Exception("Did not find key " + k + " in " + inputProfilePath + "; only found " + str(parsedValues))
42  os.makedirs(os.path.dirname(outputSummaryPath), exist_ok=True)
43  with open(outputSummaryPath, 'w') as outputFile:
44    json.dump(outputValues, outputFile, sort_keys=True)
45  print("Generated " + outputSummaryPath)
46
47# Parses inputProfilePath into keys and values, and returns a dict whose keys match interestingKeys
48def parse(inputProfilePath, interestingKeys):
49  with open(inputProfilePath) as inputProfile:
50    values = dict()
51    currentKey = None
52    for line in inputProfile.readlines():
53      line = line.strip()
54      lineText = line.replace("<td>", "").replace('<td class="numeric">', "").replace("</td>", "")
55      if currentKey is not None:
56        values[currentKey] = parseDurationSeconds(lineText) * 1000
57      if lineText in interestingKeys:
58        currentKey = lineText
59      else:
60        currentKey = None
61    return values
62
63# Given a duration such as 1h20m02.5s, returns a number of seconds like ((1*60)+20)*60+2=4802.5
64def parseDurationSeconds(durationText):
65  originalDurationText = durationText
66  secondsText = "0"
67  minutesText = "0"
68  hoursText = "0"
69  daysText = "0"
70  if "d" in durationText:
71   daysText, durationText = durationText.split("d")
72  if "h" in durationText:
73    hoursText, durationText = durationText.split("h")
74  if "m" in durationText:
75    minutesText, durationText = durationText.split("m")
76  if "s" in durationText:
77    secondsText, durationText = durationText.split("s")
78  if durationText != "":
79    raise Exception("Failed to parse '" + durationText + "'")
80  try:
81    return (((float(daysText) * 24 + float(hoursText)) * 60) + float(minutesText)) * 60 + float(secondsText)
82  except ValueError as e:
83    raise ValueError("Failed to parse '" + durationText + "'")
84
85if __name__ == "__main__":
86  main()
87