| #!/usr/bin/env python3 |
| # |
| # Copyright (C) 2023 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. |
| # |
| |
| import argparse |
| from datetime import datetime |
| import yaml |
| import os |
| import report_pb2 |
| import sys |
| import traceback |
| |
| # Usage: python3 progress_report.py --logcat logcat.txt --config config.yaml --output_dir report_dir |
| # |
| # logcat.txt should contain the "boot_progress_start" and "boot_progress_enable_screen"". |
| # config.yaml contains all the keywords to be extracted. |
| # report_dir will contain three generated files: |
| # |
| # timestamp_log.txt: contains the same content as logcat.txt, but the timestamp is replaced |
| # with relative time with boot_progress_start time. |
| # |
| # report_proto.txt: contains the report for the events related to the keywords. |
| # |
| # report.txt: contains logcat messages corresponding to the events captured in report_proto.txt |
| |
| def init_arguments(): |
| parser = argparse.ArgumentParser( |
| prog = 'progrocess_report.py', |
| description='Extract timing information and generate a report.') |
| parser.add_argument( |
| '--logcat', type=str, required=True, |
| help = 'logcat file name') |
| parser.add_argument( |
| '--config', type=str, required=True, |
| help = 'configuration file for keywords') |
| parser.add_argument( |
| '--output_dir', type= str, required=True, |
| help = 'directory name to store the generated files') |
| return parser.parse_args() |
| |
| # Find boot_progress_start line and boot_progress_enable_screen find the time difference |
| # return the start time string |
| def find_boot_progress_start_end(fp): |
| start = "" |
| end = "" |
| for line in fp: |
| if "boot_progress_start" in line: |
| start = line |
| if "boot_progress_enable_screen" in line and len(start): |
| end = line |
| break |
| |
| missing_error = "" |
| if start == "": |
| missing_error = "******logcat file missing boot_progress_start\n" |
| elif end == "": |
| missing_error += "******logcat file missing boot_progress_end " |
| if missing_error != "": |
| sys.exit("Missing required message in the logcat:\n" + missing_error) |
| return [start, end] |
| |
| # TODO(b/262259622): passing a tuple of (startDate, endDate) |
| def replace_timestamp_abs(line, timestamp_str, date_time_obj0): |
| index = line.find(" ", 6) |
| if index <= 0: |
| return line |
| substr0 = line[:index] |
| substr1 = line[index:] |
| |
| try: |
| date_time_obj = datetime.strptime(substr0, '%m-%d %H:%M:%S.%f') |
| except ValueError: |
| return line |
| |
| date_time_delta = date_time_obj - date_time_obj0 |
| date_time_delta_str = str(date_time_delta) |
| return date_time_delta_str + substr1 |
| |
| def in_time_range(start, end, line): |
| try: |
| current_time = datetime.strptime(line[:18], '%m-%d %H:%M:%S.%f') |
| except ValueError: |
| return False |
| |
| if current_time >= start and current_time <= end: |
| return True |
| |
| return False |
| |
| # Here is an example of event we would like extract: |
| # 09-15 16:04:15.655 root 991 991 I boot_progress_preload_start: 5440 |
| # for each event, it is a tuple of(timestamp, event_name, timing) |
| def extract_event(line, keywords): |
| words = line.split(" ") |
| for keyword in keywords: |
| if keyword in words[-2]: |
| return (words[0], words[-2], words[-1]) |
| return () |
| |
| def write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp, |
| report_proto_fp): |
| start_timestamp_obj = datetime.strptime(timestamps[0][:18], '%m-%d %H:%M:%S.%f') |
| end_timestamp_obj = datetime.strptime(timestamps[1][:18], '%m-%d %H:%M:%S.%f') |
| report = report_pb2.Report() |
| for line in logcat_fp: |
| ts_fixed_line = replace_timestamp_abs(line, timestamps[0][:18], start_timestamp_obj) |
| timestamp_fixed_logcat_fp.write(ts_fixed_line) |
| if in_time_range(start_timestamp_obj, end_timestamp_obj, line): |
| event = extract_event(ts_fixed_line, keywords) |
| if len(event) == 0: |
| continue |
| |
| report_fp.write(ts_fixed_line) |
| record = report.record.add() |
| record.timestamp = event[0] |
| record.event = event[1] |
| record.timing = int(event[2]) |
| report_proto_fp.write(str(report)) |
| |
| def main(): |
| args = init_arguments() |
| |
| keywords = [] |
| with open(args.config, 'r') as file: |
| keywords = yaml.safe_load(file) |
| |
| if not os.path.isdir(args.output_dir): |
| os.mkdir(args.output_dir) |
| timestamp_fixed_logcat_fp = open(os.path.join(args.output_dir, "timestamp_fixed_log.txt"), 'w') |
| report_fp = open(os.path.join(args.output_dir, "report.txt"), 'w') |
| report_proto_fp = open(os.path.join(args.output_dir, "report_proto.txt"), 'w') |
| try: |
| with open(args.logcat, 'r', errors = 'ignore') as logcat_fp: |
| timestamps = find_boot_progress_start_end(logcat_fp) |
| logcat_fp.seek(0) |
| write_to_new_file(timestamps, keywords, logcat_fp, timestamp_fixed_logcat_fp, report_fp, report_proto_fp) |
| except Exception as e: |
| traceresult = traceback.format_exc() |
| print("Caught an exception: {}".format(traceback.format_exc())) |
| |
| timestamp_fixed_logcat_fp.close() |
| report_fp.close() |
| report_proto_fp.close() |
| |
| if __name__ == '__main__': |
| main() |