Commit c51effeb authored by Matthew Cary's avatar Matthew Cary Committed by Commit Bot

Orderfile: phased_orderfile updates

This performs the per-process phased offset processing used for the orderfile.

Bug: 758566
Change-Id: I5cbdb69c4834d95a52f2e43ad3f72e4845413883
Reviewed-on: https://chromium-review.googlesource.com/1144935
Commit-Queue: Matthew Cary <mattcary@chromium.org>
Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Reviewed-by: default avatarEgor Pasko <pasko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577537}
parent a14a69a7
This diff is collapsed.
...@@ -11,20 +11,47 @@ import unittest ...@@ -11,20 +11,47 @@ import unittest
import phased_orderfile import phased_orderfile
import process_profiles import process_profiles
from test_utils import (SimpleTestSymbol, from test_utils import (ProfileFile,
SimpleTestSymbol,
TestSymbolOffsetProcessor, TestSymbolOffsetProcessor,
TestProfileManager) TestProfileManager)
class Mod10Processor(object): class Mod10Processor(process_profiles.SymbolOffsetProcessor):
"""A restricted mock for a SymbolOffsetProcessor. """A restricted mock for a SymbolOffsetProcessor.
This only implements GetReachedOffsetsFromDump, and works by mapping a dump This only implements {Translate,Get}ReacheOffsetsFromDump, and works by
offset to offset - (offset % 10). If the dump offset is negative, it is marked mapping a dump offset to offset - (offset % 10). If the dump offset is
as not found. negative, it is marked as not found.
""" """
def GetReachedOffsetsFromDump(self, dump): def __init__(self):
return [x - (x % 10) for x in dump if x >= 0] super(Mod10Processor, self).__init__(None)
def _TranslateReachedOffsetsFromDump(self, items, get, update):
for i in items:
x = get(i)
if x >= 0:
update(i, x - (x % 10))
else:
update(i, None)
class IdentityProcessor(process_profiles.SymbolOffsetProcessor):
"""A restricted mock for a SymbolOffsetProcessor.
This only implements {Translate,Get}ReachedOffsetsFromDump, and maps the dump
offset to itself. If the dump offset is negative, it is marked as not found.
"""
def __init__(self):
super(IdentityProcessor, self).__init__(None)
def _TranslateReachedOffsetsFromDump(self, items, get, update):
for i in items:
x = get(i)
if x >= 0:
update(i, x)
else:
update(i, None)
class PhasedOrderfileTestCase(unittest.TestCase): class PhasedOrderfileTestCase(unittest.TestCase):
...@@ -32,11 +59,6 @@ class PhasedOrderfileTestCase(unittest.TestCase): ...@@ -32,11 +59,6 @@ class PhasedOrderfileTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self._file_counter = 0 self._file_counter = 0
def File(self, timestamp_sec, phase):
self._file_counter += 1
return 'file-{}-{}.txt_{}'.format(
self._file_counter, timestamp_sec * 1000 * 1000 * 1000, phase)
def testProfileStability(self): def testProfileStability(self):
symbols = [SimpleTestSymbol(str(i), i, 10) symbols = [SimpleTestSymbol(str(i), i, 10)
for i in xrange(20)] for i in xrange(20)]
...@@ -46,7 +68,8 @@ class PhasedOrderfileTestCase(unittest.TestCase): ...@@ -46,7 +68,8 @@ class PhasedOrderfileTestCase(unittest.TestCase):
startup=s, common=c, interaction=i) startup=s, common=c, interaction=i)
phaser._phase_offsets = [opo(range(5), range(6, 10), range(11,15)), phaser._phase_offsets = [opo(range(5), range(6, 10), range(11,15)),
opo(range(4), range(6, 10), range(18, 20))] opo(range(4), range(6, 10), range(18, 20))]
self.assertEquals((1.25, 1, None), phaser.ComputeStability()) self.assertEquals((1.25, 1, None),
tuple(s[0] for s in phaser.ComputeStability()))
def testIsStable(self): def testIsStable(self):
symbols = [SimpleTestSymbol(str(i), i, 10) symbols = [SimpleTestSymbol(str(i), i, 10)
...@@ -64,12 +87,12 @@ class PhasedOrderfileTestCase(unittest.TestCase): ...@@ -64,12 +87,12 @@ class PhasedOrderfileTestCase(unittest.TestCase):
def testGetOrderfilePhaseOffsets(self): def testGetOrderfilePhaseOffsets(self):
mgr = TestProfileManager({ mgr = TestProfileManager({
self.File(0, 0): [12, 21, -1, 33], ProfileFile(0, 0): [12, 21, -1, 33],
self.File(0, 1): [31, 49, 52], ProfileFile(0, 1): [31, 49, 52],
self.File(100, 0): [113, 128], ProfileFile(100, 0): [113, 128],
self.File(200, 1): [132, 146], ProfileFile(200, 1): [132, 146],
self.File(300, 0): [19, 20, 32], ProfileFile(300, 0): [19, 20, 32],
self.File(300, 1): [24, 39]}) ProfileFile(300, 1): [24, 39]})
phaser = phased_orderfile.PhasedAnalyzer(mgr, Mod10Processor()) phaser = phased_orderfile.PhasedAnalyzer(mgr, Mod10Processor())
opo = lambda s, c, i: phased_orderfile.OrderfilePhaseOffsets( opo = lambda s, c, i: phased_orderfile.OrderfilePhaseOffsets(
startup=s, common=c, interaction=i) startup=s, common=c, interaction=i)
...@@ -79,6 +102,47 @@ class PhasedOrderfileTestCase(unittest.TestCase): ...@@ -79,6 +102,47 @@ class PhasedOrderfileTestCase(unittest.TestCase):
opo([10], [20, 30], [])], opo([10], [20, 30], [])],
phaser._GetOrderfilePhaseOffsets()) phaser._GetOrderfilePhaseOffsets())
def testGetCombinedProcessOffsets(self):
mgr = TestProfileManager({
ProfileFile(40, 0, ''): [1, 2, 3],
ProfileFile(50, 1, ''): [3, 4, 5],
ProfileFile(51, 0, 'renderer'): [2, 3, 6],
ProfileFile(51, 1, 'gpu-process'): [6, 7],
ProfileFile(70, 0, ''): [2, 8, 9],
ProfileFile(70, 1, ''): [9]})
phaser = phased_orderfile.PhasedAnalyzer(mgr, IdentityProcessor())
offsets = phaser._GetCombinedProcessOffsets('browser')
self.assertListEqual([1, 2, 8], sorted(offsets.startup))
self.assertListEqual([4, 5], sorted(offsets.interaction))
self.assertListEqual([3, 9], sorted(offsets.common))
offsets = phaser._GetCombinedProcessOffsets('gpu-process')
self.assertListEqual([], sorted(offsets.startup))
self.assertListEqual([6, 7], sorted(offsets.interaction))
self.assertListEqual([], sorted(offsets.common))
self.assertListEqual(['browser', 'gpu-process', 'renderer'],
sorted(phaser._GetProcessList()))
def testGetOffsetVariations(self):
mgr = TestProfileManager({
ProfileFile(40, 0, ''): [1, 2, 3],
ProfileFile(50, 1, ''): [3, 4, 5],
ProfileFile(51, 0, 'renderer'): [2, 3, 6],
ProfileFile(51, 1, 'gpu-process'): [6, 7],
ProfileFile(70, 0, ''): [2, 6, 8, 9],
ProfileFile(70, 1, ''): [9]})
phaser = phased_orderfile.PhasedAnalyzer(mgr, IdentityProcessor())
offsets = phaser.GetOffsetsForMemoryFootprint()
self.assertListEqual([1, 2, 8], offsets.startup)
self.assertListEqual([6, 3, 9], offsets.common)
self.assertListEqual([4, 5, 7], offsets.interaction)
offsets = phaser.GetOffsetsForStartup()
self.assertListEqual([1, 2, 6, 8], offsets.startup)
self.assertListEqual([3, 9], offsets.common)
self.assertListEqual([4, 5, 7], offsets.interaction)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
...@@ -56,6 +56,7 @@ class SymbolOffsetProcessor(object): ...@@ -56,6 +56,7 @@ class SymbolOffsetProcessor(object):
self._name_to_symbol = None self._name_to_symbol = None
self._offset_to_primary = None self._offset_to_primary = None
self._offset_to_symbols = None self._offset_to_symbols = None
self._offset_to_symbol_info = None
def SymbolInfos(self): def SymbolInfos(self):
"""The symbols associated with this processor's binary. """The symbols associated with this processor's binary.
...@@ -152,24 +153,14 @@ class SymbolOffsetProcessor(object): ...@@ -152,24 +153,14 @@ class SymbolOffsetProcessor(object):
Returns: Returns:
[int] Reached symbol offsets. [int] Reached symbol offsets.
""" """
dump_offset_to_symbol_info = self._GetDumpOffsetToSymbolInfo()
logging.info('Offset to Symbol size = %d', len(dump_offset_to_symbol_info))
assert max(dump) / 4 <= len(dump_offset_to_symbol_info)
already_seen = set()
reached_offsets = [] reached_offsets = []
reached_return_addresses_not_found = 0 already_seen = set()
for dump_offset in dump: def update(_, symbol_offset):
symbol_info = dump_offset_to_symbol_info[dump_offset / 4] if symbol_offset is None or symbol_offset in already_seen:
if symbol_info is None: return
reached_return_addresses_not_found += 1 reached_offsets.append(symbol_offset)
continue already_seen.add(symbol_offset)
if symbol_info.offset in already_seen: self._TranslateReachedOffsetsFromDump(dump, lambda x: x, update)
continue
reached_offsets.append(symbol_info.offset)
already_seen.add(symbol_info.offset)
if reached_return_addresses_not_found:
logging.warning('%d return addresses don\'t map to any symbol',
reached_return_addresses_not_found)
return reached_offsets return reached_offsets
def MatchSymbolNames(self, symbol_names): def MatchSymbolNames(self, symbol_names):
...@@ -185,6 +176,52 @@ class SymbolOffsetProcessor(object): ...@@ -185,6 +176,52 @@ class SymbolOffsetProcessor(object):
matched_names = our_symbol_names.intersection(set(symbol_names)) matched_names = our_symbol_names.intersection(set(symbol_names))
return [self.NameToSymbolMap()[n] for n in matched_names] return [self.NameToSymbolMap()[n] for n in matched_names]
def TranslateAnnotatedSymbolOffsets(self, annotated_offsets):
"""Merges offsets across run groups and translates to symbol offsets.
Like GetReachedOffsetsFromDump, but works with AnnotatedOffsets.
Args:
annotated_offsets (AnnotatedOffset iterable) List of annotated offsets,
eg from ProfileManager.GetAnnotatedOffsets(). This will be mutated to
translate raw offsets to symbol offsets.
"""
self._TranslateReachedOffsetsFromDump(
annotated_offsets,
lambda o: o.Offset(),
lambda o, symbol_offset: o.SetOffset(symbol_offset))
def _TranslateReachedOffsetsFromDump(self, items, get, update):
"""Translate raw binary offsets to symbol offsets.
See GetReachedOffsetsFromDump for details. This version calls
|get(i)| on each element |i| of |items|, then calls
|update(i, symbol_offset)| with the updated offset. If the offset is not
found, update will be called with None.
Args:
items: (iterable) Items containing offsets.
get: (lambda item) As described above.
update: (lambda item, int) As described above.
"""
dump_offset_to_symbol_info = self._GetDumpOffsetToSymbolInfo()
logging.info('Offset to Symbol size = %d', len(dump_offset_to_symbol_info))
reached_return_addresses_not_found = 0
for i in items:
dump_offset = get(i)
idx = dump_offset / 4
assert idx < len(dump_offset_to_symbol_info), (
'Dump offset out of binary range')
symbol_info = dump_offset_to_symbol_info[idx]
if symbol_info is None:
reached_return_addresses_not_found += 1
update(i, None)
else:
update(i, symbol_info.offset)
if reached_return_addresses_not_found:
logging.warning('%d return addresses don\'t map to any symbol',
reached_return_addresses_not_found)
def _GetDumpOffsetToSymbolInfo(self): def _GetDumpOffsetToSymbolInfo(self):
"""Computes an array mapping each word in .text to a symbol. """Computes an array mapping each word in .text to a symbol.
...@@ -192,15 +229,16 @@ class SymbolOffsetProcessor(object): ...@@ -192,15 +229,16 @@ class SymbolOffsetProcessor(object):
[symbol_extractor.SymbolInfo or None] For every 4 bytes of the .text [symbol_extractor.SymbolInfo or None] For every 4 bytes of the .text
section, maps it to a symbol, or None. section, maps it to a symbol, or None.
""" """
min_offset = min(s.offset for s in self.SymbolInfos()) if self._offset_to_symbol_info is None:
max_offset = max(s.offset + s.size for s in self.SymbolInfos()) min_offset = min(s.offset for s in self.SymbolInfos())
text_length_words = (max_offset - min_offset) / 4 max_offset = max(s.offset + s.size for s in self.SymbolInfos())
offset_to_symbol_info = [None for _ in xrange(text_length_words)] text_length_words = (max_offset - min_offset) / 4
for s in self.SymbolInfos(): self._offset_to_symbol_info = [None for _ in xrange(text_length_words)]
offset = s.offset - min_offset for s in self.SymbolInfos():
for i in range(offset / 4, (offset + s.size) / 4): offset = s.offset - min_offset
offset_to_symbol_info[i] = s for i in range(offset / 4, (offset + s.size) / 4):
return offset_to_symbol_info self._offset_to_symbol_info[i] = s
return self._offset_to_symbol_info
class ProfileManager(object): class ProfileManager(object):
...@@ -222,11 +260,11 @@ class ProfileManager(object): ...@@ -222,11 +260,11 @@ class ProfileManager(object):
example the dump for the startup could be phase 0 and then the steady-state example the dump for the startup could be phase 0 and then the steady-state
would be labeled phase 1. would be labeled phase 1.
We assume the files are named like *-TIMESTAMP.SUFFIX_PHASE, where TIMESTAMP We assume the files are named like
is in nanoseconds, SUFFIX is string without dashes, PHASE is an integer profile-hitmap-PROCESS-PID-TIMESTAMP.SUFFIX_PHASE, where PROCESS is a possibly
numbering the phases as 0, 1, 2..., and the only dot is the one between empty string, PID is the process id, TIMESTAMP is in nanoseconds, SUFFIX is
TIMESTAMP and SUFFIX. Note that the current dump filename also includes a string without dashes, PHASE is an integer numbering the phases as 0, 1, 2...,
process id which is currently unused. and the only dot is the one between TIMESTAMP and SUFFIX.
This manager supports several configurations of dumps. This manager supports several configurations of dumps.
...@@ -242,6 +280,44 @@ class ProfileManager(object): ...@@ -242,6 +280,44 @@ class ProfileManager(object):
time. This files can be grouped into run sets that are within 30 seconds of time. This files can be grouped into run sets that are within 30 seconds of
each other. Each run set is then grouped into phases as before. each other. Each run set is then grouped into phases as before.
""" """
class AnnotatedOffset(object):
"""Describes an offset with how it appeared in a profile set.
Each offset is annotated with the phase and process that it appeared in, and
can report how often it occurred in a specific phase and process.
"""
def __init__(self, offset):
self._offset = offset
self._count = {}
def __str__(self):
return '{}: {}'.format(self._offset, self._count)
def __eq__(self, other):
if other is None:
return False
return (self._offset == other._offset and
self._count == other._count)
def Increment(self, phase, process):
key = (phase, process)
self._count[key] = self._count.setdefault(key, 0) + 1
def Count(self, phase, process):
return self._count.get((phase, process), 0)
def Processes(self):
return set(k[1] for k in self._count.iterkeys())
def Phases(self):
return set(k[0] for k in self._count.iterkeys())
def Offset(self):
return self._offset
def SetOffset(self, o):
self._offset = o
class _RunGroup(object): class _RunGroup(object):
RUN_GROUP_THRESHOLD_NS = 30e9 RUN_GROUP_THRESHOLD_NS = 30e9
...@@ -295,6 +371,22 @@ class ProfileManager(object): ...@@ -295,6 +371,22 @@ class ProfileManager(object):
return self._GetOffsetsForGroup(f for f in self._filenames return self._GetOffsetsForGroup(f for f in self._filenames
if self._Phase(f) == phase) if self._Phase(f) == phase)
def GetAnnotatedOffsets(self):
"""Merges offsets across run groups and annotates each one.
Returns:
[AnnotatedOffset]
"""
offset_map = {} # offset int -> AnnotatedOffset
for g in self._GetRunGroups():
for f in g:
phase = self._Phase(f)
process = self._ProcessName(f)
for offset in self._ReadOffsets(f):
offset_map.setdefault(offset, self.AnnotatedOffset(offset)).Increment(
phase, process)
return offset_map.values()
def GetRunGroupOffsets(self, phase=None): def GetRunGroupOffsets(self, phase=None):
"""Merges files from each run group and returns offset list for each. """Merges files from each run group and returns offset list for each.
...@@ -322,11 +414,21 @@ class ProfileManager(object): ...@@ -322,11 +414,21 @@ class ProfileManager(object):
self._ComputeRunGroups() self._ComputeRunGroups()
return [g.Filenames(phase) for g in self._run_groups] return [g.Filenames(phase) for g in self._run_groups]
@classmethod
def _ProcessName(cls, filename):
# The filename starts with 'profile-hitmap-' and ends with
# '-PID-TIMESTAMP.text_X'. Anything in between is the process name. The
# browser has an empty process name, which is insterted here.
process_name_parts = os.path.basename(filename).split('-')[2:-2]
if not process_name_parts:
return 'browser'
return '-'.join(process_name_parts)
@classmethod @classmethod
def _Timestamp(cls, filename): def _Timestamp(cls, filename):
dash_index = filename.rindex('-') dash_index = filename.rindex('-')
dot_index = filename.rindex('.') dot_index = filename.rindex('.')
return int(filename[dash_index+1:dot_index]) return int(filename[dash_index+1:dot_index])
@classmethod @classmethod
def _Phase(cls, filename): def _Phase(cls, filename):
...@@ -347,6 +449,19 @@ class ProfileManager(object): ...@@ -347,6 +449,19 @@ class ProfileManager(object):
g.Add(f) g.Add(f)
self._run_groups.append(g) self._run_groups.append(g)
# Some sanity checks on the run groups.
assert self._run_groups
if len(self._run_groups) < 5:
return # Small runs have too much variance for testing.
sizes = map(lambda g: len(g.Filenames()), self._run_groups)
avg_size = sum(sizes) / len(self._run_groups)
num_outliers = len([s for s in sizes
if s > 1.5 * avg_size or s < 0.75 * avg_size])
expected_outliers = 0.1 * len(self._run_groups)
assert num_outliers < expected_outliers, (
'Saw {} outliers instead of at most {} for average of {}'.format(
num_outliers, expected_outliers, avg_size))
def GetReachedOffsetsFromDumpFiles(dump_filenames, library_filename): def GetReachedOffsetsFromDumpFiles(dump_filenames, library_filename):
"""Produces a list of symbol offsets reached by the dumps. """Produces a list of symbol offsets reached by the dumps.
......
...@@ -10,7 +10,8 @@ import unittest ...@@ -10,7 +10,8 @@ import unittest
import process_profiles import process_profiles
from test_utils import (SimpleTestSymbol, from test_utils import (ProfileFile,
SimpleTestSymbol,
TestSymbolOffsetProcessor, TestSymbolOffsetProcessor,
TestProfileManager) TestProfileManager)
...@@ -28,10 +29,10 @@ class ProcessProfilesTestCase(unittest.TestCase): ...@@ -28,10 +29,10 @@ class ProcessProfilesTestCase(unittest.TestCase):
self.symbol_2, self.symbol_3] self.symbol_2, self.symbol_3]
self._file_counter = 0 self._file_counter = 0
def File(self, timestamp_sec, phase): def MakeAnnotatedOffset(self, offset, counts):
self._file_counter += 1 ao = process_profiles.ProfileManager.AnnotatedOffset(offset)
return 'file-{}-{}.txt_{}'.format( ao._count = counts
self._file_counter, timestamp_sec * 1000 * 1000 * 1000, phase) return ao
def testGetOffsetToSymbolInfo(self): def testGetOffsetToSymbolInfo(self):
processor = TestSymbolOffsetProcessor(self.symbol_infos) processor = TestSymbolOffsetProcessor(self.symbol_infos)
...@@ -103,8 +104,9 @@ class ProcessProfilesTestCase(unittest.TestCase): ...@@ -103,8 +104,9 @@ class ProcessProfilesTestCase(unittest.TestCase):
self.assertEquals(5, process_profiles._Median([1, 4, 5, 6, 100])) self.assertEquals(5, process_profiles._Median([1, 4, 5, 6, 100]))
def testRunGroups(self): def testRunGroups(self):
files = [self.File(40, 0), self.File(100, 0), self.File(200, 1), files = [ProfileFile(40, 0), ProfileFile(100, 0),
self.File(35, 1), self.File(42, 0), self.File(95, 0)] ProfileFile(200, 1), ProfileFile(35, 1),
ProfileFile(42, 0), ProfileFile(95, 0)]
mgr = process_profiles.ProfileManager(files) mgr = process_profiles.ProfileManager(files)
mgr._ComputeRunGroups() mgr._ComputeRunGroups()
self.assertEquals(3, len(mgr._run_groups)) self.assertEquals(3, len(mgr._run_groups))
...@@ -118,11 +120,34 @@ class ProcessProfilesTestCase(unittest.TestCase): ...@@ -118,11 +120,34 @@ class ProcessProfilesTestCase(unittest.TestCase):
self.assertTrue(files[5] in mgr._run_groups[1].Filenames()) self.assertTrue(files[5] in mgr._run_groups[1].Filenames())
self.assertTrue(files[2] in mgr._run_groups[2].Filenames()) self.assertTrue(files[2] in mgr._run_groups[2].Filenames())
def testRunGroupSanity(self):
files = []
# Generate 20 sets of files in groups separated by 60s.
for ts_base in xrange(0, 20):
ts = ts_base * 60
files.extend([ProfileFile(ts, 0, 'browser'),
ProfileFile(ts + 1, 0, 'renderer'),
ProfileFile(ts + 2, 1, 'browser'),
ProfileFile(ts + 3, 0, 'gpu'),
ProfileFile(ts + 2, 1, 'renderer'),
ProfileFile(ts + 5, 1, 'gpu')])
# The following call should not assert.
process_profiles.ProfileManager(files)._ComputeRunGroups()
files.extend([ProfileFile(20 * 60, 0, 'browser'),
ProfileFile(20 * 60 + 2, 1, 'renderer'),
ProfileFile(21 * 60, 0, 'browser')] +
[ProfileFile(22 * 60, 0, 'renderer')
for _ in xrange(0, 10)])
self.assertRaises(AssertionError,
process_profiles.ProfileManager(files)._ComputeRunGroups)
def testReadOffsets(self): def testReadOffsets(self):
mgr = TestProfileManager({ mgr = TestProfileManager({
self.File(30, 0): [1, 3, 5, 7], ProfileFile(30, 0): [1, 3, 5, 7],
self.File(40, 1): [8, 10], ProfileFile(40, 1): [8, 10],
self.File(50, 0): [13, 15]}) ProfileFile(50, 0): [13, 15]})
self.assertListEqual([1, 3, 5, 7, 8, 10, 13, 15], self.assertListEqual([1, 3, 5, 7, 8, 10, 13, 15],
mgr.GetMergedOffsets()) mgr.GetMergedOffsets())
self.assertListEqual([8, 10], mgr.GetMergedOffsets(1)) self.assertListEqual([8, 10], mgr.GetMergedOffsets(1))
...@@ -130,9 +155,9 @@ class ProcessProfilesTestCase(unittest.TestCase): ...@@ -130,9 +155,9 @@ class ProcessProfilesTestCase(unittest.TestCase):
def testRunGroupOffsets(self): def testRunGroupOffsets(self):
mgr = TestProfileManager({ mgr = TestProfileManager({
self.File(30, 0): [1, 2, 3, 4], ProfileFile(30, 0): [1, 2, 3, 4],
self.File(150, 0): [9, 11, 13], ProfileFile(150, 0): [9, 11, 13],
self.File(40, 1): [5, 6, 7]}) ProfileFile(40, 1): [5, 6, 7]})
offsets_list = mgr.GetRunGroupOffsets() offsets_list = mgr.GetRunGroupOffsets()
self.assertEquals(2, len(offsets_list)) self.assertEquals(2, len(offsets_list))
self.assertListEqual([1, 2, 3, 4, 5, 6, 7], offsets_list[0]) self.assertListEqual([1, 2, 3, 4, 5, 6, 7], offsets_list[0])
...@@ -150,22 +175,54 @@ class ProcessProfilesTestCase(unittest.TestCase): ...@@ -150,22 +175,54 @@ class ProcessProfilesTestCase(unittest.TestCase):
# The fact that the ProfileManager sorts by filename is implicit in the # The fact that the ProfileManager sorts by filename is implicit in the
# other tests. It is tested explicitly here. # other tests. It is tested explicitly here.
mgr = TestProfileManager({ mgr = TestProfileManager({
self.File(40, 0): [1, 2, 3, 4], ProfileFile(40, 0): [1, 2, 3, 4],
self.File(150, 0): [9, 11, 13], ProfileFile(150, 0): [9, 11, 13],
self.File(30, 1): [5, 6, 7]}) ProfileFile(30, 1): [5, 6, 7]})
offsets_list = mgr.GetRunGroupOffsets() offsets_list = mgr.GetRunGroupOffsets()
self.assertEquals(2, len(offsets_list)) self.assertEquals(2, len(offsets_list))
self.assertListEqual([5, 6, 7, 1, 2, 3, 4], offsets_list[0]) self.assertListEqual([5, 6, 7, 1, 2, 3, 4], offsets_list[0])
def testPhases(self): def testPhases(self):
mgr = TestProfileManager({ mgr = TestProfileManager({
self.File(40, 0): [], ProfileFile(40, 0): [],
self.File(150, 0): [], ProfileFile(150, 0): [],
self.File(30, 1): [], ProfileFile(30, 1): [],
self.File(30, 2): [], ProfileFile(30, 2): [],
self.File(30, 0): []}) ProfileFile(30, 0): []})
self.assertEquals(set([0,1,2]), mgr.GetPhases()) self.assertEquals(set([0,1,2]), mgr.GetPhases())
def testGetAnnotatedOffsets(self):
mgr = TestProfileManager({
ProfileFile(40, 0, ''): [1, 2, 3],
ProfileFile(50, 1, ''): [3, 4, 5],
ProfileFile(51, 0, 'renderer'): [2, 3, 6],
ProfileFile(51, 1, 'gpu-process'): [6, 7],
ProfileFile(70, 0, ''): [2, 8, 9],
ProfileFile(70, 1, ''): [9]})
offsets = mgr.GetAnnotatedOffsets()
self.assertListEqual([
self.MakeAnnotatedOffset(1, {(0, 'browser'): 1}),
self.MakeAnnotatedOffset(2, {(0, 'browser'): 2,
(0, 'renderer'): 1}),
self.MakeAnnotatedOffset(3, {(0, 'browser'): 1,
(1, 'browser'): 1,
(0, 'renderer'): 1}),
self.MakeAnnotatedOffset(4, {(1, 'browser'): 1}),
self.MakeAnnotatedOffset(5, {(1, 'browser'): 1}),
self.MakeAnnotatedOffset(6, {(0, 'renderer'): 1,
(1, 'gpu-process'): 1}),
self.MakeAnnotatedOffset(7, {(1, 'gpu-process'): 1}),
self.MakeAnnotatedOffset(8, {(0, 'browser'): 1}),
self.MakeAnnotatedOffset(9, {(0, 'browser'): 1,
(1, 'browser'): 1})],
offsets)
self.assertListEqual(['browser', 'renderer'],
sorted(offsets[1].Processes()))
self.assertListEqual(['browser'], list(offsets[0].Processes()))
self.assertListEqual([0], list(offsets[1].Phases()))
self.assertListEqual([0, 1], sorted(offsets[2].Phases()))
self.assertListEqual([0, 1], sorted(mgr.GetPhases()))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -8,6 +8,9 @@ import collections ...@@ -8,6 +8,9 @@ import collections
import process_profiles import process_profiles
# Used by ProfileFile to generate unique file names.
_FILE_COUNTER = 0
SimpleTestSymbol = collections.namedtuple( SimpleTestSymbol = collections.namedtuple(
'SimpleTestSymbol', ['name', 'offset', 'size']) 'SimpleTestSymbol', ['name', 'offset', 'size'])
...@@ -25,3 +28,14 @@ class TestProfileManager(process_profiles.ProfileManager): ...@@ -25,3 +28,14 @@ class TestProfileManager(process_profiles.ProfileManager):
def _ReadOffsets(self, filename): def _ReadOffsets(self, filename):
return self._filecontents_mapping[filename] return self._filecontents_mapping[filename]
def ProfileFile(timestamp_sec, phase, process_name=None):
global _FILE_COUNTER
_FILE_COUNTER += 1
if process_name:
name_str = process_name + '-'
else:
name_str = ''
return 'test-directory/profile-hitmap-{}{}-{}.txt_{}'.format(
name_str, _FILE_COUNTER, timestamp_sec * 1000 * 1000 * 1000, phase)
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