Luca Farsi | 2b3b093 | 2024-11-06 14:41:15 -0800 | [diff] [blame] | 1 | # Copyright 2024, The Android Open Source Project |
| 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 | |
| 15 | """MetricsAgent is a singleton class that collects metrics for optimized build.""" |
| 16 | |
| 17 | from enum import Enum |
| 18 | import time |
| 19 | import metrics_pb2 |
| 20 | import os |
| 21 | import logging |
| 22 | |
| 23 | |
| 24 | class MetricsAgent: |
| 25 | _SOONG_METRICS_PATH = 'logs/soong_metrics' |
| 26 | _DIST_DIR = 'DIST_DIR' |
| 27 | _instance = None |
| 28 | |
| 29 | def __init__(self): |
| 30 | raise RuntimeError( |
| 31 | 'MetricsAgent cannot be instantialized, use instance() instead' |
| 32 | ) |
| 33 | |
| 34 | @classmethod |
| 35 | def instance(cls): |
| 36 | if not cls._instance: |
| 37 | cls._instance = cls.__new__(cls) |
| 38 | cls._instance._proto = metrics_pb2.OptimizedBuildMetrics() |
| 39 | cls._instance._init_proto() |
| 40 | cls._instance._target_results = dict() |
| 41 | |
| 42 | return cls._instance |
| 43 | |
| 44 | def _init_proto(self): |
| 45 | self._proto.analysis_perf.name = 'Optimized build analysis time.' |
| 46 | self._proto.packaging_perf.name = 'Optimized build total packaging time.' |
| 47 | |
| 48 | def analysis_start(self): |
| 49 | self._proto.analysis_perf.start_time = time.time_ns() |
| 50 | |
| 51 | def analysis_end(self): |
| 52 | self._proto.analysis_perf.real_time = ( |
| 53 | time.time_ns() - self._proto.analysis_perf.start_time |
| 54 | ) |
| 55 | |
| 56 | def packaging_start(self): |
| 57 | self._proto.packaging_perf.start_time = time.time_ns() |
| 58 | |
| 59 | def packaging_end(self): |
| 60 | self._proto.packaging_perf.real_time = ( |
| 61 | time.time_ns() - self._proto.packaging_perf.start_time |
| 62 | ) |
| 63 | |
| 64 | def report_optimized_target(self, name: str): |
| 65 | target_result = metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult() |
| 66 | target_result.name = name |
| 67 | target_result.optimized = True |
| 68 | self._target_results[name] = target_result |
| 69 | |
| 70 | def report_unoptimized_target(self, name: str, optimization_rationale: str): |
| 71 | target_result = metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult() |
| 72 | target_result.name = name |
| 73 | target_result.optimization_rationale = optimization_rationale |
| 74 | target_result.optimized = False |
| 75 | self._target_results[name] = target_result |
| 76 | |
| 77 | def target_packaging_start(self, name: str): |
| 78 | target_result = self._target_results.get(name) |
| 79 | target_result.packaging_perf.start_time = time.time_ns() |
| 80 | self._target_results[name] = target_result |
| 81 | |
| 82 | def target_packaging_end(self, name: str): |
| 83 | target_result = self._target_results.get(name) |
| 84 | target_result.packaging_perf.real_time = ( |
| 85 | time.time_ns() - target_result.packaging_perf.start_time |
| 86 | ) |
| 87 | |
| 88 | def add_target_artifact( |
| 89 | self, |
| 90 | target_name: str, |
| 91 | artifact_name: str, |
| 92 | size: int, |
| 93 | included_modules: set[str], |
| 94 | ): |
| 95 | target_result = self.target_results.get(target_name) |
| 96 | artifact = ( |
| 97 | metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult.OutputArtifact() |
| 98 | ) |
| 99 | artifact.name = artifact_name |
| 100 | artifact.size = size |
| 101 | for module in included_modules: |
| 102 | artifact.included_modules.add(module) |
| 103 | target_result.output_artifacts.add(artifact) |
| 104 | |
| 105 | def end_reporting(self): |
| 106 | for target_result in self._target_results.values(): |
| 107 | self._proto.target_result.append(target_result) |
| 108 | soong_metrics_proto = metrics_pb2.MetricsBase() |
| 109 | # Read in existing metrics that should have been written out by the soong |
| 110 | # build command so that we don't overwrite them. |
| 111 | with open(os.path.join(os.environ[self._DIST_DIR], self._SOONG_METRICS_PATH), 'rb') as f: |
| 112 | soong_metrics_proto.ParseFromString(f.read()) |
| 113 | soong_metrics_proto.optimized_build_metrics.CopyFrom(self._proto) |
| 114 | logging.info(soong_metrics_proto) |
| 115 | with open(os.path.join(os.environ[self._DIST_DIR], self._SOONG_METRICS_PATH), 'wb') as f: |
| 116 | f.write(soong_metrics_proto.SerializeToString()) |