• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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