Skip to content

Commit

Permalink
fix instruments leak error
Browse files Browse the repository at this point in the history
  • Loading branch information
codeskyblue committed Jun 30, 2022
1 parent 30bf5f7 commit c30b48b
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 123 deletions.
33 changes: 20 additions & 13 deletions tidevice/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@


ulogger.disable(PROGRAM_NAME)
# ulogger.remove()
# ulogger.add(sys.stderr, level="INFO")


def _complete_udid(udid: Optional[str] = None) -> str:
infos = um.device_list()
Expand Down Expand Up @@ -166,8 +169,9 @@ def cmd_install(args: argparse.Namespace):
bundle_id = d.app_install(args.filepath_or_url)

if args.launch:
pid = d.instruments.app_launch(bundle_id)
logger.info("Launch %r, process pid: %d", bundle_id, pid)
with d.instruments_context() as ts:
pid = ts.app_launch(bundle_id)
logger.info("Launch %r, process pid: %d", bundle_id, pid)


def cmd_uninstall(args: argparse.Namespace):
Expand Down Expand Up @@ -314,13 +318,14 @@ def cmd_applist(args: argparse.Namespace):

def cmd_energy(args: argparse.Namespace):
d = _udid2device(args.udid)
ts = d.connect_instruments()
try:
pid = d.instruments.app_launch(args.bundle_id,
pid = ts.app_launch(args.bundle_id,
args=args.arguments,
kill_running=args.kill)
d.instruments.start_energy_sampling(pid)
ts.start_energy_sampling(pid)
while True:
ret = d.instruments.get_process_energy_stats(pid)
ret = ts.get_process_energy_stats(pid)
if ret != None:
print(json.dumps(ret))
time.sleep(1.0)
Expand All @@ -330,10 +335,11 @@ def cmd_energy(args: argparse.Namespace):
def cmd_launch(args: argparse.Namespace):
d = _udid2device(args.udid)
try:
pid = d.instruments.app_launch(args.bundle_id,
args=args.arguments,
kill_running=args.kill)
print("PID:", pid)
with d.instruments_context() as ts:
pid = ts.app_launch(args.bundle_id,
args=args.arguments,
kill_running=args.kill)
print("PID:", pid)
except ServiceError as e:
sys.exit(e)

Expand All @@ -353,8 +359,9 @@ def cmd_kill(args: argparse.Namespace):

def cmd_system_info(args):
d = _udid2device(args.udid)
sinfo = d.instruments.system_info()
pprint(sinfo)
with d.instruments_context() as ts:
sinfo = ts.system_info()
pprint(sinfo)


def cmd_battery(args: argparse.Namespace):
Expand Down Expand Up @@ -485,7 +492,7 @@ def cmd_syslog(args: argparse.Namespace):

def cmd_dump_fps(args):
d = _udid2device(args.udid)
for data in d.instruments.iter_opengl_data():
for data in d.connect_instruments().iter_opengl_data():
if isinstance(data, str):
continue
fps = data['CoreAnimationFramesPerSecond']
Expand Down Expand Up @@ -563,7 +570,7 @@ def cmd_fsync(args: argparse.Namespace):
def cmd_ps(args: argparse.Namespace):
d = _udid2device(args.udid)
app_infos = list(d.installation.iter_installed(app_type=None))
ps = list(d.instruments.app_process_list(app_infos))
ps = list(d.connect_instruments().app_process_list(app_infos))

lens = defaultdict(int)
json_data = []
Expand Down
42 changes: 23 additions & 19 deletions tidevice/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,18 +671,18 @@ def app_stop(self, pid_or_name: Union[int, str]) -> int:
"""
return pid killed
"""
instruments = self.connect_instruments()
if isinstance(pid_or_name, int):
instruments.app_kill(pid_or_name)
return pid_or_name
elif isinstance(pid_or_name, str):
bundle_id = pid_or_name
app_infos = list(self.installation.iter_installed(app_type=None))
ps = instruments.app_process_list(app_infos)
for p in ps:
if p['bundle_id'] == bundle_id:
instruments.app_kill(p['pid'])
return p['pid']
with self.instruments_context() as ts:
if isinstance(pid_or_name, int):
ts.app_kill(pid_or_name)
return pid_or_name
elif isinstance(pid_or_name, str):
bundle_id = pid_or_name
app_infos = list(self.installation.iter_installed(app_type=None))
ps = ts.app_process_list(app_infos)
for p in ps:
if p['bundle_id'] == bundle_id:
ts.app_kill(p['pid'])
return p['pid']
return None

def app_kill(self, *args, **kwargs) -> int:
Expand All @@ -698,10 +698,8 @@ def app_start(self,
return pid
"""
instruments = self.connect_instruments()
return instruments.app_launch(bundle_id,
args=args,
kill_running=kill_running)
with self.instruments_context() as ts:
return ts.app_launch(bundle_id, args=args, kill_running=kill_running)

def app_install(self, file_or_url: Union[str, typing.IO]) -> str:
"""
Expand Down Expand Up @@ -795,11 +793,17 @@ def connect_instruments(self) -> ServiceInstruments:
LockdownService.InstrumentsRemoteServerSecure)
else:
conn = self.start_service(LockdownService.InstrumentsRemoteServer)

return ServiceInstruments(conn)


@property
def instruments(self) -> ServiceInstruments:
return self.connect_instruments()
@contextlib.contextmanager
def instruments_context(self) -> typing.Generator[ServiceInstruments, None, None]:
ts = self.connect_instruments()
try:
yield ts
finally:
ts.close()

def _launch_app_runner(self,
bundle_id: str,
Expand Down
184 changes: 95 additions & 89 deletions tidevice/_perf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import uuid
from collections import defaultdict, namedtuple
from typing import Any, Iterator, Optional, Tuple, Union
import weakref

from ._device import BaseDevice
from ._proto import *
Expand All @@ -33,12 +34,13 @@ class RunningProcess:
PID_UPDATE_DURATION = 5.0

def __init__(self, d: BaseDevice, bundle_id: str):
self._ins = d.instruments
self._ins = d.connect_instruments()
self._bundle_id = bundle_id
self._app_infos = list(d.installation.iter_installed(app_type=None))
self._next_update_time = 0.0
self._last_pid = None
self._lock = threading.Lock()
weakref.finalize(self, self._ins.close)

@property
def bundle_id(self) -> str:
Expand Down Expand Up @@ -100,19 +102,21 @@ def gen_stimestamp(seconds: Optional[float] = None) -> str:


def iter_fps(d: BaseDevice) -> Iterator[Any]:
for data in d.instruments.iter_opengl_data():
fps = data['CoreAnimationFramesPerSecond'] # fps from GPU
# print("FPS:", fps)
yield DataType.FPS, {"fps": fps, "time": time.time(), "value": fps}
with d.instruments_context() as ts:
for data in ts.iter_opengl_data():
fps = data['CoreAnimationFramesPerSecond'] # fps from GPU
# print("FPS:", fps)
yield DataType.FPS, {"fps": fps, "time": time.time(), "value": fps}


def iter_gpu(d: BaseDevice) -> Iterator[Any]:
for data in d.instruments.iter_opengl_data():
device_utilization = data['Device Utilization %'] # Device Utilization
tiler_utilization = data['Tiler Utilization %'] # Tiler Utilization
renderer_utilization = data['Renderer Utilization %'] # Renderer Utilization
yield DataType.GPU, {"device": device_utilization, "renderer": renderer_utilization,
"tiler": tiler_utilization, "time": time.time(), "value": device_utilization}
with d.instruments_context() as ts:
for data in ts.iter_opengl_data():
device_utilization = data['Device Utilization %'] # Device Utilization
tiler_utilization = data['Tiler Utilization %'] # Tiler Utilization
renderer_utilization = data['Renderer Utilization %'] # Renderer Utilization
yield DataType.GPU, {"device": device_utilization, "renderer": renderer_utilization,
"tiler": tiler_utilization, "time": time.time(), "value": device_utilization}


def iter_screenshot(d: BaseDevice) -> Iterator[Tuple[DataType, dict]]:
Expand Down Expand Up @@ -146,75 +150,76 @@ def _iter_complex_cpu_memory(d: BaseDevice,
'mem_rss': 130760704,
'pid': 1344}
"""
for info in d.instruments.iter_cpu_memory():
pid = rp.get_pid()

if info is None or len(info) != 2:
continue
sinfo, pinfolist = info
if 'CPUCount' not in sinfo:
sinfo, pinfolist = pinfolist, sinfo

if 'CPUCount' not in sinfo:
continue

cpu_count = sinfo['CPUCount']

sys_cpu_usage = sinfo['SystemCPUUsage']
cpu_total_load = sys_cpu_usage['CPU_TotalLoad']
cpu_user = sys_cpu_usage['CPU_UserLoad']
cpu_sys = sys_cpu_usage['CPU_SystemLoad']

if 'Processes' not in pinfolist:
continue

# 这里的total_cpu_usage加起来的累计值大概在0.5~5.0之间
total_cpu_usage = 0.0
for attrs in pinfolist['Processes'].values():
pinfo = ProcAttrs(*attrs)
if isinstance(pinfo.cpuUsage, float): # maybe NSNull
total_cpu_usage += pinfo.cpuUsage

cpu_usage = 0.0
attrs = pinfolist['Processes'].get(pid)
if attrs is None: # process is not running
# continue
# print('process not launched')
pass
else:
assert len(attrs) == len(SYSMON_PROC_ATTRS)
# print(ProcAttrs, attrs)
pinfo = ProcAttrs(*attrs)
cpu_usage = pinfo.cpuUsage
# next_list_process_time = time.time() + next_timeout
# cpu_usage, rss, mem_anon, pid = pinfo

# 很诡异的计算方法,不过也就这种方法计算出来的CPU看起来正常一点
# 计算后的cpuUsage范围 [0, 100]
# cpu_total_load /= cpu_count
# cpu_usage *= cpu_total_load
# if total_cpu_usage > 0:
# cpu_usage /= total_cpu_usage

# print("cpuUsage: {}, total: {}".format(cpu_usage, total_cpu_usage))
# print("memory: {} MB".format(pinfo.physFootprint / 1024 / 1024))
yield dict(
type="process",
pid=pid,
phys_memory=pinfo.physFootprint, # 物理内存
phys_memory_string="{:.1f} MiB".format(pinfo.physFootprint / 1024 /
1024),
vss=pinfo.memVirtualSize,
rss=pinfo.memResidentSize,
anon=pinfo.memAnon, # 匿名内存? 这个是啥
cpu_count=cpu_count,
cpu_usage=cpu_usage, # 理论上最高 100.0 (这里是除以过cpuCount的)
sys_cpu_usage=cpu_total_load,
attr_cpuUsage=pinfo.cpuUsage,
attr_cpuTotal=cpu_total_load,
attr_ctxSwitch=pinfo.ctxSwitch,
attr_intWakeups=pinfo.intWakeups,
attr_systemInfo=sys_cpu_usage)
with d.instruments_context() as ts:
for info in ts.iter_cpu_memory():
pid = rp.get_pid()

if info is None or len(info) != 2:
continue
sinfo, pinfolist = info
if 'CPUCount' not in sinfo:
sinfo, pinfolist = pinfolist, sinfo

if 'CPUCount' not in sinfo:
continue

cpu_count = sinfo['CPUCount']

sys_cpu_usage = sinfo['SystemCPUUsage']
cpu_total_load = sys_cpu_usage['CPU_TotalLoad']
cpu_user = sys_cpu_usage['CPU_UserLoad']
cpu_sys = sys_cpu_usage['CPU_SystemLoad']

if 'Processes' not in pinfolist:
continue

# 这里的total_cpu_usage加起来的累计值大概在0.5~5.0之间
total_cpu_usage = 0.0
for attrs in pinfolist['Processes'].values():
pinfo = ProcAttrs(*attrs)
if isinstance(pinfo.cpuUsage, float): # maybe NSNull
total_cpu_usage += pinfo.cpuUsage

cpu_usage = 0.0
attrs = pinfolist['Processes'].get(pid)
if attrs is None: # process is not running
# continue
# print('process not launched')
pass
else:
assert len(attrs) == len(SYSMON_PROC_ATTRS)
# print(ProcAttrs, attrs)
pinfo = ProcAttrs(*attrs)
cpu_usage = pinfo.cpuUsage
# next_list_process_time = time.time() + next_timeout
# cpu_usage, rss, mem_anon, pid = pinfo

# 很诡异的计算方法,不过也就这种方法计算出来的CPU看起来正常一点
# 计算后的cpuUsage范围 [0, 100]
# cpu_total_load /= cpu_count
# cpu_usage *= cpu_total_load
# if total_cpu_usage > 0:
# cpu_usage /= total_cpu_usage

# print("cpuUsage: {}, total: {}".format(cpu_usage, total_cpu_usage))
# print("memory: {} MB".format(pinfo.physFootprint / 1024 / 1024))
yield dict(
type="process",
pid=pid,
phys_memory=pinfo.physFootprint, # 物理内存
phys_memory_string="{:.1f} MiB".format(pinfo.physFootprint / 1024 /
1024),
vss=pinfo.memVirtualSize,
rss=pinfo.memResidentSize,
anon=pinfo.memAnon, # 匿名内存? 这个是啥
cpu_count=cpu_count,
cpu_usage=cpu_usage, # 理论上最高 100.0 (这里是除以过cpuCount的)
sys_cpu_usage=cpu_total_load,
attr_cpuUsage=pinfo.cpuUsage,
attr_cpuTotal=cpu_total_load,
attr_ctxSwitch=pinfo.ctxSwitch,
attr_intWakeups=pinfo.intWakeups,
attr_systemInfo=sys_cpu_usage)


def iter_cpu_memory(d: BaseDevice, rp: RunningProcess) -> Iterator[Any]:
Expand Down Expand Up @@ -244,15 +249,16 @@ def set_interval(it: Iterator[Any], interval: float):

def iter_network_flow(d: BaseDevice, rp: RunningProcess) -> Iterator[Any]:
n = 0
for nstat in d.instruments.iter_network():
# if n < 2:
# n += 1
# continue
yield DataType.NETWORK, {
"timestamp": gen_stimestamp(),
"downFlow": (nstat['rx.bytes'] or 0) / 1024,
"upFlow": (nstat['tx.bytes'] or 0) / 1024
}
with d.instruments_context() as ts:
for nstat in ts.iter_network():
# if n < 2:
# n += 1
# continue
yield DataType.NETWORK, {
"timestamp": gen_stimestamp(),
"downFlow": (nstat['rx.bytes'] or 0) / 1024,
"upFlow": (nstat['tx.bytes'] or 0) / 1024
}


def append_data(wg: WaitGroup, stop_event: threading.Event,
Expand Down
Loading

0 comments on commit c30b48b

Please sign in to comment.