1#!/usr/bin/env python3 2# 3# Copyright 2019, 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"""Helper util libraries for parsing logcat logs.""" 18 19import asyncio 20import re 21from datetime import datetime 22from typing import Optional, Pattern 23 24# local import 25import lib.print_utils as print_utils 26 27def parse_logcat_datetime(timestamp: str) -> Optional[datetime]: 28 """Parses the timestamp of logcat. 29 30 Params: 31 timestamp: for example "2019-07-01 16:13:55.221". 32 33 Returns: 34 a datetime of timestamp with the year now. 35 """ 36 try: 37 # Match the format of logcat. For example: "2019-07-01 16:13:55.221", 38 # because it doesn't have year, set current year to it. 39 timestamp = datetime.strptime(timestamp, 40 '%Y-%m-%d %H:%M:%S.%f') 41 return timestamp 42 except ValueError as ve: 43 print_utils.debug_print('Invalid line: ' + timestamp) 44 return None 45 46def _is_time_out(timeout: datetime, line: str) -> bool: 47 """Checks if the timestamp of this line exceeds the timeout. 48 49 Returns: 50 true if the timestamp exceeds the timeout. 51 """ 52 # Get the timestampe string. 53 cur_timestamp_str = ' '.join(re.split(r'\s+', line)[0:2]) 54 timestamp = parse_logcat_datetime(cur_timestamp_str) 55 if not timestamp: 56 return False 57 58 return timestamp > timeout 59 60async def _blocking_wait_for_logcat_pattern(timestamp: datetime, 61 pattern: Pattern, 62 timeout: datetime) -> Optional[str]: 63 # Show the year in the timestampe. 64 logcat_cmd = 'adb logcat -v UTC -v year -v threadtime -T'.split() 65 logcat_cmd.append(str(timestamp)) 66 print_utils.debug_print('[LOGCAT]:' + ' '.join(logcat_cmd)) 67 68 # Create subprocess 69 process = await asyncio.create_subprocess_exec( 70 *logcat_cmd, 71 # stdout must a pipe to be accessible as process.stdout 72 stdout=asyncio.subprocess.PIPE) 73 74 while (True): 75 # Read one line of output. 76 data = await process.stdout.readline() 77 line = data.decode('utf-8').rstrip() 78 79 # 2019-07-01 14:54:21.946 27365 27392 I ActivityTaskManager: Displayed 80 # com.android.settings/.Settings: +927ms 81 # TODO: Detect timeouts even when there is no logcat output. 82 if _is_time_out(timeout, line): 83 print_utils.debug_print('DID TIMEOUT BEFORE SEEING ANYTHING (' 84 'timeout={timeout} seconds << {pattern} ' 85 '>>'.format(timeout=timeout, pattern=pattern)) 86 return None 87 88 if pattern.match(line): 89 print_utils.debug_print( 90 'WE DID SEE PATTERN << "{}" >>.'.format(pattern)) 91 return line 92 93def blocking_wait_for_logcat_pattern(timestamp: datetime, 94 pattern: Pattern, 95 timeout: datetime) -> Optional[str]: 96 """Selects the line that matches the pattern and within the timeout. 97 98 Returns: 99 the line that matches the pattern and within the timeout. 100 """ 101 loop = asyncio.get_event_loop() 102 result = loop.run_until_complete( 103 _blocking_wait_for_logcat_pattern(timestamp, pattern, timeout)) 104 return result 105