blob: 70c24b931e85638df5257cf5b1a72e159922945e [file] [log] [blame]
# 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