#! /usr/bin/env python3 # # Main driver for benchmarks # # Copyright (C) 2016, Robert Cowham, Perforce # from __future__ import print_function import logging import os import P4 import platform import sys import random import string import subprocess from argparse import ArgumentParser import yaml from createfiles import create_file, generator python3 = sys.version_info[0] >= 3 CONFIG_FILE = "benchmark_config.yaml" # Configuration parameters # The following can be overridden in the config file svn/svnexe SVN = "svn" # Default SVN command if platform.system() == "Windows": SVN = "svn.exe" def readConfig(startdir): config = {} with open(os.path.join(startdir, CONFIG_FILE), "r") as f: config = yaml.load(f) return config logger = logging.getLogger("repo_benchmark") if not (0x02070000 <= sys.hexversion < 0x02080000): sys.exit("Python 2.7 is required to run this program.") LINE_LENGTH = 80 BLOCK_SIZE = 65536 class RepoBenchmark(object): """Generic benchmarking class - must be subclassed""" def __init__(self, startdir, config): self.id = id(self) # Object id - unique enough for now. if "workspace_root" in config["general"]: self.test_root = config["general"]["workspace_root"] else: self.test_root = os.path.join(startdir, "testdata") self.localfilelist = [] def createWorkspace(self): raise NotImplementedError("createWorkspace") def syncWorkspace(self): raise NotImplementedError("syncWorkspace") def addFile(self, filename): raise NotImplementedError("addFile") def editFile(self, filename): raise NotImplementedError("editFile") def deleteFile(self, filename): raise NotImplementedError("deleteFile") def basicFileActions(self): """Randomly edit/delete/add files in workspace""" numActions = random.randint(1, 20) for i in range(numActions): action = random.choice(["add", "edit", "edit", "edit", "edit", "delete"]) filename = random.choice(self.localfilelist) if action == "add": addfilename = os.path.join(os.path.dirname(filename), generator(20, eol="")) create_file(random.randint(100, 1000000), addfilename, random.choice([True, False])) self.addFile(addfilename) elif action == "edit": self.editFile(filename) with open(filename, "a") as f: f.write("Some extra data\n") else: self.deleteFile(filename) return numActions def commit(self): """Commit/Submit changes""" raise NotImplementedError("commit") def run(self): """Run the appropriate test - basically a sync/edit/add/delete and submit""" try: self.createWorkspace() self.syncWorkspace() self.basicFileActions() self.commit() except Exception as e: logger.exception(e) raise e class P4Benchmark(RepoBenchmark): """Performs basic benchmark test - Perforce specific subclass""" def __init__(self, startdir, config): super(P4Benchmark, self).__init__(startdir, config) if "P4CONFIG" in os.environ: # Problems with DVCS logger.warning("Overriding P4CONFIG from environment") os.environ["P4CONFIG"] = "dummy_p4config" self.p4 = P4.P4() self.p4.logger = logger self.config = config p4config = config["perforce"] self.p4.port = p4config["port"] if "charset" in p4config and p4config["charset"]: self.p4.charset = p4config["charset"] self.repoPath = p4config["repoPath"] self.p4.user = p4config["user"] result = self.p4.connect() if p4config["password"]: self.p4.password = p4config["password"] self.p4.run_login() self.workspace_name = "{}.{}.{}".format(self.p4.user, self.id, platform.node()) self.workspace_root = os.path.join(self.test_root, self.workspace_name) if not os.path.isdir(self.test_root): os.makedirs(self.test_root, 0o777) def createWorkspace(self): ws = self.p4.fetch_client(self.workspace_name) existed = (ws._root == self.workspace_root) ws._root = self.workspace_root ws._view = ["{} //{}/...".format(self.repoPath, self.workspace_name)] ws._options = ws._options.replace("normdir", "rmdir") result = self.p4.save_client(ws) self.p4.client = self.workspace_name return existed def syncWorkspace(self): result = None if not os.path.isdir(self.workspace_root): os.makedirs(self.workspace_root, 0o777) os.chdir(self.workspace_root) with self.p4.at_exception_level(P4.P4.RAISE_ERRORS): result = self.p4.run_sync("//{}/...".format(self.p4.client)) havelist = self.p4.run_have() self.localfilelist = [f["path"] for f in havelist] return len(result) def addFile(self, filename): self.p4.run_add(filename) def editFile(self, filename): self.p4.run_edit(filename) def deleteFile(self, filename): self.p4.run_delete(filename) def commit(self): """Submit our change""" opened = self.p4.run_opened() if len(opened) > 0: with self.p4.at_exception_level(P4.P4.RAISE_ERRORS): files = [x['depotFile'] for x in opened] self.p4.run_sync(files) self.p4.run_resolve("-ay") chg = self.p4.fetch_change() chg._description = "A test change" self.p4.save_submit(chg) with self.p4.at_exception_level(P4.P4.RAISE_ERRORS): self.p4.run_revert("//...") class P4BuildFarmBenchmark(RepoBenchmark): """Performs basic benchmark test - Perforce specific subclass""" def __init__(self, startdir, config): super(P4BuildFarmBenchmark, self).__init__(startdir, config) if "P4CONFIG" in os.environ: # Problems with DVCS logger.warning("Removing P4CONFIG from environment") del os.environ["P4CONFIG"] self.p4 = P4.P4() self.p4.logger = logger self.config = config p4config = config["perforce"] self.p4.port = p4config["port"] if "charset" in p4config and p4config["charset"]: self.p4.charset = p4config["charset"] self.repoPath = p4config["repoPath"] self.p4.user = p4config["user"] result = self.p4.connect() if p4config["password"]: self.p4.password = p4config["password"] self.p4.run_login() self.workspace_name = "{}.{}.{}".format(self.p4.user, self.id, platform.node()) self.workspace_root = os.path.join(self.test_root, self.workspace_name) if not os.path.isdir(self.test_root): os.makedirs(self.test_root, 0o777) def createWorkspace(self): ws = self.p4.fetch_client(self.workspace_name) existed = (ws._root == self.workspace_root) ws._root = self.workspace_root ws._view = ["{} //{}/...".format(self.repoPath, self.workspace_name)] ws._options = ws._options.replace("normdir", "rmdir") result = self.p4.save_client(ws) self.p4.client = self.workspace_name return existed def syncWorkspace(self): result = None if not os.path.isdir(self.workspace_root): os.makedirs(self.workspace_root, 0o777) os.chdir(self.workspace_root) with self.p4.at_exception_level(P4.P4.RAISE_ERRORS): result = self.p4.run_sync("//{}/...".format(self.p4.client)) havelist = self.p4.run_have() self.localfilelist = [f["path"] for f in havelist] return len(result) def addFile(self, filename): pass def editFile(self, filename): pass def deleteFile(self, filename): pass def commit(self): pass def basicFileActions(self): return 1 def run(self): """Run the appropriate test - basically a sync and submit""" try: self.createWorkspace() self.syncWorkspace() except Exception as e: logger.exception(e) raise e class SvnBenchmark(RepoBenchmark): """Performs basic benchmark test - SVN specific subclass""" def __init__(self, startdir, config): super(SvnBenchmark, self).__init__(startdir, config) self.config = config svnconfig = config["svn"] self.serverURL = svnconfig["serverURL"] self.repoPath = svnconfig["repoPath"] self.fullURL = "{}/{}".format(self.serverURL, self.repoPath) self.user = svnconfig["user"] self.password = svnconfig["password"] if "svnexe" in svnconfig and svnconfig["svnexe"]: self.svnexe = svnconfig["svnexe"] else: self.svnexe = SVN self.workspace_name = "{}.{}.{}".format(self.user, self.id, platform.node()) self.workspace_root = os.path.join(self.test_root, self.workspace_name) if not os.path.isdir(self.test_root): os.makedirs(self.test_root, 0o777) def svncmd(self, cmd, args, special = []): command = [self.svnexe, cmd] for arg in args: command.append(arg) logger.debug(", ".join(command)) p = subprocess.Popen(command, stdout=subprocess.PIPE) result = p.stdout.read() logger.debug(result) return result def createWorkspace(self): "Returns true if existed previously" return os.path.isdir(self.workspace_root) def syncWorkspace(self): cmd = "co" if os.path.isdir(self.workspace_root): cmd = "update" result = self.svncmd(cmd, ["--username", self.user, "--password", self.password, self.fullURL, self.workspace_root ]) self.localfilelist = [] for root, dirs, files in os.walk(self.workspace_root): if ".svn" in dirs: dirs.remove(".svn") self.localfilelist.extend([os.path.join(root, f) for f in files]) return len(result) def addFile(self, filename): os.chdir(self.workspace_root) self.svncmd("add", [filename]) def editFile(self, filename): logger.info("svn edit") pass # SVN doesn't do edits def deleteFile(self, filename): os.chdir(self.workspace_root) self.svncmd("delete", [filename]) def commit(self): description = "SVN checkin" os.chdir(self.workspace_root) self.svncmd("ci", ["-m", description]) # For testing purposes - normally expect this module to be imported and classes used directly if __name__ == "__main__": parser = ArgumentParser( description="Benchmark Testing", epilog="Copyright (C) 2016 Robert Cowham, Perforce Software Ltd") parser.add_argument("-s","--svn", action='store_true', default=False, help="Run SVN") parser.add_argument("-p","--p4", action='store_true', default=True, help="Run P4") options = parser.parse_args() startdir = os.getcwd() if options.svn: b = SvnBenchmark(startdir, readConfig(startdir)) else: b = P4Benchmark(startdir, readConfig(startdir)) try: b.run() except Exception as e: pass