1# SPDX-License-Identifier: Apache-2.0 2# 3# Copyright (C) 2017, ARM Limited, Google, and contributors. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may 6# 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, WITHOUT 13# 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# Parser for dumpsys SurfaceFlinger output 18 19import ast, re 20 21def get_value(token): 22 try: 23 v = ast.literal_eval(token) 24 except: 25 return token 26 return v 27 28def parse_value(value): 29 """ 30 Parses and evaluates the different possible value types of the properties from SurfaceFlinger. 31 Supported types: literals, tuples, arrays, multidimensional arrays (in the form [...][...]) 32 """ 33 value = value.replace('(', '[') 34 value = value.replace(')', ']') 35 36 # Parse arrays and tuples. Ex: (1024, 1980) Ex: [1, 89, 301] 37 if value[0] == '[': 38 parsed_values = [] 39 dim = -1 40 start_index = 0 41 for i, c in enumerate(value): 42 # Handle multidimensional arrays. Ex: [0, 1][2, 3] -> [[0, 1], [2, 3]] 43 if c == '[': 44 dim += 1 45 parsed_values.append([]) 46 start_index = i + 1 47 continue 48 # Finish parsing a value, evaluate it, and add it to the current array 49 elif c == ']' or c == ',': 50 parsed_values[dim].append(get_value(value[start_index:i].strip())) 51 start_index = i + 1 52 continue 53 # If the array is one dimensional, return a one dimensional array 54 if dim == 0: 55 return parsed_values[0] 56 else: 57 return parsed_values 58 else: 59 # Attempt to parse a literal 60 return get_value(value) 61 62 63class _Layer(object): 64 """ 65 Represents a layer of a view drawn in an Android app. This class holds the properties of the 66 layer that have been parsed from the output of SurfaceFlinger. 67 """ 68 69 def __init__(self, name): 70 """ 71 Initializes the layer. 72 73 :param name: the parsed name of the the layer 74 """ 75 self._properties = { 'name' : name } 76 77 def __dir__(self): 78 """ 79 List all the available properties parsed from the output of SurfaceFlinger. 80 """ 81 return self._properties.keys() 82 83 def __getattr__(self, attr): 84 """ 85 Retrieves a property of the layer through using the period operator. 86 Ex: obj.frame_counter, obj.name, obj.size 87 """ 88 return self._properties[attr] 89 90 91class SurfaceFlinger(object): 92 """ 93 Used to parse the output of dumpsys SurfaceFlinger. 94 """ 95 96 def __init__(self, path): 97 """ 98 Initializes the SurfaceFlinger parser object. 99 100 :param path: Path to file containing output of SurfaceFlinger 101 """ 102 self.path = path 103 self.layers = { } 104 self.__parse_surfaceflinger() 105 106 def __parse_surfaceflinger(self): 107 """ 108 Parses the SurfaceFlinger output and stores a map of layer names to objects that hold the 109 parsed information of the layer. 110 """ 111 with open(self.path) as f: 112 current_layer = None 113 lines_to_skip = 0 114 115 content = f.readlines() 116 for line in content: 117 if lines_to_skip > 0: 118 lines_to_skip -= 1 119 continue 120 121 # Begin parsing the properties of a layer 122 if "+ Layer" in line: 123 # Extract the name from the layer 124 # Ex: + Layer 0x745c86c000 (NavigationBar#0) -> NavigationBar#0 125 layer_name = line[line.index('(') + 1 : line.index(')')] 126 current_layer = _Layer(layer_name) 127 self.layers[layer_name] = current_layer 128 lines_to_skip = 6 129 continue 130 131 # Parse the properties of the current layer 132 if not current_layer is None and '=' in line: 133 134 # Format the line so that the first and last properties parse correctly 135 line = "_ " + line + " _" 136 137 # Ex: _ layerStack= 0, z= 231000, pos=(0,1794), size=(1080, 126) _ -> 138 # ['_ layerStack', ' 0, z', ' 231000, pos', '(0,1794), size', '(1080, 126) _'] 139 tokens = [x.strip() for x in line.split('=')] 140 141 for i in xrange(0, len(tokens) - 1): 142 143 # Parse layer property names and replace hyphens with underscores 144 # Ex: key = 'layerStack' value = '0,' 145 # Ex: key = 'z' value = '231000' 146 key = tokens[i][tokens[i].rfind(' ') + 1:].replace('-', '_') 147 value = tokens[i + 1][:tokens[i + 1].rfind(' ')].strip() 148 149 # Some properties are not separated by commas, so get rid of trailing commas 150 # if the value is separated from the next key by one 151 if value[len(value) - 1] == ',': 152 value = value[:-1] 153 154 # Parse the value and add the property to the layer 155 value = parse_value(value) 156 current_layer._properties.update({key : value}) 157 158 # Do not parse layer information after the slots field 159 if not current_layer is None and "Slots:" in line: 160 current_layer = None 161 162