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