153 lines
5.1 KiB
Python
153 lines
5.1 KiB
Python
#!/usr/bin/python2
|
|
# Copyright 2018 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
import collections
|
|
import logging
|
|
import re
|
|
import threading
|
|
|
|
from autotest_lib.client.bin import utils
|
|
|
|
class SystemSampler():
|
|
"""A sampler class used to probe various system stat along with FPS.
|
|
|
|
Sample usage:
|
|
def get_consumer_pid():
|
|
# Returns all memory consumers whose memory usage we care about.
|
|
|
|
sampler = SystemSampler(get_consumer_pid)
|
|
fps = ...
|
|
sampler.sample(fps)
|
|
"""
|
|
Sample = collections.namedtuple(
|
|
'Sample', ['pswpin', 'pswpout', 'free_mem', 'buff_mem', 'cached_mem',
|
|
'anon_mem', 'file_mem', 'swap_free', 'swap_used',
|
|
'consumer_num', 'consumer_rss', 'consumer_swap',
|
|
'cpuload', 'fps'])
|
|
|
|
VMStat = collections.namedtuple('VMStat', ['pswpin', 'pswpout'])
|
|
|
|
def __init__(self, get_consumer_pid_func):
|
|
self._get_consumer_pid_func = get_consumer_pid_func
|
|
self._samples_lock = threading.Lock()
|
|
self._samples = []
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
"""Resets its internal stats."""
|
|
self._prev_vmstat = self.get_vmstat()
|
|
self._prev_cpustat = utils.get_cpu_usage()
|
|
|
|
def get_samples(self):
|
|
with self._samples_lock:
|
|
return self._samples
|
|
|
|
def get_last_avg_fps(self, num):
|
|
"""Gets average fps rate of last |num| samples.
|
|
|
|
Returns None if there's not enough samples in hand.
|
|
"""
|
|
if num <= 0:
|
|
logging.warning('Num of samples must be > 0')
|
|
return
|
|
|
|
with self._samples_lock:
|
|
if len(self._samples) >= num:
|
|
return sum([s.fps for s in self._samples[-num:]])/float(num)
|
|
|
|
logging.warning('Not enough samples')
|
|
return None
|
|
|
|
def sample(self, fps_info):
|
|
"""Gets a fps sample with system state."""
|
|
vmstat = self.get_vmstat()
|
|
vmstat_diff = self.VMStat(*[(end - start)
|
|
for start, end in zip(self._prev_vmstat, vmstat)])
|
|
self._prev_vmstat = vmstat
|
|
|
|
cpustat = utils.get_cpu_usage()
|
|
cpuload = utils.compute_active_cpu_time(
|
|
self._prev_cpustat, cpustat)
|
|
self._prev_cpustat = cpustat
|
|
|
|
mem_info_in_kb = utils.get_meminfo()
|
|
# Converts mem_info from KB to MB.
|
|
mem_info = collections.namedtuple('MemInfo', mem_info_in_kb._fields)(
|
|
*[v/float(1024) for v in mem_info_in_kb])
|
|
|
|
consumer_pids = self._get_consumer_pid_func()
|
|
logging.info('Consumers %s', consumer_pids)
|
|
consumer_rss, consumer_swap = self.get_consumer_meminfo(consumer_pids)
|
|
|
|
# fps_info = (frame_info, frame_times)
|
|
fps_count = len([f for f in fps_info[0] if f != ' '])
|
|
|
|
sample = self.Sample(
|
|
pswpin=vmstat_diff.pswpin,
|
|
pswpout=vmstat_diff.pswpout,
|
|
free_mem=mem_info.MemFree,
|
|
buff_mem=mem_info.Buffers,
|
|
cached_mem=mem_info.Cached,
|
|
anon_mem=mem_info.Active_anon + mem_info.Inactive_anon,
|
|
file_mem=mem_info.Active_file + mem_info.Inactive_file,
|
|
swap_free=mem_info.SwapFree,
|
|
swap_used=mem_info.SwapTotal - mem_info.SwapFree,
|
|
consumer_num=len(consumer_pids),
|
|
consumer_rss=consumer_rss,
|
|
consumer_swap=consumer_swap,
|
|
cpuload=cpuload,
|
|
fps=fps_count)
|
|
|
|
logging.info(sample)
|
|
|
|
with self._samples_lock:
|
|
self._samples.append(sample)
|
|
|
|
@staticmethod
|
|
def parse_meminfo_from_proc_entry(pid):
|
|
"""Parses memory related info in /proc/<pid>/totmaps like:
|
|
|
|
Rss: 144956 kB
|
|
Pss: 74923 kB
|
|
Shared_Clean: 50596 kB
|
|
Shared_Dirty: 41660 kB
|
|
Private_Clean: 1032 kB
|
|
Private_Dirty: 51668 kB
|
|
Referenced: 137424 kB
|
|
Anonymous: 91772 kB
|
|
AnonHugePages: 30720 kB
|
|
Swap: 0 kB
|
|
"""
|
|
mem_info = {}
|
|
line_pattern = re.compile(r'^(\w+):\s+(\d+)\s+kB')
|
|
proc_entry = '/proc/%s/totmaps' % pid
|
|
try:
|
|
with open(proc_entry) as f:
|
|
for line in f:
|
|
m = line_pattern.match(line)
|
|
if m:
|
|
key, value = m.groups()
|
|
mem_info[key] = float(value)/1024
|
|
except IOError as e:
|
|
logging.warning('Failed to open %s: %s', proc_entry, e)
|
|
return mem_info
|
|
|
|
@classmethod
|
|
def get_consumer_meminfo(cls, pids):
|
|
rss = 0.0
|
|
swap = 0.0
|
|
for pid in pids:
|
|
mem_info = cls.parse_meminfo_from_proc_entry(pid)
|
|
rss += mem_info.get('Rss', 0)
|
|
swap += mem_info.get('Swap', 0)
|
|
return rss, swap
|
|
|
|
@classmethod
|
|
def get_vmstat(cls):
|
|
with open('/proc/vmstat') as f:
|
|
lines = f.readlines()
|
|
all_fields = dict([l.strip().split(' ') for l in lines])
|
|
return cls.VMStat(
|
|
*[int(all_fields.get(f, 0)) for f in cls.VMStat._fields])
|