Skip to content

29. Esercizio pratico: versione 11

29.1. Introduzione

Nelle versioni precedenti dell’applicazione client/server per il calcolo delle imposte, il livello [métier] che implementa le regole di business di tale calcolo era sul lato server. Ora si propone di spostarlo sul lato client. Qual è il vantaggio? Parte del lavoro svolto dal server verrà trasferito sul lato client. Se si considera la situazione di un server interpellato da N client, N calcoli fiscali saranno effettuati dai client. Nelle versioni precedenti, il server eseguiva questi N calcoli fiscali. Poiché non esegue più il calcolo fiscale, il server risponderà più rapidamente ai propri client e potrà quindi servirne un numero maggiore contemporaneamente.

L’architettura client/server diventa la seguente:

Image

  • il livello [métier] [10] è stato duplicato come [12] sul client;
  • è stato aggiunto al client un nuovo script [main2] [11];

Il client web avrà due modi per calcolare l’imposta dall’elenco dei contribuenti presente in [3]:

  • utilizzare il metodo della versione precedente. Questo metodo si avvale del livello [métier] [10] del server. Lo script [main] utilizzerà questo metodo;
  • limitarsi a richiedere al server i dati dell’amministrazione fiscale [2-4] e poi utilizzare il livello [métier] [12] locale sul client;

Confronteremo le prestazioni dei due metodi.

29.2. Il server web

La struttura ad albero del server web sarà la seguente:

Image

  • La cartella [http-servers/06] viene inizialmente creata tramite copia della cartella [http-servers/05]. Si manterrà infatti quanto acquisito dalla precedente versione 10. Si aggiungerà semplicemente una nuova funzionalità. Questa si concretizza nella presenza di un nuovo controller [get_admindata_controller] [1]. L’altro controller [calculate_tax_controller] non è altro che il vecchio controller [index_controller], che è stato rinominato;

29.3. Configurazione

Il server metterà a disposizione due URL di servizio:

  • [/calculate-tax] per calcolare l’imposta su un elenco di contribuenti passato nel corpo di un POST. Corrisponde quindi al URL [/] della precedente versione 10;
  • [/get-admindata] genera la stringa jSON contenente i dati dell’amministrazione fiscale;

La configurazione [config] associa ciascuno di questi URL al controller che lo elabora:


    # dizionario dei controller dell'applicazione web
    import calculate_tax_controller, get_admindata_controller
    controllers = {"calculate-tax": calculate_tax_controller, "get-admindata": get_admindata_controller}
    config['controllers'] = controllers

29.4. Lo script principale [main]

Lo script principale [main] riorganizza lo script [main] della versione precedente:


# si attende un parametro mysql o pgres
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
    sgbd = sys.argv[1].lower()
    erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()

# si configura l'applicazione
import config
config = config.configure({'sgbd': sgbd})

# dipendenze


# recupero dei dati dall'amministrazione fiscale
erreur = False
try:
    # admindata sarà un dato a livello di applicazione in sola lettura
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # log di esito positivo
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # si registra l'errore
    …

# in caso di errore, l'operazione viene interrotta
if erreur:
    sys.exit(2)

# l'applicazione Flask può avviarsi
app = Flask(__name__)

# controller principale
def main_controller() -> tuple:
    # si recupera l'azione richiesta
    dummy, action=request.path.split('/')
    logger = None
    try:
        # logger
        logger = Logger(config["logsFilename"])
        # la si memorizza in una configurazione associata al thread
        thread_config = {"logger": logger}
        thread_name = threading.current_thread().name
        config[thread_name] = {"config": thread_config}
        # si registra la richiesta
        logger.write(f"[index] requête : {request}\n")
        # si interrompe il thread se richiesto
        sleep_time = config["sleep_time"]
        if sleep_time != 0:
            # la pausa è casuale, in modo che alcuni thread vengano interrotti e altri no
            aléa = randint(01)
            if aléa == 1:
                # registrazione prima della pausa
                logger.write(f"[index] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                # pausa
                time.sleep(sleep_time)
        # la richiesta viene eseguita dal controller delle richieste
        controller = config['controllers'][action]
        résultat, status_code = controller.execute(request, config)
        # si è verificato un errore irreversibile?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            # si invia un'e-mail all'amministratore dell'applicazione
            config_mail = config["adminMail"]
            config_mail["logger"] = logger
            SendAdminMail.send(config_mail, json.dumps(résultat, ensure_ascii=False))
        # si registra la risposta
        logger.write(f"[index] {résultat}\n")
        # si invia la risposta
        print(résultat)
        return json_response(résultat, status_code)
    except BaseException as erreur:
        # si registra l'errore, se possibile
        if logger:
            logger.write(f"[index] {erreur}")
        # si prepara la risposta per il cliente
        résultat = {"réponse": {"erreurs"[f"{erreur}"]}}
        # si invia la risposta
        return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        # si chiude il file di log, se era stato aperto
        if logger:
            logger.close()

# calcolo dell'imposta
@app.route('/calculate-tax', methods=['POST'])
@auth.login_required
def calculate_tax():
    # si passa il controllo al controller principale
    return main_controller()

# si recuperano i dati dall'amministrazione fiscale
@app.route('/get-admindata', methods=['GET'])
@auth.login_required
def get_admindata():
    # passaggio di controllo al controllore principale
    return main_controller()

# solo mano
if __name__ == '__main__':
    # avvio del server
    app.config.update(ENV="development", DEBUG=True)
    app.run(threaded=True)
  • righe 88-93: la funzione [calculate_tax] elabora URL e [/calculate-tax];
  • righe 95-100: la funzione [get_admindata] elabora URL e [/get-admindata];
  • queste due funzioni non eseguono alcuna operazione autonomamente. Passano immediatamente il controllo al controller principale [main_controller] delle righe 37-86;
  • righe 37-86: il controllore principale [main_controller] non è altro che la funzione [index] della versione precedente, con una sola differenza: mentre la funzione [index] elaborava un solo URL, qui [main_controller] ne elabora due URL. Deve quindi farle elaborare da uno dei due controllori [calculate_tax_controller, get_admin,data_controller];
  • righe 39-40: si recupera l’azione richiesta [calculate_tax] o [get_admindata]. Questa informazione si trova nel percorso URL [request.path]. A seconda dei casi, [request.path] corrisponde a [/get-admindata] o a [/calculate_tax]. La suddivisione della riga 40 fornirà due elementi:
    • la stringa vuota per la parte che precede il /;
    • il nome dell’azione richiesta per la parte che segue il /;
  • righe 62-63: una volta recuperata l’azione di URL, si sa quale controller utilizzare per elaborare URL. Queste informazioni si trovano nella configurazione di [config];

29.5. I controller

Il controller [calculate_tax_controller] non è altro che il controller [index_controller] della versione precedente.

Il controller [get_admindata_controller] è invece il seguente:


from flask_api import status
from werkzeug.local import LocalProxy

def execute(request: LocalProxy, config: dict) -> tuple:
    # si restituisce la risposta
    return {"réponse": {"result": config["admindata"].asdict()}}, status.HTTP_200_OK
  • il URL [/get-admindata] deve restituire la stringa jSON dei dati dell’amministrazione fiscale;
  • riga 6: questi dati sono stati recuperati dallo script principale [main] e inseriti nel dizionario [config] sotto forma di un oggetto [AdminData]. Si restituisce il dizionario di questo oggetto;

29.6. Test Postman

Si avviano il server web, il SGBD e il server di posta [hMailServer]. Quindi, con un client Postman, si calcola l’imposta di diversi contribuenti:

Image

Nella console di Postman, il dialogo client/server è il seguente:


POST /calculate-tax HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 5e71461a-fec8-4315-85e8-41721de939e5
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 824

[ 
{ 
"marié": "oui", 
"enfants": 2, 
"salaire": 55555 
}, 

{ 
"marié": "oui", 
"enfants": 3, 
"salaire": 200000 
} 
]

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 1461
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 29 Jul 2020 07:02:07 GMT

{"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347…]}}

Ora proviamo a combinare URL e [/get-admindata] con un GET:

Image

Il dialogo client/server nella console Postman è il seguente:


GET /get-admindata HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 4af342c4-7ecb-4ab2-9e12-d653f81da424
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 596
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 29 Jul 2020 07:07:24 GMT

{"réponse": {"result": {"limites": [9964.0, 27519.0, 73779.0, 156244.0, 93749.0], "coeffr": [0.0, 0.14, 0.3, 0.41, 0.45], "coeffn": [0.0, 1394.96, 5798.0, 13913.7, 20163.4], "plafond_decote_couple": 1970.0, "valeur_reduc_demi_part": 3797.0, "plafond_revenus_celibataire_pour_reduction": 21037.0, "plafond_qf_demi_part": 1551.0, "abattement_dixpourcent_max": 12502.0, "plafond_impot_celibataire_pour_decote": 1595.0, "plafond_decote_celibataire": 1196.0, "plafond_revenus_couple_pour_reduction": 42074.0, "id": 1, "abattement_dixpourcent_min": 437.0, "plafond_impot_couple_pour_decote": 2627.0}}}

29.7. Il client web

Image

Image

La cartella [http-clients/06] viene inizialmente ottenuta copiando la cartella [http-clients/05]. Il lavoro di modifica consiste essenzialmente nel:

  • modificare la configurazione [config_layers] in modo che ora includa un livello [métier]. In precedenza aveva solo un livello [dao];
  • aggiungere un nuovo metodo al livello [dao];
  • scrivere uno script [main2] che si baserà sul livello [métier] del cliente per calcolare l’imposta dei contribuenti;

29.7.1. Configurazione dei livelli del cliente

La configurazione dei livelli avviene in due punti:

  • nella configurazione [config], che deve includere tra le dipendenze del cliente la cartella contenente l’implementazione del livello [métier]. Questa cartella era già inclusa nelle dipendenze:

absolute_dependencies = [
        # cartelle del progetto
        # BaseEntity, MyException
        f"{root_dir}/classes/02/entities",
        # InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
        f"{root_dir}/impots/v04/interfaces",
        # AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
        f"{root_dir}/impots/v04/services",
        # ImpotsDaoWithAdminDataInDatabase
        f"{root_dir}/impots/v05/services",
        # AdminData, ImpôtsError, TaxPayer
        f"{root_dir}/impots/v04/entities",
        # Costanti, fasce
        f"{root_dir}/impots/v05/entities",
        # ImpôtsDaoWithHttpClient
        f"{script_dir}/../services",
        # script di configurazione
        script_dir,
        # Logger
        f"{root_dir}/impots/http-servers/02/utilities",
    ]

Successivamente, il file [config_layers] deve essere modificato:


def configure(config: dict) -> dict:
    # istanziazione dei livelli dell'applicazione

    # livello [métier]
    from ImpôtsMétier import ImpôtsMétier
    métier = ImpôtsMétier()

    # livello DAO
    from ImpôtsDaoWithHttpClient import ImpôtsDaoWithHttpClient
    dao = ImpôtsDaoWithHttpClient(config)

    # si esegue la configurazione dei livelli
    return {
        "dao": dao,
        "métier": métier
    }
  • righe 4-6: istanziazione del livello [métier];
  • righe 13-16: il livello [métier] viene inserito nel dizionario dei livelli;

29.7.2. Implementazione del livello [dao]

Image

Il livello [dao] presenterà la seguente interfaccia [InterfaceImpôtsDaoWithHttpClient]:


from abc import abstractmethod

from AbstractImpôtsDao import AbstractImpôtsDao

class InterfaceImpôtsDaoWithHttpClient(AbstractImpôtsDao):

    # calcolo dell'imposta
    @abstractmethod
    def calculate_tax_in_bulk_mode(self, taxpayers: list):
        pass
  • riga 5: l’interfaccia [InterfaceImpôtsDaoWithHttpClient] eredita dalla classe astratta [AbstractImpôtsDao] che gestisce l’accesso al file system del cliente. Si ricorda che essa dispone di un metodo astratto [get_admindata];
  • righe 7-10: il metodo [calculate_tax_in_bulk_mode] che abbiamo definito nella versione precedente consente il calcolo dell’imposta per un elenco di contribuenti;

Questa interfaccia è implementata dalla seguente classe [ImpôtsDaoWithHttpClient]:


# importazioni

import json

import requests
from flask_api import status

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDaoWithHttpClient import InterfaceImpôtsDaoWithHttpClient

class ImpôtsDaoWithHttpClient(InterfaceImpôtsDaoWithHttpClient):

    # costruttore
    def __init__(self, config: dict):
        # inizializzazione del genitore
        AbstractImpôtsDao.__init__(self, config)
        # memorizzazione degli elementi di configurazione
        # configurazione generale
        self.__config = config
        # server
        self.__config_server = config["server"]
        # modalità debug
        self.__debug = config["debug"]
        # registratore
        self.__logger = None

    def get_admindata(self) -> AdminData:
        # si consente la segnalazione delle eccezioni

        # URL del servizio
        config_server = self.__config_server
        config_services = config_server['url_services']
        url_service = f"{config_server['urlServer']}{config_services['get-admindata']}"

        # connessione
        if config_server['authBasic']:
            response = requests.get(url_service,
                                    auth=(
                                        config_server["user"]["login"],
                                        config_server["user"]["password"]))
        else:
            response = requests.get(url_service)

        # modalità debug?
        if self.__debug:
            # logger
            if not self.__logger:
                self.__logger = self.__config['logger']
            # si effettua il login
            self.__logger.write(f"{response.text}\n")

        # codice di stato
        status_code = response.status_code
        # risultato sotto forma di dizionario
        résultat = json.loads(response.text)
        # se il codice di stato è diverso da 200 OK
        if status_code != status.HTTP_200_OK:
            raise ImpôtsError(58, résultat['réponse']['erreurs'])
        # si restituisce il risultato (un dizionario)
        return AdminData().fromdict(résultat["réponse"]["result"])

    # calcolo dell'imposta in modalità batch
    def calculate_tax_in_bulk_mode(self, taxpayers: list):
        # si lasciano risalire le eccezioni

        
  • riga 13: la classe [ImpôtsDaoWithHttpClient] implementa l’interfaccia [InterfaceImpôtsDaoWithHttpClient]. Deriva quindi dalla classe [AbstractImpôtsDao];
  • righe 65-66: il metodo [calculate_tax_in_bulk_mode] esaminato nella versione precedente;
  • righe 29-62: il metodo [get_admindata] che la classe padre [AbstractImpôtsDao] ha dichiarato astratto. È quindi implementato nella classe figlia;
  • righe 33-35: si determina l’URL del servizio web che il metodo [get-admindata] deve interrogare. Questi URL del servizio sono definiti nella configurazione [config] del client:

       # il server di calcolo delle imposte
        "server": {
            "urlServer""http://127.0.0.1:5000",
            "authBasic"True,
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                "calculate-tax""/calculate-tax",
                "get-admindata""/get-admindata"
            }
        },
  • (continua)
    • righe 9-12: i due URL del server web;
  • righe 37-44: il servizio URL viene interrogato in modo sincrono;
  • righe 46-42: se la configurazione lo richiede, la risposta del server viene registrata nel log;
  • riga 57: si sa che il server ha inviato una stringa jSON presente in un dizionario;
  • righe 58-60: se lo stato HTTP della risposta non è 200, viene generata un'eccezione;
  • righe 61-62: si restituisce l’oggetto [AdminData] che incapsula i dati dell’amministrazione fiscale inviati dal server;

29.8. Gli script [main, main2]

Lo script [main] è quello della versione precedente. Utilizza il metodo [calculate_tax_in_bulk_mode] del livello [dao] e quindi utilizza il livello [métier] del server;

Lo script [main2] svolge la stessa funzione dello script [main], ma utilizzando il livello [métier] del client:


# configurazione dell'applicazione

import config
config = config.configure({})

# dipendenze
from ImpôtsError import ImpôtsError
from Logger import Logger

logger = None
# codice
try:
    # logger
    logger = Logger(config["logsFilename"])
    # lo si memorizza nella configurazione
    config["logger"] = logger
    # log di avvio
    logger.write("début du calcul de l'impôt des contribuables\n")
    # si recupera il livello [dao]
    dao = config["layers"]["dao"]
    # si recuperano i contribuenti
    taxpayers = dao.get_taxpayers_data()["taxpayers"]
    # dei contribuenti?
    if not taxpayers:
        raise ImpôtsError(36f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
    # si recuperano i dati dell'amministrazione fiscale
    admindata = dao.get_admindata()
    # calcolo dell'imposta dei contribuenti tramite il livello [métier]
    métier = config['layers']['métier']
    for taxpayer in taxpayers:
        métier.calculate_tax(taxpayer, admindata)
    # si salvano i risultati nel file jSON
    dao.write_taxpayers_results(taxpayers)
# ad eccezione di BaseException come errore:
# # visualizzazione dell'errore
# print(f"Si è verificato il seguente errore: {errore}")
finally:
    # si chiude il logger
    if logger:
        # log di fine
        logger.write("fin du calcul de l'impôt des contribuables\n")
        # chiusura del logger
        logger.close()
    # Operazione completata
    print("Travail terminé...")
  • righe 26-27: si recuperano i dati dell’amministrazione fiscale dal server;
  • righe 28-31: successivamente il calcolo dell’imposta dei contribuenti viene effettuato localmente;

29.9. Test del client

In ciascuno degli script [main, main2] si registrano l’inizio e la fine dello script. In questo modo sarà possibile calcolare la durata di esecuzione dello script. Facciamo alcune previsioni:

  • lo script [main] della versione precedente:
    • crea N thread che vengono eseguiti simultaneamente;
    • ogni thread elabora un lotto di contribuenti per i quali fa calcolare l’imposta tramite un’unica richiesta al server;
    • poiché i N thread vengono eseguiti simultaneamente, la richiesta N+1 viene lanciata prima che la richiesta N abbia ricevuto la risposta. Pertanto, le N richieste comportano un costo maggiore rispetto a una singola richiesta, ma probabilmente non di molto. Inoltre, sul server vengono eseguiti 11 (il numero dei contribuenti) calcoli di business;
  • lo script [main2] di questa versione:
    • effettua un'unica richiesta al server;
    • esegue 11 calcoli di business localmente sul client;

I calcoli di business avranno la stessa durata sia sul server che sul client. La differenza si avrà quindi a livello delle richieste. Ci si può quindi aspettare che il tempo di esecuzione di [main] sia leggermente superiore a quello di [main2].

Si avvia il server della versione 11, il SGBD e il server di posta [hMailServer]. Sul lato server, si imposta il parametro [sleep_time] a zero affinché entrambi i test vengano eseguiti nelle stesse condizioni.

Esecuzione 1 [main]

L’esecuzione di [main] produce i seguenti log:


2020-07-29 14:35:50.016079, MainThread : début du calcul de l'impôt des contribuables
2020-07-29 14:35:50.016079, Thread-1 : début du calcul de l'impôt des 1 contribuables
2020-07-29 14:35:50.016079, Thread-2 : début du calcul de l'impôt des 4 contribuables
2020-07-29 14:35:50.016079, Thread-3 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.016079, Thread-4 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.024426, Thread-5 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.050473, Thread-1 : {"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.050473, Thread-1 : fin du calcul de l'impôt des 1 contribuables
2020-07-29 14:35:50.050473, Thread-3 : {"réponse": {"results": [{"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-3 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, Thread-5 : {"réponse": {"results": [{"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-5 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, Thread-2 : {"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-2 : fin du calcul de l'impôt des 4 contribuables
2020-07-29 14:35:50.051214, Thread-4 : {"réponse": {"results": [{"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-4 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, MainThread : fin du calcul de l'impôt des contribuables

La durata dell’esecuzione è stata di [051214-016079] nanosecondi (riga 17 – riga 1), ovvero 35 millisecondi e 135 nanosecondi.

Si nota che tra la prima richiesta inviata al server e l’ultima risposta ricevuta dal client, il tempo trascorso è lo stesso: [051214-016079] (riga 15 – riga 1), ovvero 35 millisecondi e 135 nanosecondi.

Esecuzione 2 [main2]

L’esecuzione di [main2] fornisce i seguenti log:


2020-07-29 14:41:03.303520, MainThread : début du calcul de l'impôt des contribuables
2020-07-29 14:41:03.345084, MainThread : {"réponse": {"result": {"limites": [9964.0, 27519.0, 73779.0, 156244.0, 13500.0], "coeffr": [0.0, 0.14, 0.3, 0.41, 0.45], "coeffn": [0.0, 1394.96, 5798.0, 13913.7, 20163.4], "plafond_decote_couple": 1970.0, "valeur_reduc_demi_part": 3797.0, "plafond_revenus_celibataire_pour_reduction": 21037.0, "plafond_qf_demi_part": 1551.0, "abattement_dixpourcent_max": 12502.0, "plafond_impot_celibataire_pour_decote": 1595.0, "plafond_decote_celibataire": 1196.0, "plafond_revenus_couple_pour_reduction": 42074.0, "id": 1, "abattement_dixpourcent_min": 437.0, "plafond_impot_couple_pour_decote": 2627.0}}}
2020-07-29 14:41:03.349975, MainThread : fin du calcul de l'impôt des contribuables

La durata dell’esecuzione è stata di [349975-303520] nanosecondi (riga 3 – riga 1), ovvero 46 millisecondi e 455 nanosecondi. In modo del tutto inaspettato, [main] è più veloce di [main2].

Si nota che l’unica richiesta di [main2] è durata [345084-303520] (riga 2 – riga 1), ovvero 41 millisecondi e 564 nanosecondi. Il calcolo dell’imposta ha poi richiesto un tempo pari a [349975-345084] (riga 3 – riga 2), ovvero 4 millisecondi e 91 nanosecondi. È la richiesta HTTP a determinare la durata dell’esecuzione. Sorprendentemente, si nota qui che la singola richiesta di [main2] è durata più a lungo ([41 millisecondes]) rispetto alle quattro richieste simultanee di [main] e [35 millisecondes].

Dal lato server, i log sono i seguenti:


2020-07-29 14:35:27.047721, MainThread : [serveur] démarrage du serveur
2020-07-29 14:35:27.140927, MainThread : [serveur] connexion à la base de données réussie
2020-07-29 14:35:28.790716, MainThread : [serveur] démarrage du serveur
2020-07-29 14:35:28.847518, MainThread : [serveur] connexion à la base de données réussie
2020-07-29 14:35:50.039178, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.039178, Thread-3 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.043220, Thread-4 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.044307, Thread-5 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.045796, Thread-2 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 3, 'salaire': 100000, 'impôt': 9200, 'surcôte': 2180, 'taux': 0.3, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.045796, Thread-3 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 2, 'salaire': 55555, 'impôt': 2814, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-6 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.046825, Thread-6 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'surcôte': 0, 'taux': 0.14, 'décôte': 384, 'réduction': 347}, {'marié': 'oui', 'enfants': 3, 'salaire': 50000, 'impôt': 0, 'surcôte': 0, 'taux': 0.14, 'décôte': 720, 'réduction': 0}, {'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'taux': 0.41, 'décôte': 0, 'réduction': 0}, {'marié': 'non', 'enfants': 3, 'salaire': 100000, 'impôt': 16782, 'surcôte': 7176, 'taux': 0.41, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-4 : [index] {'réponse': {'results': [{'marié': 'non', 'enfants': 0, 'salaire': 200000, 'impôt': 64210, 'surcôte': 7498, 'taux': 0.45, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'taux': 0.41, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-5 : [index] {'réponse': {'results': [{'marié': 'non', 'enfants': 0, 'salaire': 100000, 'impôt': 22986, 'surcôte': 0, 'taux': 0.41, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 2, 'salaire': 30000, 'impôt': 0, 'surcôte': 0, 'taux': 0.0, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:41:03.341582, Thread-7 : [index] requête : <Request 'http://127.0.0.1:5000/get-admindata' [GET]>
2020-07-29 14:41:03.341582, Thread-7 : [index] {'réponse': {'result': {'limites': [9964.0, 27519.0, 73779.0, 156244.0, 13500.0], 'coeffr': [0.0, 0.14, 0.3, 0.41, 0.45], 'coeffn': [0.0, 1394.96, 5798.0, 13913.7, 20163.4], 'plafond_decote_couple': 1970.0, 'valeur_reduc_demi_part': 3797.0, 'plafond_revenus_celibataire_pour_reduction': 21037.0, 'plafond_qf_demi_part': 1551.0, 'abattement_dixpourcent_max': 12502.0, 'plafond_impot_celibataire_pour_decote': 1595.0, 'plafond_decote_celibataire': 1196.0, 'plafond_revenus_couple_pour_reduction': 42074.0, 'id': 1, 'abattement_dixpourcent_min': 437.0, 'plafond_impot_couple_pour_decote': 2627.0}}}
  • riga 5: la prima richiesta del client [main];
  • riga 14: l’ultima risposta al client [main]. Tra le due intercorrono 6 millisecondi e 647 nanosecondi;
  • righe 15-16: l’unica richiesta del client [main2]. La risposta è istantanea;