changeset 158:df1f9946571c

perfs db continued: tests working with tiny DBs on all setup but the sqliteplugin
author am@osimis.io
date Fri, 17 Aug 2018 11:58:26 +0200
parents ac14100ffbd7
children 616da104a996
files PerfsDb/ConfigFileBuilder.py PerfsDb/DbPopulator.py PerfsDb/DbServer.py PerfsDb/DbSize.py PerfsDb/DbType.py PerfsDb/README.md PerfsDb/Run.py PerfsDb/Test.py PerfsDb/TestConfig.py PerfsDb/Tests/Statistics.py PerfsDb/Tests/UploadFile.py PerfsDb/Tests/__init__.py PerfsDb/run.py
diffstat 13 files changed, 268 insertions(+), 164 deletions(-) [+]
line wrap: on
line diff
--- a/PerfsDb/ConfigFileBuilder.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/ConfigFileBuilder.py	Fri Aug 17 11:58:26 2018 +0200
@@ -1,5 +1,6 @@
 import typing
 import json
+import os
 
 from DbType import DbType
 
@@ -8,7 +9,7 @@
     @staticmethod
     def generate(
         outputPath: str, 
-        plugins: typing.List[str], 
+        pluginsPath: str, 
         storagePath: str, 
         dbType: DbType, 
         dbSize: str,
@@ -16,39 +17,50 @@
         ):
 
         config = {}
-        config["Plugins"] = plugins
         config["StorageDirectory"] = storagePath
         
         dbConfig = {}
         dbConfig["EnableIndex"] = True
-        dbConfig["Host"] = "127.0.0.1"
-        dbConfig["Lock"] = False
-        dbConfig["Port"] = port
+
+        if dbType.isServer():
+            dbConfig["Host"] = "127.0.0.1"
+            dbConfig["Lock"] = False
+            dbConfig["Port"] = port
 
         if dbType == DbType.MySQL:
+            config["Plugins"] = [os.path.join(pluginsPath, "libOrthancMySQLIndex.so")]
             dbConfig["EnableStorage"] = False
+            # config["Plugins"] = [os.path.join(pluginsPath, "libOrthancMySQLStorage.so")]
+
             dbConfig["Database"] = "orthanc"
             dbConfig["Username"] = "orthanc"
             dbConfig["Password"] = "orthanc"
 
             config["MySQL"] = dbConfig
 
-        elif dbType == DbType.PG9 or dbType == DbType.PG10:
+        elif dbType.isPG():
+            config["Plugins"] = [os.path.join(pluginsPath, "libOrthancPostgreSQLIndex.so")]
             dbConfig["EnableStorage"] = False
-            dbConfig["Database"] = "orthanc"
-            dbConfig["Username"] = "orthanc"
-            dbConfig["Password"] = "orthanc"
+            # config["Plugins"] = [os.path.join(pluginsPath, "libOrthancPostgreSQLStorage.so")]
+
+            dbConfig["Database"] = "postgres"
+            dbConfig["Username"] = "postgres"
 
             config["PostgreSQL"] = dbConfig
 
         elif dbType == DbType.MSSQL:
-            dbConfig["ConnectionString"] = "Driver={ODBC Driver 13 for SQL Server};Server=tcp:index," + port + ";Database=master;Uid=sa;Pwd=MyStrOngPa55word!;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=30"
+            config["Plugins"] = [os.path.join(pluginsPath, "libOrthancMsSqlIndex.so")]
+            dbConfig["EnableStorage"] = False
+
+            dbConfig["ConnectionString"] = "Driver={ODBC Driver 13 for SQL Server};Server=tcp:127.0.0.1," + str(port) + ";Database=master;Uid=sa;Pwd=MyStrOngPa55word!;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=30"
             dbConfig["LicenseString"] = "1abaamBcReVXv6EtE_X___demo-orthanc%osimis.io___HHHnqVHYvEkR3jGs2Y3EvpbxZgTt7yaCniJa2Bz7hFWTMa" # note: this is a trial license expiring on 2018-09-30, replace with your license code
 
             config["MSSQL"] = dbConfig
 
-        elif DbType == DbType.Sqlite:
+        elif dbType.isSqlite():
             config["IndexDirectory"] = storagePath
+            if dbType == DbType.SqlitePlugin:
+                config["Plugins"] = [os.path.join(pluginsPath, "libOrthancSQLiteIndex.so")]
 
         else:
             raise NotImplementedError
--- a/PerfsDb/DbPopulator.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/DbPopulator.py	Fri Aug 17 11:58:26 2018 +0200
@@ -13,16 +13,30 @@
     def populate(self):
         self._sourceInstanceId = self._orthanc.uploadDicomFile("../Database/DummyCT.dcm")
 
-        if self._dbSize == DbSize.Small:
-            patientCount = 3
+        if self._dbSize == DbSize.Tiny:
+            patientCount = 1
+            smallStudiesPerPatient = 2
+            largeStudiesPerPatient = 1
+            instancesPerLargeSeries = 5
+        elif self._dbSize == DbSize.Small:
+            patientCount = 20
             smallStudiesPerPatient = 2
             largeStudiesPerPatient = 1
-        else:
-            patientCount = 100
+            instancesPerLargeSeries = 300
+        elif self._dbSize == DbSize.Medium:
+            patientCount = 1000
+            smallStudiesPerPatient = 2
+            largeStudiesPerPatient = 1
+            instancesPerLargeSeries = 500
+        elif self._dbSize == DbSize.Large:
+            patientCount = 20000
             smallStudiesPerPatient = 4
             largeStudiesPerPatient = 8
+            instancesPerLargeSeries = 500
+        else:
+            raise NotImplementedError
 
-        # data that are the same in small and large DBs (and that can be used in tests for comparing the same things !!)
+        # first add data that are the same in small and large DBs (and that can be used in tests for comparing the same things !!)
 
         # used in TestFindStudyByPatientId5Results
         self.createStudy(studyIndex=99994, patientIndex=99998, seriesCount=1, instancesPerSeries=1)
@@ -35,21 +49,19 @@
         # used in TestFindStudyByPatientId1Result
         self.createStudy(studyIndex=99999, patientIndex=99999, seriesCount=1, instancesPerSeries=1)
 
-        # data to make the DB "large" or "small"
+        # then, add data to make the DB "large" or "small"
         for patientIndex in range(0, patientCount):
             studyIndex=0
             print("Generating data for patient " + str(patientIndex))
             for i in range(0, smallStudiesPerPatient):
                 print("Generating small study " + str(i))
-                self.createStudy(studyIndex=studyIndex, patientIndex=patientIndex, seriesCount=2, instancesPerSeries=2)
+                self.createStudy(studyIndex=studyIndex, patientIndex=patientIndex, seriesCount=2, instancesPerSeries=instancesPerLargeSeries)
                 studyIndex+=1
             for i in range(0, largeStudiesPerPatient):
                 print("Generating large study " + str(i))
-                self.createStudy(studyIndex=studyIndex, patientIndex=patientIndex, seriesCount=4, instancesPerSeries=500)
+                self.createStudy(studyIndex=studyIndex, patientIndex=patientIndex, seriesCount=4, instancesPerSeries=instancesPerLargeSeries)
                 studyIndex+=1
 
-
-
         print("Generation completed")    
 
     def createStudy(self, studyIndex: int, patientIndex: int, seriesCount: int, instancesPerSeries: int):
--- a/PerfsDb/DbServer.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/DbServer.py	Fri Aug 17 11:58:26 2018 +0200
@@ -26,16 +26,19 @@
     def setLabel(self, label: str):
         self._label = label
 
-    def launch(self):
-        dockerDefinition = self.getDockerDefinition()
-
-        # check if the container is already running
+    def isRunning(self) -> bool:
         ret = subprocess.call([
             "docker",
             "top",
             self._label
         ])            
-        if ret == 0:
+        return ret == 0
+
+    def launch(self):
+        dockerDefinition = self.getDockerDefinition()
+
+        # check if the container is already running
+        if self.isRunning():
             print("DbServer is already running")
             return
 
@@ -85,12 +88,14 @@
             raise TimeoutError
 
     def stop(self):
-        subprocess.check_call([
-            "docker",
-            "stop",
-            self._label
-        ])
-        subprocess.check_call([
+        if self.isRunning():
+            subprocess.check_call([
+                "docker",
+                "stop",
+                self._label
+            ])
+
+        subprocess.call([
             "docker",
             "rm",
             self._label
@@ -99,7 +104,7 @@
     def clear(self):
         # remove the volume
         self.stop()        
-        subprocess.check_call([
+        subprocess.call([
             "docker",
             "volume",
             "rm",
@@ -131,11 +136,13 @@
                 },
                 storagePath="/var/opt/mssql/data"
             )
-        elif self.dbType == DbType.PG9 or self.dbType == DbType.PG10:
+        elif self.dbType == DbType.PG9 or self.dbType == DbType.PG10 or self.dbType == DbType.PG11:
             if self.dbType == DbType.PG9:
                 image = "postgres:9"
             elif self.dbType == DbType.PG10:
                 image = "postgres:10"
+            elif self.dbType == DbType.PG11:
+                image = "postgres:11"
             return DbServer.DockerDefinition(
                 image=image, 
                 internalPort=5432,
--- a/PerfsDb/DbSize.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/DbSize.py	Fri Aug 17 11:58:26 2018 +0200
@@ -3,3 +3,5 @@
 class DbSize(Enum):
     Small = 1
     Large = 2
+    Tiny = 3
+    Medium = 4
--- a/PerfsDb/DbType.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/DbType.py	Fri Aug 17 11:58:26 2018 +0200
@@ -6,3 +6,14 @@
     MySQL = 3
     MSSQL = 4
     PG10 = 5
+    PG11 = 5
+    SqlitePlugin = 6
+
+    def isPG(self):
+        return self.value in [DbType.PG9.value, DbType.PG10.value, DbType.PG11.value]
+
+    def isSqlite(self):
+        return self.value in [DbType.Sqlite.value, DbType.SqlitePlugin.value]
+
+    def isServer(self):
+        return not self.isSqlite()
\ No newline at end of file
--- a/PerfsDb/README.md	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/README.md	Fri Aug 17 11:58:26 2018 +0200
@@ -51,19 +51,19 @@
 -----------------
 
 ```bash
-python run.py --orthanc-path=/home/amazy/builds/orthanc-build-release/ --plugins-path=/home/amazy/builds/mysql-release/ --init --mysql-small
+python Run.py --orthanc-path=/home/amazy/builds/orthanc-build-release/ --plugins-path=/home/amazy/builds/mysql-release/ --init --mysql-tiny
 ```
 
 Clearing a DB
 -----------------
 
 ```bash
-python run.py --orthanc-path=/home/amazy/builds/orthanc-build-release/ --plugins-path=/home/amazy/builds/mysql-release/ --clear --mysql-small
+python Run.py --orthanc-path=/home/amazy/builds/orthanc-build-release/ --plugins-path=/home/amazy/builds/mysql-release/ --clear --mysql-tiny
 ```
 
-Runing tests on two DBs
+Runing tests on multiple DBs
 -----------------
 
 ```bash
-python run.py --orthanc-path=/home/amazy/builds/orthanc-build-release/ --plugins-path=/home/amazy/builds/mysql-release/ --run --mysql-large --mysql-small 
+python Run.py --orthanc-path=/home/amazy/builds/orthanc-build-release/ --plugins-path=/home/amazy/builds/orthanc-build-release/ --run --pg9-tiny --pg10-tiny --pg11-tiny --mysql-tiny --sqlite-tiny --mssql-tiny
 ```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PerfsDb/Run.py	Fri Aug 17 11:58:26 2018 +0200
@@ -0,0 +1,129 @@
+import argparse
+from ConfigFileBuilder import ConfigFileBuilder
+from TestConfig import TestConfig
+from DbServer import DbServer
+from DbType import DbType
+from DbSize import DbSize
+
+testConfigs = {
+    "mysql-small" : TestConfig(label= "mysql-small", dbSize=DbSize.Small, dbServer=DbServer(dbType=DbType.MySQL, port=2000)),
+    "pg9-small": TestConfig(label= "pg9-small", dbSize=DbSize.Small, dbServer=DbServer(dbType=DbType.PG9, port=2001)),
+    "pg10-small": TestConfig(label= "pg10-small", dbSize=DbSize.Small, dbServer=DbServer(dbType=DbType.PG10, port=2002)),
+    "pg11-small": TestConfig(label= "pg11-small", dbSize=DbSize.Small, dbServer=DbServer(dbType=DbType.PG11, port=2003)),
+    "mssql-small" : TestConfig(label= "mssql-small", dbSize=DbSize.Small, dbServer=DbServer(dbType=DbType.MSSQL, port=2004)),
+    "sqlite-small": TestConfig(label= "sqlite-small", dbSize=DbSize.Small, dbType=DbType.Sqlite),
+    "sqliteplugin-small": TestConfig(label= "sqliteplugin-small", dbSize=DbSize.Small, dbType=DbType.SqlitePlugin),
+
+    "mysql-tiny" : TestConfig(label= "mysql-tiny", dbSize=DbSize.Tiny, dbServer=DbServer(dbType=DbType.MySQL, port=3000)),
+    "pg9-tiny": TestConfig(label= "pg9-tiny", dbSize=DbSize.Tiny, dbServer=DbServer(dbType=DbType.PG9, port=3001)),
+    "pg10-tiny": TestConfig(label= "pg10-tiny", dbSize=DbSize.Tiny, dbServer=DbServer(dbType=DbType.PG10, port=3002)),
+    "pg11-tiny": TestConfig(label= "pg11-tiny", dbSize=DbSize.Tiny, dbServer=DbServer(dbType=DbType.PG11, port=3003)),
+    "mssql-tiny" : TestConfig(label= "mssql-tiny", dbSize=DbSize.Tiny, dbServer=DbServer(dbType=DbType.MSSQL, port=3004)),
+    "sqlite-tiny": TestConfig(label= "sqlite-tiny", dbSize=DbSize.Tiny, dbType=DbType.Sqlite),
+    "sqliteplugin-tiny": TestConfig(label= "sqliteplugin-tiny", dbSize=DbSize.Tiny, dbType=DbType.SqlitePlugin),
+
+    "mysql-medium" : TestConfig(label= "mysql-medium", dbSize=DbSize.Medium, dbServer=DbServer(dbType=DbType.MySQL, port=4000)),
+    "pg9-medium": TestConfig(label= "pg9-medium", dbSize=DbSize.Medium, dbServer=DbServer(dbType=DbType.PG9, port=4001)),
+    "pg10-medium": TestConfig(label= "pg10-medium", dbSize=DbSize.Medium, dbServer=DbServer(dbType=DbType.PG10, port=4002)),
+    "pg11-medium": TestConfig(label= "pg11-medium", dbSize=DbSize.Medium, dbServer=DbServer(dbType=DbType.PG11, port=4003)),
+    "mssql-medium" : TestConfig(label= "mssql-medium", dbSize=DbSize.Medium, dbServer=DbServer(dbType=DbType.MSSQL, port=4004)),
+    "sqlite-medium": TestConfig(label= "sqlite-medium", dbSize=DbSize.Medium, dbType=DbType.Sqlite),
+    "sqliteplugin-medium": TestConfig(label= "sqliteplugin-medium", dbSize=DbSize.Medium, dbType=DbType.SqlitePlugin),
+}
+
+selectedTestConfigs = []
+
+parser = argparse.ArgumentParser(description = "Initializes/Runs/Clears PerfsDb setup.")
+
+# create a cli option for each config
+for testConfigName in testConfigs.keys():
+    parser.add_argument("--" + testConfigName, action = "store_true")
+
+parser.add_argument("--init", help = "initializes DBs", action = "store_true")
+parser.add_argument("--run", help = "runs tests", action = "store_true")
+parser.add_argument("--clear", help = "clear DBs", action = "store_true")
+
+parser.add_argument("--orthanc-path", help = "path to the folder containing Orthanc executable", default=".")
+parser.add_argument("--plugins-path", help = "path to the folder containing Orthanc executable", default=".")
+parser.add_argument("--repeat", help = "number of times to repeat each test to average timings", type=int, default=50)
+
+args = parser.parse_args()
+
+for testConfigName in testConfigs.keys():
+    if args.__dict__[testConfigName.replace("-", "_")]:
+        selectedTestConfigs.append(testConfigName)
+
+# if no test config specified, take them all
+if len(selectedTestConfigs) == 0:
+    selectedTestConfigs = testConfigs.keys()
+
+selectedTestConfigs = sorted(selectedTestConfigs)
+
+# if no action specified, it means only run
+if not (args.init | args.run | args.clear):
+    args.init = False
+    args.run = True
+    args.clear = False
+
+print("***** Orthanc *******")
+print("path    :", args.orthanc_path)
+
+results = {}
+
+for configName in selectedTestConfigs:
+    testConfig = testConfigs[configName]
+    testConfig.setName(configName)
+    testConfig.setRepeatCount(args.repeat)
+    
+    print("======= " + configName + " ========")
+
+    if args.clear:
+        print("** Clearing Db")
+        testConfig.clearDb()
+
+    if args.init or args.run:
+        print("** Generating config files")
+        testConfig.generateOrthancConfigurationFile(args.plugins_path)
+
+        print("** Launching DbServer")
+        testConfig.launchDbServer()
+        
+        print("** Launching Orthanc")
+        orthancWasAlreadyRunning = not testConfig.launchOrthanc(args.orthanc_path)
+        if orthancWasAlreadyRunning and len(selectedTestConfigs) > 1:
+            print("Error: Can't execute multiple configuration on already running Orthanc.  Exit Orthanc and let this script start Orthanc instances")
+            exit(-1)
+
+    if args.init:
+        testConfig.initializeDb()
+
+    if args.run:
+        print("** Runnnig tests")
+        results[configName] = testConfig.runTests()
+    
+    print("** Stopping Orthanc")
+    testConfig.stopOrthanc()
+    
+print("++++++++++++++ results summary +++++++++++++++")
+testNames = set()
+resultsByTestName = {}
+for configName, configResult in results.items():
+    for result in configResult:
+        testNames.add(result.name)
+        if not result.name in resultsByTestName:
+            resultsByTestName[result.name] = {}
+        resultsByTestName[result.name][configName] = result
+
+headerLine = "{empty:<40}|".format(empty="")
+for configName in selectedTestConfigs:
+    headerLine += "{configName:^15}|".format(configName=configName)
+
+print(headerLine)
+
+for testName in sorted(testNames):
+    resultLine = "{name:<40}|".format(name=testName)
+    for configName in selectedTestConfigs:
+        resultLine += "{avg:>11.2f} ms |".format(avg = resultsByTestName[testName][configName].averageTimeInMs)
+    print(resultLine)
+
+print("** Done")
--- a/PerfsDb/Test.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/Test.py	Fri Aug 17 11:58:26 2018 +0200
@@ -17,9 +17,17 @@
     def setRepeatCount(self, repeatCount: int):
         self.repeatCount = repeatCount
 
-    def prepare(self):
+    def beforeAll(self):
+        """
+        Code to execute before the execution of all repetitions of a test; i.e: upload a file.
+        This code is not included in timings
         """
-        Code to execute before the execution of a test; i.e: upload a file (not including in timings)
+        pass
+
+    def beforeEach(self):
+        """
+        Code to execute before the execution of each repetition of a test.
+        This code is not included in timings
         """
         pass
 
@@ -29,24 +37,35 @@
         """
         pass
 
-    def cleanup(self):
+    def afterEach(self):
+        """
+        Code to execute after the execution of each repetition of a test.
+        This code is not included in timings
         """
-        Code to execute after the execution of a test; i.e: remove an instance (not including in timings)
+        pass
+
+    def afterAll(self):
+        """
+        Code to execute after the execution of all repetitions of a test.
+        This code is not included in timings
         """
         pass
 
     def run(self) -> TestResult:
         result = TestResult(self.name)
 
+        self.beforeAll()
+
         for i in range(0, self.repeatCount):
-            self.prepare()
+            self.beforeEach()
             startTime = time.time()
             self.test()
             endTime = time.time()
-            self.cleanup()
+            self.afterEach()
 
             result.add((endTime - startTime) * 1000)
 
+        self.afterAll()
         result.compute()
         return result
 
--- a/PerfsDb/TestConfig.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/TestConfig.py	Fri Aug 17 11:58:26 2018 +0200
@@ -1,6 +1,7 @@
 import typing
 import subprocess
 import os
+import shutil
 from orthancRestApi import OrthancClient
 
 from DbSize import DbSize
@@ -45,23 +46,24 @@
         if self._dbServer is not None:
             self._dbServer.launch()
 
-    def launchOrthanc(self, orthancPath):
+    def launchOrthanc(self, orthancPath) -> bool:
         orthanc = OrthancClient("http://127.0.0.1:8042")
         
         print("Checking if Orthanc is already running")
         if orthanc.isAlive():
             print("Orthanc is already running")
-            return
+            return False
         
         print("Launching Orthanc")
         self._orthancProcess = subprocess.Popen([
             os.path.join(orthancPath, "Orthanc"), 
-            os.path.join("ConfigFiles", self._name + ".json"), 
+            os.path.join(os.path.abspath(os.path.dirname(__file__)), "ConfigFiles", self._name + ".json"), 
         ])
        
         print("Waiting for Orthanc to start")
         orthanc.waitStarted(timeout=30)
         print("Orthanc has started")
+        return True
 
     def stopOrthanc(self):
         if self._orthancProcess is not None:
@@ -74,12 +76,13 @@
 
     def runTests(self) -> typing.List[TestResult]:
         allTests = [
+            TestStatistics(),
             TestFindStudyByStudyDescription1Result(),
             TestFindStudyByPatientId1Result(),
             TestFindStudyByStudyDescription0Results(),
             TestFindStudyByPatientId0Results(),
             TestFindStudyByPatientId5Results(),
-            TestUploadFile()
+            TestUploadFile(),
         ]
 
         results = []
@@ -95,13 +98,16 @@
     def clearDb(self):
         if self._dbServer is not None:
             self._dbServer.clear()
+        
+        # clear storage (in case of Sqlite DB, it will also clear the DB)
+        shutil.rmtree(os.path.join(os.path.abspath(os.path.dirname(__file__)), "Storages/{name}".format(name=self._name)), ignore_errors=True)
 
     def generateOrthancConfigurationFile(self, pluginsPath: str):
         
         ConfigFileBuilder.generate(
-            outputPath="ConfigFiles/{name}.json".format(name=self._name), 
-            plugins=[pluginsPath],
-            storagePath="Storages/{name}".format(name=self._name),
+            outputPath=os.path.join(os.path.abspath(os.path.dirname(__file__)), "ConfigFiles/{name}.json".format(name=self._name)), 
+            pluginsPath=pluginsPath,
+            storagePath=os.path.join(os.path.abspath(os.path.dirname(__file__)), "Storages/{name}".format(name=self._name)),
             dbType=self._dbType,
             dbSize=self._dbSize,
             port=self._port
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PerfsDb/Tests/Statistics.py	Fri Aug 17 11:58:26 2018 +0200
@@ -0,0 +1,15 @@
+import json
+
+from Test import Test
+
+class TestStatistics(Test):
+
+    def __init__(self, name:str = "Statistics"):
+        super().__init__(name)
+        self._response = None
+
+    def test(self):
+        self._statistics = self._orthanc.getJson(relativeUrl="statistics")
+
+    def afterAll(self):
+        print("Statistics:" + json.dumps(self._statistics))
\ No newline at end of file
--- a/PerfsDb/Tests/UploadFile.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/Tests/UploadFile.py	Fri Aug 17 11:58:26 2018 +0200
@@ -8,12 +8,13 @@
         self._filePath = filePath
         self._dicomFileContent = None
 
-    def prepare(self):
+    def beforeAll(self):
         # get the instance Id and dicom file content
         if self._instanceId is None:
             self._instanceId = self._orthanc.uploadDicomFile(self._filePath)
             self._dicomFileContent = self._orthanc.instances.getDicom(self._instanceId)
 
+    def beforeEach(self):
         # make sure the file is not in Orthanc before the upload
         self._orthanc.instances.delete(self._instanceId)
 
--- a/PerfsDb/Tests/__init__.py	Thu Aug 16 17:14:05 2018 +0200
+++ b/PerfsDb/Tests/__init__.py	Fri Aug 17 11:58:26 2018 +0200
@@ -1,2 +1,3 @@
 from .UploadFile import TestUploadFile
-from .FindStudy import *
\ No newline at end of file
+from .FindStudy import *
+from .Statistics import *
\ No newline at end of file
--- a/PerfsDb/run.py	Thu Aug 16 17:14:05 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-import argparse
-from ConfigFileBuilder import ConfigFileBuilder
-from TestConfig import TestConfig
-from DbServer import DbServer
-from DbType import DbType
-from DbSize import DbSize
-
-testConfigs = {
-    "mysql-small" : TestConfig(label= "mysql-small", dbSize=DbSize.Small, dbServer=DbServer(dbType=DbType.MySQL, port=2000)),
-    "mysql-large" : TestConfig(label= "mysql-large", dbSize=DbSize.Large, dbServer=DbServer(dbType=DbType.MySQL, port=2001)),
-    "sqlite-small": TestConfig(label= "sqlite-small", dbSize=DbSize.Small, dbType=DbType.Sqlite),
-    "pg9-small": TestConfig(label= "pg9-small", dbSize=DbSize.Small, dbServer=DbServer(dbType=DbType.PG9, port=2002)),
-}
-
-selectedTestConfigs = []
-
-parser = argparse.ArgumentParser(description = "Initializes/Runs/Clears PerfsDb setup.")
-
-# create a cli option for each config
-for testConfigName in testConfigs.keys():
-    parser.add_argument("--" + testConfigName, action = "store_true")
-
-parser.add_argument("--init", help = "initializes DBs", action = "store_true")
-parser.add_argument("--run", help = "runs tests", action = "store_true")
-parser.add_argument("--clear", help = "clear DBs", action = "store_true")
-
-parser.add_argument("--orthanc-path", help = "path to the folder containing Orthanc executable", default=".")
-parser.add_argument("--plugins-path", help = "path to the folder containing Orthanc executable", default=".")
-parser.add_argument("--repeat", help = "number of times to repeat each test to average timings", type=int, default=50)
-
-args = parser.parse_args()
-
-for testConfigName in testConfigs.keys():
-    if args.__dict__[testConfigName.replace("-", "_")]:
-        selectedTestConfigs.append(testConfigName)
-
-# if no test config specified, take them all
-if len(selectedTestConfigs) == 0:
-    selectedTestConfigs = testConfigs.keys()
-
-# if no action specified, it means only run
-if not (args.init | args.run | args.clear):
-    args.init = False
-    args.run = True
-    args.clear = False
-
-print("***** Orthanc *******")
-print("path    :", args.orthanc_path)
-
-
-# libOrthancMySQLIndex.so
-# libOrthancMySQLStorage.so
-# libOrthancPostgreSQLIndex.so
-# libOrthancPostgreSQLStorage.so
-# libOrthancMSSQLIndex.so
-
-results = {}
-
-for configName in selectedTestConfigs:
-    testConfig = testConfigs[configName]
-    testConfig.setName(configName)
-    testConfig.setRepeatCount(args.repeat)
-    
-    print("======= " + configName + " ========")
-
-    if args.clear:
-        print("** Clearing Db")
-        testConfig.clearDb()
-
-    if args.init or args.run:
-        print("** Generating config files")
-        testConfig.generateOrthancConfigurationFile(args.plugins_path)
-
-        print("** Launching DbServer")
-        testConfig.launchDbServer()
-        
-        print("** Launching Orthanc")
-        testConfig.launchOrthanc(args.orthanc_path)
-
-    if args.init:
-        testConfig.initializeDb()
-
-    if args.run:
-        print("** Runnnig tests")
-        results[configName] = testConfig.runTests()
-        print("** Stoping Orthanc")
-        testConfig.stopOrthanc()
-    
-print("++++++++++++++ results summary +++++++++++++++")
-testNames = set()
-resultsByTestName = {}
-for configName, configResult in results.items():
-    for result in configResult:
-        testNames.add(result.name)
-        if not result.name in resultsByTestName:
-            resultsByTestName[result.name] = {}
-        resultsByTestName[result.name][configName] = result
-
-headerLine = "{empty:<40}|".format(empty="")
-for configName in selectedTestConfigs:
-    headerLine += "{configName:^15}|".format(configName=configName)
-
-print(headerLine)
-
-for testName in sorted(testNames):
-    resultLine = "{name:<40}|".format(name=testName)
-    for configName in selectedTestConfigs:
-        resultLine += "{avg:>11.2f} ms |".format(avg = resultsByTestName[testName][configName].averageTimeInMs)
-    print(resultLine)
-
-print("** Done")