1#!/usr/bin/env 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 18import argparse 19from datetime import datetime 20import yaml 21import os 22import report_pb2 23import sys 24import traceback 25 26# Usage: python3 progress_report.py --logcat logcat.txt --config config.yaml --output_dir report_dir 27# 28# logcat.txt should contain the "boot_progress_start" and "boot_progress_enable_screen"". 29# config.yaml contains all the keywords to be extracted. 30# report_dir will contain three generated files: 31# 32# timestamp_log.txt: contains the same content as logcat.txt, but the timestamp is replaced 33# with relative time with boot_progress_start time. 34# 35# report_proto.txt: contains the report for the events related to the keywords. 36# 37# report.txt: contains logcat messages corresponding to the events captured in report_proto.txt 38 39def init_arguments(): 40 parser = argparse.ArgumentParser( 41 prog = 'progrocess_report.py', 42 description='Extract timing information and generate a report.') 43 parser.add_argument( 44 '--logcat', type=str, required=True, 45 help = 'logcat file name') 46 parser.add_argument( 47 '--config', type=str, required=True, 48 help = 'configuration file for keywords') 49 parser.add_argument( 50 '--output_dir', type= str, required=True, 51 help = 'directory name to store the generated files') 52 return parser.parse_args() 53 54# Find boot_progress_start line and boot_progress_enable_screen find the time difference 55# return the start time string 56def find_boot_progress_start_end(fp): 57 start = "" 58 end = "" 59 for line in fp: 60 if "boot_progress_start" in line: 61 start = line 62 if "boot_progress_enable_screen" in line and len(start): 63 end = line 64 break 65 66 missing_error = "" 67 if start == "": 68 missing_error = "******logcat file missing boot_progress_start\n" 69 elif end == "": 70 missing_error += "******logcat file missing boot_progress_end " 71 if missing_error != "": 72 sys.exit("Missing required message in the logcat:\n" + missing_error) 73 return [start, end] 74 75# TODO(b/262259622): passing a tuple of (startDate, endDate) 76def replace_timestamp_abs(line, timestamp_str, date_time_obj0): 77 index = line.find(" ", 6) 78 if index <= 0: 79 return line 80 substr0 = line[:index] 81 substr1 = line[index:] 82 83 try: 84 date_time_obj = datetime.strptime(substr0, '%m-%d %H:%M:%S.%f') 85 except ValueError: 86 return line 87 88 date_time_delta = date_time_obj - date_time_obj0 89 date_time_delta_str = str(date_time_delta) 90 return date_time_delta_str + substr1 91 92def in_time_range(start, end, line): 93 try: 94 current_time = datetime.strptime(line[:18], '%m-%d %H:%M:%S.%f') 95 except ValueError: 96 return False 97 98 if current_time >= start and current_time <= end: 99 return True 100 101 return False 102 103# Here is an example of event we would like extract: 104# 09-15 16:04:15.655 root 991 991 I boot_progress_preload_start: 5440 105# for each event, it is a tuple of(timestamp, event_name, timing) 106def extract_event(line, keywords): 107 words = line.split(" ") 108 for keyword in keywords: 109 if keyword in words[-2]: 110 return (words[0], words[-2], words[-1]) 111 return () 112 113def write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp, 114 report_proto_fp): 115 start_timestamp_obj = datetime.strptime(timestamps[0][:18], '%m-%d %H:%M:%S.%f') 116 end_timestamp_obj = datetime.strptime(timestamps[1][:18], '%m-%d %H:%M:%S.%f') 117 report = report_pb2.Report() 118 for line in logcat_fp: 119 ts_fixed_line = replace_timestamp_abs(line, timestamps[0][:18], start_timestamp_obj) 120 timestamp_fixed_logcat_fp.write(ts_fixed_line) 121 if in_time_range(start_timestamp_obj, end_timestamp_obj, line): 122 event = extract_event(ts_fixed_line, keywords) 123 if len(event) == 0: 124 continue 125 126 report_fp.write(ts_fixed_line) 127 record = report.record.add() 128 record.timestamp = event[0] 129 record.event = event[1] 130 record.timing = int(event[2]) 131 report_proto_fp.write(str(report)) 132 133def main(): 134 args = init_arguments() 135 136 keywords = [] 137 with open(args.config, 'r') as file: 138 keywords = yaml.safe_load(file) 139 140 if not os.path.isdir(args.output_dir): 141 os.mkdir(args.output_dir) 142 timestamp_fixed_logcat_fp = open(os.path.join(args.output_dir, "timestamp_fixed_log.txt"), 'w') 143 report_fp = open(os.path.join(args.output_dir, "report.txt"), 'w') 144 report_proto_fp = open(os.path.join(args.output_dir, "report_proto.txt"), 'w') 145 try: 146 with open(args.logcat, 'r', errors = 'ignore') as logcat_fp: 147 timestamps = find_boot_progress_start_end(logcat_fp) 148 logcat_fp.seek(0) 149 write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp, report_proto_fp) 150 except Exception as e: 151 traceresult = traceback.format_exc() 152 print("Caught an exception: {}".format(traceback.format_exc())) 153 154 timestamp_fixed_logcat_fp.close() 155 report_fp.close() 156 report_proto_fp.close() 157 158if __name__ == '__main__': 159 main() 160