Home | History | Annotate | Download | only in android
      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 
     19 import ast, re
     20 
     21 def get_value(token):
     22     try:
     23         v = ast.literal_eval(token)
     24     except:
     25         return token
     26     return v
     27 
     28 def 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 
     63 class _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 
     91 class 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