# Copyright 2024, The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """MetricsAgent is a singleton class that collects metrics for optimized build.""" from enum import Enum import time import metrics_pb2 import os import logging class MetricsAgent: _SOONG_METRICS_PATH = 'logs/soong_metrics' _DIST_DIR = 'DIST_DIR' _instance = None def __init__(self): raise RuntimeError( 'MetricsAgent cannot be instantialized, use instance() instead' ) @classmethod def instance(cls): if not cls._instance: cls._instance = cls.__new__(cls) cls._instance._proto = metrics_pb2.OptimizedBuildMetrics() cls._instance._init_proto() cls._instance._target_results = dict() return cls._instance def _init_proto(self): self._proto.analysis_perf.name = 'Optimized build analysis time.' self._proto.packaging_perf.name = 'Optimized build total packaging time.' def analysis_start(self): self._proto.analysis_perf.start_time = time.time_ns() def analysis_end(self): self._proto.analysis_perf.real_time = ( time.time_ns() - self._proto.analysis_perf.start_time ) def packaging_start(self): self._proto.packaging_perf.start_time = time.time_ns() def packaging_end(self): self._proto.packaging_perf.real_time = ( time.time_ns() - self._proto.packaging_perf.start_time ) def report_optimized_target(self, name: str): target_result = metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult() target_result.name = name target_result.optimized = True self._target_results[name] = target_result def report_unoptimized_target(self, name: str, optimization_rationale: str): target_result = metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult() target_result.name = name target_result.optimization_rationale = optimization_rationale target_result.optimized = False self._target_results[name] = target_result def target_packaging_start(self, name: str): target_result = self._target_results.get(name) target_result.packaging_perf.start_time = time.time_ns() self._target_results[name] = target_result def target_packaging_end(self, name: str): target_result = self._target_results.get(name) target_result.packaging_perf.real_time = ( time.time_ns() - target_result.packaging_perf.start_time ) def add_target_artifact( self, target_name: str, artifact_name: str, size: int, included_modules: set[str], ): target_result = self.target_results.get(target_name) artifact = ( metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult.OutputArtifact() ) artifact.name = artifact_name artifact.size = size for module in included_modules: artifact.included_modules.add(module) target_result.output_artifacts.add(artifact) def end_reporting(self): for target_result in self._target_results.values(): self._proto.target_result.append(target_result) soong_metrics_proto = metrics_pb2.MetricsBase() # Read in existing metrics that should have been written out by the soong # build command so that we don't overwrite them. with open(os.path.join(os.environ[self._DIST_DIR], self._SOONG_METRICS_PATH), 'rb') as f: soong_metrics_proto.ParseFromString(f.read()) soong_metrics_proto.optimized_build_metrics.CopyFrom(self._proto) logging.info(soong_metrics_proto) with open(os.path.join(os.environ[self._DIST_DIR], self._SOONG_METRICS_PATH), 'wb') as f: f.write(soong_metrics_proto.SerializeToString())