| # Copyright 2024 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Library to parse /proc/*/smaps into python dataclasses. | |
| Example usage: | |
| import pandas | |
| import smaps | |
| pandas.DataFrame(smaps.parse("/proc/123/smaps")) | |
| """ | |
| import re | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import List, Optional | |
| @dataclass | |
| class SmapEntry: | |
| addr_start: str | |
| addr_end: str | |
| perms: str | |
| offset: str | |
| dev: str | |
| inode: str | |
| pathname: Optional[str] = None | |
| # Add smap fields below as needed with the same case as in smaps output. | |
| Size: Optional[int] = None | |
| Rss: Optional[int] = None | |
| Pss: Optional[int] = None | |
| HEADER_RE = re.compile( | |
| r"(?P<addr_start>\w+)-(?P<addr_end>\w+)" | |
| r" (?P<perms>[\w-]{4})" | |
| r" (?P<offset>\w+)" | |
| r" (?P<dev>\w+:\w+)" | |
| r" (?P<inode>\d+)" | |
| r"(\s+(?P<pathname>.*))?" | |
| ) | |
| ATTR_RE = re.compile( | |
| r"(?P<name>\w+):\s*(((?P<value_kb>\d+) kB)|(?P<value>\d+)|(?P<flags>( \w\w)+))" | |
| ) | |
| def parse(smaps_path: Path) -> List[SmapEntry]: | |
| """Reads /proc/*/smaps file into dataclasses.""" | |
| entries = [] | |
| with open(smaps_path, "rt") as f: | |
| for line_no, line in enumerate(f, start=1): | |
| line = line.strip() | |
| if not line: | |
| continue | |
| hm = HEADER_RE.match(line) | |
| if hm: | |
| entry = SmapEntry(**hm.groupdict()) | |
| entries.append(entry) | |
| continue | |
| am = ATTR_RE.match(line) | |
| if am: | |
| if am.group("value_kb") is not None: | |
| value = int(am.group("value_kb")) | |
| elif am.group("value") is not None: | |
| value = int(am.group("value")) | |
| else: | |
| value = am.group("flags") | |
| setattr(entry, am.group("name"), value) | |
| continue | |
| raise Exception(f"{smaps_path}:{line_no} does not match\n{line!r}") | |
| return entries |