#/usr/bin/env python
# -*- coding: utf-8 -*-

"""
===========================================================
benchMAI.py - les tests de montée en charge de l'AutoImport
===========================================================


Principe
--------

* Une *suite* est une série de tests caractérisée par le nombre 
  d'enregistrement qui existent dans la base avant le lancement d'un test.
* un *test* est une execution de l'autoimport caractérisé par le nombre
  de documents à traiter. Étapes de l'éxecution d'un test (méthode
  ``test.execute()`` ):

  1. ``test.setup()``: Préparation de l'environnement (préchargement de la base, 
     génération des documents à traiter, etc.)
  2. ``test.do_test()`` : le lancement de l'autoimport. C'est le temps 
     d'éxecution de cette méthode qui est mesuré.
  3. ``test.teardown()`` : nettoyage de l'environnement (suppression des traces
     de l'execution de l'autoimport, nettoyage de la base, nettoyage des 
     docservers, etc.)


Usage
-----

Exemple d'utilisation ::

  import benchMAI
  
  suite = benchMAI.Suite(0, [1, 2])
  suite.run()

Cet exemple lancera 2 tests de 1 et 2 documents importés avec une base vide au
départ. Les temps d'éxécution mesurés pourront être lus dans le fichier 
``results/results_0.csv``.


Constantes pour le paramétrage
------------------------------

:DOCSERVER_PATH: Chemin vers le DocServer
:INCOMING_PATH: Chemin vers le dossier incomming de l'autoimport
:ORIG_PATH: Chemin vers les fichiers pdf et xml originaux
:RESULTS_PATH: Dossier dans lequel écrire les résultats et les rapports
:NB_REPETITIONS: Nombre de répétition de chaque test
:DB_HOST: Hôte de la base de donnée
:DB_DATABASE: Base de donnée à utiliser
:DB_USER: Utilisateur de la base de donnée
:DB_PASSWORD: Mot de passe de l'utilisateur de la base de données


Todo
----

* permettre 2 modes de génération de custom_t5 : tous distincts et par groupe de n
* optimiser les performances de préparation de tests (test.setup)

  * charger la base avec des CSV générés au niveau des suites
  * conserver certains fichiers plutôt que de le regénérer à chaque fois

* générer des rapports
* séparer les temps d'execution de l'autoimport selon ses étapes (scan, 
  génération sql, chargement sql, backup)
* Créer les dossiers non-existants au lancement du script
* contrôle des erreurs de fonctionnement de l'autoimport
"""

import time
import psycopg2 as pg
import os
from subprocess import Popen, call

#
# PARAMÈTRES
#

# Chemin vers le DocServer (pour le nettoyer après chaque passage
DOCSERVER_PATH = os.path.join(os.getcwd(), 'tmp', 'docserver')

# Chemin vers le dossier incomming de l'autoimport
INCOMING_PATH = os.path.join(os.getcwd(), 'tmp', 'incoming')

# Chemin vers les fichiers pdf et xml originaux
ORIG_PATH = os.path.join(os.getcwd(), 'tmp', 'orig')

# Dossier dans lequel écrire les résultats et les rapports
RESULTS_PATH = os.path.join(os.getcwd(), 'results')

# Nombre de répétition de chaque test
NB_REPETITIONS = 3

# Paramètres de connection à la base de donnée
DB_HOST = 'localhost'
DB_DATABASE = 'mep_test_mai'
DB_USER = 'postgres'
DB_PASSWORD = 'qsDfGh12'

#
# FIN DES PARAMÈTRES
#


conn = pg.connect(user=DB_USER, 
                  password=DB_PASSWORD, 
                  database=DB_DATABASE, 
                  host=DB_HOST)
cur = conn.cursor()


def prepare_database(number_of_records=0):
    """
    Prépare la base de donnée en la chargean avec le nombre d'enregistrement 
    voulu.
    
    :number_of_records: nombre d'enregistrement à créer dans la base
    """
    limit_index = int(number_of_records/100)
    for i in range(number_of_records):
        cur.execute("""INSERT INTO res_letterbox(
                                                    typist, 
                                                    creation_date, 
                                                    docserver_id, 
                                                    path, 
                                                    filename, 
                                                    offset_doc, 
                                                    fingerprint, 
                                                    filesize, 
                                                    format, 
                                                    work_batch , 
                                                    doc_date, 
                                                    type_id, 
                                                    identifier, 
                                                    custom_t1, 
                                                    custom_t2, 
                                                    custom_t3, 
                                                    custom_n1, 
                                                    title, 
                                                    status, 
                                                    custom_t4, 
                                                    custom_t5
                                                ) 
                        VALUES(
                                'AUTOIMPORT',
                                '2010-01-28  15:10',
                                'letterbox_ai',
                                '2010#01#23#0000#',
                                '0000.pdf',
                                1,
                                '76b1e4caeb9ba890167ff880d1d963cd',
                                109349,
                                'pdf',
                                23,
                                '2007-01-03',
                                70,
                                'F2007-001',
                                'PO2007-001',
                                'Eric BARRAT',
                                'France',
                                650,
                                'F2007-001',
                                'NEW',
                                'THOMSON-CSF',
                                '%s'
                            )"""%(i%(limit_index+1),))

def empty_database():
    """
    Nettoie la base.
    
    Prévu pour être lancé après un test
    """
    cur.execute("TRUNCATE TABLE mlb_coll_ext;")
    cur.execute("TRUNCATE TABLE res_letterbox;")
    cur.execute("update docservers set actual_size=0;")
    conn.commit()

def delete_documents():
    """
    Supprime tous les documents crés par un test
    """
    call("rm -rf " + DOCSERVER_PATH + "/*", shell=True)
    call('rm -rf ' + INCOMING_PATH + "/*", shell=True)
    call('rm -rf ' + os.path.join(os.getcwd(), "mai31","log")+"/*", shell=True)
    

def prepare_documents(number_of_docs=0):
    """
    Prépare les documents que l'autoimport va importer dans la base.
    Le nom des documents est F_i.pdf et F_i.xml ou i est un autoincrement.
    
    :number_of_docs: Nombre de documents à créer
    """
    
    f=open(ORIG_PATH+"/orig.pdf")
    doc = f.read()
    f.close()
    f=open(ORIG_PATH+"/orig.xml")
    xml = f.read()
    f.close()
    limit_index = int(number_of_docs/100)+1
    for i in range(number_of_docs):
        f=open(INCOMING_PATH + "/F_" + str(i) + ".pdf", "w")
        f.write(doc)
        f.close()
        f=open(INCOMING_PATH+"/F_" + str(i) + ".xml", "w")
        f.write(xml%{'limit_index':i%(limit_index+1)})
        f.close()
    
    
class test(object):
    """
    Cette classe correspond à un test.
    
    Les méthodes setup() et teardown() seront executées respectivement avant et
    après le test.
    """
    
    def __init__(self, existing_records=0, docs_to_import=0):
        """
        Initialisation de la classe.
        
        :existing_records: nombre d'enregistrements présents dans la base au 
                           lancement de l'autoimport
        :docs_to_import: nombre de documents que l'autoimport doit traiter
        """
        self.existing_records = existing_records
        self.docs_to_import = docs_to_import
        
    def setup(self):
        """
        Fonction executée avant le test pour préparer l'environnement:
        
        * préparation et chargement de la base
        * création des documents à importer
        """
        prepare_database(self.existing_records)
        prepare_documents(self.docs_to_import)
        
    def teardown(self):
        """
        Fonction executée après le test pour nettoyer l'environnement:
        
        * nettoyage de la base de la base
        * suppression des documents créés par l'autoimport (docserver, backups,
          logs, ...)
        """
        empty_database()
        delete_documents()
    
    def do_test(self):
        """
        Script du test : lancement de l'autoimport.
        
        C'est ce code dont le temps d'éxecution est mesuré
        """
        Popen(['php', 'mai31/maarch_autoimport/maarch_auto_import.php', 'mai31/maarch_autoimport/config_entreprise_cold.xml'], stdout=open('/dev/null', 'w')).wait()
        #Popen(['php', 'mai31/maarch_autoimport/maarch_auto_import.php', 'mai31/maarch_autoimport/config_entreprise_cold.xml']).wait()
        
    
    def execute(self):
        """
        Méthode à lancer pour executer le test.
        
        Retourne le temps d'execution de l'autoimport (en secondes)
        """
        start_time = time.time()
        self.do_test()
        end_time = time.time()
        return end_time - start_time

class suite(object):
    """
    Cette classe représente une suite de tests.
    
    Une suite est actuellement caractérisée par le nombre d'enregistrement 
    présents dans la base avant le test.
    """
    
    def __init__(self, charge_base=0, steps=[]):
        """
        Initialisation de la suite
        
        :charge_base: nombre d'enregistrement présents dans la base 
                      avant le test
        :steps: liste d'entier : chaque nombre représente la taille d'un batch,
                et constitue un test. par exemple :
                
                * [10, 50, 100] lancera trois tests avec des batchs de 10, 50
                  puis 100 documents.
                * range(0, 1000, 100) lancera 10 tests avec des batchs de 
                  0, 100, 200, ..., 800, 900 documents.
        """
        self.steps = steps
        self.charge_base = charge_base
        self.report = open(RESULTS_PATH+'/report_'+str(charge_base)+'.csv', 'w', 0)
        self.report.write('batch;time\n')
    
    def run(self):
        """
        Lancement de la série de tests. Chaque batch dont la taille a été 
        définie dans steps sera lancé succéssivement et dans l'ordre.
        
        Chaque batch pourra être exécuté plusieurs fois (NB_REPETITIONS fois).
        
        Les temps d'executions rapportés par test.execute() sont consignés dans
        un fichier csv pour chaque batch.
        """
        for step in self.steps:
            print
            for j in NB_REPETITIONS:
                print "** running test with cb=%s b=%s (%s/%s)" % (self.charge_base, 
                                                                   step, 
                                                                   j, 
                                                                   NB_REPETITIONS)
                cur_test = test(self.charge_base, step)
                t = cur_test.execute()
                del cur_test
                self.report.write(str(step)+";"+str(t)+'\n')

# Si le script est importé, il lance les tests, si il est importé, il se 
# comporte comme une librairie.
if __name__=='__main__':
    #nettoyage de l'environnement de test
    delete_documents()
    empty_database()
    
    # préparation des suites de test
    bench = [
        suite(0, range(0, 100500, 500)),
        suite(100000, range(0, 100500, 500)),
        suite(1000000, range(0, 100500, 500)),
    ]
    
    # Esecution des suites
    for step in bench:
        step.run()