[LNT] r263559 - [profile] Add the initial scaffold for the LNT profiling infrastructure
James Molloy via llvm-commits
llvm-commits at lists.llvm.org
Tue Mar 15 09:52:04 PDT 2016
Author: jamesm
Date: Tue Mar 15 11:52:04 2016
New Revision: 263559
URL: http://llvm.org/viewvc/llvm-project?rev=263559&view=rev
Log:
[profile] Add the initial scaffold for the LNT profiling infrastructure
This adds the API that will be used to query profiles in LNT's web stack along with a dumb implementation.
The dumb implementation serves as a way to easily import profile data (from whatever profile source). The auto-upgrade functionality in the profile API will then allow that to be upgraded to a more complex, more compact profile representation to be added later.
Added:
lnt/trunk/lnt/testing/profile/
lnt/trunk/lnt/testing/profile/__init__.py
lnt/trunk/lnt/testing/profile/profile.py
lnt/trunk/lnt/testing/profile/profilev1impl.py
lnt/trunk/tests/testing/profilev1impl.py
Added: lnt/trunk/lnt/testing/profile/__init__.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/testing/profile/__init__.py?rev=263559&view=auto
==============================================================================
--- lnt/trunk/lnt/testing/profile/__init__.py (added)
+++ lnt/trunk/lnt/testing/profile/__init__.py Tue Mar 15 11:52:04 2016
@@ -0,0 +1,4 @@
+# This is the profile implementation registry. Register new profile implementations here.
+
+from profilev1impl import ProfileV1
+IMPLEMENTATIONS = {1: ProfileV1}
Added: lnt/trunk/lnt/testing/profile/profile.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/testing/profile/profile.py?rev=263559&view=auto
==============================================================================
--- lnt/trunk/lnt/testing/profile/profile.py (added)
+++ lnt/trunk/lnt/testing/profile/profile.py Tue Mar 15 11:52:04 2016
@@ -0,0 +1,206 @@
+import os, tempfile, base64
+
+class Profile(object):
+ """Profile objects hold a performance profile.
+
+ The Profile class itself is a thin wrapper around a ProfileImpl
+ object, which is what actually holds, reads, writes and dispenses
+ the profile information.
+ """
+ # Import this late to avoid a cyclic dependency
+ import lnt.testing.profile
+
+ def __init__(self, impl):
+ """Internal constructor. Users should not call this; use fromFile or
+ fromRendered."""
+ assert isinstance(impl, ProfileImpl)
+ self.impl = impl
+
+ @staticmethod
+ def fromFile(f):
+ """
+ Load a profile from a file.
+ """
+ for impl in lnt.testing.profile.IMPLEMENTATIONS.values():
+ if impl.checkFile(f):
+ ret = impl.deserialize(open(f, 'rb'))
+ if ret:
+ return Profile(ret)
+ else:
+ return None
+ raise RuntimeError('No profile implementations could read this file!')
+
+ @staticmethod
+ def fromRendered(s):
+ """
+ Load a profile from a string, which must have been produced
+ with Profile.render(). The format of this is not the same as the
+ on-disk format; it is base64 encoded to survive wire transfer.
+ """
+ s = base64.b64decode(s)
+ with tempfile.NamedTemporaryFile() as fd:
+ fd.write(s)
+ # Rewind to beginning.
+ fd.flush()
+ fd.seek(0)
+
+ for impl in lnt.testing.profile.IMPLEMENTATIONS.values():
+ if impl.checkFile(fd.name):
+ ret = impl.deserialize(fd)
+ if ret:
+ return Profile(ret)
+ else:
+ return None
+ raise RuntimeError('No profile implementations could read this file!')
+
+ def save(self, filename=None, profileDir=None, prefix=''):
+ """
+ Save a profile. One of 'filename' or 'profileDir' must be given.
+ - If 'filename' is given, that is where the profile is saved.
+ - If 'profileDir' is given, a new unique filename is created
+ inside 'profileDir', optionally with 'prefix'.
+
+ The filename written to is returned.
+ """
+ if filename:
+ self.impl.serialize(filename)
+ return filename
+
+ assert profileDir is not None
+ if not os.path.exists(profileDir):
+ os.makedirs(profileDir)
+ tf = tempfile.NamedTemporaryFile(prefix=prefix,
+ suffix='.lntprof',
+ dir=profileDir,
+ delete=False)
+ self.impl.serialize(tf.name)
+
+ # FIXME: make the returned filepath relative to baseDir?
+ return tf.name
+
+ def render(self):
+ """
+ Return a string representing this profile suitable for storing inside a
+ JSON object.
+
+ Implementation note: the string is base64 encoded.
+ """
+ return base64.b64encode(self.impl.serialize())
+
+ def upgrade(self):
+ """
+ Upgrade to the latest implementation version.
+
+ Returns self.
+ """
+ while True:
+ version = self.impl.getVersion()
+ new_version = version + 1
+ if new_version not in lnt.testing.profile.IMPLEMENTATIONS:
+ return self
+ self.impl = lnt.testing.profile.IMPLEMENTATIONS[new_version].upgrade(self.impl)
+
+ #
+ # ProfileImpl facade - see ProfileImpl documentation below.
+ #
+
+ def getVersion(self):
+ return self.impl.getVersion()
+
+ def getTopLevelCounters(self):
+ return self.impl.getTopLevelCounters()
+
+ def getDisassemblyFormat(self):
+ return self.impl.getDisassemblyFormat()
+
+ def getFunctions(self):
+ return self.impl.getFunctions()
+
+ def getCodeForFunction(self, fname):
+ return self.impl.getCodeForFunction(fname)
+
+################################################################################
+
+class ProfileImpl(object):
+ @staticmethod
+ def upgrade(old):
+ """
+ Takes a previous profile implementation in 'old' and returns a new ProfileImpl
+ for this version. The only old version that must be supported is the immediately
+ prior version (e.g. version 3 only has to handle upgrades from version 2.
+ """
+ raise NotImplementedError("Abstract class")
+
+ @staticmethod
+ def checkFile(fname):
+ """
+ Return True if 'fname' is a serialized version of this profile implementation.
+ """
+ raise NotImplementedError("Abstract class")
+
+ @staticmethod
+ def deserialize(fobj):
+ """
+ Reads a profile from 'fobj', returning a new profile object. This can be lazy.
+ """
+ raise NotImplementedError("Abstract class")
+
+ def serialize(self, fname=None):
+ """
+ Serializes the profile to the given filename (base). If fname is None, returns
+ as a bytes instance.
+ """
+ raise NotImplementedError("Abstract class")
+
+ def getVersion(self):
+ """
+ Return the profile version.
+ """
+ raise NotImplementedError("Abstract class")
+
+ def getTopLevelCounters(self):
+ """
+ Return a dict containing the counters for the entire profile. These will
+ be absolute numbers: {'cycles': 5000.0} for example.
+ """
+ raise NotImplementedError("Abstract class")
+
+ def getDisassemblyFormat(self):
+ """
+ Return the format for the disassembly strings returned by getCodeForFunction().
+ Possible values are:
+ 'raw' - No interpretation available - pure strings.
+ 'marked-up-disassembly' - LLVM marked up disassembly format.
+ """
+ raise NotImplementedError("Abstract class")
+
+ def getFunctions(self):
+ """
+ Return a dict containing function names to information about that function.
+
+ The information dict contains:
+ - 'counters' - counter values for the function.
+ - 'length' - number of times to call getCodeForFunction to obtain all
+ instructions.
+
+ The dict should *not* contain disassembly / function contents.
+ The counter values must be percentages, not absolute numbers.
+
+ E.g. {'main': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 200},
+ 'dotest': {'counters': {'cycles': 50.0, 'branch-misses': 0}, 'length': 4}}
+ """
+ raise NotImplementedError("Abstract class")
+
+ def getCodeForFunction(self, fname):
+ """
+ Return a *generator* which will return, for every invocation, a three-tuple:
+
+ (counters, address, text)
+
+ Where counters is a dict : (e.g.) {'cycles': 50.0}, text is in the
+ format as returned by getDisassemblyFormat(), and address is an integer.
+
+ The counter values must be percentages (of the function total), not
+ absolute numbers.
+ """
+ raise NotImplementedError("Abstract class")
Added: lnt/trunk/lnt/testing/profile/profilev1impl.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/testing/profile/profilev1impl.py?rev=263559&view=auto
==============================================================================
--- lnt/trunk/lnt/testing/profile/profilev1impl.py (added)
+++ lnt/trunk/lnt/testing/profile/profilev1impl.py Tue Mar 15 11:52:04 2016
@@ -0,0 +1,81 @@
+from lnt.testing.profile.profile import ProfileImpl
+import cPickle, zlib
+
+class ProfileV1(ProfileImpl):
+ """
+ProfileV1 files not clever in any way. They are simple Python objects with
+the profile data layed out in the most obvious way for production/consumption
+that are then pickled and compressed.
+
+They are expected to be created by simply storing into the self.data member.
+
+The self.data member has this format:
+
+{
+ counters: {'cycles': 12345.0, 'branch-misses': 200.0}, # Counter values are absolute.
+ disassembly-format: 'raw',
+ functions: {
+ name: {
+ counters: {'cycles': 45.0, ...}, # Note counters are now percentages.
+ data: [
+ [463464, {'cycles': 23.0, ...}, '\tadd r0, r0, r1'}],
+ ...
+ ]
+ }
+ }
+}
+ """
+
+ def __init__(self, data):
+ """
+ Create from a raw data dict. data has the format given in the class docstring.
+ """
+ self.data = data
+
+ @staticmethod
+ def upgrade(old):
+ raise RuntimeError("Cannot upgrade to version 1!")
+
+ @staticmethod
+ def checkFile(fn):
+ # "zlib compressed data" - 78 9C
+ return open(fn).read(2) == '\x78\x9c'
+
+ @staticmethod
+ def deserialize(fobj):
+ o = zlib.decompress(fobj.read())
+ data = cPickle.loads(o)
+ return ProfileV1(data)
+
+ def serialize(self, fname=None):
+ obj = cPickle.dumps(self.data)
+ compressed_obj = zlib.compress(obj)
+
+ if fname is None:
+ return bytes(compressed_obj)
+ else:
+ with open(fname, 'w') as fd:
+ fd.write(compressed_obj)
+
+ def getVersion(self):
+ return 1
+
+ def getTopLevelCounters(self):
+ return self.data['counters']
+
+ def getDisassemblyFormat(self):
+ if 'disassembly-format' in self.data:
+ return self.data['disassembly-format']
+ return 'raw'
+
+ def getFunctions(self):
+ d = {}
+ for fn in self.data['functions']:
+ f = self.data['functions'][fn]
+ d[fn] = dict(counters=f.get('counters', {}),
+ length=len(f.get('data', [])))
+ return d
+
+ def getCodeForFunction(self, fname):
+ for l in self.data['functions'][fname].get('data', []):
+ yield (l[0], l[1], l[2])
Added: lnt/trunk/tests/testing/profilev1impl.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/tests/testing/profilev1impl.py?rev=263559&view=auto
==============================================================================
--- lnt/trunk/tests/testing/profilev1impl.py (added)
+++ lnt/trunk/tests/testing/profilev1impl.py Tue Mar 15 11:52:04 2016
@@ -0,0 +1,45 @@
+# RUN: python %s
+import unittest, logging, sys, copy, tempfile, io
+from lnt.testing.profile.profilev1impl import ProfileV1
+
+
+logging.basicConfig(level=logging.DEBUG)
+
+class ProfileV1Test(unittest.TestCase):
+ def setUp(self):
+ self.test_data = {
+ 'counters': {'cycles': 12345.0, 'branch-misses': 200.0},
+ 'disassembly-format': 'raw',
+ 'functions': {
+ 'fn1': {
+ 'counters': {'cycles': 45.0, 'branch-misses': 10.0},
+ 'data': [
+ [{}, 0x100000, 'add r0, r0, r0'],
+ [{'cycles': 100.0}, 0x100004, 'sub r1, r0, r0']
+ ]
+ }
+ }
+ }
+
+ def test_serialize(self):
+ p = ProfileV1(copy.deepcopy(self.test_data))
+ with tempfile.NamedTemporaryFile() as f:
+ s = p.serialize(f.name)
+ self.assertTrue(ProfileV1.checkFile(f.name))
+
+ def test_deserialize(self):
+ p = ProfileV1(copy.deepcopy(self.test_data))
+ s = p.serialize()
+ fobj = io.BytesIO(s)
+ p2 = ProfileV1.deserialize(fobj)
+
+ self.assertEqual(p2.data, self.test_data)
+
+ def test_getFunctions(self):
+ p = ProfileV1(copy.deepcopy(self.test_data))
+ self.assertEqual(p.getFunctions(),
+ {'fn1': {'counters': {'cycles': 45.0, 'branch-misses': 10.0},
+ 'length': 2}})
+
+if __name__ == '__main__':
+ unittest.main(argv=[sys.argv[0], ])
More information about the llvm-commits
mailing list