Commit edde24b3 authored by jeremy's avatar jeremy Committed by Commit bot

[Telemetry] Fix idle wakeup reporting in the face of dead processes

Telemetry does not account for process death when computing deltas of various process statistics over a browser run.

CPU stats are currently stored in a dictionary of POD types aggregated by process type which gives no room for separating dead processes over a run.

Ultimately the goal is to report CPU stats in the timeline on a per-process basis.

Towards this goal and to fix counting of dead processes, this CL adds an IdleStatsData object which inherits from TimelineData. IdleStatsData keeps
track of idle wake ups by PID and is able to omit processes which have died during a run from the final reported result.

BUG=343373

Review URL: https://codereview.chromium.org/501813002

Cr-Commit-Position: refs/heads/master@{#292422}
parent 5648b809
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import time import time
from metrics import Metric from metrics import Metric
from telemetry.core.platform import process_statistic_timeline_data
from telemetry.value import scalar from telemetry.value import scalar
...@@ -178,7 +179,10 @@ def _SubtractCpuStats(cpu_stats, start_cpu_stats): ...@@ -178,7 +179,10 @@ def _SubtractCpuStats(cpu_stats, start_cpu_stats):
if (('IdleWakeupCount' not in cpu_stats[process_type]) or if (('IdleWakeupCount' not in cpu_stats[process_type]) or
('IdleWakeupCount' not in start_cpu_stats[process_type])): ('IdleWakeupCount' not in start_cpu_stats[process_type])):
continue continue
assert isinstance(cpu_stats[process_type]['IdleWakeupCount'],
process_statistic_timeline_data.IdleWakeupTimelineData)
idle_wakeup_delta = (cpu_stats[process_type]['IdleWakeupCount'] - idle_wakeup_delta = (cpu_stats[process_type]['IdleWakeupCount'] -
start_cpu_stats[process_type]['IdleWakeupCount']) start_cpu_stats[process_type]['IdleWakeupCount'])
cpu_delta[process_type] = idle_wakeup_delta cpu_delta[process_type] = idle_wakeup_delta.total_sum()
return cpu_delta return cpu_delta
...@@ -9,6 +9,7 @@ import time ...@@ -9,6 +9,7 @@ import time
from telemetry import decorators from telemetry import decorators
from telemetry.core.platform import platform_backend from telemetry.core.platform import platform_backend
from telemetry.core.platform import posix_platform_backend from telemetry.core.platform import posix_platform_backend
from telemetry.core.platform import process_statistic_timeline_data
from telemetry.core.platform.power_monitor import powermetrics_power_monitor from telemetry.core.platform.power_monitor import powermetrics_power_monitor
try: try:
...@@ -46,13 +47,14 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend): ...@@ -46,13 +47,14 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
# Sometimes top won't return anything here, just ignore such cases - # Sometimes top won't return anything here, just ignore such cases -
# crbug.com/354812 . # crbug.com/354812 .
if top_output[-2] != 'IDLEW': if top_output[-2] != 'IDLEW':
return 0 return process_statistic_timeline_data.IdleWakeupTimelineData(pid, 0)
# Numbers reported by top may have a '+' appended. # Numbers reported by top may have a '+' appended.
wakeup_count = int(top_output[-1].strip('+ ')) wakeup_count = int(top_output[-1].strip('+ '))
return wakeup_count return process_statistic_timeline_data.IdleWakeupTimelineData(pid,
wakeup_count)
def GetCpuStats(self, pid): def GetCpuStats(self, pid):
"""Return current cpu processing time of pid in seconds.""" """Returns a dict of cpu statistics for the process represented by |pid|."""
class ProcTaskInfo(ctypes.Structure): class ProcTaskInfo(ctypes.Structure):
"""Struct for proc_pidinfo() call.""" """Struct for proc_pidinfo() call."""
_fields_ = [("pti_virtual_size", ctypes.c_uint64), _fields_ = [("pti_virtual_size", ctypes.c_uint64),
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
class ProcessStatisticTimelineData(object):
"""Holds value of a stat for one or more processes.
This object can hold a value for more than one pid by adding another
object."""
def __init__(self, pid, value):
super(ProcessStatisticTimelineData, self).__init__()
assert value >= 0
self._value_by_pid = {pid: value}
def __sub__(self, other):
"""The results of subtraction is an object holding only the pids contained
in |self|.
The motivation is that some processes may have died between two consecutive
measurements. The desired behavior is to only make calculations based on
the processes that are alive at the end of the second measurement."""
# pylint: disable=W0212
ret = self.__class__(0, 0)
my_dict = self._value_by_pid
ret._value_by_pid = (
{k: my_dict[k] - other._value_by_pid.get(k, 0) for
k in my_dict.keys()})
return ret
def __add__(self, other):
"""The result contains pids from both |self| and |other|, if duplicate
pids are found between objects, an error will occur. """
# pylint: disable=W0212
intersecting_pids = (set(self._value_by_pid.keys()) &
set(other._value_by_pid.keys()))
assert len(intersecting_pids) == 0
ret = self.__class__(0, 0)
ret._value_by_pid = {}
ret._value_by_pid.update(self._value_by_pid)
ret._value_by_pid.update(other._value_by_pid)
return ret
@property
def value_by_pid(self):
return self._value_by_pid
def total_sum(self):
"""Returns the sum of all values contained by this object. """
return sum(self._value_by_pid.values())
class IdleWakeupTimelineData(ProcessStatisticTimelineData):
"""A ProcessStatisticTimelineData to hold idle wakeups."""
pass
# Copyright 2014 The Chromium 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 unittest
from telemetry.core.platform import process_statistic_timeline_data
class ProcessStatisticTimelineDataTest(unittest.TestCase):
def testProcessStatisticValueMath(self):
pid1 = 1
pid2 = 2
a = process_statistic_timeline_data.ProcessStatisticTimelineData(pid1, 5)
b = process_statistic_timeline_data.ProcessStatisticTimelineData(pid2, 1)
c = process_statistic_timeline_data.ProcessStatisticTimelineData(pid1, 1)
# Test addition.
addition_result = (a + b).value_by_pid
self.assertEquals(5, addition_result[pid1])
self.assertEquals(1, addition_result[pid2])
self.assertEquals(2, len(addition_result.keys()))
# Test subtraction.
subtraction_result = ((a + b) - c).value_by_pid
self.assertEquals(4, subtraction_result[pid1])
self.assertEquals(1, subtraction_result[pid2])
self.assertEquals(2, len(subtraction_result.keys()))
# Test subtraction with a pid that exists only in rhs.
subtraction_results1 = (a - (b + c)).value_by_pid
self.assertEquals(4, subtraction_results1[pid1])
self.assertEquals(1, len(subtraction_results1.keys()))
# Test calculation of total sum.
self.assertEquals(6, (a + b).total_sum())
def testProcessStatisticValueSummary(self):
pid1 = 1
pid2 = 2
a = process_statistic_timeline_data.ProcessStatisticTimelineData(pid1, 1)
b = process_statistic_timeline_data.ProcessStatisticTimelineData(pid2, 99)
c = a + b
self.assertEquals(100, c.total_sum())
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment