1#!/usr/bin/python3 2 3# Copyright (C) 2023 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 17"""Performance stats proto parser utils.""" 18 19import datetime 20from typing import Optional, TypeVar 21from . import carwatchdog_dump_parser 22from . import deviceperformancestats_pb2 23from . import performancestats_pb2 24 25 26def _create_date_pb(date: datetime.datetime) -> performancestats_pb2.Date: 27 return performancestats_pb2.Date( 28 year=date.year, month=date.month, day=date.day 29 ) 30 31 32def _create_timeofday_pb(date) -> performancestats_pb2.TimeOfDay: 33 return performancestats_pb2.TimeOfDay( 34 hours=date.hour, minutes=date.minute, seconds=date.second 35 ) 36 37 38def _add_system_event_pb( 39 system_event_stats: carwatchdog_dump_parser.SystemEventStats, 40 system_event_pb: performancestats_pb2.SystemEventStats, 41) -> None: 42 """Adds the parser SystemEventStats object to the proto object.""" 43 for collection in system_event_stats.collections: 44 stats_collection_pb = performancestats_pb2.StatsCollection( 45 id=collection.id, 46 date=_create_date_pb(collection.date), 47 time=_create_timeofday_pb(collection.date), 48 total_cpu_time_ms=collection.total_cpu_time_ms, 49 total_cpu_cycles=collection.total_cpu_cycles, 50 idle_cpu_time_ms=collection.idle_cpu_time_ms, 51 io_wait_time_ms=collection.io_wait_time_ms, 52 context_switches=collection.context_switches, 53 io_blocked_processes=collection.io_blocked_processes, 54 major_page_faults=collection.major_page_faults, 55 ) 56 57 for package_cpu_stats in collection.package_cpu_stats: 58 package_cpu_stats_pb = performancestats_pb2.PackageCpuStats( 59 user_id=package_cpu_stats.user_id, 60 package_name=package_cpu_stats.package_name, 61 cpu_time_ms=package_cpu_stats.cpu_time_ms, 62 total_cpu_time_percent=package_cpu_stats.total_cpu_time_percent, 63 cpu_cycles=package_cpu_stats.cpu_cycles, 64 ) 65 66 for process_cpu_stats in package_cpu_stats.process_cpu_stats: 67 package_cpu_stats_pb.process_cpu_stats.append( 68 performancestats_pb2.ProcessCpuStats( 69 command=process_cpu_stats.command, 70 cpu_time_ms=process_cpu_stats.cpu_time_ms, 71 package_cpu_time_percent=( 72 process_cpu_stats.package_cpu_time_percent 73 ), 74 cpu_cycles=process_cpu_stats.cpu_cycles, 75 ) 76 ) 77 78 stats_collection_pb.package_cpu_stats.append(package_cpu_stats_pb) 79 system_event_pb.collections.append(stats_collection_pb) 80 81 for ( 82 package_storage_io_read_stats 83 ) in collection.package_storage_io_read_stats: 84 stats_collection_pb.package_storage_io_read_stats.append( 85 performancestats_pb2.PackageStorageIoStats( 86 user_id=package_storage_io_read_stats.user_id, 87 package_name=package_storage_io_read_stats.package_name, 88 fg_bytes=package_storage_io_read_stats.fg_bytes, 89 fg_bytes_percent=package_storage_io_read_stats.fg_bytes_percent, 90 fg_fsync=package_storage_io_read_stats.fg_fsync, 91 fg_fsync_percent=package_storage_io_read_stats.fg_fsync_percent, 92 bg_bytes=package_storage_io_read_stats.bg_bytes, 93 bg_bytes_percent=package_storage_io_read_stats.bg_bytes_percent, 94 bg_fsync=package_storage_io_read_stats.bg_fsync, 95 bg_fsync_percent=package_storage_io_read_stats.bg_fsync_percent, 96 ) 97 ) 98 99 for ( 100 package_storage_io_write_stats 101 ) in collection.package_storage_io_write_stats: 102 stats_collection_pb.package_storage_io_read_stats.append( 103 performancestats_pb2.PackageStorageIoStats( 104 user_id=package_storage_io_write_stats.user_id, 105 package_name=package_storage_io_write_stats.package_name, 106 fg_bytes=package_storage_io_write_stats.fg_bytes, 107 fg_bytes_percent=package_storage_io_write_stats.fg_bytes_percent, 108 fg_fsync=package_storage_io_write_stats.fg_fsync, 109 fg_fsync_percent=package_storage_io_write_stats.fg_fsync_percent, 110 bg_bytes=package_storage_io_write_stats.bg_bytes, 111 bg_bytes_percent=package_storage_io_write_stats.bg_bytes_percent, 112 bg_fsync=package_storage_io_write_stats.bg_fsync, 113 bg_fsync_percent=package_storage_io_write_stats.bg_fsync_percent, 114 ) 115 ) 116 117 118def _get_system_event( 119 system_event_pb: performancestats_pb2.SystemEventStats, 120) -> Optional[carwatchdog_dump_parser.SystemEventStats]: 121 """Generates carwatchdog_dump_parser.SystemEventStats from the given proto.""" 122 if not system_event_pb.collections: 123 return None 124 125 system_event_stats = carwatchdog_dump_parser.SystemEventStats() 126 for stats_collection_pb in system_event_pb.collections: 127 stats_collection = carwatchdog_dump_parser.StatsCollection() 128 stats_collection.id = stats_collection_pb.id 129 date_pb = stats_collection_pb.date 130 time_pb = stats_collection_pb.time 131 stats_collection.date = datetime.datetime( 132 date_pb.year, 133 date_pb.month, 134 date_pb.day, 135 time_pb.hours, 136 time_pb.minutes, 137 time_pb.seconds, 138 ) 139 stats_collection.total_cpu_time_ms = stats_collection_pb.total_cpu_time_ms 140 stats_collection.total_cpu_cycles = stats_collection_pb.total_cpu_cycles 141 stats_collection.idle_cpu_time_ms = stats_collection_pb.idle_cpu_time_ms 142 stats_collection.io_wait_time_ms = stats_collection_pb.io_wait_time_ms 143 stats_collection.context_switches = stats_collection_pb.context_switches 144 stats_collection.io_blocked_processes = ( 145 stats_collection_pb.io_blocked_processes 146 ) 147 stats_collection.major_page_faults = stats_collection_pb.major_page_faults 148 149 for package_cpu_stats_pb in stats_collection_pb.package_cpu_stats: 150 package_cpu_stats = carwatchdog_dump_parser.PackageCpuStats.from_proto( 151 package_cpu_stats_pb 152 ) 153 for process_cpu_stats_pb in package_cpu_stats_pb.process_cpu_stats: 154 package_cpu_stats.process_cpu_stats.append( 155 carwatchdog_dump_parser.ProcessCpuStats.from_proto( 156 process_cpu_stats_pb 157 ) 158 ) 159 stats_collection.package_cpu_stats.append(package_cpu_stats) 160 161 for ( 162 package_storage_io_read_stats_pb 163 ) in stats_collection_pb.package_storage_io_read_stats: 164 stats_collection.package_storage_io_read_stats.append( 165 carwatchdog_dump_parser.PackageStorageIoStats.from_proto( 166 package_storage_io_read_stats_pb 167 ) 168 ) 169 170 for ( 171 package_storage_io_write_stats_pb 172 ) in stats_collection_pb.package_storage_io_write_stats: 173 stats_collection.package_storage_io_write_stats.append( 174 carwatchdog_dump_parser.PackageStorageIoStats.from_proto( 175 package_storage_io_write_stats_pb 176 ) 177 ) 178 179 system_event_stats.add(stats_collection) 180 181 return system_event_stats 182 183 184def _get_perf_stats( 185 perf_stats_pb: performancestats_pb2.PerformanceStats, 186) -> carwatchdog_dump_parser.PerformanceStats: 187 """Generates carwatchdog_dump_parser.PerformanceStats from the given proto.""" 188 perf_stats = carwatchdog_dump_parser.PerformanceStats() 189 perf_stats.boot_time_stats = _get_system_event(perf_stats_pb.boot_time_stats) 190 perf_stats.last_n_minutes_stats = _get_system_event( 191 perf_stats_pb.last_n_minutes_stats 192 ) 193 perf_stats.custom_collection_stats = _get_system_event( 194 perf_stats_pb.custom_collection_stats 195 ) 196 return perf_stats 197 198 199def _get_build_info( 200 build_info_pb: deviceperformancestats_pb2.BuildInformation, 201) -> carwatchdog_dump_parser.BuildInformation: 202 """Generates carwatchdog_dump_parser.BuildInformation from the given proto.""" 203 build_info = carwatchdog_dump_parser.BuildInformation() 204 build_info.fingerprint = build_info_pb.fingerprint 205 build_info.brand = build_info_pb.brand 206 build_info.product = build_info_pb.product 207 build_info.device = build_info_pb.device 208 build_info.version_release = build_info_pb.version_release 209 build_info.id = build_info_pb.id 210 build_info.version_incremental = build_info_pb.version_incremental 211 build_info.type = build_info_pb.type 212 build_info.tags = build_info_pb.tags 213 build_info.sdk = build_info_pb.sdk 214 build_info.platform_minor = build_info_pb.platform_minor 215 build_info.codename = build_info_pb.codename 216 return build_info 217 218 219def write_pb( 220 perf_stats: carwatchdog_dump_parser.PerformanceStats, 221 out_file: str, 222 build_info: Optional[carwatchdog_dump_parser.BuildInformation] = None, 223 out_build_file: Optional[str] = None, 224) -> bool: 225 """Generates proto from parser objects and writes text proto to out files.""" 226 if perf_stats.is_empty(): 227 print("Cannot write proto since performance stats are empty") 228 return False 229 230 perf_stats_pb = performancestats_pb2.PerformanceStats() 231 232 # Boot time proto 233 if (stats := perf_stats.get_boot_time_stats()) is not None: 234 boot_time_stats_pb = performancestats_pb2.SystemEventStats() 235 _add_system_event_pb(stats, boot_time_stats_pb) 236 perf_stats_pb.boot_time_stats.CopyFrom(boot_time_stats_pb) 237 238 if (stats := perf_stats.get_last_n_minutes_stats()) is not None: 239 last_n_minutes_stats_pb = performancestats_pb2.SystemEventStats() 240 _add_system_event_pb(stats, last_n_minutes_stats_pb) 241 perf_stats_pb.last_n_minutes_stats.CopyFrom(last_n_minutes_stats_pb) 242 243 # TODO(b/256654082): Add user switch events to proto 244 245 # Custom collection proto 246 if (stats := perf_stats.get_custom_collection_stats()) is not None: 247 custom_collection_stats_pb = performancestats_pb2.SystemEventStats() 248 _add_system_event_pb(stats, custom_collection_stats_pb) 249 perf_stats_pb.custom_collection_stats.CopyFrom(custom_collection_stats_pb) 250 251 # Write pb binary to disk 252 if out_file: 253 with open(out_file, "wb") as f: 254 f.write(perf_stats_pb.SerializeToString()) 255 256 if build_info is not None: 257 build_info_pb = deviceperformancestats_pb2.BuildInformation( 258 fingerprint=build_info.fingerprint, 259 brand=build_info.brand, 260 product=build_info.product, 261 device=build_info.device, 262 version_release=build_info.version_release, 263 id=build_info.id, 264 version_incremental=build_info.version_incremental, 265 type=build_info.type, 266 tags=build_info.tags, 267 sdk=build_info.sdk, 268 platform_minor=build_info.platform_minor, 269 codename=build_info.codename, 270 ) 271 272 device_run_perf_stats_pb = ( 273 deviceperformancestats_pb2.DevicePerformanceStats() 274 ) 275 device_run_perf_stats_pb.build_info.CopyFrom(build_info_pb) 276 device_run_perf_stats_pb.perf_stats.add().CopyFrom(perf_stats_pb) 277 278 with open(out_build_file, "wb") as f: 279 f.write(device_run_perf_stats_pb.SerializeToString()) 280 281 return True 282 283 284T = TypeVar("T") 285 286 287def _read_proto_from_file(pb_file: str, proto: T) -> Optional[T]: 288 """Reads the text proto from the given file and returns the proto object.""" 289 pb_type = ( 290 "DevicePerformanceStats" 291 if isinstance(proto, deviceperformancestats_pb2.DevicePerformanceStats) 292 else "PerformanceStats" 293 ) 294 with open(pb_file, "rb") as f: 295 try: 296 proto.ParseFromString(f.read()) 297 proto.DiscardUnknownFields() 298 except UnicodeDecodeError: 299 print(f"Error: Proto in {pb_file} probably is not '{pb_type}'") 300 return None 301 302 if not proto: 303 print(f"Error: Proto stored in {pb_file} has incorrect format.") 304 return None 305 306 return proto 307 308 309def read_performance_stats_pb( 310 pb_file: str, 311) -> Optional[carwatchdog_dump_parser.PerformanceStats]: 312 """Reads text proto from file and returns a PerformanceStats object.""" 313 performance_stats_pb = performancestats_pb2.PerformanceStats() 314 performance_stats_pb = _read_proto_from_file(pb_file, performance_stats_pb) 315 if performance_stats_pb is None: 316 return None 317 return _get_perf_stats(performance_stats_pb) 318 319 320def read_device_performance_stats_pb( 321 pb_file: str, 322) -> Optional[carwatchdog_dump_parser.DevicePerformanceStats]: 323 """Reads text proto from file and returns a DevicePerformanceStats object.""" 324 325 device_performance_stats_pb = ( 326 deviceperformancestats_pb2.DevicePerformanceStats() 327 ) 328 device_performance_stats_pb = _read_proto_from_file( 329 pb_file, device_performance_stats_pb 330 ) 331 if device_performance_stats_pb is None: 332 return None 333 334 device_run_perf_stats = carwatchdog_dump_parser.DevicePerformanceStats() 335 device_run_perf_stats.build_info = _get_build_info( 336 device_performance_stats_pb.build_info 337 ) 338 339 for perf_stat in device_performance_stats_pb.perf_stats: 340 device_run_perf_stats.perf_stats.append(_get_perf_stats(perf_stat)) 341 342 return device_run_perf_stats 343