diff options
Diffstat (limited to 'src/3rdparty/v8/tools/testrunner/network')
-rw-r--r-- | src/3rdparty/v8/tools/testrunner/network/__init__.py | 26 | ||||
-rw-r--r-- | src/3rdparty/v8/tools/testrunner/network/distro.py | 90 | ||||
-rw-r--r-- | src/3rdparty/v8/tools/testrunner/network/endpoint.py | 124 | ||||
-rw-r--r-- | src/3rdparty/v8/tools/testrunner/network/network_execution.py | 253 | ||||
-rw-r--r-- | src/3rdparty/v8/tools/testrunner/network/perfdata.py | 120 |
5 files changed, 613 insertions, 0 deletions
diff --git a/src/3rdparty/v8/tools/testrunner/network/__init__.py b/src/3rdparty/v8/tools/testrunner/network/__init__.py new file mode 100644 index 0000000..202a262 --- /dev/null +++ b/src/3rdparty/v8/tools/testrunner/network/__init__.py @@ -0,0 +1,26 @@ +# Copyright 2012 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/3rdparty/v8/tools/testrunner/network/distro.py b/src/3rdparty/v8/tools/testrunner/network/distro.py new file mode 100644 index 0000000..9d5a471 --- /dev/null +++ b/src/3rdparty/v8/tools/testrunner/network/distro.py @@ -0,0 +1,90 @@ +# Copyright 2012 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +class Shell(object): + def __init__(self, shell): + self.shell = shell + self.tests = [] + self.total_duration = 0.0 + + def AddSuite(self, suite): + self.tests += suite.tests + self.total_duration += suite.total_duration + + def SortTests(self): + self.tests.sort(cmp=lambda x, y: cmp(x.duration, y.duration)) + + +def Assign(suites, peers): + total_work = 0.0 + for s in suites: + total_work += s.CalculateTotalDuration() + + total_power = 0.0 + for p in peers: + p.assigned_work = 0.0 + total_power += p.jobs * p.relative_performance + for p in peers: + p.needed_work = total_work * p.jobs * p.relative_performance / total_power + + shells = {} + for s in suites: + shell = s.shell() + if not shell in shells: + shells[shell] = Shell(shell) + shells[shell].AddSuite(s) + # Convert |shells| to list and sort it, shortest total_duration first. + shells = [ shells[s] for s in shells ] + shells.sort(cmp=lambda x, y: cmp(x.total_duration, y.total_duration)) + # Sort tests within each shell, longest duration last (so it's + # pop()'ed first). + for s in shells: s.SortTests() + # Sort peers, least needed_work first. + peers.sort(cmp=lambda x, y: cmp(x.needed_work, y.needed_work)) + index = 0 + for shell in shells: + while len(shell.tests) > 0: + while peers[index].needed_work <= 0: + index += 1 + if index == len(peers): + print("BIG FAT WARNING: Assigning tests to peers failed. " + "Remaining tests: %d. Going to slow mode." % len(shell.tests)) + # Pick the least-busy peer. Sorting the list for each test + # is terribly slow, but this is just an emergency fallback anyway. + peers.sort(cmp=lambda x, y: cmp(x.needed_work, y.needed_work)) + peers[0].ForceAddOneTest(shell.tests.pop(), shell) + # If the peer already has a shell assigned and would need this one + # and then yet another, try to avoid it. + peer = peers[index] + if (shell.total_duration < peer.needed_work and + len(peer.shells) > 0 and + index < len(peers) - 1 and + shell.total_duration <= peers[index + 1].needed_work): + peers[index + 1].AddTests(shell) + else: + peer.AddTests(shell) diff --git a/src/3rdparty/v8/tools/testrunner/network/endpoint.py b/src/3rdparty/v8/tools/testrunner/network/endpoint.py new file mode 100644 index 0000000..5dc2b9f --- /dev/null +++ b/src/3rdparty/v8/tools/testrunner/network/endpoint.py @@ -0,0 +1,124 @@ +# Copyright 2012 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +import multiprocessing +import os +import Queue +import threading +import time + +from ..local import execution +from ..local import progress +from ..local import testsuite +from ..local import utils +from ..server import compression + + +class EndpointProgress(progress.ProgressIndicator): + def __init__(self, sock, server, ctx): + super(EndpointProgress, self).__init__() + self.sock = sock + self.server = server + self.context = ctx + self.results_queue = [] # Accessors must synchronize themselves. + self.sender_lock = threading.Lock() + self.senderthread = threading.Thread(target=self._SenderThread) + self.senderthread.start() + + def HasRun(self, test): + # The runners that call this have a lock anyway, so this is safe. + self.results_queue.append(test) + + def _SenderThread(self): + keep_running = True + tests = [] + self.sender_lock.acquire() + while keep_running: + time.sleep(0.1) + # This should be "atomic enough" without locking :-) + # (We don't care which list any new elements get appended to, as long + # as we don't lose any and the last one comes last.) + current = self.results_queue + self.results_queue = [] + for c in current: + if c is None: + keep_running = False + else: + tests.append(c) + if keep_running and len(tests) < 1: + continue # Wait for more results. + if len(tests) < 1: break # We're done here. + result = [] + for t in tests: + result.append(t.PackResult()) + try: + compression.Send(result, self.sock) + except: + self.runner.terminate = True + for t in tests: + self.server.CompareOwnPerf(t, self.context.arch, self.context.mode) + tests = [] + self.sender_lock.release() + + +def Execute(workspace, ctx, tests, sock, server): + suite_paths = utils.GetSuitePaths(os.path.join(workspace, "test")) + suites = [] + for root in suite_paths: + suite = testsuite.TestSuite.LoadTestSuite( + os.path.join(workspace, "test", root)) + if suite: + suites.append(suite) + + suites_dict = {} + for s in suites: + suites_dict[s.name] = s + s.tests = [] + for t in tests: + suite = suites_dict[t.suite] + t.suite = suite + suite.tests.append(t) + + suites = [ s for s in suites if len(s.tests) > 0 ] + for s in suites: + s.DownloadData() + + progress_indicator = EndpointProgress(sock, server, ctx) + runner = execution.Runner(suites, progress_indicator, ctx) + try: + runner.Run(server.jobs) + except IOError, e: + if e.errno == 2: + message = ("File not found: %s, maybe you forgot to 'git add' it?" % + e.filename) + else: + message = "%s" % e + compression.Send([[-1, message]], sock) + progress_indicator.HasRun(None) # Sentinel to signal the end. + progress_indicator.sender_lock.acquire() # Released when sending is done. + progress_indicator.sender_lock.release() diff --git a/src/3rdparty/v8/tools/testrunner/network/network_execution.py b/src/3rdparty/v8/tools/testrunner/network/network_execution.py new file mode 100644 index 0000000..ddb59e6 --- /dev/null +++ b/src/3rdparty/v8/tools/testrunner/network/network_execution.py @@ -0,0 +1,253 @@ +# Copyright 2012 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +import os +import socket +import subprocess +import threading +import time + +from . import distro +from . import perfdata +from ..local import execution +from ..objects import peer +from ..objects import workpacket +from ..server import compression +from ..server import constants +from ..server import local_handler +from ..server import signatures + + +def GetPeers(): + data = local_handler.LocalQuery([constants.REQUEST_PEERS]) + if not data: return [] + return [ peer.Peer.Unpack(p) for p in data ] + + +class NetworkedRunner(execution.Runner): + def __init__(self, suites, progress_indicator, context, peers, workspace): + self.suites = suites + num_tests = 0 + datapath = os.path.join("out", "testrunner_data") + self.perf_data_manager = perfdata.PerfDataManager(datapath) + self.perfdata = self.perf_data_manager.GetStore(context.arch, context.mode) + for s in suites: + for t in s.tests: + t.duration = self.perfdata.FetchPerfData(t) or 1.0 + num_tests += len(s.tests) + self._CommonInit(num_tests, progress_indicator, context) + self.tests = [] # Only used if we need to fall back to local execution. + self.tests_lock = threading.Lock() + self.peers = peers + self.pubkey_fingerprint = None # Fetched later. + self.base_rev = subprocess.check_output( + "cd %s; git log -1 --format=%%H --grep=git-svn-id" % workspace, + shell=True).strip() + self.base_svn_rev = subprocess.check_output( + "cd %s; git log -1 %s" # Get commit description. + " | grep -e '^\s*git-svn-id:'" # Extract "git-svn-id" line. + " | awk '{print $2}'" # Extract "repository@revision" part. + " | sed -e 's/.*@//'" % # Strip away "repository@". + (workspace, self.base_rev), shell=True).strip() + self.patch = subprocess.check_output( + "cd %s; git diff %s" % (workspace, self.base_rev), shell=True) + self.binaries = {} + self.initialization_lock = threading.Lock() + self.initialization_lock.acquire() # Released when init is done. + self._OpenLocalConnection() + self.local_receiver_thread = threading.Thread( + target=self._ListenLocalConnection) + self.local_receiver_thread.daemon = True + self.local_receiver_thread.start() + self.initialization_lock.acquire() + self.initialization_lock.release() + + def _OpenLocalConnection(self): + self.local_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + code = self.local_socket.connect_ex(("localhost", constants.CLIENT_PORT)) + if code != 0: + raise RuntimeError("Failed to connect to local server") + compression.Send([constants.REQUEST_PUBKEY_FINGERPRINT], self.local_socket) + + def _ListenLocalConnection(self): + release_lock_countdown = 1 # Pubkey. + self.local_receiver = compression.Receiver(self.local_socket) + while not self.local_receiver.IsDone(): + data = self.local_receiver.Current() + if data[0] == constants.REQUEST_PUBKEY_FINGERPRINT: + pubkey = data[1] + if not pubkey: raise RuntimeError("Received empty public key") + self.pubkey_fingerprint = pubkey + release_lock_countdown -= 1 + if release_lock_countdown == 0: + self.initialization_lock.release() + release_lock_countdown -= 1 # Prevent repeated triggering. + self.local_receiver.Advance() + + def Run(self, jobs): + self.indicator.Starting() + need_libv8 = False + for s in self.suites: + shell = s.shell() + if shell not in self.binaries: + path = os.path.join(self.context.shell_dir, shell) + # Check if this is a shared library build. + try: + ldd = subprocess.check_output("ldd %s | grep libv8\\.so" % (path), + shell=True) + ldd = ldd.strip().split(" ") + assert ldd[0] == "libv8.so" + assert ldd[1] == "=>" + need_libv8 = True + binary_needs_libv8 = True + libv8 = signatures.ReadFileAndSignature(ldd[2]) + except: + binary_needs_libv8 = False + binary = signatures.ReadFileAndSignature(path) + if binary[0] is None: + print("Error: Failed to create signature.") + assert binary[1] != 0 + return binary[1] + binary.append(binary_needs_libv8) + self.binaries[shell] = binary + if need_libv8: + self.binaries["libv8.so"] = libv8 + distro.Assign(self.suites, self.peers) + # Spawn one thread for each peer. + threads = [] + for p in self.peers: + thread = threading.Thread(target=self._TalkToPeer, args=[p]) + threads.append(thread) + thread.start() + try: + for thread in threads: + # Use a timeout so that signals (Ctrl+C) will be processed. + thread.join(timeout=10000000) + self._AnalyzePeerRuntimes() + except KeyboardInterrupt: + self.terminate = True + raise + except Exception, _e: + # If there's an exception we schedule an interruption for any + # remaining threads... + self.terminate = True + # ...and then reraise the exception to bail out. + raise + compression.Send(constants.END_OF_STREAM, self.local_socket) + self.local_socket.close() + if self.tests: + self._RunInternal(jobs) + self.indicator.Done() + return not self.failed + + def _TalkToPeer(self, peer): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(self.context.timeout + 10) + code = sock.connect_ex((peer.address, constants.PEER_PORT)) + if code == 0: + try: + peer.runtime = None + start_time = time.time() + packet = workpacket.WorkPacket(peer=peer, context=self.context, + base_revision=self.base_svn_rev, + patch=self.patch, + pubkey=self.pubkey_fingerprint) + data, test_map = packet.Pack(self.binaries) + compression.Send(data, sock) + compression.Send(constants.END_OF_STREAM, sock) + rec = compression.Receiver(sock) + while not rec.IsDone() and not self.terminate: + data_list = rec.Current() + for data in data_list: + test_id = data[0] + if test_id < 0: + # The peer is reporting an error. + with self.lock: + print("\nPeer %s reports error: %s" % (peer.address, data[1])) + continue + test = test_map.pop(test_id) + test.MergeResult(data) + try: + self.perfdata.UpdatePerfData(test) + except Exception, e: + print("UpdatePerfData exception: %s" % e) + pass # Just keep working. + with self.lock: + perf_key = self.perfdata.GetKey(test) + compression.Send( + [constants.INFORM_DURATION, perf_key, test.duration, + self.context.arch, self.context.mode], + self.local_socket) + self.indicator.AboutToRun(test) + if test.suite.HasUnexpectedOutput(test): + self.failed.append(test) + if test.output.HasCrashed(): + self.crashed += 1 + else: + self.succeeded += 1 + self.remaining -= 1 + self.indicator.HasRun(test) + rec.Advance() + peer.runtime = time.time() - start_time + except KeyboardInterrupt: + sock.close() + raise + except Exception, e: + print("Got exception: %s" % e) + pass # Fall back to local execution. + else: + compression.Send([constants.UNRESPONSIVE_PEER, peer.address], + self.local_socket) + sock.close() + if len(test_map) > 0: + # Some tests have not received any results. Run them locally. + print("\nNo results for %d tests, running them locally." % len(test_map)) + self._EnqueueLocally(test_map) + + def _EnqueueLocally(self, test_map): + with self.tests_lock: + for test in test_map: + self.tests.append(test_map[test]) + + def _AnalyzePeerRuntimes(self): + total_runtime = 0.0 + total_work = 0.0 + for p in self.peers: + if p.runtime is None: + return + total_runtime += p.runtime + total_work += p.assigned_work + for p in self.peers: + p.assigned_work /= total_work + p.runtime /= total_runtime + perf_correction = p.assigned_work / p.runtime + old_perf = p.relative_performance + p.relative_performance = (old_perf + perf_correction) / 2.0 + compression.Send([constants.UPDATE_PERF, p.address, + p.relative_performance], + self.local_socket) diff --git a/src/3rdparty/v8/tools/testrunner/network/perfdata.py b/src/3rdparty/v8/tools/testrunner/network/perfdata.py new file mode 100644 index 0000000..2979dc4 --- /dev/null +++ b/src/3rdparty/v8/tools/testrunner/network/perfdata.py @@ -0,0 +1,120 @@ +# Copyright 2012 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +import os +import shelve +import threading + + +class PerfDataEntry(object): + def __init__(self): + self.avg = 0.0 + self.count = 0 + + def AddResult(self, result): + kLearnRateLimiter = 99 # Greater value means slower learning. + # We use an approximation of the average of the last 100 results here: + # The existing average is weighted with kLearnRateLimiter (or less + # if there are fewer data points). + effective_count = min(self.count, kLearnRateLimiter) + self.avg = self.avg * effective_count + result + self.count = effective_count + 1 + self.avg /= self.count + + +class PerfDataStore(object): + def __init__(self, datadir, arch, mode): + filename = os.path.join(datadir, "%s.%s.perfdata" % (arch, mode)) + self.database = shelve.open(filename, protocol=2) + self.closed = False + self.lock = threading.Lock() + + def __del__(self): + self.close() + + def close(self): + if self.closed: return + self.database.close() + self.closed = True + + def GetKey(self, test): + """Computes the key used to access data for the given testcase.""" + flags = "".join(test.flags) + return str("%s.%s.%s" % (test.suitename(), test.path, flags)) + + def FetchPerfData(self, test): + """Returns the observed duration for |test| as read from the store.""" + key = self.GetKey(test) + if key in self.database: + return self.database[key].avg + return None + + def UpdatePerfData(self, test): + """Updates the persisted value in the store with test.duration.""" + testkey = self.GetKey(test) + self.RawUpdatePerfData(testkey, test.duration) + + def RawUpdatePerfData(self, testkey, duration): + with self.lock: + if testkey in self.database: + entry = self.database[testkey] + else: + entry = PerfDataEntry() + entry.AddResult(duration) + self.database[testkey] = entry + + +class PerfDataManager(object): + def __init__(self, datadir): + self.datadir = os.path.abspath(datadir) + if not os.path.exists(self.datadir): + os.makedirs(self.datadir) + self.stores = {} # Keyed by arch, then mode. + self.closed = False + self.lock = threading.Lock() + + def __del__(self): + self.close() + + def close(self): + if self.closed: return + for arch in self.stores: + modes = self.stores[arch] + for mode in modes: + store = modes[mode] + store.close() + self.closed = True + + def GetStore(self, arch, mode): + with self.lock: + if not arch in self.stores: + self.stores[arch] = {} + modes = self.stores[arch] + if not mode in modes: + modes[mode] = PerfDataStore(self.datadir, arch, mode) + return modes[mode] |