Skip to content

Commit 35b5a8e

Browse files
cwd-googV8 LUCI CQ
authored andcommitted
android_adb use proto parsers for meminfo
Use new generated proto parsers to parse the output of dumpsys meminfo instead of using regex. Also plumb through action timeout so that we can specify a longer timeout on tests that have lots of processes in a package. Add tests for timeouts on Linux. Change-Id: Ie4ef7de9a2112e9113e207b7638a8535a477c994 Reviewed-on: https://chromium-review.googlesource.com/c/crossbench/+/6638795 Reviewed-by: Kameron Lutes <[email protected]> Commit-Queue: Charles Dick <[email protected]>
1 parent 77e83bc commit 35b5a8e

File tree

12 files changed

+222
-523
lines changed

12 files changed

+222
-523
lines changed

crossbench/action_runner/default_action_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ def dump_meminfo_impl(self, run: Run, action: i_action.MeminfoAction) -> None:
368368
logging.warning("No meminfo probe for dump on %s", repr(self.info_stack))
369369
return
370370
assert isinstance(ctx, MeminfoProbeContext)
371-
ctx.dump_meminfo(action.target, action.package)
371+
ctx.dump_meminfo(action.target, action.timeout, action.package)
372372

373373
def wait_for_download(self, run: Run,
374374
action: i_action.WaitForDownloadAction) -> None:

crossbench/browsers/browser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ def is_running_process(self) -> Optional[bool]:
205205
# we don't get the status back.
206206
return False
207207

208-
def meminfo(self) -> dict[str, ProcessMeminfo]:
209-
return self.platform.meminfo(str(self.path))
208+
def meminfo(self, timeout: dt.timedelta) -> dict[str, ProcessMeminfo]:
209+
return self.platform.meminfo(str(self.path), timeout)
210210

211211
@property
212212
def is_running(self) -> bool:

crossbench/browsers/chromium/webdriver.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from __future__ import annotations
66

77
import atexit
8+
import datetime as dt
89
import logging
910
import re
1011
import subprocess
@@ -146,8 +147,8 @@ def force_quit(self) -> None:
146147
self._restore_chrome_flags()
147148

148149
@override
149-
def meminfo(self) -> dict[str, ProcessMeminfo]:
150-
return self.platform.meminfo(self.android_package)
150+
def meminfo(self, timeout: dt.timedelta) -> dict[str, ProcessMeminfo]:
151+
return self.platform.meminfo(self.android_package, timeout)
151152

152153
def _restore_chrome_flags(self) -> None:
153154
atexit.unregister(self._restore_chrome_flags)

crossbench/plt/android_adb.py

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from __future__ import annotations
66

7+
import datetime as dt
78
import functools
89
import logging
910
import math
@@ -25,6 +26,8 @@
2526
from crossbench.plt.posix import RemotePosixPlatform
2627
from crossbench.plt.process_meminfo import ProcessMeminfo
2728

29+
from android_protoc import activitymanagerservice_pb2
30+
2831
# Defines the Android permissions to be granted.
2932
# TODO(381985595): make this configurable.
3033
ANDROID_PERMISSIONS = ["POST_NOTIFICATIONS", "CAMERA", "RECORD_AUDIO"]
@@ -755,9 +758,13 @@ def processes(self,
755758
return res
756759

757760
@override
758-
def meminfo(self, process_name: str) -> dict[str, ProcessMeminfo]:
759-
dumpsys_output = self.sh_stdout("dumpsys", "meminfo", "--package",
760-
process_name)
761+
def meminfo(
762+
self, process_name: str, timeout: dt.timedelta = dt.timedelta(seconds=10)
763+
) -> dict[str, ProcessMeminfo]:
764+
timeout_ms = int(timeout / dt.timedelta(milliseconds=1))
765+
dumpsys_output = self.sh_stdout_bytes("dumpsys", "-T", str(timeout_ms),
766+
"meminfo", "--proto", "--package",
767+
process_name)
761768
return self._parse_dumpsys_meminfo(dumpsys_output)
762769

763770
@functools.lru_cache(maxsize=1)
@@ -837,37 +844,21 @@ def display_resolution(self) -> tuple[int, int]:
837844
def user_id(self) -> int:
838845
return NumberParser.any_int(self.sh_stdout("am", "get-current-user"))
839846

840-
def _parse_dumpsys_meminfo(self,
841-
meminfo_output: str) -> dict[str, ProcessMeminfo]:
842-
pid_sections = re.split(r"\*\* MEMINFO in pid (\d+) \[(.*?)] \*\*",
843-
meminfo_output)[1:]
847+
_DUMPSYS_TIMEOUT_RE = re.compile(
848+
rb"\*\*\* SERVICE '[^']+' DUMP TIMEOUT \(\d+ms\) EXPIRED \*\*\*")
844849

850+
def _parse_dumpsys_meminfo(
851+
self, meminfo_output: bytes) -> dict[str, ProcessMeminfo]:
852+
if self._DUMPSYS_TIMEOUT_RE.search(meminfo_output):
853+
raise TimeoutError("dumpsys meminfo timed out")
854+
proto_dump = activitymanagerservice_pb2.MemInfoDumpProto()
855+
proto_dump.ParseFromString(meminfo_output)
845856
meminfos = {}
846-
847-
for i in range(0, len(pid_sections), 3):
848-
pid = pid_sections[i]
849-
process_name = pid_sections[i + 1].strip()
850-
raw_process_info = pid_sections[i + 2]
851-
852-
pss_rss_total_v1 = re.search(
853-
r"TOTAL PSS:\s+(?P<pss_total>\d+)\s+TOTAL RSS:\s+(?P<rss_total>\d+)"
854-
r"\s+TOTAL SWAP \(KB\):\s+(?P<swap_total>\d+)", raw_process_info)
855-
856-
# TOTAL PSS: 91273 TOTAL RSS: 259028 TOTAL SWAP PSS: 209
857-
pss_rss_total_v2 = re.search(
858-
r"TOTAL PSS:\s+(?P<pss_total>\d+)\s+TOTAL RSS:\s+(?P<rss_total>\d+)"
859-
r"\s+TOTAL SWAP PSS:\s+(?P<swap_total>\d+)", raw_process_info)
860-
861-
if pss_rss_total_v1:
862-
pss_rss_total = pss_rss_total_v1
863-
elif pss_rss_total_v2:
864-
pss_rss_total = pss_rss_total_v2
865-
else:
866-
raise ValueError("Failed to parse meminfo.")
867-
868-
meminfos[process_name] = ProcessMeminfo(
869-
pid=int(pid),
870-
pss_total=int(pss_rss_total["pss_total"]),
871-
rss_total=int(pss_rss_total["rss_total"]),
872-
swap_total=int(pss_rss_total["swap_total"]))
857+
for app_process in proto_dump.app_processes:
858+
mem_info = app_process.process_memory.total_heap.mem_info
859+
meminfos[app_process.process_memory.process_name] = ProcessMeminfo(
860+
pid=app_process.process_memory.pid,
861+
pss_total=mem_info.total_pss_kb,
862+
rss_total=mem_info.total_rss_kb,
863+
swap_total=mem_info.dirty_swap_pss_kb or mem_info.dirty_swap_kb)
873864
return meminfos

crossbench/plt/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,9 @@ def process_info(self, process: ProcessLike) -> Optional[dict[str, Any]]:
563563
except proc_helper.PROCESS_NOT_FOUND_EXCEPTIONS:
564564
return None
565565

566-
def meminfo(self, process_name: str) -> dict[str, ProcessMeminfo]:
566+
def meminfo(self, process_name: str,
567+
timeout: dt.timedelta) -> dict[str, ProcessMeminfo]:
568+
del process_name, timeout
567569
raise NotImplementedError(f"meminfo not implemented for {self}.")
568570

569571
def foreground_process(self) -> Optional[dict[str, Any]]:

crossbench/plt/linux.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from __future__ import annotations
66

77
import dataclasses
8+
import datetime as dt
89
import functools
910
import logging
1011
import os
@@ -174,12 +175,17 @@ def display_details(self) -> tuple[DisplayInfo, ...]:
174175
return tuple()
175176

176177
@override
177-
def meminfo(self, process_name: str) -> dict[str, ProcessMeminfo]:
178+
def meminfo(
179+
self, process_name: str, timeout: dt.timedelta = dt.timedelta(seconds=10)
180+
) -> dict[str, ProcessMeminfo]:
181+
deadline = dt.datetime.now() + timeout
178182
matching_pids = self.sh_stdout("pgrep", "-f", process_name).splitlines()
179183

180184
meminfos: dict[str, ProcessMeminfo] = {}
181185

182186
for pid in matching_pids:
187+
if dt.datetime.now() > deadline:
188+
raise TimeoutError("meminfo timed out")
183189
try:
184190
proc_name = self.cat(f"/proc/{pid}/cmdline")
185191
except (SubprocessError, OSError):

crossbench/probes/meminfo.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import csv
88
import dataclasses
9+
import datetime as dt
910
from typing import TYPE_CHECKING, Optional, Self, Type
1011

1112
from typing_extensions import override
@@ -59,15 +60,16 @@ def start(self) -> None:
5960
def stop(self) -> None:
6061
pass
6162

62-
def dump_meminfo(self, target: MeminfoTarget, package: Optional[str]) -> None:
63+
def dump_meminfo(self, target: MeminfoTarget, timeout: dt.timedelta,
64+
package: Optional[str]) -> None:
6365
timestamp = self.browser_platform.sh_stdout("date",
6466
"+%Y-%m-%d %H:%M:%S").rstrip()
6567

6668
if target is MeminfoTarget.BROWSER:
67-
meminfo = self.browser.meminfo()
69+
meminfo = self.browser.meminfo(timeout)
6870
package_path = self.browser.unique_name
6971
elif package is not None:
70-
meminfo = self.browser_platform.meminfo(package)
72+
meminfo = self.browser_platform.meminfo(package, timeout)
7173
package_path = pth.safe_filename(package).lower()
7274
else:
7375
raise ValueError("Cannot dump meminfo without package name.")

tests/crossbench/mock_browser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ def current_url(self) -> str:
263263
return self._current_url
264264

265265
@override
266-
def meminfo(self) -> dict[str, ProcessMeminfo]:
266+
def meminfo(self, timeout: dt.timedelta) -> dict[str, ProcessMeminfo]:
267+
del timeout
267268
return {
268269
"process_1": ProcessMeminfo(1, 2, 3, 4),
269270
"process_2": ProcessMeminfo(2, 3, 4, 5)

tests/crossbench/mock_helper.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,16 @@ def download_to(self, url: str, path: pth.AnyPath) -> pth.AnyPath:
190190
self.touch(path)
191191
return path
192192

193-
def expect_sh(self, *args: CmdArg,
194-
result: str | ShResult = ShResult()) -> None:
193+
def expect_sh(
194+
self, *args: CmdArg, result: bytes | str | ShResult = ShResult()) -> None:
195195
if args:
196196
if self._expected_sh_cmds is None:
197197
self._expected_sh_cmds = []
198198
self._expected_sh_cmds.append(self._convert_sh_args(*args))
199199
if isinstance(result, str):
200200
result = ShResult(result)
201+
if isinstance(result, bytes):
202+
result = ShResult(result)
201203
assert isinstance(result, ShResult)
202204
self._sh_results.append(result)
203205

tests/crossbench/plt/pb/dumpsys_meminfo.pb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
���S���S"�
2+
���&com.android.chrome:privileged_process0)
3+

4+
Native Heap� �(�0�HP�"�j�4 �"(
5+

6+
Dalvik Heap� ,(�0�0HP�5�� �*
7+
Dalvik Other� (�0�P� *
8+
Stack� (�0P�*
9+
Ashmem�
10+
�(�0P�*
11+
Other dev
12+
�8P�*#
13+
.so mmap�#� �(�0��8�HP��*
14+
.jar mmap�D0��8DP��*
15+
.apk mmap��((�0��8�(P��*
16+
.dex mmap�0�$P�$*
17+
.oat mmap�0�EP�E*"
18+
.art mmap� �(�0�g8H�P̎*
19+
20+
Other mmap, (0�8P�2
21+
Unknown�' �(�&0�8P�*:-
22+
"
23+
TOTAL���0 �D(�b0�� 8�1H�P�� �}�B �J'� ��: �0�-8��@�P��X�"`��h�x�\ (8@,H P"�
24+
���Ycom.android.chrome:sandboxed_process0:org.chromium.content.app.SandboxedProcessService0:0)
25+

26+
Native Heap� �(� 0�H P��_�* �"(
27+

28+
Dalvik Heap� d(�0�0HP�4�� �*
29+
Dalvik Other� 8(�0�HP� *
30+
Stack� (�0P�*
31+
Ashmem� �((�0P�-*
32+
Other dev �8P�*
33+
.so mmap� �(H0��HP��*
34+
.jar mmap�0��P��*#
35+
.apk mmap���S �(�0��8�SPД*
36+
.ttf mmap�&|0�M8|P�N*
37+
.dex mmap�0�P�*
38+
.oat mmapN0�<P�<*
39+
.art mmap�
40+
�(�0�eH�P��*
41+
42+
Other mmap� (0�8�P�2
43+
Unknown�= �(�<0�P�A:-
44+
"
45+
TOTAL��T �W(�\0� 8�WH�P�� �q�8 �J(�� �W �0�G8��@�P̺X�`��h�xĘ (8@HP "�
46+
���Ycom.android.chrome:sandboxed_process0:org.chromium.content.app.SandboxedProcessService0:1)
47+

48+
Native Heap� �(� 0�HP��_�* �"(
49+

50+
Dalvik Heap� d(�0�0HP�4�� �*
51+
Dalvik Other� 8(�0�HP� *
52+
Stack� (�0P�*
53+
Ashmem�
54+
�#0P�#*
55+
Other dev �8P�*
56+
.so mmap� �(H0��HP��*
57+
.jar mmap�0��P��*
58+
.apk mmap�Xx �(�0��8xP��*
59+
.ttf mmap�&,0�M8,P�N*
60+
.dex mmap�0�P�*
61+
.oat mmapN0�<P�<*
62+
.art mmap�
63+
�(�0�eH�P��*
64+
65+
Other mmap� (0�P�2
66+
Unknown�( �(�'0�P�,:-
67+
"
68+
TOTAL��� �R(�A0Ĵ8�H�P�� �q�8 �J'�� � �0�+8ì@�P̺X�`��h�x�w (8@HP "�
69+
�֟com.android.chrome0
70+
#
71+
Native Heap�� �(��0�8$HP��ԕ�� �%"+
72+

73+
Dalvik Heap�� ,(�{0�/HP�����} �}*
74+
Dalvik Other�$ �(�"0�P�)*
75+
Stack�
76+
(�
77+
0P�
78+
*
79+
Ashmem� �)(�P�1*
80+
Other dev� �8�P�*#
81+
.so mmap�%� �(�0Ȼ8�H
82+
P��*
83+
.jar mmap�00��80P��*"
84+
.apk mmap���(�0ܟ8��P܄*
85+
.ttf mmap��0�8�P�
86+
*
87+
.dex mmap�o�g0�&8�gP��*
88+
.oat mmap�0�dP�d*#
89+
.art mmap�� �(�0�}8�HhP��*!
90+
91+
Other mmap�� �(�0�8��P��2
92+
Unknown�� �(��0�P��:3
93+
%
94+
TOTAL�� �� �S(Ĺ0��8��H�P������ �J-����� �
95+
0��8��@�P��X��`��h�
96+
x�� (8�@iHPMX

0 commit comments

Comments
 (0)