1b1994897Sopenharmony_ci#!/usr/bin/env python3
2b1994897Sopenharmony_ci# -- coding: utf-8 --
3b1994897Sopenharmony_ci# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
4b1994897Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
5b1994897Sopenharmony_ci# you may not use this file except in compliance with the License.
6b1994897Sopenharmony_ci# You may obtain a copy of the License at
7b1994897Sopenharmony_ci#
8b1994897Sopenharmony_ci# http://www.apache.org/licenses/LICENSE-2.0
9b1994897Sopenharmony_ci#
10b1994897Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
11b1994897Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
12b1994897Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b1994897Sopenharmony_ci# See the License for the specific language governing permissions and
14b1994897Sopenharmony_ci# limitations under the License.
15b1994897Sopenharmony_ci
16b1994897Sopenharmony_ci"""
17b1994897Sopenharmony_ciA tool to get memory usage reports
18b1994897Sopenharmony_ci"""
19b1994897Sopenharmony_ci
20b1994897Sopenharmony_ciimport argparse
21b1994897Sopenharmony_cifrom operator import attrgetter
22b1994897Sopenharmony_cifrom time import sleep
23b1994897Sopenharmony_ci
24b1994897Sopenharmony_ciPANDA_REGION_RE = r"""\[panda:([^\]]+)\]"""
25b1994897Sopenharmony_ci
26b1994897Sopenharmony_ci
27b1994897Sopenharmony_cidef read_file(path):
28b1994897Sopenharmony_ci    """Reads file"""
29b1994897Sopenharmony_ci
30b1994897Sopenharmony_ci    with open(path) as file:
31b1994897Sopenharmony_ci        return file.readlines()
32b1994897Sopenharmony_ci
33b1994897Sopenharmony_ci
34b1994897Sopenharmony_ci# pylint: disable=too-few-public-methods
35b1994897Sopenharmony_ciclass MemInfo:
36b1994897Sopenharmony_ci    """Memory region information: name, size, rss, pss"""
37b1994897Sopenharmony_ci
38b1994897Sopenharmony_ci    def __init__(self, name):
39b1994897Sopenharmony_ci        self.name = name
40b1994897Sopenharmony_ci        self.size = 0
41b1994897Sopenharmony_ci        self.rss = 0
42b1994897Sopenharmony_ci        self.pss = 0
43b1994897Sopenharmony_ci
44b1994897Sopenharmony_ci
45b1994897Sopenharmony_cidef is_hex_digit(char):
46b1994897Sopenharmony_ci    """Checks whether char is hexadecimal digit"""
47b1994897Sopenharmony_ci
48b1994897Sopenharmony_ci    return '0' <= char <= '9' or 'a' <= char <= 'f'
49b1994897Sopenharmony_ci
50b1994897Sopenharmony_ci
51b1994897Sopenharmony_cidef is_start_of_map(line):
52b1994897Sopenharmony_ci    """Checks whether line is the start of map"""
53b1994897Sopenharmony_ci
54b1994897Sopenharmony_ci    return len(line) > 0 and is_hex_digit(line[0])
55b1994897Sopenharmony_ci
56b1994897Sopenharmony_ci
57b1994897Sopenharmony_cidef is_stack_region(name):
58b1994897Sopenharmony_ci    """Checks whether memory region is stack"""
59b1994897Sopenharmony_ci
60b1994897Sopenharmony_ci    return name == '[stack]'
61b1994897Sopenharmony_ci
62b1994897Sopenharmony_ci
63b1994897Sopenharmony_cidef is_heap_region(name, remote):
64b1994897Sopenharmony_ci    """Checks whether memory region is heap"""
65b1994897Sopenharmony_ci
66b1994897Sopenharmony_ci    if remote:
67b1994897Sopenharmony_ci        return name == '[anon:libc_malloc]'
68b1994897Sopenharmony_ci    return name == '[heap]'
69b1994897Sopenharmony_ci
70b1994897Sopenharmony_ci
71b1994897Sopenharmony_cidef is_file_path(name):
72b1994897Sopenharmony_ci    """Checks whether name is file path"""
73b1994897Sopenharmony_ci
74b1994897Sopenharmony_ci    return name.startswith('/')
75b1994897Sopenharmony_ci
76b1994897Sopenharmony_ci
77b1994897Sopenharmony_cidef get_name(line):
78b1994897Sopenharmony_ci    """Parses line from /proc/pid/maps and returns name.
79b1994897Sopenharmony_ci
80b1994897Sopenharmony_ci    The line has format as follow:
81b1994897Sopenharmony_ci    55e88a5ab000-55e88a7c8000 r-xp 00068000 08:02 14434818                   /usr/bin/nvim
82b1994897Sopenharmony_ci    The last element can contain spaces so we cannot use split here.
83b1994897Sopenharmony_ci    Find it manually
84b1994897Sopenharmony_ci    """
85b1994897Sopenharmony_ci    pos = 0
86b1994897Sopenharmony_ci    for _ in range(5):
87b1994897Sopenharmony_ci        pos = line.index(' ', pos) + 1
88b1994897Sopenharmony_ci    return line[pos:].strip()
89b1994897Sopenharmony_ci
90b1994897Sopenharmony_ci
91b1994897Sopenharmony_ci# pylint: disable=too-many-instance-attributes
92b1994897Sopenharmony_ciclass Mem:
93b1994897Sopenharmony_ci    """Represents panda memory regions"""
94b1994897Sopenharmony_ci
95b1994897Sopenharmony_ci    def __init__(self):
96b1994897Sopenharmony_ci        self.stack = MemInfo("Stack")
97b1994897Sopenharmony_ci        self.heap = MemInfo("Heap")
98b1994897Sopenharmony_ci        self.so_files = MemInfo(".so files")
99b1994897Sopenharmony_ci        self.abc_files = MemInfo(".abc files")
100b1994897Sopenharmony_ci        self.an_files = MemInfo(".an files")
101b1994897Sopenharmony_ci        self.other_files = MemInfo("Other files")
102b1994897Sopenharmony_ci        self.other = MemInfo("Other")
103b1994897Sopenharmony_ci        # This list must be synchronized with panda list in
104b1994897Sopenharmony_ci        # libpandabase/mem/space.h
105b1994897Sopenharmony_ci        self.panda_regions = {
106b1994897Sopenharmony_ci            "[anon:ark-Object Space]": MemInfo("ark-Object Space"),
107b1994897Sopenharmony_ci            "[anon:ark-Humongous Space]": MemInfo("ark-Humongous Space"),
108b1994897Sopenharmony_ci            "[anon:ark-Non Movable Space]": MemInfo("ark-Non Movable Space"),
109b1994897Sopenharmony_ci            "[anon:ark-Internal Space]": MemInfo("ark-Internal Space"),
110b1994897Sopenharmony_ci            "[anon:ark-Code Space]": MemInfo("ark-Code Space"),
111b1994897Sopenharmony_ci            "[anon:ark-Compiler Space]": MemInfo("ark-Compiler Space")
112b1994897Sopenharmony_ci        }
113b1994897Sopenharmony_ci
114b1994897Sopenharmony_ci    def get_mem_info(self, name, remote):
115b1994897Sopenharmony_ci        """Gets memory region information by name"""
116b1994897Sopenharmony_ci
117b1994897Sopenharmony_ci        info = self.other
118b1994897Sopenharmony_ci        if is_stack_region(name):
119b1994897Sopenharmony_ci            info = self.stack
120b1994897Sopenharmony_ci        elif is_heap_region(name, remote):
121b1994897Sopenharmony_ci            info = self.heap
122b1994897Sopenharmony_ci        elif self.panda_regions.get(name) is not None:
123b1994897Sopenharmony_ci            info = self.panda_regions.get(name)
124b1994897Sopenharmony_ci        elif is_file_path(name):
125b1994897Sopenharmony_ci            if name.endswith('.so'):
126b1994897Sopenharmony_ci                info = self.so_files
127b1994897Sopenharmony_ci            elif name.endswith('.abc'):
128b1994897Sopenharmony_ci                info = self.abc_files
129b1994897Sopenharmony_ci            elif name.endswith('.an'):
130b1994897Sopenharmony_ci                info = self.an_files
131b1994897Sopenharmony_ci            else:
132b1994897Sopenharmony_ci                info = self.other_files
133b1994897Sopenharmony_ci        return info
134b1994897Sopenharmony_ci
135b1994897Sopenharmony_ci    def gen_report(self, smaps, remote):
136b1994897Sopenharmony_ci        """Parses smaps and returns memory usage report"""
137b1994897Sopenharmony_ci
138b1994897Sopenharmony_ci        info = self.other
139b1994897Sopenharmony_ci        for line in smaps:
140b1994897Sopenharmony_ci            if is_start_of_map(line):
141b1994897Sopenharmony_ci                # the line of format
142b1994897Sopenharmony_ci                # 55e88a5ab000-55e88a7c8000 r-xp 00068000 08:02 14434818    /usr/bin/nvim
143b1994897Sopenharmony_ci                name = get_name(line)
144b1994897Sopenharmony_ci                info = self.get_mem_info(name, remote)
145b1994897Sopenharmony_ci            else:
146b1994897Sopenharmony_ci                # the line of format
147b1994897Sopenharmony_ci                # Size:               2164 kB
148b1994897Sopenharmony_ci                elems = line.split()
149b1994897Sopenharmony_ci                if elems[0] == 'Size:':
150b1994897Sopenharmony_ci                    if len(elems) < 3:
151b1994897Sopenharmony_ci                        raise Exception('Invalid file format')
152b1994897Sopenharmony_ci                    info.size += int(elems[1])
153b1994897Sopenharmony_ci                elif elems[0] == 'Rss:':
154b1994897Sopenharmony_ci                    if len(elems) < 3:
155b1994897Sopenharmony_ci                        raise Exception('Invalid file format')
156b1994897Sopenharmony_ci                    info.rss += int(elems[1])
157b1994897Sopenharmony_ci                elif elems[0] == 'Pss:':
158b1994897Sopenharmony_ci                    if len(elems) < 3:
159b1994897Sopenharmony_ci                        raise Exception('Invalid file format')
160b1994897Sopenharmony_ci                    info.pss += int(elems[1])
161b1994897Sopenharmony_ci
162b1994897Sopenharmony_ci        memusage = [self.stack, self.heap, self.so_files, self.abc_files,
163b1994897Sopenharmony_ci                    self.an_files, self.other_files, self.other]
164b1994897Sopenharmony_ci        memusage.extend(self.panda_regions.values())
165b1994897Sopenharmony_ci
166b1994897Sopenharmony_ci        return memusage
167b1994897Sopenharmony_ci
168b1994897Sopenharmony_ci
169b1994897Sopenharmony_cidef aggregate(reports):
170b1994897Sopenharmony_ci    """Aggregates memory usage reports"""
171b1994897Sopenharmony_ci
172b1994897Sopenharmony_ci    count = len(reports)
173b1994897Sopenharmony_ci    memusage = reports.pop(0)
174b1994897Sopenharmony_ci    while reports:
175b1994897Sopenharmony_ci        for left, right in zip(memusage, reports.pop(0)):
176b1994897Sopenharmony_ci            left.size = left.size + right.size
177b1994897Sopenharmony_ci            left.rss = left.rss + right.rss
178b1994897Sopenharmony_ci            left.pss = left.pss + right.pss
179b1994897Sopenharmony_ci
180b1994897Sopenharmony_ci    for entry in memusage:
181b1994897Sopenharmony_ci        entry.size = int(float(entry.size) / float(count))
182b1994897Sopenharmony_ci        entry.rss = int(float(entry.rss) / float(count))
183b1994897Sopenharmony_ci        entry.pss = int(float(entry.pss) / float(count))
184b1994897Sopenharmony_ci
185b1994897Sopenharmony_ci    return memusage
186b1994897Sopenharmony_ci
187b1994897Sopenharmony_ci
188b1994897Sopenharmony_cidef print_report_row(col1, col2, col3, col4):
189b1994897Sopenharmony_ci    """Prints memory usage report row"""
190b1994897Sopenharmony_ci
191b1994897Sopenharmony_ci    print("{: >20} {: >10} {: >10} {: >10}".format(col1, col2, col3, col4))
192b1994897Sopenharmony_ci
193b1994897Sopenharmony_ci
194b1994897Sopenharmony_cidef print_report(report):
195b1994897Sopenharmony_ci    """Prints memory usage report"""
196b1994897Sopenharmony_ci
197b1994897Sopenharmony_ci    print('Memory usage')
198b1994897Sopenharmony_ci    print_report_row('Region', 'Size', 'RSS', 'PSS')
199b1994897Sopenharmony_ci    for record in report:
200b1994897Sopenharmony_ci        print_report_row(record.name, record.size, record.rss, record.pss)
201b1994897Sopenharmony_ci
202b1994897Sopenharmony_ci
203b1994897Sopenharmony_cidef main():
204b1994897Sopenharmony_ci    """Script's entrypoint"""
205b1994897Sopenharmony_ci
206b1994897Sopenharmony_ci    parser = argparse.ArgumentParser()
207b1994897Sopenharmony_ci    parser.add_argument(
208b1994897Sopenharmony_ci        '-f', '--follow', action='store_true',
209b1994897Sopenharmony_ci        help='Measure memory for a process periodically until it gets died')
210b1994897Sopenharmony_ci    parser.add_argument(
211b1994897Sopenharmony_ci        '-i', '--interval', default=200, type=int,
212b1994897Sopenharmony_ci        help='Interval in ms between following process ping')
213b1994897Sopenharmony_ci    parser.add_argument('pid', type=int)
214b1994897Sopenharmony_ci
215b1994897Sopenharmony_ci    args = parser.parse_args()
216b1994897Sopenharmony_ci
217b1994897Sopenharmony_ci    reports = []
218b1994897Sopenharmony_ci    smaps = read_file('/proc/{}/smaps'.format(args.pid))
219b1994897Sopenharmony_ci    report = Mem().gen_report(smaps, False)
220b1994897Sopenharmony_ci    reports.append(report)
221b1994897Sopenharmony_ci    cont = args.follow
222b1994897Sopenharmony_ci    while cont:
223b1994897Sopenharmony_ci        sleep(args.interval / 1000)
224b1994897Sopenharmony_ci        try:
225b1994897Sopenharmony_ci            smaps = read_file('/proc/{}/smaps'.format(args.pid))
226b1994897Sopenharmony_ci            report = Mem().gen_report(smaps, False)
227b1994897Sopenharmony_ci            reports.append(report)
228b1994897Sopenharmony_ci        except FileNotFoundError:
229b1994897Sopenharmony_ci            cont = False
230b1994897Sopenharmony_ci
231b1994897Sopenharmony_ci    report = aggregate(reports)
232b1994897Sopenharmony_ci    report.sort(key=attrgetter('size'), reverse=True)
233b1994897Sopenharmony_ci    print_report(report)
234b1994897Sopenharmony_ci
235b1994897Sopenharmony_ci
236b1994897Sopenharmony_ciif __name__ == "__main__":
237b1994897Sopenharmony_ci    main()
238