Blog ENI : Toute la veille numérique !
🎁 Jusqu'au 25/12 : 1 commande de contenus en ligne
= 1 chance de gagner un cadeau*. Cliquez ici
🎁 Jusqu'au 31/12, recevez notre
offre d'abonnement à la Bibliothèque Numérique. Cliquez ici
  1. Livres et vidéos
  2. Scripting Python sous Linux
  3. Simulation d'activité
Extrait - Scripting Python sous Linux Développez vos outils système (2e édition)
Extraits du livre
Scripting Python sous Linux Développez vos outils système (2e édition)
3 avis
Revenir à la page d'achat du livre

Simulation d'activité

Introduction

Lors de la mise en exploitation d’un système informatique quel qu’il soit, la montée en charge reste un passage critique. Ce qu’il y a de bien en informatique, c’est que l’on peut simuler la réalité. Et cela devient vraiment intéressant quand il est possible de simuler des dizaines, voire des centaines d’utilisateurs. Même si ceux-ci se comportent de manière idéale, sans faire d’erreur (après tout, il s’agit d’automates), cela permet au moins de valider des outils dans un contexte optimal. Rappelez-vous, tout n’est que question de volume et de fréquence. 

Dans ce chapitre, nous allons voir comment simuler une activité d’informatique de gestion avec une pseudo entreprise qui pratique le négoce. C’est un cas facile et classique, mais cela permet de générer de l’activité sur un serveur et du volume de données.

À l’origine, ce petit projet devait me permettre de tester les sauvegardes RMAN de la base de données Oracle, or sauvegarder une base de données sans volume n’a quasiment aucun intérêt.

Effectuer une sauvegarde est toujours la partie la plus facile, c’est la restauration qui est compliquée et difficile. De plus, on restaure des données généralement dans un contexte tendu et stressant avec...

Description

L’entreprise "ACME Corporation" (le nom n’a pas d’importance) vend des produits à ses clients, et en fonction des quantités commandées, achète à ses fournisseurs le volume nécessaire pour honorer les commandes.

C’est ce que l’on appelle communément du négoce.

Il y a ici trois entités principales : Client/Produit/Fournisseur, qui deviendront des objets et des tables dans notre base de données.

Le client passe des commandes clients qui, une fois totalement livrées, pourront être facturées. Cela nous donne un objet "commande client" qui, au niveau de la base de données, deviendra une table d’en-têtes de commande et de lignes de commandes.

L’entreprise passe des commandes fournisseurs qui, elles, devront être réceptionnées. Cela donne un autre objet : lignes de commandes fournisseurs.

Et pour savoir où on en est pour chaque produit, on va gérer du stock, d’où le dernier objet : le stock.

La structure des données

Voici une description de la structure des données.

D’abord, sous forme de schéma :

images/14EI01.png

Structure des données

Puis plus détaillée :

Les tables Client/Produit/Fournisseur :

Table

Champ

Type

Remarque

CLIENT

Nom

Char(20)

Clé unique

PRODUIT

Desig

Char(30)

Clé Unique/Désignation du produit 

PRODUIT

Prix

Flottant

Le prix du produit

FOURN

Nom

Char(20)

Clé Unique

FOURN

Delai

Entier

Délai de réapprovisionnement

La Table Stock :

Table

Champ

Type

Remarque

STOCK

Dépôt

Char(10)

Code du Dépôt de marchandise

STOCK

PRO_ID

 

Identifiant produit/clé étrangère

STOCK

QSTOCK

Entier

Quantité en stock

Les commandes clients : les tables COMCLI et LIGCLI.

Table COMCLI (Commandes Client) :

Table

Champ

Type

Remarque

COMCLI

NUMCOM

Entier

Clé unique

COMCLI

DATCOM

Date

Par défaut date de création

COMCLI

CLI_ID

 

Identifiant Client/Clé étrangère

COMCLI

FACTURE

Entier

N° de facture

Table LIGCLI (Lignes de commande client) :

Table

Champ

Type

Remarque

LIGCLI

NUMCOM

Entier

N° de commande client

LIGCLI

NUMLIG

Entier

N° de ligne

LIGCLI

PRO_ID

 

Identifiant produit/clé étrangère

LIGCLI

QCOM

Entier

Quantité commandée

LIGCLI

PRIX

Flottant

Prix du produit

LIGCLI

QLIV

 

Quantité livrée

LIGCLI

QFAC

Entier

Quantité facturée

La table LIGFOU des commandes fournisseurs :

Table

Champ

Type

Remarque

LIGFOU

NUMCOM

Entier

N° de commande client

LIGFOU

NUMLIG

Entier

N° de ligne

LIGFOU

FOU_ID

 

Identifiant fournisseur/clé étrangère

LIGFOU

PRO_ID

 

Identifiant produit/clé étrangère

LIGFOU

QCOM

Entier

Quantité commandée

LIGFOU

PRIX

Flottant

Prix du produit

LIGFOU

DPREVU

Date

Date de livraison prévisionnelle

LIGFOU

QREC

Entier

Quantité reçue

La gestion des compteurs nécessite la gestion d’une table supplémentaire. Un compteur permet de gérer les numéros de commande client, de commandes fournisseurs, de facture, etc.

Il est nécessaire de se coder une gestion des compteurs pour être indépendant de la base de données.

Table

Champ

Type

Remarque

COMPTEUR

NOM

Char(10)

Nom du compteur

COMPTEUR

VAL

Entier

Valeur du compteur

Cette...

L’initialisation de la base de données

SQLAlchemy a déjà été étudié un peu plus haut dans cet ouvrage et c’est pour cette raison que nous n’allons décrire que l’essentiel.

1. definitions.py

La première chose à faire est de décrire la structure des données sous forme d’objet Python.

Donc, le premier script s’appellera definitions.py et il devra définir la base de données que l’on veut créer.

Tout d’abord, il est nécessaire d’effectuer les bons imports de modules. SQLAlchemy est une grosse librairie et un import global (import SQLAlchemy.*) risque d’être coûteux en mémoire.

Il est nécessaire d’importer :

La définition des colonnes et des clés étrangères : Column ForeignKey.

Les types de données : Integer, String, DateTime, Date, Float, BigInteger

Des méthodes et fonctions comme relationship, declarative_base et create_engine.

ForeignKey : une clé étrangère, dans une base de données relationnelle, est une contrainte qui garantit l’intégrité référentielle entre deux tables. Une clé étrangère identifie une colonne ou un ensemble de colonnes d’une table comme référençant une colonne ou un ensemble de colonnes d’une autre table.

Source Wikipédia : https://fr.wikipedia.org/wiki/Cl%C3%A9_%C3%A9trang%C3%A8re

D’un côté plus "pratique" ici : la déclaration d’une clé étrangère d’une table sur une autre garantit que, lors de la saisie d’une commande pour un client, ce client existe dans la table des clients, sinon la commande est refusée.

Le module de définitions doit être conçu pour être importé par les autres scripts ; les classes décrites ont besoin d’être représentées spécifiquement, d’où l’utilisation de la méthode __repr__.

Et comme il s’agit de classes Python, il est tout à fait possible de définir une classe de base contenant les champs revenant sur chaque classe.

Voici le fichier definitions.py :

#fichier : db-activity-gestion/definitions.py 
## ------------------------------------- ...

La connexion à la base de données

Le script précédent n’est à lancer qu’une seule fois pour l’initialisation de la base de données.

Pour la suite de la simulation, chaque script sera autonome, mais fonctionnera de la même manière :

  • Connexion à la base de données

  • Exécution de la fonction principale

Chaque script aura donc besoin d’une fonction connect() qui renverra un objet session.

C’est le but du script base.py que voici, agrémenté d’une simple fonction de récupération du catalogue des produits pour test :

#fichier : db-activity-gestion/base.py 
 
from sqlalchemy import create_engine, select 
from sqlalchemy.orm import sessionmaker 
 
from definitions import Base, BASE_NAME 
from definitions import Client, Produit, Fourn, Stock, ComCli, 
LigCli, LigFou 
 
def connect(): 
   engine = create_engine(BASE_NAME, echo=False) 
   Base.metadata.bind = engine 
   DBSession = sessionmaker(bind=engine) 
   session = DBSession() 
   return session 
 
def get_catalog(): 
   session = connect() 
   s = select([Produit.id]) 
   prods = session.execute(s) 
   p = [ x[0] for x in prods ] 
   session.close() 
   return p ...

Les compteurs

Initialement, cette simulation a été conçue pour la base de données Oracle, qui possède de base une gestion de séquence. Ces séquences permettent d’obtenir une suite de numéros unique qui se suivent, mais sans garantir la continuité.

Malheureusement, cet objet de base de données n’existe pas dans certaines bases, dont SQLite. Cependant, c’est assez facile à coder, surtout avec Python.

Pour un compteur, c’est juste un nom et une valeur ; on a simplement besoin d’une fonction pour créer un compteur, une autre pour obtenir la prochaine valeur et, au cas où, une fonction pour obtenir la valeur courante.

Chaque fonction sera autonome et créera sa propre session.

Voici le script :

# fichier :  db-activity-gestion/sequence.py 
 
## ------------------------- 
## Librairie des sequences 
## ------------------------- 
 
from sqlalchemy import create_engine, select 
from sqlalchemy.orm import sessionmaker 
 
from base import connect 
from definitions import Compteur 
 
def cr_Compteur(nom): 
   session = connect() 
   c = Compteur() 
   c.nom = nom 
   c.val = 1 
   session.add(c) 
   session.commit() 
   session.close() 
 
def Curr_val(nom): 
 ...

Les commandes clients

Une fois que la base est initialisée, il est possible de se pencher sur la base du négoce, à savoir la commande client.

Pour cette simulation, ce script devra générer une commande client à partir du catalogue complet des produits et pour un client choisi au hasard.

Le script commence donc par récupérer le catalogue des produits, et surtout les informations intéressantes, à savoir l’identifiant du produit et son prix.

Ensuite, il faut trouver un client, et pour cela extraire les identifiants des clients et utiliser la fonction random.choice().

Il reste à sélectionner les produits commandés. Pour cela, la fonction random.sample() permet d’extraire un échantillon significatif.

Dans l’état actuel des choses, le maximum de produits commandés est calculé en fonction du nombre de produits ; cela convient quand il y a peu de produits. Mais si l’on veut un fichier produits plus imposant, il sera nécessaire de borner le nombre de produits commandés, pour ne pas avoir des commandes surréalistes de plusieurs milliers de lignes (même si cela existe réellement…).

Quand on a le client, la liste des produits commandés, on peut générer une commande. On prendra bien soin d’encadrer l’écriture dans la base de données par une gestion...

La livraison des commandes clients

Si on a des commandes et du stock, alors on peut procéder à la livraison des commandes clients.

Ce n’est guère plus compliqué que le reste, il faut rechercher les lignes de commandes dont la quantité commandée moins la quantité livrée est supérieure à zéro.

La requête SQL est beaucoup plus succincte qu’en langue française, car il s’agit ni plus ni moins que de :

select numcom, numlig, produit_id, qcom, qliv 
from LIGCLI 
where qcom-qliv > 0 
order by numcom, numlig 

SQLAlchemy permet d’utiliser directement des requêtes avec la méthode execute() de la session en cours.

À partir du résultat de cette requête, nous pouvons travailler sur chaque commande et chaque ligne de chaque commande.

On commence par récupérer le stock de chaque produit, et si le stock est supérieur à la quantité nécessaire (qcom - qliv), alors nous pouvons allouer notre ligne et décrémenter le stock.

On remarquera la solution trouvée par les développeurs de SQLAlchemy pour exprimer des tests multiples dans la fonction get_stock() en cumulant les ".where".

Il n’y a pas trop de contrôle à ce niveau pour le stock négatif, ni trop de risques tant que ce script est lancé de manière...

La facturation des commandes livrées

Les commandes sont livrées, il est donc possible de les facturer. En fait, il s’agit plus d’un exercice obligeant à réfléchir sur la question.

Le principe de ce script est de rechercher les commandes clients non facturées (champ FACTURE == 0) et de vérifier si toutes les lignes ont été livrées.

C’est ce que réalise la fonction get_ccl_a_facturer() ; elle renvoie une liste des commandes à facturer.

Cette liste permet de lancer, pour chaque commande à facturer, la fonction facture_ccl(). Celle-ci a pour rôle de créer un numéro de facture, de lancer une commande SQL update pour mettre à jour la commande, et de mettre à jour les lignes de commandes en positionnant la quantité facturée à la quantité livrée.

Voici le script :

#fichier : db-activity-gestion/fact_client.py 
 
from sqlalchemy import select 
 
from base import connect 
from definitions import Client, Produit, Fourn, Stock, ComCli, 
LigCli, LigFou 
 
import sequence as seq 
 
import datetime 
import random 
 
# ------------------------------------- 
# Recherche des commandes facturables 
# ------------------------------------- 
def get_ccl_a_facturer(): 
   ccl_a_facturer = [] 
   session...

Le réapprovisionnement du stock

Au bout d’un moment, il n’y aura plus de stock et il faudra bien se fournir quelque part. C’est là qu’intervient le réapprovisionnement.

Dans cette simulation, chaque fournisseur est capable de livrer tous les produits. Dans la vie réelle, c’est beaucoup plus complexe.

Au début, pour effectuer des tests, le délai de réapprovisionnement est positionné à 0 jour pour éviter l’attente. Dans un contexte de simulation plus long, il est souhaitable de le modifier pour reproduire les flux d’une exploitation.

Comment réapprovisionne-t-on un stock ?

En calculant le besoin pour chaque produit. Cela se fait en deux passes :

La première cherche dans les commandes clients le volume de stock nécessaire pour chaque produit, avec une requête comme :

select produit_id, sum(qcom) 'qte' 
from LIGCLI 
where qcom-qliv > 0 
group by produit_id 
order by produit_id 

Cela signifie que l’on veut la liste des identifiants produits avec la somme des quantités commandées pour les lignes de commandes non livrées (qcom-qliv > 0) et, pour se simplifier la vie, on trie et on regroupe par identifiant produit.

Le résultat est la liste triée des besoins (la somme des quantités à livrer) pour chaque produit.

Cette liste brute des besoins...

La réception des commandes fournisseurs

Dernier script pour la gestion de cette petite entreprise de négoce : la réception des commandes fournisseurs.

Il suffit de rechercher les lignes de commandes fournisseurs dont la quantité commandée moins la quantité reçue est supérieur à zéro (qcom - qrecu > 0) et dont la date prévue est inférieure ou égale à la date du jour.

Dans l’affirmative, on récupère l’enregistrement de stock correspondant et l’on incrémente le stock avec la quantité reçue.

Dans la même transaction, on effectue une mise à jour de la ligne de commande fournisseur en incrémentant aussi la quantité reçue.

Voici le script :

# fichier : db-activity-gestion/recept_fou.py 
 
from sqlalchemy import select 
 
from base import connect 
from definitions import Client, Produit, Fourn, Stock, ComCli, 
LigCli, LigFou 
 
import sequence as seq 
 
import datetime 
import random 
 
def get_cfou_ar(date_jour): 
   session = connect() 
   r = select([LigFou]).\ 
       where(LigFou.qcom-LigFou.qrecu > 0).\ 
       where(LigFou.dprevu <= date_jour) 
   cfouar = session.execute(r).fetchall() 
   c...

Utilisation

Voici un exemple d’utilisation pour ces scripts.

La première chose à faire est de choisir la base de données que l’on veut tester.

Ces scripts sont facilement adaptables pour une autre base de données grâce à l’utilisation de SQLAlchemy.

Le deuxième point est d’adapter le script populate.py au volume désiré ; par défaut, ce script crée 10 clients, produits, fournisseurs, ce qui permet de tester les scripts, mais même SQLite ne répond pas trop mal sur ces volumes.

Le dernier point est de choisir une période : heure, journée, 30 minutes. Tout dépend du temps et du volume que vous voulez générer.

Voici le tableau récapitulatif des scripts décrits précédemment :

Scripts

Fréquence

Remarque

Populate.py

1 seule fois

Initialise la base de données

Client.py

Plusieurs fois/période

La fréquence est à adapter en fonction du volume de flux désiré

livraison_client.py

2 fois par période

 

fact_client.py

1 fois par période

Hors période d’exploitation

reappro_fou.py

1 fois par période

Hors période d’exploitation

recept_fou.py

1 à 2 fois par période

 

La période peut correspondre à une journée comme à une heure ou une minute, tout dépend du type de simulation souhaitée.

Voici un tableau de déclenchement des scripts qui pourrait correspondre à une exploitation classique, et qui permet aussi de bien séquencer les opérations afin de ne pas trop agacer le serveur.

Toutes les opérations devaient être terminées pour 22 h, heure à laquelle nous avions prévu une sauvegarde globale de la base de données.

Horaire

 

 

8

9

10

11

12

13

14

15

16

17

18

19

20

21

scripts

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

client.py

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

livraison_client.py

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

reappro_fou.py

 

 

 

 

 

 

 

 ...

Résumé

À l’origine, ce projet devait permettre de tester sur une grande période les scripts de sauvegarde et de purge liés aux mécanismes d’archivage des fichiers de journalisation d’Oracle, plus connus sous les noms de RMAN et ARCHIVELOG. 

En effet, il est difficile de tester ce genre de choses sur une base qui ne bouge pas et nous voulions un environnement de laboratoire pour comprendre un peu mieux comment évoluaient la consommation des ressources et surtout la ressource disque.

Au bout de quelques jours, cela a dépassé nos attentes, car nous pouvions aussi étudier sur du volume comment se comportait la base en fonction de paramètres volontairement réduits, comme la mémoire cache allouée (SGA, System GlobalArea).

Mais cela peut être transposé sur une autre base de données comme MySQL ou PostgreSQL et ainsi permettre de voir comment celle-ci réagit avec un volume que l’on a décidé et que l’on maîtrise.

Ces scripts sont volontairement simples, voire simplistes. Lorsqu’on a codé dans différents langages des équivalences plus complexes, il ne faut pas longtemps pour créer ces six scripts.

Cependant, il ne s’agit que de simulation, certainement imparfaite, et il ne faut pas trop pousser cette simulation pour que se déclenchent des erreurs...