CONSERVATOIRE NATIONAL DES ARTS ET METIERS

PARIS

__________

 

Mémoire présenté en vue d’obtenir

le diplôme d’ingénieur C.N.A.M.

en informatique

par Eric JEURY

__________

 

 

 

 

Outils pour l’observation d’applications réparties en JAVA

au dessus de CORBA.

 

Soutenu le 17 juin 1998

 

__________

Jury

Président : Pr. Gérard FLORIN Professeur des universités

Membres : Mme Laurence DUCHIEN Maître de conférences

M. Jean-Marc FARINONE Maître de conférences

M. Eric GRESSIER Maître de conférences

M. Yves LE ROLLAND Ingénieur réseau

M. Frédéric BERGDOLT Ingénieur CNAM

M. Lionel SEINTURIER ATER

Résumé :

Outils pour l’observation d’applications réparties en JAVA au dessus de CORBA.

Mémoire d’ingénieur CNAM, Paris 1998.

 

JAVA, nouveau langage de programmation orienté objet, est apparu en 1990. Il introduit une indépendance vis à vis des plates-formes grâce à son code interprété qui est portable sur de nombreux systèmes d’exploitation. L’utilisation de JAVA est liée à la croissance exponentielle d’Internet car ses propriétés sont utilisées pour le développement d’applications qui fonctionnent au travers de ce réseau sur des plates-formes hétérogènes.

Ce langage de programmation, associé à des bus logiciels conformes à la norme CORBA (Common Object Request Broker), permet de développer des applications distribuées de plus en plus complexes. CORBA définit des éléments qui participent à la répartition, à la localisation et à la communication entre les objets de telles applications.

La mise au point d’applications réparties reste difficile de par la nature non déterministe de leur exécution. Celle-ci dépend notamment du degré de synchronisation entre les entités et des conditions d’exécution : règles d’ordonnancement des processus, charge du système et des médiums de communication. Deux approches, l’une statique et l’autre dynamique, permettent le déboguage de telles applications.

L’objectif de notre travail est de créer un outil d’observation qui réalise une analyse dynamique fondée sur l’observation de toute application JAVA répartie au moyen d’un bus logiciel conforme à CORBA. Cette technique de mise au point est fondée sur une relation de causalité, inspirée de celle définie par Lamport, adaptée à un univers à objets répartis. La récolte logicielle d’information est réalisée à l’aide de traces qui sont ensuite analysées d’une manière post-mortem.

A l’issue de ce mémoire, le programmeur dispose d’une librairie spécialisée dans l’observation d’applications JAVA réparties. Il pourra l’utiliser dès la phase de codage pour vérifier que le comportement de son application est conforme aux spécifications et visualiser ainsi le graphe des appels réalisés lors de l’exécution.

 

Mots clés :

Observation, objets répartis, relations d’ordre partiel, CORBA, JAVA

KeyWords :

Distributed debug, partial order relations, distributed objects, CORBA, JAVA

CONSERVATOIRE NATIONAL DES ARTS ET METIERS

PARIS

__________

 

Mémoire présenté en vue d’obtenir

le diplôme d’ingénieur C.N.A.M.

en informatique

par Eric JEURY

__________

 

 

 

 

Outils pour l’observation d’applications réparties en JAVA

au dessus de CORBA.

 

Soutenu le 17 juin 1998

 

__________

Jury

Président : Pr. Gérard FLORIN Professeur des universités

Membres : Mme Laurence DUCHIEN Maître de conférences

M. Jean-Marc FARINONE Maître de conférences

M. Eric GRESSIER Maître de conférences

M. Yves LE ROLLAND Ingénieur réseau

M. Frédéric BERGDOLT Ingénieur CNAM

M. Lionel SEINTURIER ATER

 

Remerciements.

Ce mémoire a été réalisé au sein du laboratoire CEDRIC (Centre d’Etude et de Recherche en Informatique du CNAM).

Je remercie Gérard Florin, Professeur des universités, de bien avoir voulu m’accueillir dans son laboratoire.

Je remercie Laurence Duchien, Maître de conférences et responsable pédagogique de mon stage, pour m’avoir proposé ce sujet et suivi mon projet tout au long de ce congé de formation.

Je remercie Lionel Seinturier, Docteur en informatique, pour m’avoir initié aux objets répartis.

Je remercie Jean Marc Farinone pour son cours de présentation de JAVA.

Je remercie Eric Gressier pour la qualité de son accueil.

Je remercie Frédéric Bergdolt pour m’avoir encouragé à préparer ce mémoire d’ingénieur.

Je remercie Pascal Placide pour son travail dans le cadre de son DEA sur la mise au point d’applications réparties.

Je remercie Michel Maybon pour son amitié et pour son assiduïté aux nombreux cours que nous avons suivis en commun.

Je remercie les enseignants du département d’informatique du CNAM pour leur accueil chaleureux.

Enfin, je remercie tous mes camarades stagiaires pour l’amitié qu’ils m’ont témoignée  et les conseils qu’ils m’ont prodigués : Christian, Carlos, Jose, Véronique, Laurent, Françoise, Pascale et Pascaline.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

A Carmen, Florence et Ruby…

 

 

 

 

INTRODUCTION *

PARTIE 1 : JAVA ET CORBA. *

CHAPITRE 1 : LES OBJETS DISTRIBUES. *

1. L’APPROCHE ORIENTEE OBJET. *

1.1. LA CONCEPTION MODULAIRE. *

1.2. CONCEPTION ORIENTEE OBJET. *

2. LES OBJETS. *

2.1. LA CLASSE. *

2.2. L’ENCAPSULATION. *

2.3. LE POLYMORPHISME. *

2.4. L’HERITAGE. *

3. LES OBJETS DISTRIBUES ET CONCURRENTS. *

3.1. L’EXECUTION A DISTANCE DES METHODES. *

3.2. LA SYNCHRONISATION INTRA-OBJET. *

3.3. LA MIGRATION D’OBJET. *

CHAPITRE 2 : PRESENTATON DU LANGAGE JAVA. *

1. DESCRIPTION DU LANGAGE JAVA. *

1.1. JAVA EST ORIENTE OBJET FORTEMENT TYPE. *

1.2. JAVA EST INTERPRETE. *

1.3. JAVA FONCTIONNE COMME UN SYSTEME D’EXPLOITATION VIRTUEL. *

2. LES THREADS. *

2.1. LA CREATION DE THREADS *

2.1.1. PAR LA CLASSE THREAD *

2.1.2. PAR LA CLASSE RUNNABLE *

2.2. L’EXCLUSION MUTUELLE. *

2.2.1. L’EXCLUSION MUTUELLE D’UNE METHODE. *

2.2.2. L’EXCLUSION MUTUELLE D’UN BLOC. *

2.2.3. JAVA ET SES MONITEURS RE-ENTRANTS. *

2.3. LA SYNCHRONISATION ENTRE THREADS. *

2.3.1. wait (). *

2.3.2. notify(). *

2.3.3. EXEMPLE. *

3. RMI. *

4. LES DEVELOPPEMENTS FUTURS DE JAVA. *

CHAPITRE 3 : CORBA *

1. INTRODUCTION. *

1.1. LES OBJETS : UNE REVOLUTION DANS LA REVOLUTION CLIENT/SERVEUR. *

1.2. LE CLIENT/SERVEUR AVEC DES OBJETS DISTRIBUES. *

1.3. LES STANDARDS DE L’OMG. *

2. PRESENTATION DE CORBA 2.0. *

2.1. L’ARCHITECTURE DE GESTION D’OBJETS DE L’OMG. *

2.2. LE NEGOCIATEUR DE REQUETES OBJET (ORB). *

2.3. L'IDL DE CORBA. *

2.3.1. L'IDL ET LE REFERENTIEL D’INTERFACES. *

2.3.2. LE MAPPING C++. *

2.3.3. LE MAPPING JAVA. *

2.4. LE PROCESSUS DE DEVELOPPEMENT D’UNE APPLICATION CORBA. *

2.5. CORBA : L'ORB ETENDU GRACE A IIOP. *

2.6. INITIALISATION DE CORBA. *

3. LES DIFFERENTS ELEMENTS COMPOSANT LE BUS LOGICIEL. *

3.1. L’INVOCATION D’OBJETS. *

3.1.1. LES SOUCHES CLIENT STATIQUES ET DYNAMIQUES. *

3.1.2. LE REFERENTIEL D’INTERFACES. *

3.1.2.1. Fonctionnement du référentiel d’interfaces. *

3.1.2.2. Structure d’un référentiel d’interfaces. *

3.1.3. L’INTERFACE DE L’ORB. *

3.2. L’IMPLANTATION ET L’ACCES AUX OBJETS. *

3.2.1. L’ADAPTATEUR D’OBJET. *

3.2.1.1. Les services fournis. *

3.2.1.2. Les différents types de BOA. *

3.2.2. LES SQUELETTES SERVEUR STATIQUES ET DYNAMIQUES. *

3.2.3. LE REFERENTIEL D’IMPLANTATION. *

3.2.4. LES DEMI-PONTS GENERIQUES. *

4. EXEMPLES DE BUS LOGICIEL. *

4.1. OMNIBROKER. *

4.1.1. PRESENTATION D’OMNIBROKER. *

4.1.2. L’ORB D’OMNIBROKER. *

4.1.2.1. En JAVA. *

4.1.2.2. En C++. *

4.1.3. L’ADAPTATEUR D’OBJET D’OMNIBROKER. *

4.1.3.1. En JAVA. *

4.1.3.2. En C++. *

4.2. VISIBROKER. *

4.2.1. PRESENTATION DE VSIBROKER. *

4.2.2. LES SPECIFICITES TECHNIQUES DE VISIBROKER. *

4.2.3. CAFFEINE. *

5. CONCLUSION. *

5.1. COMPARAISON RMI/CORBA. *

5.2. COMPARAISON DCOM/CORBA. *

PARTIE 2 : LA MISE AU POINT D’APPLICATIONS REPARTIES. *

CHAPITRE 4 : DEBOGAGE D’APPLICATIONS REPARTIES. *

1. INTRODUCTION. *

2. LES PROBLEMES LIES A LA MISE AU POINT. *

2.1. LES DIFFERENTS TYPES D’EXECUTION. *

2.1.1. L’EXECUTION SEQUENTIELLE. *

2.1.2. L’EXECUTION PARALLELE. *

2.1.3. L’EXECUTION REPARTIE. *

2.2. LES PROBLEMES LIES A L’OBSERVABILITE. *

2.2.1. INTRODUCTION. *

2.2.2. RELATION DE CAUSALITE. *

2.2.3. ETAT GLOBAL COHERENT. *

2.2.4. ALGORITHMES DE DATATION. *

2.2.4.1. L’horloge de Lamport. *

2.2.4.2. Les horloges vectorielles. *

3. LES DIFFERENTES APPROCHES DE LA MISE AU POINT. *

3.1. ANALYSE STATIQUE. *

3.1.1. ANALYSE STATIQUE MANUELLE. *

3.1.2. ANALYSE STATIQUE AUTOMATIQUE. *

3.2. ANALYSE DYNAMIQUE. *

3.2.1. TECHNIQUES FONDEES SUR L’OBSERVATION. *

3.2.1.1.Introduction. *

3.2.1.2. Récolte logicielle. *

3.2.1.3. Récolte matérielle. *

3.2.1.4. Avantages et inconvénients des outils post-mortems. *

3.2.2. TECHNIQUES A BASE D’INTERACTIONS. *

3.2.2.1. Analyse par interaction directe. *

3.2.2.1.1. Présentation de cette technique. *

3.2.2.1.2. Avantages et inconvénients de l’analyse par interaction directe. *

3.2.2.2. Analyse par interaction indirecte. *

3.2.2.2.1. Ré-exécution dirigée par les données. *

3.2.2.2.2. Ré-exécution dirigée par le contrôle. *

3.2.2.2.3. Avantages et inconvénients de l’analyse par interaction indirecte. *

4. CONCLUSION. *

CHAPITRE 5. LES RELATIONS D’ORDRE DANS UNE APPROCHE OBJETS REPARTIS. *

1. INTRODUCTION. *

2. NOTION DE CAUSALITE EN APPROCHE OBJETS REPARTIS. *

2.1. PRESENTATION. *

2.2. EXEMPLE. *

3. DEFINITION DE LA RELATION DE CAUSALITE UTILISEE. *

3.1. ORDRE D’INTERACTION. *

3.2. ORDRE PROGRAMME. *

3.2.1. Ordre LOCAL. *

3.2.2. Ordre intra objet. *

3.2.3. Ordre transactionnel. *

4. CONCLUSION. *

PARTIE 3 : EXPERIMENTATION. *

CHAPITRE 6 : LA MISE EN œuvre DE L’OBSERVATION. *

1. INTRODUCTION. *

1.1 LE BUT RECHERCHE. *

1.2. LES MOYENS UTILISES. *

1.3. L’UTILISATION DES OUTILS. *

2. LA CONSTRUCTION DES TRACES. *

2.1. LA CONSTRUCTION DE L’ORDRE. *

2.2. L’ESTAMPILLAGE TEMPORELLE. *

2.2.1. L’horloge logique implémentée. *

2.2.2. Invocation de méthodes distantes. *

2.2.3. Retour d’invocation de méthodes distantes. *

2.2.4. Autres cas. *

2.3. DEFINITION DES TRACES. *

2.3.1. INTRODUCTION. *

2.3.2. LA GESTION DES TRACES. *

2.3.2.1. LE FICHIER IDL D’UN OBJET TRACE. *

2.3.2.2. LES DONNEES D’UN OBJET TRACE. *

2.3.2.3. LES CONSTRUCTEURS D’UN OBJET TRACE. *

2.3.2.3.1. Constructeur d’ordre zéro. *

2.3.2.3.2. Constructeur d’ordre un. *

2.3.2.3.3. Constructeur d’ordre deux. *

2.3.2.3.4. Constructeur d’ordre trois. *

2.3.2.3.5. Exemple. *

2.3.2.4. LES METHODES DE TYPE FONCTION D’ORDRE D’UN OBJET TRACE. *

Description *

2.3.3. LE CONTENU DES FICHIERS TRACE. *

2.3.3.1. Les macro-expressions des fichiers trace. *

2.3.3.2. La fonction d’ordre " DebutE ". *

2.3.3.3. La fonction d’ordre " FinE ". *

2.3.3.4. La fonction d’ordre " Creat_obj " . *

2.3.3.5. La fonction " GetRef_obj ". *

2.3.3.6. La fonction d’ordre " DebutM ". *

2.3.3.7. La fonction d’ordre " FinM ". *

2.3.3.8. La fonction d’ordre " Iappel". *

2.3.3.9. La fonction d’ordre " Retour_Iappel ". *

2.3.4. EXEMPLE DE FICHIER TRACE. *

3. ANALYSE DES TRACES ET OBSERVATION. *

3.1. L’ANALYSEUR LEXICAL JLEX. *

3.1.1. Introduction à JLex. *

3.1.1.1.Présentation de JLex. *

3.1.1.2. Le moteur du compilateur. *

3.1.1.3. Les fichiers de spécification *.lex. *

3.1.1.4. Exemple d’un fichier de spécification. *

3.1.2. Utilisation de JLex. *

3.1.2.1. La structure de sauvegarde des tokens. *

3.1.2.2. Description des différents analyseurs lexicaux utilisés. *

3.1.2.3. La compilation d’un analyseur lexical JLex. *

3.1.2.4. Les macro-expressions reconnues. *

3.1.2.5. Les expressions régulières reconnues. *

3.1.2.5.1. La fonction d’ordre " DebutE ". *

3.1.2.5.2.La fonction d’ordre " FinE ". *

3.1.2.5.3. La fonction d’ordre " Creat_obj " . *

3.1.2.5.4. La fonction " GetRef_obj ". *

3.1.2.5.5. La fonction d’ordre " DebutM ". *

3.1.2.5.6. La fonction d’ordre " FinM ". *

3.1.2.5.7. La fonction d’ordre " Iappel". *

3.1.2.5.8. La fonction d’ordre " Retour_Iappel ". *

3.2. LE VISUALISEUR D’ARBRES. *

3.2.1. PRESENTATION DE LA LIBRAIRIE JAVA Tree. *

3.2.2. MODIFICATION DE LA CLASSE TreeNode. *

3.2.3. UTILISATION DE LA LIBRAIRIE JAVA Tree. *

CHAPITRE 7 : MISE EN œuvre DE L’ANALYSE. *

1. INSTRUMENTATION D’UN PROGRAMME SOURCE. *

1.1. PRINCIPE DE FONCTIONNEMENT. *

1.2. DESCRIPTION DU PROGRAMME INSTRUMENTE. *

1.2.1. Introduction. *

1.2.2. Le courtier. *

1.2.3. Interconnexion des courtiers. *

1.2.4. Développement du réseau de courtiers. *

1.2.4.1. Les programmes courtiers. *

1.2.4.2. Les programmes serveur. *

1.2.4.3. Le programme client. *

1.3. MISE EN PLACE DE L’INSTRUMENTATION. *

1.3.1. Vérifications importantes. *

1.3.2. Instrumentations des objets. *

1.3.2.1. Données spécifiques. *

1.3.2.2. Constructeur spécifique. *

1.3.2.3. Fonctions spécifiques. *

1.3.3. Instrumentation d’un programme JAVA avec un objet de type trace. *

1.3.3.1. Instanciation. *

1.3.3.2. Remarque importante. *

1.3.4. Récupération du nom et du port de la machine. *

1.3.4.1. Instrumentation. *

1.3.4.2. Remarque importante. *

1.3.4.3. Sauvegarde dans l’objet trace. *

1.3.5. Récupération de la référence IOR d’un objet trace. *

1.3.5.1. Instrumentation. *

1.3.5.2. Sauvegarde dans l’objet trace. *

1.3.5.3. Ecriture dans le fichier trace. *

1.3.6. Début d'exécution d'un programme JAVA. *

1.3.6.1. Instrumentation. *

1.3.6.2. Ecriture dans le fichier trace. *

1.3.7. Creation d'objet. *

1.3.7.1. Instrumentation. *

1.3.7.2. Ecriture dans le fichier trace. *

1.3.8. Instrumentation d’une méthode. *

1.3.8.1. Exemple d’instrumentation. *

1.3.8.2. Modification du nombre d’arguments de la méthode instrumentée. *

1.3.8.3. Début de méthode. *

1.3.8.4.Fin de méthode. *

1.3.9. Invocation et retour d’invocation d'une méthode. *

1.3.9.1. Instrumentation du programme. *

1.3.9.2. Ecriture dans le fichier trace. *

1.3.10. Commentaires. *

1.3.10.1. Instrumentation. *

1.3.10.2. Ecriture dans le fichier trace. *

1.3.11. Terminaison d'un programme JAVA. *

1.3.11.1. Instrumentation. *

1.3.11.2. Ecriture dans le fichier trace. *

1.3.12. REMARQUE IMPORTANTE. *

1.4. EXEMPLES D’INSTRUMENTATION. *

1.4.1. Exemple 1. *

1.4.1.1. Instrumentation du programme source. *

1.4.1.2. La trace générée. *

1.4.2. Exemple 2. *

1.4.2.1. La création des objets trace. *

1.4.2.2. Les traces générées. *

2. LANCEMENT DU PROGRAMME COURTIER. *

2.1. LANCEMENT DES COURTIERS. *

2.2. LANCEMENT DES SERVEURS. *

2.3. LANCEMENT DU CLIENT. *

3. ANALYSE DES TRACES. *

3.1. INTRODUCTION. *

3.2. DESCRIPTION DE LA TECHNIQUE D’ANALYSE. *

3.2.1. Le commencement de l’analyse. *

3.2.2. Le traitement d’une token de type " Iappel ". *

3.2.2.1. Le changement d’objet trace. *

3.2.2.2. Le positionnement de l’analyseur dans le fichier trace. *

3.2.3. Le traitement d’un token de type " FinM ". *

4. LES RESULTATS DE L’ANALYSE. *

PARTIE 4 : LES PERSPECTIVES DE DEVELOPPEMENT. *

CHAPITRE 8 : LES EVOLUTIONS ENVISAGEES. *

1. INTRODUCTION. *

2. D’AUTRES TYPES D’EVENEMENTS A TRACER. *

3. LES MODIFICATIONs APPORTEES AUX OBJETS DE TYPE TRACE. *

4. DES SONDES LOGICIELLES SUPPLEMENTAIRES. *

4.1. TRACE DES METHODES SUPPORTANT UNE SYNCHRONISATION. *

4.2. TRACES DES ACCES AUX DONNEES PARTAGEES. *

4.3. TRACE DE L’ACTIVITE D’UN PROCESSUS LEGER. *

4.3.1. INSTRUMENTATION DU PROCESSUS LEGER. *

4.3.2. CREATION, LANCEMENT ET FIN D’UN PROCESSUS LEGER. *

4.4. TRACE DE LA SYNCHRONISATION ENTRE PROCESSUS LEGERS. *

5. LA MODIFICATION DE L’ANALYSEUR LEXICAL. *

5.1. LA FONCTION D’ORDRE " DebutMJ ". *

5.2. LA FONCTION D’ORDRE " FinMJ ". *

5.3. LA FONCTION D’ORDRE " Evar ". *

5.4. LA FONCTION D’ORDRE " Lvar ". *

5.5. LA FONCTION D’ORDRE " Creat_thread ". *

5.6. LA FONCTION D’ORDRE " DebutF ". *

5.7. LA FONCTION D’ORDRE " FinF ". *

5.8. LA FONCTION D’ORDRE " Tjoin ". *

5.9. LA FONCTION D’ORDRE " Twait ". *

5.10. LA FONCTION D’ORDRE " Tnotify ". *

CONCLUSION. *

1.L’OBJECTIF. *

2. REALISATION. *

3. BILAN. *

3. PERSPECTIVES. *

Bibliographie *

ANNEXES *

PARTIE 1 : LE RESEAU DE COURTIERS. *

ANNEXE A : *

Programme client.java *

ANNEXE B : *

Fichier IDL d’un objet courtier *

ANNEXE C : *

Programme Courtier_impl *

ANNEXE D : *

Programme courtage1.java *

ANNEXE E : *

Fichier reseau.java *

PARTIE 2 : LES OBJETS DE TYPE TRACE. *

ANNEXE F : *

Fichier IDL d’un objet trace. *

PARTIE 3 : RESULTATS. *

ANNEXE G : *

Exemple de fichier trace, client.trc *

ANNEXE H : *

Fichier résultat, Analyse.trc *

ANNEXE I. *

Exemple de visualisation des relations d’ordre *

au moyen d’un arbre. *

PARTIE 4 : EXEMPLE D’UNE APPLICATION MULTITHREADEE. *

ANNEXE J : *

L’application producteur/consommateur. *

TABLE DES INDEX *

 

INTRODUCTION

Depuis quelques décennies sont apparus des systèmes informatiques qui nécessitent le développement de logiciels complexes et exigent un haut niveau de qualité, notamment du point de vue de la sûreté de fonctionnement. La complexité de ces logiciels est due à leur taille et à leur structure. Le contrôle de la qualité durant tout le processus de développement de tels logiciels est devenu une nécessité.

L'approche orientée objet dans le domaine du développement des logiciels a vu le jour dans les années 70. Elle permet de répondre à certains problèmes de génie logiciel liés à la complexité des programmes. En considérant le cycle de développement classique, l'approche objet a pénétré l'industrie du logiciel par l'activité la plus en aval : le codage. Ainsi de nombreux langages de programmation ont été diffusés par les éditeurs tels que SmallTalk, C++, Eiffel et JAVA. Ils utilisent les principales caractéristiques de l'approche objet : abstraction, encapsulation, communication par messages, héritage et polymorphisme. Actuellement, les premières phases du cycle de développement (analyse et conception) sont également couvertes grâce à des méthodes orientées objets comme OMT ou UML [Carmichael 94].

Jusqu’à une période récente, les outils pour la validation du logiciel n’étaient pas, sur le plan de la productivité, à la hauteur des outils de développement. Méthodes de programmation objet, générateurs d’applications et générateurs d’interfaces hommes-machines permettent, en effet, aux développeurs de logiciels de créer des programmes en moins de temps qu’il n’en faut aux ingénieurs de l’assurance de la qualité pour les tester et effectuer leur mise au point. Il en résultait donc un goulet d’étranglement dans le développement des applications.

Actuellement, ce problème se pose avec plus d’ampleur suite à la mise récente sur le marché de nouvelles architectures qui utilisent des objets distribués comme, par exemple, le World Wide Web. Celui-ci fournit une grande variété de ressources accédées par de nombreux utilisateurs et réparties sur un ensemble de machines hétérogènes. Une application est maintenant constituée de composants distribués sur les réseaux et coopérant entre eux.

Ces applications réparties nécessitent la définition de normes qui prennent en compte la communication, l’hétérogénéité, l’intégration et l’interopérabilité des applications distribuées. La norme CORBA [OMG-CORBA] [Geib 1997] s’inscrit dans ce processus : elle fournit une plate-forme d’exécution à base de technologies orientées objet pour l’intégration et l’interopérabilité d’applications distribuées et hétérogènes. Le bus CORBA assure la transparence des communications entre les objets distribués et implantés à l’aide de différents langages. Les souches de communication sont générées automatiquement à partir d’un fichier de description des interfaces écrit en langage OMG-IDL.

Toute la chaîne de développement et la gestion de telles applications ne sont pas, de part la complexité des architectures distribuées, totalement maîtrisées. En particulier, la phase d’observation et de déverminage est délicate car les applications distribuées génèrent des interactions entre des objets qui sont exécutés sur des sites différents. Ainsi, il est intéressant de faire le point sur les techniques de validation, vérification et test, en particulier dans un environnement de type objets distribués.

La mise au point des applications réparties reste difficile de part la nature indéterministe de leur exécution qui est répartie sur différents sites. Celle-ci dépend notamment du degré de synchronisation entre les entités et des conditions d’exécution : règles d’ordonnancement des processus, charge du système et des médiums de communication. Néanmoins, il existe de nombreuses techniques de déboguage qui permettent d’observer et de mettre au point de telles applications. Deux approches, l’une statique, l’autre dynamique, permettent leur mise au point. La vérification d’un système sans activation réelle de celui-ci est appelée analyse statique. L’analyse dynamique utilise des techniques basées soit sur l’observation, soit sur l’interaction.

L’objectif de notre travail est de créer un outil de déboguage (ou déverminage) qui réalise une analyse dynamique basée sur l’observation de toute application JAVA répartie au moyen d’un négociateur de requête conforme à CORBA. La récolte logicielle d’information est réalisée à l’aide de traces qui sont ensuite analysées d’une manière post-mortem. Le code des programmes ou des objets observés est instrumenté à l’aide de sondes logicielles qui associent à chaque objet ou programme observé un objet spécialisé dans la gestion de ses traces. A la fin de l’exécution du programme étudié, l’analyse des fichiers trace permet de visualiser son comportement, en particulier le graphe d’appel des méthodes.

Ce rapport fait suite aux travaux réalisés, au sein du laboratoire CEDRIC, par Pascal Placide [Placide 95] [Placide 95a] [Placide 95b] et Frédéric Bergdolt [Bergdolt 97] [Bergdolt 97a]. Dans un environnement de développement GUIDE, système d’exploitation réparti, leurs études ont pour but de traduire de manière globale le comportement d’une application répartie au moyen de graphes hiérarchiques. Pascal Placide s’est particulièrement attaché à construire des observations à partir d’une relation de causalité en approche objets répartis. Frédéric Bergdolt a développé une technique pour vérifier la correction d’algorithmes spécifiés avec le modèle CAOLAC [Seinturier 96] [Bonnet 97] [Seinturier 98]. Nous complétons ce travail en nous plaçant dans un environnement CORBA.

Dans un univers d’applications réparties, des notions de causalité et d’observation sont abordées dans ce mémoire. Nous nous fondons sur des environnements CORBA & JAVA.

Notre observation des applications distribuées est basée sur une relation de causalité, inspirée de celle définie par Lamport [Lamport 78], adaptée à un univers à objets répartis [Coste et al 98]. Notre objectif est de définir les relations d’ordre existantes dans un univers à objets répartis avec des invocations de méthodes et le partage de variables. Dans un premier temps le modèle est décrit.

Il inclut un ordre programme, c’est-à-dire un ensemble ordonné d’actions et les politiques de synchronisation entre ces actions. Les schémas de synchronisation correspondent par exemple à l’exclusion mutuelle. Cet ordre programme correspond aux relations d’ordre partiel que l’on trouve à l’intérieur d’objets. Il est composé de trois types d’ordre. Il s’agit de l’ordre local qui permet de décrire les séquences ou le parallélisme d’actions. L’ordre intra-objet décrit la synchronisation à l’intérieur de l’objet. Enfin l’ordre transactionnel permet de connaître les dépendances par partages de variables.

Un second type d’ordre correspond à l’ordre d’interactions qui permet de décrire les invocations de méthodes entre objets.

L’ordre causal objet est la fermeture transitive de l’ordre programme et l’ordre d’interaction.

La norme CORBA rend transparente la localisation des objets distribués et permet de créer des applications client/serveur où la distribution des objets est cachée. Associé à des bus logiciels conformes à ce standard, le langage de programmation JAVA permet de développer des applications distribuées de plus en plus complexes. Par exemple, il est de plus en plus utilisé dans le système d’information distribué du réseau Internet, le World WideWeb. En effet, d’une part, il comporte tous les paradigmes liés à la programmation objet : notion de classes, d’encapsulation, d’héritage et de polymorphisme. D’autre part, son code interprété est portable sur de nombreux systèmes d’exploitation ce qui le rend transparent vis à vis des architectures matérielles.

Le langage JAVA est utilisé dans la partie expérimentale de ce mémoire. A l’issue de ce travail, le programmeur dispose d’une librairie spécialisée dans l’observation d’applications JAVA réparties. Il pourra l’utiliser dès la phase de codage pour vérifier que le comportement de son application est conforme aux spécifications.

Ce mémoire est composé de quatre parties.

La première partie, constituée des paragraphes 1, 2 et 3, aborde le concept d’objets distribués et décrit l’environnement de programmation JAVA et CORBA.

La deuxième partie est un état de l’art de la mise au point d’applications réparties. Le chapitre 4 est consacré aux problèmes et aux différentes approches liées au déboguage de telles applications. Le chapitre 5 définit la relation de causalité utilisée dans ce mémoire pour créer un ordre partiel entre les événements observés.

La troisième partie, les paragraphes 6 et 7, contient l’étude expérimentale de ce mémoire. Elle décrit la création d’un outil de traces, développé en JAVA, qui, associé à un analyseur, permet d’instrumenter et de visualiser l’activité d’un programme distribué.

La dernière partie, constituée par le paragraphe 8, donne des pistes techniques pour l’évolution du produit.

Enfin une conclusion et des perspectives sont développées.

 

 

 

 

 

 

 

 

 

 

 

 

PARTIE 1 : JAVA ET CORBA.

 

CHAPITRE 1 : LES OBJETS DISTRIBUES.

La programmation objet correspond à l’association de concepts venant de deux approches différentes. La première correspond à une approche procédurale ; la seconde à une approche dirigée par les données.

Dans les méthodes classiques de développement de type structurel, l’algorithme sert d’unité de décomposition fondamentale. Ce paradigme est fondé sur le fait que, dans de nombreux langages de programmation, l’élément structurel de base est le sous-programme ou la procédure. On parle alors d’approche procédurale ou de programmation structurée.

La seconde approche met l’accent sur les données. Elle considère que la croissance en volume et en complexité des données manipulées dans les applications, nécessite des techniques de structuration et de regroupement en fonctions des caractéristiques communes. Ce courant aboutit à la programmation dirigée par les données.

L’approche objet résulte des différentes tentatives de concilier ces deux courants [Meyer 88]. Les premiers jalons de la programmation orientée objet ont été posés par le langage Simula (1966) dans lequel apparait déjà la notion de classe, c’est-à-dire une structure décrivant les données et les procédures permettant de les manipuler. L’approche objet a ensuite été reprise et développée par les communautés scientifiques de l’intelligence artificielle et des bases de données pour enfin être utilisée dans des contextes plus généraux comme les méthodes de conception [Carmichael 94] et les systèmes distribués. Ses mécanismes – encapsulation, polymorphisme et héritage – permettent de structurer de larges et complexes applications en un graphe d’objets communiquant par appel de méthodes.

Une des dernières évolutions est le mariage de l’informatique répartie et de l’approche orientée objet pour offrir un cadre conceptuel unique allant de l’analyse jusqu’au déploiement d’applications distribuées. Cette partie aborde les techniques de conceptions orientées objet et les concepts du paradigme objet. Enfin, la technique qui permet l’appel de méthodes à distance est décrite car elle est le fondement de l’interaction entre les objets distribués.

1. L’APPROCHE ORIENTEE OBJET.

L’analyse orientée objet applique les principes des paradigmes objet (abstraction, encapsulation, modularité et hiérarchie) à la description des exigences d’un système à partir de classes et d’objets. Elle utilise également ces mêmes principes pour satisfaire ces exigences.

Une conception orientée objet d’une application permet de voir un système à partir de perspectives différentes mais néanmoins unifiées. Le modèle logique permet de considérer l’existence des abstractions et des mécanismes clés du système comme l’encapsulation ou l’héritage. Le modèle physique décrit comment ces éléments sont réunis en unités physiques. De la même façon, la vue statique de ces éléments reproduit les aspects structurels de l’architecture, tandis que la vue dynamique indique comment ces éléments se comportent individuellement et collectivement pour satisfaire les exigences fonctionnelles du système en développement.

 

1.1. LA CONCEPTION MODULAIRE.

Le but de la conception modulaire est d’obtenir un ensemble de composants qui interagissent les uns avec les autres tout en restant suffisamment isolés, notion d’encapsulation. La notion de module est associée au regroupement des traitements et des données. La partie publique du module est l’interface. Celle-ci peut être utilisée pour interagir avec d’autres modules ou pour modifier les données propres au module. Elle est complètement définie et est indépendante du code interne au module (figure 1.1).

Ainsi on peut modifier le fonctionnement interne d’un module sans affecter le comportement global du système. On peut alors réutiliser les composants existants, limiter les conséquences d’une modification à un sous-ensemble de modules, masquer des défaillances localement, etc...Le résultat d’une conception modulaire est un ensemble de modules interconnectés tel que les parties exportées et importées décrites dans les interfaces soient en correspondance.

Le développement et le test de chaque module peuvent se faire indépendamment. Toute modification n’affectant pas les interfaces ne se propage pas à l’extérieur du module concerné. Lorsqu’une interface est modifiée, il est possible de déterminer les modules affectés en considérant le graphe d’appel des modules.

Figure 1.1. Description d’un module

 

 

 

1.2. CONCEPTION ORIENTEE OBJET.

Dans les langages évolués, l’outil de base pour la décomposition et l’abstraction est le concept de type abstrait de données. Un type abstrait de données correspond à un ensemble de données légales et d’opérations définies applicables à celles-ci. Contrairement aux modules, les types abstraits exportent des définitions de types et créent de multiples instances de ces types. Ainsi un objet peut être défini comme un type abstrait de données.

La signature d’un type abstrait ou d’un objet est la liste du nom du type et des noms d’opérations accompagnées du type de leurs arguments et de leurs résultats. La signature est la base de l’interface de l’objet avec l’extérieur. Elle indique d’une part ce qui est importé par l’objet et d’autre part, elle définit sommairement les règles d’utilisation des opérations. Une méthode est une procédure ou une fonction associée à une classe et invoquée par envoi de messages au travers de l’interface de l’objet.

Ainsi, un objet peut être caractérisé par une combinaison d’états et de comportements. Son état est décrit par les variables d’instances tandis que son comportement est caractérisé par ses méthodes. Le résultat d’une telle conception est un système qui met à la disposition de son environnement un ensemble de méthodes. L’enchaînement de ces méthodes est assuré par l’envoi de messages entre objets. De ce fait la conception orientée objet permet de vérifier plus facilement, grâce à la modularité, les programmes en utilisant des techniques d’analyse de comportement à l’aide de graphes, d’automates à états finis ou de réseaux de Pétri.

 

2. LES OBJETS.

Le concept d’objet est issu des langages de programmation à objets. Un objet est une entité qui regroupe des données, c’est-à-dire l’état de l’objet, et des opérations qui correspondent aux méthodes qui manipulent les données. Celles-ci ne sont accessibles et modifiables qu’à travers l’appel de méthodes. Les méthodes d’un objet définissent l’interface qui permet de manipuler son état. Un objet possède un identificateur unique dans le système considéré.

Le paradigme objet regroupe principalement quatre concepts : les notions de classe, d’encapsulation, de polymorphisme et d’héritage.

2.1. LA CLASSE.

L’unité de décomposition fondamentale du modèle objet est la classe qui représente la structure et le comportement communs à une collection logique d’objets. C’est dans la classe, par exemple, que le corps des méthodes est décrit. Instancier une classe revient à créer un nouvel objet possédant toutes les caractéristiques de cette classe. Un objet membre est appelé instance de la classe. Il hérite alors de ses structures de données, les attributs, et des traitements sur ces données, les méthodes. Dans un objet, traitements et données sont bien différenciés : on parle de la séparation des données et des méthodes.

Ce concept de classe est supporté directement par les fonctionnalités des langages de programmation objet. Dans les langages orientés objet, le monde est perçu comme une collection d’objets coopérant entre eux pour aboutir à une fonctionnalité désirée. Ces structures de collaboration forment le mécanisme de base du système et produisent ensemble le comportement observable de l’extérieur.

 

2.2. L’ENCAPSULATION.

On parle d’encapsulation lorsque l’on relie données et opérations permettant de les manipuler dans une même entité.

Une classe est dite encapsulée car l’utilisateur n’a accès qu’à l’interface publique de l’objet, qui constitue la visibilité de l’objet vis à vis du monde extérieur. Celle-ci est constituée par les signatures (noms et type de paramètres) des méthodes associées à l’objet : le code des méthodes n’est pas accessible à l’utilisateur. L’implantation de ces interfaces est privée. L’interface avec l’extérieur est complètement définie et est indépendante du code interne au module (figure 1.1). Ceci permet d’utiliser le module en faisant abstraction de la manière dont il est programmé (principe d’abstraction).

Ce concept est fondé sur la séparation nette au sein de l’objet entre la spécification et l’implantation. Il a l’avantage d’une part, de dissimuler pour les utilisateurs les détails de l’implantation de la classe et d’autre part, d’assurer l’intégrité des données de l’objet qui ne peuvent être manipulées que par ses propres méthodes. De plus, le code peut être modifié sans en avertir les utilisateurs.

2.3. LE POLYMORPHISME.

La notion de polymorphisme est synonyme de celle liée à la réutilisation de références. En effet, un objet ou plus exactement une instance d’objet peut référencer pendant son exécution plusieurs formes différentes de l’objet. Plusieurs implantations d’une méthode peuvent exister et effectuer, sous un même nom, des traitements différents par rapport aux types de données utilisés.

EXEMPLE.

ajouter(in real x) ;

ajouter(in integer x) ;

2.4. L’HERITAGE.

La relation d’héritage correspond à la définition de nouvelles classes, de nouvelles instances d’objets à partir de définitions existantes. Elle permet la réutilisation de propriétés par spécialisation de classes. La classe spécialisée est appelée sous-classe et la classe que l’on spécialise est appelée super-classe. La relation entre classe et sous-classe introduit des liens spécifiques appelés liens d’héritage. Ces liens montrent les transferts de propriétés (attribut et méthodes) de la classe mère (super-classe) vers les sous-classes. Les liens d’héritages forment un graphe d’héritage appelé aussi hiérarchie d’héritage.

Il existe deux types d’héritage : héritage simple et héritage multiple. L’héritage est dit simple lorsqu’une classe donnée ne peut hériter que d’une seule super-classe à la fois. Dans le cas de l’héritage multiple, une classe peut hériter de plusieurs classes à la fois.

Les différents paradigmes objet présentés précédemment sont utilisés par de nombreux langages de programmation. P. Wegner [Wegner 87] propose la classification suivante pour les langages à objets. Tout d’abord, il définit les langages " basé objet " pour les langages ayant un mécanisme d’encapsulation (ADA). Ensuite, il donne le nom " basé classe " aux langages ayant un mécanisme d’instanciation (SIMULA). Enfin, il présente les langages " orienté objet " comme langages ayant en plus un mécanisme d’héritage (SMALLTALK, C++, JAVA).

 

3. LES OBJETS DISTRIBUES ET CONCURRENTS.

Les objets distribués [Orfali 96] [Duchien 98] sont des composants qui peuvent interopérer avec d’autres objets au travers de systèmes d’exploitation, de réseaux, de langages, d’applications, d’outils et de matériels provenant d’éditeurs et de constructeurs différents. Ils sont implantés dans cet environnement réparti et interagissent entre eux grâce à des composants logiciels intermédiaires de type réseau, le bus logiciel à objets (middleware), pour exécuter à la demande des méthodes sur des objets situés sur des sites quelconques. Ce mécanisme rend fonctionnel les systèmes client/serveur fondés sur les objets distribués [Orfali 94].

De nombreux concepts sont nécessaires pour gérer des objets distribués. Les principaux peuvent être énumérés de la manière suivante : la création dynamique d’instances à distance pour l’implantation, à la demande, des objets sur un serveur distant ; le nommage universel des objets dans le système réparti qui permet d’identifier tout objet à l’aide d’une référence unique ; l’exécution à distance des méthodes ; la concurrence dans l’objet et la synchronisation entre objets et la possibilité de migration d’objets. Nous décrivons, dans les paragraphes suivants, ces concepts.

 

3.1. L’EXECUTION A DISTANCE DES METHODES.

Dans un contexte réparti, le langage objet doit coopérer avec le système d’exploitation et les couches de communication réseau pour réaliser les invocations entre les objets localisés sur des sites distants. L’invocation d’une méthode sur un objet est le mécanisme permettant d’exécuter le code d’une méthode sur l’objet.

Les applications distribuées ont besoin, pour leur exécution, de plates-formes réparties. Elles sont composées d’objets répartis [Orfali 96] qui communiquent alors de manière transparente par l’intermédiaire d’objets souches appelés aussi mandataires ou proxies. Une souche est un représentant local d’un objet distant. Elle encapsule les informations de localisation de l’objet distant, c’est-à-dire sa référence sur le réseau, et elle implante les mécanismes de communication réseau pour invoquer l’objet distant.

Deux types d’invocations distantes sont utilisés. La première, l’invocation synchrone point à point est la plus couramment utilisée. Elle attend systématiquement la fin de l’exécution de l’opération invoquée pour continuer son exécution. Ce mécanisme d’invocation d’objets distants est en fait une extension de l’appel de procédure à distance (RPC, Remote Procedure Call) dans le contexte des objets. La deuxième, l’invocation asynchrone en point à point, permet de réaliser des invocations en parallèle avec le fil d’exécution qui est à l’origine de l’appel. Cette méthode permet d’exprimer la concurrence mais pose le problème de la récupération des réponses liées à l’invocation.

 

3.2. LA SYNCHRONISATION INTRA-OBJET.

La synchronisation intra-objet à pour but de contrôler l’exécution des méthodes d’un objet. Pour cela, il est nécessaire de définir d’une part, des règles qui autorisent une méthode à s’exécuter et d’autre part, l’ordre d’exécution des méthodes. Ceci peut être réalisé par exemple grâce à l’exclusion mutuelle des exécutions des méthodes, sur un objet partagé, qui ne peuvent pas, de ce fait, être invoquées par plusieurs objets. Ainsi, les données de l’objet partagé restent cohérentes.

Des contrôles explicites, implicites, internes ou externes existent dans les différents langages objets concurrents [Guerraoui 95]. Lors d’un contrôle intra-objet externe les objets clients se synchronisent entre eux avant d’accéder à l’objet partagé. Un contrôle intra-objet interne voit la synchronisation entre les clients se réaliser au sein de l’objet partagé. Ce dernier mécanisme de synchronisation est réalisé dans le langage JAVA sous le nom de moniteur associé à un objet partagé.

 

3.3. LA MIGRATION D’OBJET.

Des systèmes comme SOR ou OBLIQ [Balter 91] [Atkinson] permettent de gérer des objets mobiles. Un objet est déplacé d’un espace d’adressage vers un autre espace d’adressage, d’un type de mémoire à un autre (cache, disque), d’un site à un autre.

Cette technique est utilisée pour plusieurs usages.

 

Après avoir réalisé un rappel concernant la technologie objet, le chapitre suivant est consacré à JAVA. Dans sa version 1.1.2, ce langage orienté objet concurrent gère, par l’intégration au niveau du langage, d’une part le multithread à l’aide de processus légers, d’autre part le contrôle intra-objet interne grâce à un moniteur que l’on peut associer à tout objet partagé et enfin, l’invocation de méthodes distantes, RMI, Remote Methode Invocation [Wollrath 96]. Le langage JAVA, à cause de ces aspects novateurs, est utilisé dans la partie expérimentale de ce mémoire.

 

 

CHAPITRE 2 : PRESENTATON DU LANGAGE JAVA.

Le développement de JAVA a commencé en 1990 chez SUN Microsystems [Clavel 97] [JAVA 90]. A cette époque, une équipe se consacre au développement d’un nouveau langage de programmation pour piloter les appareils électroménagers. Dès le départ, le projet veut éliminer la compilation spécifique au système que réclament les autres langages.

Dans un deuxième temps, ce concept de programmation est étendu au domaine de la vidéo à la demande (VDO, Video On Demand). JAVA rend ainsi possible l’interaction de l’utilisateur avec les services de vidéo à la demande par l’intermédiaire du réseau câblé de télévision.

Dans un troisième temps, JAVA connaît un franc succès avec le développement du réseau Internet à cause de sa capacité à fonctionner, d’une part, au travers de réseaux locaux et distants, et d’autre part sur des plate-formes hétérogènes [Farinone 97]. En effet, sa neutralité en matière de plate-forme lui donne un atout de taille : toutes les applications sont théoriquement opérationnelles sur n’importe quel système d’exploitation. En 1995, SUN développe la première version du programme de navigation HotJAVA entièrement développé en JAVA.

Depuis 1996 le kit de développement JAVA est disponible (JDK, JAVA Developper’s Kit) et les principaux éditeurs de logiciels ont conclu avec SUN des accords de licence pour le diffuser [API JAVA].

1. DESCRIPTION DU LANGAGE JAVA.

JAVA est un langage de programmation orienté objet, fortement typé, dont la syntaxe est basée sur celle du C et du C++. Il est à la fois compilé et interprété. En effet, le code source est dans un premier temps compilé. Un code, appelé byte code, est alors produit et peut être interprété par la machine virtuelle JAVA propre au système d’exploitation utilisé. De plus, JAVA autorise le multitâche réalisé à l’aide de processus légers.

Il permet de développer deux classes d’applications :

- Les applets : application fonctionnant dans une page au format html,

- Les applications classiques qui se lancent, par exemple, à partir du prompt du système utilisé.

1.1. JAVA EST ORIENTE OBJET FORTEMENT TYPE.

Comme C++, JAVA organise un programme en unités logiques constituées de données et de méthodes : les classes. Contrairement à C++, afin de limiter la complexité des programmes, JAVA ne supporte que la notion d’héritage simple.

Le contrôle du type des variables en JAVA est rigoureux. La conversion automatique de type n’est pas implantée. Si le code source présente une initialisation ou une affectation erronée, le compilateur détecte l’erreur, évitant ainsi la construction de programmes défectueux à un stade précoce du développement. Contrairement au langage C++, la notion d’adresse ou de pointeurs n’existe pas en JAVA.

1.2. JAVA EST INTERPRETE.

Un programme source JAVA est d’abord compilé en pseudo-code ou byte-code. Sur le plan de la structure, ce dernier se rapproche du langage machine ce qui lui donne une certaine rapidité. Une fois compilé, le programme en pseudo-code est exécuté par un interpréteur JAVA, appelé machine virtuelle, qui dépend de la plate-forme matérielle où s’exécute le programme. Ainsi, grâce à son code de bas niveau qui est normalisé par SUN Microsystems, JAVA introduit une indépendance du logiciel vis à vis du matériel ce qui constitue l’un des atouts de ce langage.

Dans l’univers distribué du réseau Internet, le World Wide Web, le pseudo-code JAVA est appelé à partir d’une page Web, au format html, qui est visualisée grâce à un navigateur (application Explorer de MicroSoft ou Communicator de Netscape). On parle alors d’applet JAVA. Dans cette approche, à la place d’invoquer une méthode sur un objet distant comme pour les systèmes client/serveur classiques, le code de la classe fournissant la méthode est, dans un premier temps, transféré au travers du réseau vers le client. Dans un deuxième temps, il est exécuté localement. De ce fait, la méthode est invoquée sur une instance locale de l’objet. A cause de l’utilisation des applets pour la programmation des pages Web, les navigateurs compatibles JAVA deviennent l’interface graphique universelle pour les applications destinées au réseau Internet ou Intranet [Vogel 97].

1.3. JAVA FONCTIONNE COMME UN SYSTEME D’EXPLOITATION VIRTUEL.

Le compilateur de JAVA, javac, génère un pseudo-code (byte-code JAVA ou J-code) destiné à une machine virtuelle.

Celle-ci peut être considérée comme un programme qui simule un processeur capable d’interpréter ce pseudo-code. Ainsi, un programme peut être exécuté sur tout ordinateur pour lequel une machine virtuelle est disponible. Etant donné que de nombreuses machines virtuelles sont disponibles, la majorité des systèmes d’exploitation fonctionnant en 32 bits, les programmes JAVA sont très portables.

La machine virtuelle gère également le séquencement des tâches et la mémoire utilisée par les processus : allocation et désallocation mémoire. Ce mécanisme permet de désallouer automatiquement des espaces mémoire qui ne sont plus utilisés. Grâce à ce procédé de ramasse miettes, le langage peut s’affranchir du type pointeur utilisé en C ou C++. Les pointeurs ne sont pas implantés en JAVA afin d’en simplifier la syntaxe et de supprimer les erreurs dues à leur mise en oeuvre.

2. LES THREADS.

Un thread est un processus léger. Ce nom anglais peut être également traduit en français par le terme fil d’activité ou de contrôle. Contrairement à un processus lourd de type UNIX qui s’exécute dans son propre environnement, un fil d’activité partage avec d’autres processus légers un même espace d’adressage.

Les applications JAVA peuvent gérer plusieurs fils d’activité à la fois : elles sont multithread. Les fils de contrôle se synchronisent entre eux à l’aide d’un moniteur. Le moniteur gère l’exclusion mutuelle. Des messages spécifiques entre processus permettent de réveiller un processus qui est en attente.

La machine virtuelle JAVA supporte des fils d’activités multiples, ce qui signifie qu'elle permet l'exécution concurrente de plusieurs pseudo-processus effectuant chacun une tâche différente.

L’ordonnanceur (scheduler) répartit le processeur entre les différents fils d’activité. L’implantation et le comportement de l’ordonnanceur de processus ne sont pas spécifiés. Cela signifie que les différentes machines virtuelles n’ont pas nécessairement le même comportement.

L'un des avantages des fils d’activités est d’améliorer les performances interactives des applications. Les interfaces graphiques actuelles, par exemple, sont de plus en plus multitâches en intégrant, dans une même fenêtre, des données, de la voix et des images animées. En C ou C++, il est possible, sur certains systèmes d’exploitation, d’ajouter une librairie permettant la gestion des threads. Malheureusement, l'écriture du code capable de gérer plusieurs fils d’activité est complexe. La principale difficulté réside dans le fait qu'il faut s'assurer que les différentes routines ou procédures sont réalisées de façon à pouvoir être effectivement exécutées de façon concurrente par plusieurs fils d’activité. Prenons l'exemple d'une routine modifiant la valeur d'une variable d'état : elle ne pourra être exécutée que par un seul processus léger à la fois. L'utilisation des différents fils d’activité dans une application C ou C++ implique donc la gestion d'un mécanisme de verrouillage. Le fait que cette gestion soit faite de façon explicite s'avère difficile à mettre en oeuvre et conduit souvent à des risques d'interblocage.

JAVA rend ce type de programmation beaucoup plus simple en offrant, par l’intégration au niveau du langage, des facilités pour manipuler les processus légers. Le paquetage JAVA.lang contient une classe Thread supportant des méthodes pour démarrer un fil d’activité (start()), l'arrêter (stop()), le mettre en sommeil pour une durée donnée (sleep()), ainsi que bien d’autres méthodes.

Chaque processus léger dispose d'une priorité. Les fils d’activité de haute priorité sont exécutés d'une façon préférentielle par rapport à des processus de basse priorité. Lorsqu’un objet de ce type est créé à partir d'un autre fil d’activité, il hérite de la priorité de son créateur. Lorsque la machine virtuelle JAVA est lancée, il n’y a généralement qu’un seul fil d’activité qui est en cours d’exécution et qui supporte la méthode main.

JAVA dispose également d'un certain nombre de primitives de synchronisation. Celles-ci sont fondées sur le paradigme des moniteurs, un schéma de synchronisation très largement utilisé, développé par C.A.R. Hoare [Hoare 74]. On peut préciser que certaines méthodes d'une classe ne peuvent être exécutées qu'en exclusion mutuelle en utilisant le mot clef synchronized. Ces méthodes sont sous le contrôle d'un moniteur, ce qui garantit le fait que les données restent dans un état cohérent.

 

 

2.1. LA CREATION DE THREADS

Le cycle de vie d’un thread est toujours le même. L’objet correspondant au thread à exécuter est d’abord créé, puis la méthode start() est appelée qui, à son tour, invoque la méthode run(). La méthode stop() permet l’interruption du fil d’activité. Il existe deux techniques pour créer un fil d’activité ; l’une par la classe Thread, l’autre par la classes Runnable.

 

2.1.1. PAR LA CLASSE THREAD

Pour la création d’un fil d’activité, la classe java.lang.Thread peut être héritée et sa méthode run() peut être surchargée par une nouvelle. Lorsque l’on appelle la méthode start() d’un processus, la méthode run() est déclenchée et elle s’arrête lors d’un appel à stop().

La méthode yield() est utilisée pour redonner le contrôle d’une façon explicite à l’ordonnanceur.

public class maClasse extends Thread {

public void start(){

thisThread = new Thread(this) ;

thisThread.start() ; //démarrage du thread

}

public void run(){

// Code source pour programmer le thread

}

}

 

2.1.2. PAR LA CLASSE RUNNABLE

Cette technique est utilisée pour créer des fils d’activité qui héritent des propriétés de la classe java.lang.Runnable. La méthode start() de l’objet sera responsable de la création et de l’activation du fil d’activité.

public class maClasse extends Appletimplements Runnable {

public void start(){

thisThread = new Thread(this) ;

thisThread.start() ; //démarrage du thread

}

public void run(){

// Code source pour programmer le thread

}

}

 

2.2. L’EXCLUSION MUTUELLE.

Pour réaliser des exclusions mutuelles, JAVA utilise le mécanisme du moniteur [Hoare 74]. Le but est de mettre en place des blocs d’exclusion mutuelle pour assurer la cohérence des données critiques.

Cette technique d’exclusion mutuelle est définie au niveau du langage. Les méthodes d’une classe déclarées synchronized ne sont pas exécutées en parallèle. De telles méthodes sont contrôlées à l’aide de moniteurs. Chaque objet instancié possède son propre moniteur qui est activé pour gérer l’accès aux parties critiques.

Deux procédés peuvent être employés : l’exclusion mutuelle d’une méthode ou l’exclusion mutuelle d’un bloc. De plus, les moniteurs JAVA sont ré-entrants. En effet, une méthode peut acquérir le même moniteur plus d’une fois.

2.2.1. L’EXCLUSION MUTUELLE D’UNE METHODE.

Il est possible de créer en JAVA des méthodes synchronisées. Pour cela, il suffit d’employer le modificateur synchronized.

public synchronized void methode1(){

//Partie critique

}

 

2.2.2. L’EXCLUSION MUTUELLE D’UN BLOC.

L'instruction synchronised spécifie une section critique dans un source JAVA. Le mot-clef synchronized est suivi par une expression entre parenthèses et une instruction ou bloc d’instructions. L'évaluation de l'expression doit être un objet ou un tableau. JAVA obtient l’accès en exclusion mutuelle par pose d’un verrou sur l’instance de cet objet ou de ce tableau avant d'exécuter l'instruction ou le bloc d'instructions.

public void methode2(){

synchronized (this){

//Partie critique

}

}

 

2.2.3. JAVA ET SES MONITEURS RE-ENTRANTS.

Les moniteurs de JAVA sont ré-entrants. Cette propriété est importante car elle empêche qu’un processus léger ne se bloque de lui-même sur un moniteur qui est déjà actionné.

Donnons un exemple pour expliquer cette propriété.

Exemple.

Soit l’objet Reentrant décrit dans le code suivant.

public class Reentrant{

public synchronized void a(){

b() ;

system.out.println(" Me voici dans la methode a() .") ;

}

public synchronized void b(){

system.out.println(" Me voici dans la methode b(). ") ;

}

}

L’objet Reentrant possède deux méthodes, a et b, en exclusion mutuelle. La première méthode synchronised a() appelle l’autre méthode synchronised b(). Lorsque le contrôle du processeur est donné à la méthode a(), le fil d’exécution qui utilise cette méthode acquière le moniteur de l’objet Reentrant. Puis, la méthode a() appelle la méthode b() et, puisque b() utilise également le modificateur synchronized, le processus léger peut acquérir de nouveau le même moniteur car il est ré-entrant. Les méthodes a() et b() s’exécutent séquentiellement pour donner, en sortie, les messages suivants.

Me voici dans la methode a().

Me voici dans la methode b().

Dans un système qui ne supporte par de moniteurs ré-entrants, cette séquence d’appels de méthodes aurait causé un blocage du processus utilisateur.

2.3. LA SYNCHRONISATION ENTRE THREADS.

Les processus légers qui accèdent aux mêmes ressources peuvent être synchronisés par l’invocation des méthodes wait (), notify(), notifyall() de la classe JAVA.lang.Object. Attention, celles-ci ne peuvent être utilisées que dans des méthodes synchronisées par des moniteurs. Ces méthodes permettent d’agir directement sur les moniteurs et donc sur la synchronisation.

 

2.3.1. wait ().

Dans un programme JAVA, l'invocation de la méthode wait() sur un objet déclenche l'interruption de son exécution. Elle ne reprendra que lorsqu'un autre fil d’activité prévient d'un changement d'état de cet objet.

Le processus qui dispose du moniteur de l'objet invoque la méthode wait(). Il libère alors ce moniteur et se met en attente. Le moniteur de l'objet est alors utilisé par les autres threads de l'application. L'un d'eux réveillera le premier fil d’activité en invoquant sur l'objet la méthode notify ou notifyall.

Variante de la méthode.

- wait(long milli) : attente en millisecondes;

- wait(long milli, int nano) : attente en millisecondes et nanosecondes.

Il faut remarquer qu’il n’est pas possible de savoir si wait s ’est terminé à cause de la réception d’un appel à notify() par un autre processus ou de l’épuisement du temps.

2.3.2. notify().

L'invocation de la méthode notify() sur un objet ne réveille qu'un seul fil d’activité en attente du moniteur de cet objet. Ce processus était en attente pour l'accès au moniteur en ayant au préalable invoqué la méthode wait().

Cette méthode ne peut être appelée que par un fil d’activité qui est le propriétaire de ce moniteur objet. Un processus ne devient le propriétaire du moniteur de l'objet que dans l'un des trois cas suivants :

- en invoquant une méthode de cet objet;

- en exécutant le corps d'une méthode synchronisée de l'objet;

- pour les objets de type Class, en exécutant une méthode statique synchronisée de cette classe.

 

Variante de la méthode.

La méthode notifyall() réveille tous les processus en attente sur un wait() et, dès que le moniteur sera libre, ils se réveillent tour à tour.

Il faut remarquer que le noyau JAVA ne donne aucune garantie concernant l’élection des processus lors d’un appel à notify() ou notifyall(). En particulier, il ne garantit pas que les processus seront débloqués dans l’ordre où ils ont été bloqués.

 

2.3.3. EXEMPLE.

Le schéma producteur/consommateur est utilisé dans l’exemple suivant pour la synchronisation de processus légers.

Soient un producteur et un consommateur qui partagent une donnée commune, contents, contenue dans un objet de type CubbyHole (voir ANNEXE J). Le consommateur attend que le producteur dépose une donnée dans la structure de type CubbyHole et le producteur notifie le consommateur lorsque cette action se produit. De même, le producteur attend que le consommateur prenne en compte la donnée et le prévienne, une fois l’opération précédente réalisée, pour produire une nouvelle donnée. Afin de réaliser ces activités, les deux fils d’exécution doivent se coordonner. Pour cela, ils utilisent les méthodes wait et notifyall héritées de le classe Object.

L’objet de type CubbyHole gère l’accès à sa donnée critique, contents, à l’aide de deux méthodes synchronised, get et put ; get est utilisée par le consommateur pour récupérer la valeur de la donnée partagée ; put est invoquée par le producteur pour affecter à la variable contents une nouvelle valeur. Elles se notifient l’une à l’autre de leur activité par l’utilisation du code suivant.

public synchronized int get(){

while (available == false)

{

try{

//attendre que le producteur produisse une valeur

wait ;

} catch (InterruptedException e){}

}

available=false ;

//notifier le producteur que la valeur a été traitée

notifyAll() ;

return contents ;

}

public synchronized int put(){

while (available == true)

{

try{

//attendre que le consommateur traite la valeur

wait ;

} catch (InterruptedException e){}

}

contents=value ;

available=true ;

//notifier le consommateur que la valeur a été mise en place

notifyAll() 

}

Au niveau du langage JAVA, l’intégration des mécanismes de création de fils d’activité et de synchronisation facilite le développement d’applications qui nécessitent des exécutions parallèles et concurrentes.

 

3. RMI.

Le système RMI, Remote Method Invocation, offre la possibilité au programmeur d’invoquer des objets JAVA distants selon le modèle d’appel de procédure distante (RPC, Remote Procedure Call) et ceci sans se soucier de l’hétérogénéité du parc informatique [Wollrath 96].

L’idée est de créer des objets dont les méthodes peuvent être invoquées d’une autre machine virtuelle JAVA. Cette approche fournit des mécanismes de type appel de procédures distantes pour des objets JAVA. Pour cela, JAVA dispose en standard de classes spécialisées, inclues dans la bibliothèque RMI, qui permettent d’invoquer des méthodes d’objets distants écrites en JAVA.

Un programme JAVA peut effectuer un appel sur un objet distant en obtenant sa référence, soit en la cherchant dans le service de nommage proposé par RMI, soit en l’obtenant grâce à la lecture d’un champ spécifique. Un client a la possibilité d’appeler un objet distant d’un serveur et ce dernier peut se comporter comme un client vis à vis d’autres objets distants.

Les classes de type souche ou squelette, qui gèrent la communication entre l’applicatif et le réseau, sont directement générées à partir d’une classe JAVA identifiée comme distante. La classe souche sert de lien avec l’objet distant et gère les invocations coté client. La classe squelette prend en charge les invocations entrantes au niveau du serveur.

RMI est une approche intéressante pour les petites ou moyennes applications écrites en JAVA. Les mécanismes de type RMI permettent l’optimisation des protocoles de communication entre les classes JAVA distantes. Néanmoins, ce mécanisme ne supporte que des composants écrits en JAVA, ce qui exclut la réutilisation d’applications anciennes.

 

4. LES DEVELOPPEMENTS FUTURS DE JAVA.

Le langage JAVA est prometteur car les nombreuses classes proposées par l’environnement de développement [API JAVA] et par les développeurs, en ligne sur le réseau Internet, ainsi que la machine d’exécution multi-plateforme associée, laissent envisager son utilisation future dans de nombreuses applications réparties. Néanmoins, compte tenu des enjeux commerciaux liés au développement de ce langage, il est difficile de garder une seule machine virtuelle. Pour rendre ses applications propriétaires, l’éditeur de logiciel MicroSoft la modifie ce qui nuit gravement à sa portabilité qui est l’atout principal de JAVA.

Une utilisation du langage JAVA est envisagée, par exemple, pour les ordinateurs NC, Network Computers, de réseaux : ordinateurs sans unités de stockage qui exécutent des applications écrites en JAVA. Ce système permet, par exemple, une gestion centralisée de la bureautique caractérisée par la diminution des coûts d’investissement et de maintenance. Pour ce type d’applications et afin d’augmenter leur vitesse de traitement, SUN développe des puces pour exécuter directement du byte-code.

Des applications qui nécessitent l’intégration de composants déjà existants ou l’utilisation d’un langage de programmation particulier pour certains composants, ont besoin d’une solution autre que RMI. Celle-ci peut être fondée sur la norme CORBA. Pour la première fois dans l’histoire de la technologie objet qui a plus de 20 ans d’age, il existe des standards pour construire des composants objet. Cela permet de définir des objets interopérables, transactionnels, sûrs et auto-gérables. L’industrie informatique a donc créé un standard pour caractériser les objets distribués : Common Object Request Broker Architecture, de l’OMG (Object Management Group). La norme CORBA est décrite dans le chapitre suivant.

 

 

CHAPITRE 3 : CORBA

 

1. INTRODUCTION.

 

1.1. LES OBJETS : UNE REVOLUTION DANS LA REVOLUTION CLIENT/SERVEUR.

L’informatique client/serveur [Orfali 94] a créé une profonde révolution dans l’industrie de l’informatique. Elle remplace les applications monolithiques orientées gros systèmes par des applications légères réparties de type client/serveur. Le client, généralement un ordinateur personnel, fournit l’interface graphique tandis que le serveur donne l’accès à des ressources partagées, une base de données par exemple.

Les objets distribués [Orfali 96], quant à eux, sont une révolution dans la révolution des applications client/serveur. Ils font éclater les clients et les serveurs d’une application en petits composants logiciels, les objets, qui peuvent fonctionner et dialoguer dans un environnement distribué.

Cette évolution est liée, d’une part, à l’arrivée sur le marché de matériels plus performants et d’autre part, à la demande d’applications qui utilisent ces nouvelles technologies telles que celles qui sont appelées à fonctionner sur Internet.

La première révolution est liée à l’arrivée de nouveaux matériels tels que les ordinateurs personnels et les réseaux LAN (Local Area Network), qui a changé la façon d’utiliser les applications informatiques. Elle a mis fin aux terminaux monochromes de l’informatique départementale. A la place de cela, nous disposons actuellement d’interfaces graphiques, les GUIs (Graphic User Interfaces) sur les terminaux X ou sur les ordinateurs individuels. Les réseaux étendus de type WAN (Wide Area Network) brisent les domaines de proximité associés aux réseaux de type LAN. La seconde ère du client/serveur est caractérisée par la diminution des coûts et par des vitesses de transmission plus élevées : les opérateurs de réseaux câblés et les fournisseurs de canaux de communication installent de nombreuses fibres optiques et lancent une multitude de satellites de communication à travers le monde.

Sur le plan logiciel, l’utilisateur d’ordinateurs individuels s’est habitué à utiliser les services du réseau Internet tels que la messagerie électronique, le Web, les forums de discussions. Les entreprises souhaitent utiliser de plus en plus ce média à des fins commerciales : transactions commerciales, paiement de prestations en ligne, agents électroniques, etc. Les systèmes d’exploitation, utilisés actuellement dans le domaine de la micro informatique, disposent, en version de base, de systèmes d’exploitation multitâches comme OS/2 Warp Connect d’IBM, Windows 95 et Windows NT de Microsoft ou UNIX. Ainsi, ils sont capables de lancer sur une même machine à la fois des clients et des serveurs.

Dans cet environnement logiciel et matériel, nous aurons la possibilité, dans un proche avenir, d’accéder à des millions de serveurs, interconnectés à travers le monde à des vitesses dix fois supérieures à celles des réseaux LAN actuels, qui pourront fournir une multitude de services. Néanmoins, l’architecture client/serveur actuelle n’est adaptée que pour un seul serveur départemental connecté à un réseau LAN : elle ne peut prendre en charge des milliards de transactions distribuées. Les objets distribués représentent, pour certains, l’espoir de pouvoir assurer le cahier des charges de ces nouvelles architectures client/serveur.

1.2. LE CLIENT/SERVEUR AVEC DES OBJETS DISTRIBUES.

Un logiciel client/serveur, dans un environnement objets répartis, a besoin d’un bus logiciel à objets. L’environnement distribué doit fournir l’image d’un système unique qui regroupe des milliers de machines client/serveur potentielles qui sont en majorité des ordinateurs personnels. Les utilisateurs et les programmes doivent être capables de joindre dynamiquement et de quitter le réseau, de se détecter les uns les autres, d’utiliser les mêmes conventions de nommage pour pouvoir localiser les ressources n’importe où sur le réseau, de communiquer avec toutes les entités du réseau sans se préoccuper des protocoles des différentes couches réseau ou des supports de transmission. On parle essentiellement d’interopérabilité entre systèmes ouverts.

Les objets distribués permettent de subdiviser les applications monolithiques des systèmes client/serveur actuels en composants qui peuvent interagir et être invoqués aux travers de réseaux et à partir de systèmes d’exploitation quelconques. Les composants objets permettent de créer des architectures client/serveur en assemblant des briques logicielles, les objets, contenant des méthodes et des données. Cette technologie objets distribués tout comme la technologie objet classique, permet de développer rapidement et économiquement des applications client/serveur très flexibles : encapsulation des méthodes et des données des objets distribués pour assurer la stabilité du code de l’ensemble du système, réutilisation du code. Un objet classique, en C++ ou en JAVA, est un composant qui encapsule du code et des données. Ces langages objet fournissent des facilités pour la réutilisation de leur code grâce à de l’héritage. Cependant, ces objets classiques ne vivent qu’au sein d’un seul programme. Seul le compilateur du langage qui crée les objets connaît leur existence. Le monde extérieur n’en a pas connaissance et n’a pas de moyen pour les accéder.

Contrairement à cela, les objets distribués sont des composants qui peuvent s’implanter n’importe où sur le réseau. Ils sont construits à l’aide de morceaux de codes indépendants, les méthodes, qui peuvent être invoqués par des clients distants et communiquent entre eux par échanges de messages Les clients n’ont pas à savoir où résident physiquement les objets distribués ou quels systèmes les abritent : ceci peut se réaliser sur une même machine ou sur un ordinateur distant.

1.3. LES STANDARDS DE L’OMG.

Depuis 1989 un consortium regroupant des théoriciens et des utilisateurs du monde objet, l’OMG [OMG-CORBA] (Object Management Group), a pris en charge la spécification d’un bus logiciel ouvert sur lequel les composants objets développés par différents vendeurs peuvent interopérés au travers de réseaux et de systèmes d’exploitations différents. Actuellement, l’OMG regroupe plus de 700 entreprises et le bus objet CORBA est sur le point de devenir le standard en matière de bus logiciel pour les systèmes client/serveur.

Après avoir présenté les nouveaux concepts objets associés aux systèmes client/serveur, la norme CORBA 2.0 qui rend transparente la localisation des objets répartis est détaillée dans le paragraphe suivant.

 

2. PRESENTATION DE CORBA 2.0.

CORBA [OMG-CORBA] [Orfali 96] [Siegel 97] normalise les bus logiciels à objets. Il implante des services dont les composants peuvent être hérités à la création de l’objet ou à son lancement pour que celui-ci atteigne un haut niveau de collaboration avec d’autres objets distribués. La puissance du bus logiciel vient du fait qu’il supporte aussi bien des composants qui fonctionnent sur la même machine que des objets qui fonctionnent au travers d’un réseau local ou étendu. Les composants interagissent en temps réel pour créer des applications développées pour des utilisations particulières : agence de voyage, financière, transport, etc.

La principale caractéristique de CORBA 2.0 est l’interopérabilité [Geib 97]. Cette norme met l’accent sur l’interopérabilité entre, d’une part, les langages de programmation grâce au langage de description d’interfaces et d’autre part, entre les bus logiciels.

Un objet classique ne peut être manipulé qu’en utilisant son interface. Celle-ci définit comment le composant présente ses fonctions au monde extérieur. De la même façon, CORBA fournit également un langage de définition d’interfaces nommé IDL (Interface Definition Language) qui est utilisé pour définir les méthodes distantes que les clients peuvent invoquer. L'OMG utilise l'IDL pour spécifier les limites d'un composant et ses interfaces contractuelles vis à vis de clients potentiels. L'IDL de l'OMG est purement déclaratif. Cela signifie qu'il ne fournit aucun détail sur l’implantation des méthodes. De ce fait, l’interface des objets est clairement séparée de l’implantation. Cela permet ainsi de faire interagir des objets hétérogènes au sein d’une même implantation d’un bus CORBA.

De plus, CORBA 2.0 apporte un ensemble de règles et un protocole commun IIOP (Inter Internet Object Protocol) qui permettent le dialogue et l’interopérabilité entre différentes implantations du bus. Cela permet ainsi de concevoir, de déployer et de relier des applications distribuées sur plusieurs bus fournis par des constructeurs indépendants.

2.1. L’ARCHITECTURE DE GESTION D’OBJETS DE L’OMG.

A l’automne 1990, l’OMG publie son guide d’architectures de gestion objet OMA (Object Management Architecture).

Fin 1994, l’OMG [OMG 97] approuve un ensemble de spécifications appelées CORBA 2.0, qui définit un bus de communication inter-ORB fondé sur le protocole de communication du monde UNIX : TCP/IP. De plus, CORBA 2.0 spécifie un service optionnel de communication inter-ORB fondé sur DCE de l’OSF.

Le négociateur de requêtes objet ou ORB (Object Request Broker) est le mécanisme qui permet aux objets d’émettre et de recevoir de manière transparente des requêtes locales ou distantes. L’ORB sert de fondation à la construction d’applications objets réparties. Selon l’OMG " le composant ORB garantira la portabilité et l’interopérabilité des objets dans un réseau de systèmes hétérogènes ". Le composant ORB est aussi appelé communément CORBA (Common Object Request Broker Architecture).

Le bus de l’OMG est complété par des services objet modulaires. Chaque ajout fournit un service objet au bus. L’OMG définit des standards qui spécifient comment les objets sont créés, stockés, définis et nommés sur le bus. Il normalise également un service événements qui laisse les objets communiquer entre eux d’une manière faiblement couplée. D’autres services sont disponibles sur CORBA comme le transactionnel, le contrôle de concurrence, la gestion de licences. Les services sont au nombre de seize.

 

2.2. LE NEGOCIATEUR DE REQUETES OBJET (ORB).

Le négociateur de requêtes objet est un bus logiciel qui établit les relations client/serveur entre les objets. Le client n’a pas à connaître l’endroit où se trouve l’objet, son langage de programmation, le système d’exploitation qui l’héberge et aucun autre aspect du système ne faisant pas partie de l’interface objet.

Le bus objet fournit un ORB, Object Request Broker, qui laisse les clients invoquer, d’une manière statique ou dynamique, les méthodes des objets distants. CORBA fournit à ces services à la fois des interfaces statiques (API statiques) et dynamiques (API dynamiques). Le C de Common du sigle CORBA représente cette proposition de l’OMG à deux APIs.

 

 

Figure 3.1 : Structure d’un ORB CORBA

Examinons d’abord les composants d’un ORB coté client (voir figure 3.1).

 

Les composants d’un ORB coté serveur peuvent être décrits de la manière suivante.

La référence d’un objet est l’information nécessaire pour spécifier et désigner d’une façon unique un objet avec un ORB. Deux implantations d’ORB peuvent différer par leur choix de la représentation des références des objets. Il est nécessaire d’avoir des références communes aux différents ORB. Pour cela, la représentation IOR (Interoperability Object Reference) est normalisée par CORBA. Lorsqu’elle est utilisée pour référencer un objet serveur utilisé par un client, elle n’est valide que pendant la durée de vie de l’objet serveur.

Une référence objet peut être convertie en une chaîne de caractères de type IOR et sauvegardée sous cette forme dans des fichiers. La chaîne de caractères ainsi conservée peut être de nouveau convertie en référence objet par l’ORB qui l’a produite.

Cette technique est utilisée, dans la dernière partie de ce mémoire, pour référencer les objets trace qui permettent de tracer les objets, d’une application distribuée, que l’on désire observer.

2.3. L'IDL DE CORBA.

L'IDL est utilisé pour définir d'une façon concise les APIs (Application Programming Interface) et couvre des points importants tels que les exceptions ou la gestion de contexte. Les méthodes spécifiées dans l'IDL peuvent être écrites et invoquées par tout langage disposant de liens avec CORBA (C, C++, ADA, COBOL, Objective C et JAVA). Les programmes gèrent les objets CORBA en utilisant des implantations en langages natifs. L'IDL fournit des interfaces, indépendantes des langages de programmation, des systèmes d’exploitation, des réseaux et des ORB, à tous les services et composants qui résident dans le bus CORBA. Cela permet aux objets de type client ou serveur, écrits avec des langages différents, d'interopérer au travers de réseaux et de systèmes d’exploitation différents.

L'IDL est utilisé pour spécifier les attributs des composants, les classes parents dont les classes filles peuvent hériter, les exceptions levées, les types d’événements émis par les méthodes que les interfaces supportent. Les méthodes déclarées dans l'IDL disposent de plusieurs modes de passage de paramètre : in si la valeur est passée du client vers le serveur, out du serveur vers le client, inout dans les deux sens (voir exemple de fichier IDL ci-dessous). La grammaire de l’IDL est une sous classe de C++ munie de mots réservés supplémentaires.

Exemple : soit un fichier IDL qui contient un module (un espace de nommage), une interface (l’interface d’une classes d’objets) et des méthodes qui manipulent les données de cet objet.

module monModule

{

interface monInterface

{

// Donnée

attribut long Nombre ;

// Exception

exception Probleme (string explication) ;

// Méthodes

long ajouter(in long i, out long resultat);

long retrancher(in long i, out long resultat);

long multiplier(in long i, out long resultat) raises(Probleme);

long diviser(in long i, out long resultat);

long remiseZero(in long i );

};

};

2.3.1. L'IDL ET LE REFERENTIEL D’INTERFACES.

Le contrat, défini dans l'IDL, lie le fournisseur de services d'objets distribués, le serveur, à ses clients. Pour qu'un objet demande un service à un autre objet, il doit connaître l'interface de l'objet cible. Le référentiel d’interfaces (IR : Interface Repository) de CORBA contient la définition de toutes ces interfaces et en particulier la base de données metadata qui permet aux composants de se découvrir entre eux, d'une façon dynamique au moment de leurs exécutions et donc de faire des applets dynamiques. Ce mécanisme fait de CORBA un système qui s'auto-décrit.

2.3.2. LE MAPPING C++.

La traduction des éléments définis avec l'IDL en éléments d'un langage de programmation est appelée le mapping. Cette liaison est définie dans la norme pour un ensemble de langages tels que C++, JAVA ou COBOL.

Un exemple de traduction d'IDL en C++ pour les types de base est donné dans le tableau ci-dessous.

IDL

C++

boolean

CORBA_boolean

char

CORBA_char

octet

CORBA_octet

string

CORBA_string

short

CORBA_short

unsigned short

CORBA_ushort

long

CORBA_long

unsigned long

CORBA_ulong

float

CORBA_float

double

CORBA_double

Tableau 3.1 : mapping C++

EXEMPLE.

Soit un objet i2 qui implante une méthode op1. Celle-ci prend un argument de type entier long et renvoie une valeur de même type. Le code, contenu dans le fichier IDL et dans la classe qui implante l’objet, est donné ci-dessous.

Code contenu dans le fichier IDL.

interface i2

{

long op1(in long arg1) ;

}

Code implanté.

class i2_impl : virtual public i2_skel

{

CORBA_long op1(in CORBA_long arg1){…} ;

} ;

 

 

 

LE COMPILATEUR IDL/C++.

Le compilateur d’IDL/C++ est lancé à l'aide du programme idl :

idl <nom_interface>.idl.

Cette commande produit les fichiers suivants :

- <nom_interface>_skel.cc et <nom_interface>_skel.h : squelettes ou souches serveur statique (Static Skeletton Interface). Les objets serveur fournissent une implantation de ces squelettes.

- <nom_interface>.cc et <nom_interface>.h : souches client (Static IDL Stub). Les objets client utilisent ces souches pour déterminer les méthodes légales qu'ils peuvent invoquer sur le serveur.

 

2.3.3. LE MAPPING JAVA.

De même, pour le langage JAVA, nous donnons un exemple de traduction pour les types de base.

IDL

JAVA

boolean

boolean

char

char

octet

byte

string

JAVA lang.String

short

short

unsigned short

short

long

int

unsigned short

int

float

float

double

double

Tableau 3.2 : mapping JAVA

EXEMPLE.

Soit un objet Face qui implante deux méthodes. La première, de nom meth, est décrite dans le fichier IDL et est donc visible des autres objets. Elle prend un argument de type entier signé, renvoie une valeur de même type et peut lever une exception de nom e. Par contre, la deuxième méthode appelée a n’est pas visible de l’extérieur de l’objet Face. Elle prend un argument de type entier signé. L’objet Face contient également deux variables, a et b de type entier signé, qui sont décrites dans le fichier interface. Le code, contenu dans le fichier IDL et dans la classe qui implante l’objet, est donné ci-dessous.

Code contenu dans le fichier IDL.

module Example {

Interface Face {

long meth(in long arg) raises(e) ;

attribut long a ;

readonly attribute long b ;

}

}

Code implanté dans la classe.

package Example ;

public interface Face extends org.omg.CORBA.Object

{

int meth(int arg) throws Example.e{…} ;

int a() ;

void a(int value){…} ;

int b() ;

}

LE COMPILATEUR IDL/JAVA.

Le compilateur d’IDL/JAVA est lancé à l'aide du programme jidl :

jidl <nom_interface>.idl.

Cette commande produit les fichiers suivants :

- _monInterfaceImplBase.java : squelettes ou souches serveur (Static Skeletton Interface). Les objets serveur fournissent une implantation de ces squelettes.

- StubFormonInterface.java et monInterface.java : souche client (Static IDL Stub). Les objets client utilisent ces souches pour déterminer les méthodes légales qu'ils peuvent invoquer sur le serveur.

- monInterfaceHelper.java et monInterfaceHolder.java : classes support pour le passage de paramètres en entrée/sortie et en sortie de classes. CORBA copie les valeurs des paramètres in du client vers le serveur. De plus, il copie les valeurs des paramètres out du serveur vers le client. Enfin, il copie les paramètres inout dans les deux directions au cours de l’invocation et de la réponse.

 

2.4. LE PROCESSUS DE DEVELOPPEMENT D’UNE APPLICATION CORBA.

 

Figure 3.2. De l’IDL aux souches d’interfaces

 

La figure 3.2 montre les diverses étapes à franchir pour créer une application dans un environnement de développement conforme à CORBA.

  1. Définir les classes d’objets en utilisant le langage de définition d’interfaces (IDL).
  2. Soumettre le fichier IDL au compilateur du langage utilisé.
  3. Ajouter le code d’implantation. Le code qui définit les objets doit être développé.
  4. Compiler le code. Un compilateur conforme à CORBA est généralement capable de générer au moins quatre types de fichiers : 1) des fichiers d’import qui décrivent les objets pour le référentiel d’interfaces ; 2) des souches IDL client pour les méthodes définies dans l’IDL ; 3) des souches IDL serveur appelant les méthodes sur le serveur ; 4)Le code d’implantation des classes du serveur. Il est à remarquer que la tâche du programmeur est facilitée par la génération automatique des souches.
  5. Associer les définitions de classes au référentiel d’interfaces. Habituellement, un utilitaire est fourni pour lier ou pour compiler l’information IDL dans une mémoire durable, le référentiel d’interfaces, que les programmes pourront accéder durant leur exécution.
  6. Instancier les objets sur le serveur. Au moment du lancement du serveur, l’adaptateur objet instancie, sur le serveur, les objets serveur qui répondent aux appels distants de méthodes invoquées par le client.
  7. Enregistrer les objets exécutables dans le référentiel d’implantation. L’adaptateur d’objet enregistre dans le référentiel d’implantation la référence de l’objet et le type de tout objet qu’il instancie sur le serveur. Le référentiel d’implantation sait aussi quelles sont les classes d’objets supportées par un serveur particulier.

 

2.5. CORBA : L'ORB ETENDU GRACE A IIOP.

CORBA 2.0 apporte l'interopérabilité entre ORBs de constructeurs différents en spécifiant un protocole inter ORB qui est GIOP (General Inter-ORB Protocol). Celui-ci supporte la transmission de sept types de messages définis par CORBA. Il sert de protocole commun inter-ORB.

Les messages de cette norme de communication sont les suivants.

Tout ORB conforme à CORBA 2.0 doit, soit implanter le protocole IIOP en natif, soit fournir un "demi-pont" pour pouvoir communiquer en IIOP avec l'ORB distant. Ceci est appelé un demi-pont car chaque ORB propriétaire peut communiquer avec le monde des ORBs standards en traduisant les requêtes de ou vers l'IIOP. IIOP sert de protocole commun entre tous les ORBs.

 

 

Figure 3.4. Demi-pont IIOP entre deux ORBs propriétaires

 

2.6. INITIALISATION DE CORBA.

Le service d’initialisation fournit un service de noms qui permet aux objets de découvrir les autres services dont ils ont besoin pour fonctionner sur un ORB.

Les étapes suivantes montrent les appels classiques qu’un objet doit invoquer pour s’amorcer.

1. Obtenir une référence d’objet de l’ORB : la fonction ORB_init permet d’informer l’ORB de la présence d’un nouvel objet et d’obtenir une référence à un pseudo-objet ORB. Il est à noter qu’une API est utilisée pour cela et non pas un appel de méthode : pour invoquer une méthode, l’objet doit d’abord avoir été amorcé dans le monde CORBA.

2. Obtenir un pointeur vers l’adaptateur d’objet.

La méthode BOA_init est invoquée à partir du pseudo-objet pour avertir le BOA de la présence d’un nouvel objet et pour obtenir sa référence (le BOA est aussi un pseudo-objet).

3. Découvrir quels services initiaux sont disponibles.

La méthode list_initial_services est invoquée sur le pseudo-objet ORB pour obtenir la liste des objets de service de base disponibles comme, par exemple, le référentiel d’interfaces ou le service de nommage. Les références à ces objets de service sont envoyées sous la forme d’une liste de chaînes de caractères.

4. Obtenir les références objet pour les services demandés.

La méthode resolve_initial_reference permet d’obtenir les références objet pour les services demandés.

Après avoir présenté d’une façon générale la norme CORBA, rentrons plus en détail dans l’implantation du bus logiciel, des clients et des serveurs.

3. LES DIFFERENTS ELEMENTS COMPOSANT LE BUS LOGICIEL.

 

3.1. L’INVOCATION D’OBJETS.

 

 

 

Figure 3.5 : structure d’un ORB côté client.

Le rôle du client est de réaliser des requêtes par l’invocation d’opérations sur les objets distants. Les clients accèdent aux implantations des objets au travers des souches client qui permettent d’invoquer l’instance de l’objet via sa référence. Ces interfaces isolent le client des détails de la réalisation de l’objet tandis que la référence à l’objet isole le client de la localisation de celui-ci. Les souches client permettent de relier le client à l’ORB.

Le client peut invoquer des objets par deux interfaces (voir figure 3.5). L’une, statique, correspond à l’utilisation de la souche client statique (SII, Static Invocation Interface). L’autre, dynamique, est liée à la mise en œuvre de la souche client dynamique (DII, Dynamic Invocation Interface). De plus, l’interface client de l’ORB fournit d’autres services au client. Les services fournis par le référentiel d’interface, par exemple, sont accessibles au travers de l’interface de l’ORB ou indirectement via les interfaces statiques ou dynamiques.

3.1.1. LES SOUCHES CLIENT STATIQUES ET DYNAMIQUES.

Le client lance une requête qui est transmise à l’ORB soit d’une façon statique, via la souche client, soit d’une façon dynamique via l’interface d’invocation dynamique. La différence entre ces deux invocations d’objet est la suivante : pour les invocations dynamiques, les types d’objets et d’opérations à utiliser sont déterminés à l’exécution ; pour les invocations statiques, ils sont définis une fois pour toutes au moment de la compilation des programmes.

L’interface statique est directement générée sous forme de souches par le précompilateur IDL qui dépend du langage utilisé. Il se crée alors une liaison statique entre les souches statiques et les objets implémentés. Cette technique est adaptée pour les programmes qui connaissent, au moment de la compilation, les détails des opérations qu’ils auront à appeler. L’interface à souche statique est liée lors de la compilation ; par rapport à l’appel de méthode dynamique, elle offre les avantages suivants.

Le mécanisme d’invocation dynamique utilise, côté client, l’interface d’invocations dynamiques ou DII (Dynamic Invocation Interface) et côté serveur, l’interface de squelettes dynamiques ou DSI (Dynamic Skeleton Interface). Il permet à une application cliente d’invoquer dynamiquement des objets sans utiliser les souches IDL prégénérées. Un client peut invoquer n’importe quelle opération sur n’importe quel objet via ce mécanisme. Pour cela, le client doit découvrir les informations relatives à l’interface des objets, au moment de l’exécution, en consultant le référentiel d’interfaces. A partir de ces métadonnées, il peut construire et invoquer dynamiquement des requêtes via les souches dynamiques DII.

Ainsi l’appel de méthodes dynamiques permet d’ajouter de nouvelles classes au système sans nécessiter de changement dans le code client. Ce mécanisme est très utile pour les programmes qui ne découvrent les services fournis qu’au moment de l’exécution. Avec des APIs dynamiques, le programmeur peut écrire des codes très génériques.

La plupart des applications ne réclament pas un certain niveau de souplesse apporté par les interfaces dynamiques et se satisfont généralement des implantations des souches statiques.

3.1.2. LE REFERENTIEL D’INTERFACES.

Le référentiel d’interfaces (IR, Interface Repository) est un élément crucial pour le bon fonctionnement de CORBA. Celui-ci a besoin que chaque ORB gère et implante une base de données qui permet de définir tous les types IDL de tous les objets gérés par le bus logiciel.

Le langage de cette base de données est l'IDL. La base contient les spécifications des interfaces générées par l'IDL, décrit tous les objets contenus dans l'ORB et peut être interrogée. Le compilateur d'interfaces IDL et le référentiel d’interfaces sont livrés avec tout ORB conforme à la norme CORBA 2.0.

Ces définitions peuvent être utilisées de différentes manières pour :

- fournir une interopérabilité entre les implantations des différents ORB ;

- vérifier les signatures des requêtes ;

- vérifier la justesse des graphes d’héritage ;

- permettre la gestion des appels dynamiques.

De plus, cette information est grandement utile pour les objets client. La norme précise quels sont les moyens à mettre en œuvre pour que ceux-ci puissent utiliser le référentiel d’interfaces et la manière de gérer l’installation et la distribution des définitions d’interfaces sur le réseau.

3.1.2.1. Fonctionnement du référentiel d’interfaces.

L’implantation d’un référentiel d’interfaces se rapproche de la mise en œuvre d’un magasin d’objets persistants. Pour un ORB qui peut gérer plusieurs milliers d’interfaces différentes, la taille de son référentiel d’interfaces peut être importante. Les spécifications de l’OMG ne précisent pas le type d’implantation du référentiel d’interfaces. Celui-ci est réalisé par les éditeurs en fonction de leurs plates-formes et de leurs systèmes d’exploitation cibles alors que le standard au niveau des interfaces IDL garantit l’interopérabilité et la portabilité des ORB.

3.1.2.2. Structure d’un référentiel d’interfaces.

L’interface mandatée traite les composants du référentiel d’interfaces (modules, interfaces, opérations, attributs, paramètres, constantes, définitions de types, exceptions et constantes) comme des objets pouvant contenir d’autres objets.

En plus de ces neuf interfaces qui représentent les structures de type IDL, CORBA spécifie une interface de nom référentiel (Repository) qui sert de racine aux modules contenus dans l’espace de nommage du référentiel. Gérée d’une façon hiérarchique, cette structure permet aux fichiers IDL d’être recréés à l’exception des commentaires (voir figure 3.6).

 

 

 

Figure 3.6. Structure d’un référentiel d’interfaces

 

3.1.3. L’INTERFACE DE L’ORB.

Le rôle important attribué à l’interface de l’ORB est l’initialisation. Lorsque le premier client est lancé, il a besoin de la référence objet de son ORB, d’un service de nommage et du référentiel d’interfaces. L’interface de l’ORB permet de lui fournir tous ces services.

Après avoir décrit les mécanismes de CORBA liés à l’invocation des objets, étudions ses caractéristiques côté implantation.

 

3.2. L’IMPLANTATION ET L’ACCES AUX OBJETS.

Pour invoquer un objet distant, un objet client fait appel à une souche. Celle-ci emballe les arguments dans une requête qui est ensuite transportée par le réseau. Du côté du processus serveur, une souche serveur reçoit la requête, déballe les arguments et invoque alors l’objet réel à l’aide du référentiel d’implantation. A la fin de l’exécution de la méthode invoquée, la souche serveur emballe les résultats dans un message transporté par le réseau. La souche client déballe ces résultats et les retourne à l’objet appelant.

Comme il est présenté dans la figure 3.7, les interfaces suivantes communiquent avec le serveur et permettent d’accéder aux implantations des objets : la souche statique (SIS, Static IDL Skeletons), la souche dynamique (DSI, Dynamic Skeleton Interfaces), le BOA (Basic Object Adaptater) et l’interface serveur de l’ORB. De plus, l’interface dynamique a la possibilité de communiquer avec des ORBs distants via des demi-ponts génériques. Enfin, les services liés au référentiel d’implantations sont accessibles au travers de l’interface de l’ORB.

 

Figure 3.7 : ORB côté serveur.

3.2.1. L’ADAPTATEUR D’OBJET.

Côté serveur, une structure enregistre les classes des applications, instancie les nouveaux objets à l'aide d'une identification unique, avertit de leurs existences, appelle les méthodes lors de leur invocation par le client et gère les requêtes courantes liées aux services. En d’autres termes, un programme spécialisé est nécessaire pour transformer les librairies des classes de base en environnement serveur multi-utilisateurs. Il a pour nom adaptateur d’objet de base ou BOA (Basic Object Adaptor).

La norme CORBA spécifie un BOA qui peut être utilisé pour tout objet ORB conforme à CORBA. Pour cela les fonctions décrites dans le paragraphe suivant doivent être fournies par le BOA.

3.2.1.1. Les services fournis.

L’adaptateur d’objet est le mécanisme qui permet à un objet d’accéder aux services de l’ORB. Il fournit un environnement global pour exécuter l’application serveur. Un adaptateur d’objet définit la manière dont les objets sont activés. Il peut le faire en créant de toute pièce un nouveau fil d’exécution, un nouveau à partir d’un autre existant ou en activant de nouveau un fil d’exécution existant.

Voici quelques services fournis par l’adaptateur d’objet.

1. Mise à jour du référentiel d’implantation.

Le référentiel d’implantation est un lieu de stockage des classes gérées par l’adaptateur d’objet. Il permet d’installer et d’enregistrer l’implantation d’un objet et fournit des informations pour le décrire.

2. Instanciation de nouveaux objets.

L’adaptateur d’objet est responsable de la création des instances d’objets à partir des classes d’implantation et assure l’instanciation d’objets en fonction de la demande des clients : le nombre des instances créées est fonction du trafic client entrant dans le serveur.

3. Génération et gestion des références objets.

L’adaptateur d’objet assigne une référence, identificateur unique, à chaque nouvel objet créé. Cela permet de réaliser, dans l’ORB, une correspondance entre l’implantation de l’objet et sa représentation sous forme de référence. Ces mécanismes permettent d’activer et de désactiver les implantations des objets, d’invoquer des méthodes et de passer à celles-ci des paramètres.

4. Diffusion de la présence des objets serveur.

L’adaptateur d’objet diffuse au monde extérieur les services qu’il fournit à l’ORB ou répond aux requêtes qui viennent de l’ORB.

5. Prise en charge des appels entrants.

L’adaptateur d’objet dialogue avec la couche de communication de l’ORB, prend en charge les requêtes et les transmet à la souche serveur. Celle-ci interprète les paramètres d’entrée et les présente d’une façon acceptable aux méthodes invoquées. Un mécanisme pour authentifier le client qui effectue l’appel est réalisé. Le BOA n’impose aucun type spécifique de sécurité. Il garantit seulement qu’il pourra, pour chaque objet ou méthode invoquée, identifier le client qui a lancé la requête.

6. L’activation et la désactivation des objets.

7. Appel de la méthode appropriée.

L’adaptateur d’objets est directement responsable de l’invocation des méthodes décrites dans la souche serveur. Par exemple, l’adaptateur objet active l’implantation et authentifie les requêtes entrantes. Il gère les appels entrants.

3.2.1.2. Les différents types de BOA.

Plusieurs types d’adaptateurs d’objet sont proposés par l’OMG. CORBA 3.0 devrait normaliser un seul type de BOA afin d’améliorer la portabilité des serveurs. Pour couvrir l’ensemble des applications, CORBA 2.0 préconise quatre politiques d’activation. Celles-ci spécifient les règles qu’une implantation donnée doit suivre pour activer les objets. Ceci n’est qu’une recommandation de l’OMG. Chaque constructeur d’ORB suit ses propres règles.

  1. Un BOA pour serveur partagé.
  2. Plusieurs objets peuvent résider dans le même processus. Le BOA active le serveur la première fois qu’une requête est lancée en direction d’un objet quelconque implémenté dans le serveur. Après son initialisation, celui-ci prévient le BOA qu’il est prêt à gérer des requêtes en appelant la fonction impl_is_ready. Toutes les requêtes sont alors dirigées vers ce processus serveur : le BOA n’activera pas d’autres processus serveur pour cette implantation. Le serveur ne gère qu’une seule requête à la fois et prévient le BOA via la fonction deactivate_obj lorsque qu’il a fini de traiter une requête et qu’il est prêt à en traiter une nouvelle. Quand le processus est sur le point de se terminer, il prévient le BOA en invoquant la méthode desactivate_impl.

  3. Un BOA pour serveur non partagé.
  4. Chaque objet réside dans un processus serveur qui lui est spécifique. Un nouveau serveur est activé la première fois qu’une requête est lancée en direction d’un objet. Lorsque l’objet est initialisé, il notifie le BOA qu’il est prêt à gérer des requêtes en appelant la fonction obj_is_ready. Un nouveau processus serveur est lancé chaque fois qu’une requête est faite en direction d’un objet qui n’est pas encore activé sur le serveur. Un serveur objet reste actif jusqu’au moment où il appelle la fonction desactive_obj.

    3.Un BOA avec un serveur par méthode.

    Un nouveau processus serveur est activé chaque fois qu’une méthode d’un objet est invoquée. Il ne fonctionne que pendant l’exécution de celle-ci. Ainsi, plusieurs processus serveur pour le même objet - ou même pour la même méthode du même objet - peuvent être actifs en même temps.

  5. Un BOA avec un serveur persistant.

Dans un serveur persistant, les serveurs sont activés par des moyens qui sont en dehors du BOA. Le BOA lance une application serveur. Celle-ci prévient le BOA qu’elle est prête à accepter des requêtes au moyen de l’appel impl_is_ready. Le BOA traite alors toutes les requêtes suivantes comme il le ferait pour un serveur partagé. Il existe un ordonnanceur qui dirige les requêtes vers les serveurs. Ce mécanisme permet de réaliser des ORBs temps réel.

 

3.2.2. LES SQUELETTES SERVEUR STATIQUES ET DYNAMIQUES.

L’interface de squelettes statiques (SSI, Static Skeleton Interface) est l’équivalent, pour les serveurs d’objets, de l’interface d’invocation statique pour les clients. Elle s’occupe de traiter les requêtes pour les transmettre aux objets d’implantation et, en retour, se charge de transmettre les résultats à destination des clients. Le transport des invocations et des retours d’invocation est assuré par le noyau de communication. Les souches et les squelettes sont générés automatiquement par le compilateur IDL.

L’interface de squelettes dynamiques (DSI, Dynamique Skeleton Interface) est l’équivalent, pour les serveurs d’objets, de l’interface d’invocation dynamique pour les clients. Elle permet de recevoir des requêtes sans disposer à priori de squelettes de déballage. Cette interface est utilisée pour traiter les invocations dynamiques générées, côté client, par les souches dynamiques. Elle permet également d’implanter, côté serveur, des passerelles entre bus et aussi de réaliser la liaison avec des langages interprétés comme le langage CorbaScript [Geib 97].

3.2.3. LE REFERENTIEL D’IMPLANTATION.

Le référentiel d’implantation contient l’ensemble des informations décrivant l’implantation des objets : les références des objets, le nom des exécutables contenants le code des objets, la politique d’activation de ces exécutables, les droit d’accès aux serveurs et à leurs objets. Il peut contenir aussi des informations pour l’administration, la gestion, l’audit ou bien le déverminage des objets. Actuellement, ce référentiel n’a pas encore été spécifié par l’OMG : il est donc spécifique à chaque implantation de CORBA.

3.2.4. LES DEMI-PONTS GENERIQUES.

L’OMG propose une forme de passerelle dont l’implantation est indépendante des protocoles propriétaires des bus. Cette passerelle est composée de deux demi-ponts génériques. Sur la figure ci-dessous, le demi-pont A intercepte les requêtes émises sur le bus A à destination d’un objet du bus B via le mécanisme de squelettes dynamiques DSI. La requête est ensuite transmise au demi-pont B. Celui-ci utilise le mécanisme d’invocation dynamique pour réémettre l’invocation à destination de l’objet cible. Pour assurer l’interopérabilité, ce mécanisme fonctionne dans les deux sens.

 

Figure 3.8. Communication entre ORBs par des demi-ponts génériques

Après avoir présenté les caractéristiques de CORBA 2.0, nous abordons, dans le paragraphe suivant, la description de deux bus logiciel conformes à cette norme. L’un est utilisé dans la partie expérimentale de ce mémoire. Il s’agit d’OmniBroker. Nous donnons également quelques éléments d’information sur VisiBroker qui est un ORB commercial largement utilisé.

 

4. EXEMPLES DE BUS LOGICIEL.

 

4.1. OMNIBROKER.

 

4.1.1. PRESENTATION D’OMNIBROKER.

OmniBroker est un ORB, développé par l’entreprise Object Oriented Concepts, conforme aux spécifications de CORBA, version 2.0, définies dans les documents [OMG 97] et [OMG-IDL 97] de l’OMG. Il est disponible en accès libre pour les universités [OOC] .

Les caractéristiques de la version 2.0b2 utilisée sont les suivantes.

OmniBroker est disponible pour les systèmes d’exploitation Windows 95, NT et UNIX (LINUX ou Solaris).

OmniBroker supporte des fichiers IDL conformes à la norme CORBA. Deux types de liaisons sont possibles : mapping IDL-C++ ou mapping IDL-JAVA.

Il propose un traducteur d’IDL vers HTML pour générer des fichiers de documentation de type javadoc.

OmniBroker dispose d’un bus logiciel muni d’un référentiel d’interfaces, d’interfaces d’invocation statiques et dynamiques. Les APIs gérées dynamiquement sont appelées DynAny API.

OmniBroker utilise le protocole IIOP comme protocole natif. Il communique d’égal à égal à l’aide d’invocations de méthodes non bloquantes et supporte des délais de temps en cas de non-réponse.

OmniBroker propose un service de nommage appelé CNS (Compliant Naming Service) [OOC].

OmniBroker s’intègre directement avec X11 et Windows.

En Java, l’adaptateur d’objet gère des serveurs partagés, non partagés ou par requête. En C++, l’adaptateur d’objet ne gère que des serveurs partagés.

La version 2.0b2 utilisée dans la partie expérimentale de ce mémoire a les limitations suivantes. Seuls des serveurs persistants, chargés manuellement, sont actuellement supportés. OmniBroker ne supporte pas d’applications multithreadées en C++. En JAVA, par contre, il peut supporter un fil d’activité par client et un fil d’activité par requête.

 

4.1.2. L’ORB D’OMNIBROKER.

4.1.2.1. En JAVA.

Initialisation de l’ORB.

En JAVA, l’initialisation de l’ORB se réalise en insérant dans le code source les instructions suivantes :

ORB orb = ORB.init(args,new JAVA.util.Properties()) ;

4.1.2.2. En C++.

Initialisation de l’ORB.

En C++, l’initialisation de l’ORB se réalise en insérant dans le code source les instructions suivantes :

CORBA_ORB_var orb = CORBA_ORB_init(argc,argv) ;

4.1.3. L’ADAPTATEUR D’OBJET D’OMNIBROKER.

  1. En JAVA plusieurs types d’adaptateurs d’objet sont disponibles.
  2. 4.1.3.1. En JAVA.

  3. Initialisation de l’adaptateur d’objets.
  4. En JAVA, l’initialisation de l’adaptateur d’objets se réalise en insérant dans le code source les instructions suivantes :

  5. BOA boa=orb.BOA_init(args, new JAVA.util.Properties()) ;

Serveur partagé (simplement threadé).

Un seul processus léger est lancé pour toute requête de tout client.

L’option à utiliser pour initialiser le BOA est SingleThread.

Serveur non partagé.

Un seul processus léger est lancé pour chaque client, qui gère toutes les requêtes de ce client.

L’option à utiliser pour initialiser le BOA est ThreadPerClient.

Serveur par requête.

Un processus léger est créé pour chaque requête de chaque client.

L’option à utiliser pour initialiser le BOA est ThreadPerRequest.

Remarques.

Le mode de fonctionnement par défaut du BOA est de type serveur partagé.

La fonction d’Omnibroker set_thread_model() permet de définir le mode de fonctionnement du BOA après l’avoir initialisé.

Le mode de fonctionnement du BOA peut être récupéré en utilisant la fonction get_thread_model().

4.1.3.2. En C++.

Initialisation de l’adaptateur d’objets.

En C++, l’initialisation de l’adaptateur d’objets se réalise en insérant dans le code source les instructions suivantes :

CORBA_BOA_var boa=orb->BOA_init(argc,argv) ;

Serveur partagé (simplement threadé).

Un seul thread est lancé pour toute requête de tout client.

 

4.2. VISIBROKER.

 

4.2.1. PRESENTATION DE VSIBROKER.

La société Visigenic propose deux versions du produit VisiBroker. Une version pour C++ et une version pour JAVA. Elles fournissent toutes deux les interfaces et les fonctionnalités, côté client et côté serveur, définies par la norme CORBA. En particulier, les interfaces d’invocation dynamique peuvent être utilisées pour que les objets client effectuent des invocations dynamiques au moment de leur exécution. De même, un répertoire d’interfaces est disponible pour stocker les informations concernant les interfaces des objets et pour les rendre accessibles au moment de l’exécution des programmes les invoquant. De plus, VisiBroker permet une interopérabilité avec les autres bus logiciel au travers du protocole IIOP qui est implanté en natif.

VisiBroker pour C++ v2.0.

VisiBroker pour C++ dispose des caractéristiques suivantes.

VisiBroker pour JAVA.

VisiBroker pour JAVA dispose des caractéristiques suivantes.

 

 

4.2.2. LES SPECIFICITES TECHNIQUES DE VISIBROKER.

La spécificité technique principale de VisiBroker est l’utilisation d’objets spécialisés qui sont les agents. Le rôle des agents est d’implanter le service d’annuaire distribué défini dans le COS (Compliant Naming Service). Ils mettent à jour un registre étendu du domaine qui contient les références de tous les objets actifs ou enregistrés. L’ensemble de ces agents crée un système nommé OsAgent qui est caractérisé, d’une part, par la distribution, chaque agent n’a la connaissance que d’un sous ensemble d’objets distribués, et d’autre part, par la tolérance aux pannes : si un agent tombe en panne, d’autres agents prennent le relais.

VisiBroker dispose d’un service Evénements conforme à CORBA. Des gestionnaires d’événements permettent aux applications de recourir à des actions spécifiques lorsque certains événements surviennent. Le programmeur a la possibilité de le mettre en œuvre pour la sécurité, la gestion des applications, le déboguage et la mise en place de traces. Il peut l’installer au niveau d’un objet ou d’un processus.

VisiBroker pour C++ est un bus logiciel tolérant aux pannes. Comme il est précisé dans le paragraphe précédent, une tolérance aux pannes existe vis à vis des agents. De plus, ce principe est généralisé à tout objet serveur. En effet, si un objet serveur devient inaccessible par le client, l’agent concerné établit une connexion entre le client et l’une des objets réplique du serveur originel.

Pour le déboguage d’applications distribuées, un observateur existe dans les versions C++ et JAVA de VisiBroker. Il est fondé sur la trace des messages IIOP échangés entre les clients et les serveurs. Il permet de visualiser les messages suivants : envoi d’une requête côté client  ; envoi, côté serveur, de messages contenant l’accusé de réception et la réponse à la requête précédente ; envoi, côté client, de l’accusé de réception de la réponse. Ce débogueur peut être utile lorsqu’une application distribuée connaît des problèmes de performances. Il permet de détecter, par exemple, un trafic trop important de messages entre un client et son serveur. Un autre cas d’utilisation peut être envisagé pour tester l’interopérabilité entre des composants implantés sur VisiBroker et d’autres proposés par des vendeurs différents.

 

4.2.3. CAFFEINE.

Caffeine [Orfali 97], produit développé conjointement par Netscape et Visigenic, est une solution pure JAVA pour les développeurs qui souhaitent travailler dans un environnement CORBA. Il fournit un environnement de programmation qui ressemble à RMI et se place au-dessus de VisiBroker pour JAVA qui est le bus logiciel conforme à CORBA produit par Visigenic.

Caffeine est composé des outils suivants.

 

5. CONCLUSION.

Pour conclure sur cette partie, réalisons une comparaison entre CORBA et ses deux principaux concurrents qui sont RMI et DCOM.

 

5.1. COMPARAISON RMI/CORBA.

L’avantage principal de CORBA sur RMI/JAVA est qu’il supporte de multiples langages de programmation. Bien que JAVA soit un langage de programmation plus facile à utiliser que COBOL, C ou C++ et qu’il est susceptible de les remplacer dans un avenir proche, il y aura toujours des applications implantées avec des langages autres que JAVA car les coûts engendrés par leurs réécritures seraient trop prohibitifs. Les applications JAVA peuvent accéder à ces dernières applications si elles sont emballées dans des objets conformes à CORBA. De plus CORBA donne aux applications distribuées des facilités liées à ses services. Enfin, l’interopérabilité de CORBA 2.0 permet aux développeurs de choisir des ORBs, conformes à cette norme, de différents vendeurs et d’y intégrer des applications nouvelles ou patrimoines. Ainsi, les bus logiciels de type CORBA créent un environnement particulièrement adapté aux applications qui nécessitent un haut niveau de coopération entre toutes leurs entités.

Comparaison CAFFEINE/RMI.

Contrairement à RMI, CAFFEINE s’appuyant sur le bus logiciel VisiBroker pour JAVA conforme à CORBA, laisse les objets JAVA communiquer avec tous les objets de l’univers CORBA/IIOP, incluant les objets C, C++ et Smalltalk. De plus, il donne aux objets JAVA les bénéfices des services CORBA tels que le service Evénements, le service Transactions ou le service Sécurité.

La comparaison entre un produit comme CAFFEINE conforme à CORBA et RMI peut être synthétisée dans le tableau ci-dessous.

Caractéristiques

CORBA

RMI

Plates-formes

Toutes les plates-formes

Toutes les plates-formes JAVA

Passage de paramètres

in, out et in/out

in

Description des interfaces

Oui (via les fichiers IDL de CORBA)

Oui (via les interfaces JAVA)

Invocations dynamiques

Oui (via DII)

Non

Performances

Très rapide

3,2 ms pour un ping

Moins rapide

5,5 ms pour un ping (extrapolation)

Sécurité

Oui (via le service de Sécurité)

Non

Transactionnel

Oui (via le service Transactions)

Non

Interopérabilité

Oui (via IORs, DSI, IIOP)

Non

Tableau 3.3. Comparaison CORBA/RMI

 

5.2. COMPARAISON DCOM/CORBA.

OLE/COM (Object Linking & Embedding/Common ou Component Object Model) de MicroSoft est une architecture logicielle permettant l’activation d’un serveur sur des références d’objets. Il effectue des appels de procédures locales. Associé au réseau, il donne naissance au produit DCOM (Distributed Component Object Model) de MicroSoft. Il est fondé sur le RPC de la norme DCE (Distributed Computer Environnement) de l’OSF. Le client d’une classe COM utilise un objet distant mais ne possède pas vraiment de pointeur réel sur cet objet.

Les similitudes de DCOM avec CORBA peuvent être identifiées de la manière suivante. DCOM sépare l’interface de l’objet de son implantation. Il utilise un Langage de Définition d’Interface (IDL) pour déclarer ses interfaces fondé sur l’IDL de DCE mais différent de l’IDL de CORBA et de DCE . DCOM fournit des interfaces dynamiques et statiques pour les invocations de méthodes.

Les principales différences opposant DCOM et CORBA sont les suivantes. DCOM ne supporte pas l’héritage multiple d’interfaces. Un composant DCOM peut avoir des interfaces multiples et faire de la réutilisation par encapsulation des interfaces dans des composants internes. Une interface DCOM est simplement un groupe de fonctions. Un pointeur est donné aux clients pour accéder aux fonctions d’une interface. Un client DCOM ne peut pas se reconnecter exactement sur la même instance d’objet avec le même état. Il peut seulement se reconnecter sur un pointeur d’interface d’une même classe.

 

Caractéristiques

CORBA

DCOM

Plates-formes

Toutes les plates-formes

Jview (JAVA VM pour Windows 95 et NT)

Passage de paramètres

in, out et in/out

in, out et in/out

Description des interfaces

Oui (via les fichiers IDL )

Oui (via les fichiers IDL de DCOM)

Invocations dynamiques

Oui (via DII)

Oui (via Idispatch)

Performances

Très rapide

3,2 ms pour un ping

Rapide

3,9 ms pour un ping

Sécurité

Oui (via le service de Sécurité)

Oui (via NT)

Transactionnel

Oui (via le service Transactions)

Oui (via OLE DTS)

Interopérabilité

Oui (via IORs, DSI, IIOP)

Non

Tableau 3.4. Comparaison CORBA/DCOM

CORBA respecte les concepts objets tels qu’ils sont définis dans les langages à objets. Il fournit une invocation statique et dynamique de méthodes distantes et un ensemble de services qui permettent de prendre en compte la distribution. On peut dire que c’est un environnement distribué respectant l’approche objet réparti.

Malgré son retard sur les concepts objets, l’avantage de DCOM vient de la force commerciale de MicroSoft.

RMI n’est pas vraiment un concurrent direct. Il apporte une solution temporaire pour une communication entre objets JAVA.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PARTIE 2 : LA MISE AU POINT D’APPLICATIONS REPARTIES.

 

CHAPITRE 4 : DEBOGAGE D’APPLICATIONS REPARTIES.

 

1. INTRODUCTION.

Aujourd’hui, les systèmes parallèles et répartis sont disponibles et leurs technologies ont atteint un certain degré de maturité. Bien que des recherches substantielles aient été menées dans ce domaine, il nous manque encore des outils complets qui permettent la compréhension globale de tels systèmes afin de les analyser, de les réaliser et de les tester.

L’analyse d’un programme distribué reste encore très complexe. L’une des raisons de cette difficulté est le non-déterminisme inhérent à ces programmes.

En particulier, il est notoirement difficile de conserver et surtout de comprendre des traces de différentes activités locales qui apparaissent d’une façon concurrente et qui peuvent interagir. De même, il est compliqué de prévoir les erreurs potentielles de synchronisation ou d’interblocage. De plus, les systèmes distribués sont peu couplés. En effet, d’une part les vitesses d’exécution de leurs activités locales ne sont généralement pas connues à l’avance et d’autre part, les délais de transmission des messages peuvent varier substantiellement entre plusieurs répétitions du même algorithme en fonction de la charge du réseau et des systèmes. Enfin, une horloge système globale ou un mécanisme d’horloges locales synchronisées n’est généralement pas disponible.

Le but de cette partie est de faire le point sur les techniques de validation, vérification et test en particulier en environnement objets répartis.

La première partie de ce chapitre est consacrée aux problèmes liés à la mise au point d’applications réparties. Pour pouvoir décrire leurs comportements une relation de causalité est définie. Le concept de temps logique est également abordé dans le but de dater les événements.

La deuxième partie brosse l’inventaire des différentes techniques employées pour déboguer les applications réparties.

 

2. LES PROBLEMES LIES A LA MISE AU POINT.

Les difficultés à déboguer les applications sont dues d’une part, à la nature de leur exécution et d’autre part, à la difficulté à les observer.

2.1. LES DIFFERENTS TYPES D’EXECUTION.

Une application informatique s’exécute selon trois modes de fonctionnement : séquentiel, parallèle ou réparti. Chaque type d’exécution pose des problèmes spécifiques de mise au point.

2.1.1. L’EXECUTION SEQUENTIELLE.

Un programme séquentiel est composé d’un seul processus exécutant des instructions sur un seul processeur, les unes à la suite des autres. Toutes les instructions s’exécutant en séquence, il ne se pose pas de problème d’ordre. De plus, l’environnement monoprocesseur fournit une référence de temps unique. A tout instant, l’utilisateur peut alors observer un état global cohérent du programme.

Les problèmes liés à ce type d’application, de part leur déterminisme qui rend leurs exécutions reproductibles, sont limités par rapport au déverminage d’applications parallèles ou réparties.

2.1.2. L’EXECUTION PARALLELE.

Une exécution parallèle [Roos 94] se compose de plusieurs processus pouvant partager une mémoire commune qui interagissent. Les difficultés rencontrées sont les suivantes.

Les informations ne sont plus ordonnées de façon linéaire. Pour obtenir une observation cohérente, il est nécessaire de définir une relation de dépendance causale entre les événements.

Il n’est plus aussi simple d’arrêter l’exécution tout en laissant l’application dans un état cohérent et significatif, c’est à dire proche au niveau temporel des causes qui ont généré cet arrêt.

Les origines de l’indéterminisme, source de nombreuses difficultés de mise au point, dans le déroulement d’exécutions parallèles sont de différentes natures. Elles dépendent notamment :

Cet indéterminisme ne permet plus de garantir la reproductibilité des exécutions ce qui s’avère souvent fort gênant pour le développeur car la localisation d’une erreur nécessite souvent des ré-exécutions.

2.1.3. L’EXECUTION REPARTIE.

Les applications réparties [Prun 98] sont par nature distribuées sur plusieurs sites distincts, ce qui complique leur mise au point : l’observation de l’application répartie doit être globale et doit, de ce fait, prendre en compte le comportement de celle-ci sur chacun des sites qui participent à son exécution. Ainsi, le processus de mise au point doit incorporer la gestion de la répartition. De surcroît, d’autres sources de difficultés attendent le programmeur en phase de déboguage d’applications réparties.

 

2.2. LES PROBLEMES LIES A L’OBSERVABILITE.

2.2.1. INTRODUCTION.

Le problème central est la représentation de l’exécution telle qu’elle est observée. Le principal modèle utilisé pour représenter l’exécution d’une application distribuée est la causalité. La relation de causalité définie par Lamport [Lamport 78] crée un séquencement entre les événements qui se produisent au sein d’une application répartie. Elle conduit, dans la première partie de cet exposé, à la mise en évidence d’états globaux qui autorisent, par exemple, l’arrêt d’une façon cohérente d’une application répartie en phase de déboguage. Dans la seconde partie, elle donne au programmeur la possibilité de gérer des horloges logiques qui rendent possible la datation des événements qu’il souhaite observer.

2.2.2. RELATION DE CAUSALITE.

Le modèle de Lamport, sur lequel la relation de causalité est définie, fournit un cadre pour la description des exécutions des applications distribuées et sert de base à de nombreuses techniques développées pour aider leur mise au point et plus généralement leur maintenance. La relation de causalité permet de structurer une exécution répartie en ordonnant partiellement les événements. Un événement précède un autre s’il en est potentiellement la cause.

Lamport a défini la relation " précède " sur les événements. Elle peut être vue comme une relation de causalité. Celle-ci notée " < " est définie de la manière suivante.

a<b si et seulement si l’une des trois conditions suivante est vérifiée :

Remarque : cette relation de causalité est une relation d’ordre partiel (transitive, anti-symétrique, anti-réflexive). Il peut exister des événements qui ne sont pas ordonnés.

Par exemple, deux événements sont dits indépendants causalement si et seulement si Ø (a<b) et Ø (b<a). On parle également d’événements concurrents ou parallèles.

Nous allons maintenant présenter plusieurs définitions et plusieurs horloges vectorielles qui utilisent la relation de causalité précédente.

2.2.3. ETAT GLOBAL COHERENT.

Une des raisons pour laquelle il est plus difficile de mettre au point une application répartie qu’un programme séquentiel de complexité équivalente, est qu’un état global réparti est beaucoup plus difficile à déterminer et à manipuler. On peut cependant, de manière analogue à la causalité de messages, définir les notions de coupures cohérentes, de coupures non cohérentes et un état global associé à une coupure cohérente.

Une coupure, dans un système distribué, est un sous-ensemble fini E d’événements.

Une coupure est dite cohérente (c1,c2,c3) si elle est fermée pour la relation de dépendance causale (voir figure 4.1).

si a Î E et b précède causalement a alors b Î E.

Un état global est cohérent si cet état est associé à une coupure cohérente.

Remarque : la coupure (c1’,c2’,c3’) donnée dans la figure 4.1 n’est pas cohérente. En effet e33 Î E et e13 précède causalement e33 et pourtant e13 n’appartient pas à E.

Figure 4.1 : représentation de coupures cohérente et non cohérente

 

 

2.2.4. ALGORITHMES DE DATATION.

Il est nécessaire d’ordonner globalement l’ensemble des événements survenant dans une application répartie. Dans les architectures distribuées, il n’y a généralement pas d’horloge globale : chaque site possède sa propre horloge indépendante des autres. Comment dégager un ordre global entre les événements ?

Le problème de l’horloge globale a été résolu par Lamport qui a proposé la construction d’une horloge dont le fonctionnement est fondé sur le calcul de la relation de causalité.

2.2.4.1. L’horloge de Lamport.

Une horloge logique est en fait le moyen de numéroter un événement. Ce numéro est considéré comme la date d’occurrence de l’événement. A chaque processus est assigné une horloge logique qui est implantée comme un simple compteur. Le mécanisme de datation fonctionne de la manière suivante :

L’exemple de la figure 4.2 présente l’utilisation des horloges de Lamport avec deux processus A et B. Les événements datés grâce à l’horloge de Lamport (t) sont représentés respectivement par les lettres a et b pour les processus A et B.

 

Figure 4.2. Utilisation des horloges de Lamport

Si h est la relation qui associe à un événement sa date et a, b deux événements, on peut établir la relation suivante.

a<b => h(a)<h(b)

La relation inverse n’est pas vraie. En effet, si nous prenons l’exemple de la figure 4.2, alors que h(a3)<h(b3) nous n’avons pas a3<b3 car les événements a3 et b3 sont concurrents. La connaissance seule des dates de deux événements obtenues avec l’horloge de Lamport, pour des processus distincts, ne permet donc pas de les ordonner avec la relation " précède ". Il faut connaître des informations supplémentaires comme le type des événements et le processus qui les a générés.

2.2.4.2. Les horloges vectorielles.

Pour pouvoir comparer deux événements en connaissant uniquement leur date, Fidge [Fidge 88] et Mattern [Mattern 88] ont défini une horloge comme un vecteur d’entiers, une composante du vecteur pour chaque processus. Le vecteur dans son ensemble représente la date courante du processus. La mise à l’heure s’effectue de la même manière que précédemment mais avec un changement pour la réception de messages :

Pour comparer deux événements, on procède de la façon suivante. Soit a un événement du processus Pi et b un événement du processus Pj. Soient Ta et Tb leurs dates respectives.

L’événement a précède l’événement b si et seulement si la i ème composante de Ta est inférieure à la i ème composante de Tb.

La figure 4.3 ci-dessous présente un exemple de l’utilisation des horloges de Mattern/Fidge.

Les horloges de Mattern/Fidge ordonnent totalement les événements grâce à l’estampillage des messages entre processus. Elles permettent de résoudre le problème de la datation des événements observés mais elles sont plutôt lourdes à gérer :

Figure 4.3. Utilisation des horloges de Mattern/Fidge

 

Après cet aperçu des problèmes liés à la mise au point des applications réparties, abordons, dans le paragraphe suivant, les solutions apportées par le génie logiciel pour mettre en œuvre de nouvelles techniques d’analyse.

 

 

 

 

3. LES DIFFERENTES APPROCHES DE LA MISE AU POINT.

Pour une meilleure compréhension d’une application en phase de mise au point, le programmeur cherche à établir certaines de ses propriétés. Pour cela deux types d’analyse, l’analyse statique et l’analyse dynamique, peuvent être effectués. Elles sont représentées sur la figure suivante [Letondal 92]&[Prun 98].

Figure 4.4 : une classification des techniques de déverminage

 

3.1. ANALYSE STATIQUE.

La vérification d’un système sans activation réelle de celui-ci, appelée analyse statique, peut être conduite :

La vérification des programmes est facilitée par l’utilisation de nouvelles techniques de développement des logiciels.

L’analyse statique vérifie les spécifications et les propriétés structurelles d’une application. Elle peut être manuelle ou automatique.

3.1.1. ANALYSE STATIQUE MANUELLE.

L’analyse statique manuelle correspond aux techniques de " revues " ou " d’inspections " qui sont applicables à tous les niveaux de vérification. Le principe consiste simplement en une analyse détaillée des documents produits aux cours des différentes phases de développement du logiciel, spécifications, dossiers de conception, code source, effectuée, dans la mesure du possible, par une équipe différente de celle qui a réalisé le document. Des " listes de contrôle " (questions à se poser, standards à respecter, etc.), différentes selon l’étape de développement, existent pour guider ce travail d’analyse. Selon des études de cas réels, cette analyse statique manuelle permet de déceler 40 à 70% des fautes de logique ou de programmation avant toute exécution du logiciel.

3.1.2. ANALYSE STATIQUE AUTOMATIQUE.

L’analyse statique automatique est plus liée à la phase de réalisation par codage du logiciel. Elle est réalisée à l’aide d’outils informatiques analysant le code source ou générant du code compilé : analyseurs plus ou moins évolués ou compilateurs. Ceux-ci fournissent des informations intéressantes à propos du programme : graphes de contrôle du programme, mesures de complexité, vérifications de types, etc.

Des outils plus sophistiqués effectuent l’analyse du flot de données (détermination des variables non initialisées ou non utilisées), l’analyse du domaine de variation des variables ou la détermination des conditions d’exécution de certains chemins dans le programme. De ce fait, ils peuvent guider l’effort de test vers les parties de programmes les plus complexes.

Ces méthodes d’analyse statique sont peu coûteuses car elles ne nécessitent pas la réalisation de modules spécifiques de tests.

3.2. ANALYSE DYNAMIQUE.

Pour obtenir le déverminage efficace d’un programme, il est indispensable d’en étudier le comportement à l’exécution. Cette approche est classique et est souvent considérée par le développeur comme la véritable phase de mise au point.

On peut classer les approches de ce type en deux groupes :

 

3.2.1. TECHNIQUES FONDEES SUR L’OBSERVATION.

3.2.1.1.Introduction.

Les techniques fondées sur l’observation sont dites passives car l’exécution des structures de contrôle de l’application n'est pas modifiée par l’activité d’analyse dynamique (occurrence d’événements, succession d’états atteints, contenu des données) qui peut être traitée soit pendant l’exécution, traitement " à la volée ", soit différée, analyse post-mortem. Ces événements, reçus par le biais de messages, sont enregistrés dans des fichiers trace.

La récolte d’événements a pour but de constituer une trace qui soit une collection des événements observés pendant l’exécution du programme. Mais cette récolte doit être effectuée sans induire de perturbations majeures sur l’exécution. L’ordre des événements, par exemple, ne doit pas être modifié. Par contre une certaine perturbation peut être introduite lors de la détection et lors de la sauvegarde sur disque des événements. L’observation sous la forme de traces ne peut être que séquentielle. En effet une trace représente un fil d’exécution qui notifie des événements les uns après les autres sous la forme d’une suite de messages.

Les événements peuvent être récoltés de manière logicielle ou matérielle.

3.2.1.2. Récolte logicielle.

Une application observée subit des modifications statique et dynamique.

La modification statique d’une application consiste à changer son code source sans transformer sa structure de contrôle. Cette technique consiste à y inclure des sondes logicielles dédiées à cette observation [Chaumette 94].

Un observateur, par sa présence, perturbe et modifie l’exécution de l’application observée. On parle alors de modification dynamique. Quand il est placé dans le code source, par exemple en lui ajoutant des instructions de traces, le comportement du logiciel, en particulier son temps d’exécution, est modifié. Ce rallongement du temps d’exécution est dû à l’exécution des instructions d’observation qui génèrent une charge supplémentaire des machines et du réseau qui abritent l’application répartie.

3.2.1.3. Récolte matérielle.

Comme il est précisé dans le paragraphe précédent, dans le cas d’une récolte logicielle, on ne peut empêcher une certaine perturbation sur l’exécution du programme : une modification temporelle de l’exécution du programme est donc possible. L’idéal est de disposer d’un processeur, attaché à chaque nœud, dédié à la reconnaissance d’événements. Ainsi, à chaque processeur est associé un processeur espion. Celui-ci possède plusieurs liens de communication particuliers avec le processeur de calcul et un lien avec le cache de ce dernier. Il peut ainsi détecter et récolter les événements sans perturber le processeur de calcul. Cette solution est très intéressante sur le plan technique mais elle reste très coûteuse.

 

3.2.1.4. Avantages et inconvénients des outils post-mortems.

Avantages

Les outils de déboguage post-mortem permettent :

Inconvénients.

3.2.2. TECHNIQUES A BASE D’INTERACTIONS.

Ces méthodes d’analyse sont fondées sur des exécutions interactives. Ces techniques ne sont plus passives car les exécutions des structures de contrôle de l’application sont modifiées : modification de l’application ou de certains de ses paramètres ; correction de fautes ; tests de tolérance en injectant des fautes.

3.2.2.1. Analyse par interaction directe.

3.2.2.1.1. Présentation de cette technique.

Ces techniques peuvent, par exemple, se rapprocher des débogueurs classiques d’applications centralisées mais adaptés aux applications distribuées. On parle alors d’analyse interactive de l’exécution par interaction directe. Celle-ci se caractérise par la pose de points d’arrêts, par l’accès et la modification de certaines données ou de certains messages,

par la modification de l’ordre d’arrivée de ceux-ci.

Il n’est pas simple d’arrêter l’exécution tout en laissant l’application dans un état cohérent. Des solutions ont été proposées et s’orientent dans plusieurs directions. Dans [Miller 88], l’arrêt de l’application s’effectue dans un état ayant des propriétés de cohérence connues, on parle alors de coupure cohérente (voir §. 2.2.3). Fowler [Fowler 90] propose des mécanismes de point de reprise et de retour arrière permettant ainsi de remettre les processus dans un état cohérent correspondant aux conditions d’arrêt. Cependant, il faut rappeler que tout arrêt de l’application risque de provoquer des perturbations dans l’exécution.

3.2.2.1.2. Avantages et inconvénients de l’analyse par interaction directe.

Avantage.

L’analyse par interaction directe permet l’interaction du programmeur avec l’exécution du programme ;

Inconvénients.

Ces techniques à base d’interactions :

3.2.2.2. Analyse par interaction indirecte.

Dans le cas de l’analyse par interaction indirecte, le développeur travaille sur une ré-exécution de l’exécution initiale. L’application est relancée plusieurs fois dans le même contexte de données, dans ce cas on parle de ré-exécution dirigée par les données, ou dans le même contexte de contrôle, on parle alors de ré-exécution dirigée par le contrôle, afin de mieux examiner son comportement.

3.2.2.2.1. Ré-exécution dirigée par les données.

Dans le cas d’une ré-exécution dirigée par les données, toutes les données de l’application observée sont enregistrées lors de l’exécution initiale. La ré-exécution est alors ordonnée dans le but d’obtenir les mêmes valeurs.

3.2.2.2.2. Ré-exécution dirigée par le contrôle.

Une ré-exécution dirigée par le contrôle est appelée " instant replay ". Cette technique de ré-exécution basée sur le contrôle enregistre l’ordre des accès aux objets communs. Cette technique est employée dans de nombreux débogueurs d’applications distribuées [Prun 98].

3.2.2.2.3. Avantages et inconvénients de l’analyse par interaction indirecte.

Avantages.

L’analyse par interaction indirecte permet :

Inconvénient.

La collecte et la sauvegarde d’événements pour diriger la ré-exécution peuvent créer des changements du comportement du programme observé.

 

4. CONCLUSION.

Cette partie nous a permis de faire le point sur les questions théoriques et les techniques liées à la mise au point et, plus généralement, à la maintenance d’applications réparties.

Du point de vue méthodologique, les phases de déboguage et de maintenance doivent être prévues au plus tôt dans le cycle de vie du logiciel. Plutôt que de les considérer comme une activité indépendante du cycle de développement, elles doivent être placées au centre de toutes les étapes du cycle de vie du logiciel, en particulier lors de sa phase de conception.

L’activité première permettant une meilleure compréhension des applications réparties consiste à observer leurs comportements. Cette activité d’observation est nécessaire car elles possèdent des propriétés dont la vérification n’est possible qu’au moment de l’exécution. Quatre propriétés dynamiques peuvent être citées [Prun 98] : les propriétés de sûreté, d’équité, de vivacité et comptables.

 

La partie qui suit à pour but d’utiliser les connaissances théoriques sur la relation de causalité définies dans ce chapitre. Elle permettra d’appréhender le comportement d’une application composée d’objets répartis et concurrents.

 

CHAPITRE 5. LES RELATIONS D’ORDRE DANS UNE APPROCHE OBJETS REPARTIS.

 

1. INTRODUCTION.

 

Le rappel de la notion de causalité définie par Lamport, montre que la relation de causalité proposée est applicable à tous les systèmes répartis. Néanmoins, cette relation de causalité traduit souvent des dépendances potentielles très nombreuses. Et, dans le cadre spécifique d’un système réparti orienté objet, où la sémantique du système de communication est l’appel de méthodes distantes, il est plus intéressant de disposer d’une relation de causalité qui tient compte des dépendances réelles.

Nous nous proposons dans ce chapitre de présenter la définition d’une relation de causalité en approche objets répartis. En particulier, les notions de graphes de dépendance causale et d’ordre d’interaction, utilisées dans la dernière partie de ce mémoire, sont détaillées. Cette partie montre également comment la notion de concurrence peut être fondée sur la relation de causalité entre les événements : concurrence pour l’accès à une méthode ou à des variables en exclusion mutuelle [Placide 95] [Placide 95a] [Placide 95b] [Bergdolt 97] [Bergdolt 97a].

2. NOTION DE CAUSALITE EN APPROCHE OBJETS REPARTIS.

2.1. PRESENTATION.

Dans un environnement de développement orienté objets répartis, où le mode de communication entre les objets est l’appel de méthode distante, il paraît intéressant de tenter d’adapter la causalité des échanges en mode message aux échanges par appel de méthode distante. Si le mode d’appel de méthode distante n’est en fait qu’une implantation particulière d’un protocole à base de communication par messages, la causalité des messages traduit un trop grand nombre d’informations non significatives pour la compréhension du comportement de l’application observée. L’objectif poursuivi dans cette nouvelle formulation de la causalité est de prendre en compte les causalités réelles d’exécutions ou causalités sémantiques, laissant de côté les autres relations inutiles.

 

2.2. EXEMPLE.

 

Soit un objet o1 qui possède les méthodes m1 et m2 (figure 5.1). Ces méthodes invoquent une méthode m3 d’un objet o2. On considère deux programmes p1 et p2 qui invoquent respectivement et d’une façon indépendante les méthodes o1.m1 et o1.m2.

La sémantique des communications est celle de l’appel de méthode distante. La sémantique des exécutions est celle d’un parallélisme des méthodes au niveau de chaque objet.

Figure 5.1 : exécutions de deux programmes p1 et p2

 

Si on se tient aux seules relations qui apparaissent dans les hypothèses du comportement et qui sont les relations liées aux appels de méthodes, on obtient les graphes suivants de dépendances causales (figure 5.2).

Figure 5.2 : graphes de dépendances causales

De plus, si l’on prend en compte des notions d’horloges au niveau des objets o1 et o2, on obtient le graphe du comportement global suivant (figure 5.3).

Figure 5.3 : graphe du comportement global

Il faut noter que ce graphe correspond à une analyse du comportement global de l’application répartie. Elle n’est pas directement accessible au système et doit être fournie par le programmeur sous la forme de déclarations complémentaires s’il veut observer, par exemple, des relations de causalité qui lui paraissent pertinentes.

Les derniers chapitres suivants de ce mémoire donnent des outils pour visualiser les graphes de dépendances causales liés à certains ordres de la relation de causalité définis dans le paragraphe ci-dessous.

 

3. DEFINITION DE LA RELATION DE CAUSALITE UTILISEE.

Dans un objectif de mise au point, nous cherchons à mettre en évidence le maximum d’informations sur l’ordre partiel des exécutions. Le déboguage nous conduit à nous placer sous l’angle de l’observation d’une exécution. A partir de cette exécution, nous tentons de déterminer des relations entre les différentes séquences d’exécution observées au sein de programmes ou d’objets observés.

Pour notre approche de la causalité, nous proposons les notions suivantes :

La relation de causalité des appels en approche objets répartis est la fermeture transitive des relations d’ordre programme et d’appel de méthodes distantes.

 

3.1. ORDRE D’INTERACTION.

L’ordre d’interaction, aussi appelé ordre d’appel, est l’ordre déduit des relations de causalité qui existent entre actions lors d’invocation de méthodes. Au niveau de l’objet, cet ordre est mis en évidence par le suivi de l’exécution des méthodes.

Considérons le programme de la figure 5.4 mettant en jeu deux objets O1 et O2. L’exécution de la méthode m de O1 nécessite l’invocation de la méthode p sur O2.

Figure 5.4 : invocation de la méthode O2.p

Les deux événements essentiels lors de l’exécution d’une méthode sont le début et la fin de l’exécution de celle-ci. Ainsi, pour la méthode m de l’objet O1, on enregistre les événements " a : début méthode m " et " f : fin méthode m ". Pour la méthode p de l’objet O2, on enregistre les événements " c : début méthode p " et " d : fin de méthode p ". Chaque objet possédant plusieurs méthodes, il est naturel de noter dans ces événements le nom de la méthode exécutée. Pour suivre le fil d’exécution, il faut noter l’invocation de la méthode distante. L’événement " b : invocation méthode p " est donc enregistré dans la trace. La reprise de l’exécution de la méthode m est enregistrée : " e : retour d’invocation méthode p ".

Les événements a et f sont indentifiés par le nom de l’objet et le nom de la méthode.

Les événements b et e sont caractérisés, d’une part, par le nom de l’objet et de la méthode appelants et d’autre part, par le nom de l’objet et de la méthode invoqués.

Les événements c et d sont définis d’une part, par le nom de l’objet et de la méthode appelés et d’autre part, par le nom de l’objet et de la méthode appelants.

3.2. ORDRE PROGRAMME.

L’ordre programme peut être défini par la fermeture transitive des trois relations d’ordres définies dans les paragraphes ci-dessous.

3.2.1. Ordre LOCAL.

En plus de l’ordre déduit des enchaînements de méthodes et de blocs d’instructions par appel séquentiel, nous introduisons la notion de parallélisme. Rappelons que dans notre modèle d’exécution plusieurs méthodes peuvent s’exécuter sur un même objet. De même une méthode peut appeler et créer en parallèle deux fils d’activité.

Figure 5.5 : exécution entrelacée d’une même méthode

Dans un environnement CORBA, les objets sont mono-sites et les machines sont monoprocesseur, le parallélisme intra-objet n’est donc pas réel. A sein d’un même objet, il existe un ordre total temporaire entre chacun des événements " début de méthode " et " fin de méthode ", l’ordonnancement est donné par la gestion du moteur d’exécution.

Il est nécessaire d’identifier de manière unique toute exécution de méthode sur un objet. A chaque exécution de méthode on associe un numéro d’ordre unique, ce numéro d’ordre sert d’identifiant unique à une exécution de méthode. Les événements c et c’ (début de méthode p) ainsi que d et d’ (fin de méthode p) sont différenciés par le numéro d’exécution de la méthode. Un numéro unique est transmis à la méthode distante p lors de son invocation.

 

 

3.2.2. Ordre intra objet.

Le langage JAVA fournit un service de synchronisation au moyen de l’instruction ou du modificateur synchronized (voir chapitre 2, §2.2). A l’appel de la méthode, si celle-ci est dite synchronisée, on vérifie qu’il n’y pas d’autres instances de cette méthode ou d’autres méthodes en cours.

De manière pratique, il n’est pas possible d’avoir une trace de la synchronisation pour expliquer le comportement d’un objet. La solution adoptée pour observer la file d’attente d’exécution d’une méthode synchronisée consiste à encapsuler le corps de la méthode dans une méthode appelée méthode jumelle. Cette méthode étant libre de tout contrôle de concurrence, cela permet de différencier le moment de l’invocation et le moment du début de l’exécution.

Soient deux méthodes synchronisées meth_1 et meth_2, soient jum_méth_1 et jum_méth_2 leurs méthodes jumelles associées. On suppose que la trace théorique d’exécution des méthodes meth_1 et meth_2 est la suivante :

Début méthode meth_1

Fin méthode meth_1

Début méthode meth_2

Fin méthode meth_2

Il n’est pas possible de savoir si l’exécution de la seconde méthode a dû attendre réellement la fin de la première pour commencer. De plus, on ne peut pas visualiser la file d’attente des méthodes qui attentent l’accès à la partie critique. L’utilisation de méthodes jumelles permet, d’une part, de lever cette ambiguïté et d’autre part, de visualiser cette file d’attente.

  1. Début de la méthode meth_1 (réception de l’invocation de la méthode meth_1).
  2. Début de la méthode jum_meth_1 (début de l’exécution de la méthode jum_meth_1).
  3. Début de la méthode meth_2 (réception de l’invocation de la méthode meth_2).
  4. Fin méthode jum_meth_1 (fin de la méthode jum_meth1).
  5. Début de la méthode jum_meth_2 (début de l’exécution de la méthode jum_meth_2).
  6. Fin de la méthode meth_1.
  7. Fin de la méthode jum_meth_2.
  8. Fin de la méthode meth_2.

On constate qu’à l’étape 3, une invocation de la méthode meth_2 est arrivée mais que sa prise en compte (début de la méthode jump_meth_2) a été retardée.

 

3.2.3. Ordre transactionnel.

Cet ordre est induit par les accès en lecture et en écriture aux données partagées mis en jeu lors d’une exécution. Le parallélisme intra-objet n’étant qu’un pseudo-parallélisme et les opérations d’accès étant considérées comme atomiques, il existe un ordre total sur tous les accès à une variable partagée de l’objet.

Ainsi, les événements correspondant à la lecture et à l’écriture d’une variable élémentaire peuvent être tracés pour savoir si les règles d’accès aux données critiques sont respectées.

Considérons les deux transactions T1 et T2 correspondant à l’exécution d’une même méthode m d’un objet O1. Cette méthode contient séquentiellement la lecture puis l’écriture d’une variable globale X (voir figure 5.6).

Figure 5.6. Dépendances causales transactionnelles

On a les règles suivantes : la lecture d’une variable dépend causalement de l’écriture précédente. L’écriture d’une variable dépend causalement de l’écriture précédente et des lectures qui l’ont suivie. On obtient l’historique suivant : L1(X) précède causalement E1(X) et L2(X) précède causalement E2(X) car ces couples d’opérations appartiennent respectivement à la même activité. Mais aussi d’après les règles précédentes, L2(X) précède causalement E1(X) et E1(X) précède causalement E2(X). Il est ainsi possible de tracer le graphe des dépendances transactionnelles : T2->T1->T2.

Le théorème de sérialisation stipule qu’un historique H est sérialisable si et seulement si le graphe des dépendances transactionnelles est acyclique. Cette exécution n’est donc pas sérialisable car son graphe de dépendances transactionnelles contient un cycle. Dans le cadre de la mise au point d’applications, la théorie de la sérialisation nous permet de mettre en évidence, à partir de l’historique, des incohérences sur les traitements des données.

 

4. CONCLUSION.

Les relations d’ordre partiel définies dans ce chapitre nous permettent de définir la relation de causalité suivante en approche objets répartis.

La relation de causalité utilisée dans ce mémoire est la fermeture transitive de l’ordre programme et de l’ordre d’interaction.

Néanmoins, notre démarche reste très analogue à la causalité des échanges en mode message. Ainsi, nous définissons une causalité qui repose sur un ordre programme à l’objet et sur les interactions entre objets par invocation de méthode distante.

Les relations d’ordre abordées dans ce chapitre et qui peuvent être tracées sont répertoriées dans le tableau 5.1.

Type d’ordre

Relation d’ordre

Evénements enregistrés

Eléments enregistrés

Ordre d’appel

Ordre d’interaction

Invocation de méthode

Début de méthode

Fin de méthode

Retour d’invocation de méthode

Nom de l’objet

Nom de la méthode appelante

Nom de la méthode appelée 

Type d’événement (début, fin, appel, retour d’appel)

 

Ordre local

Exécution d’une méthode

Création d’un fil d’activité

Nom  de l’objet

Nom de la méthode

Numéro d’exécution de la méthode

Ordre programme

Ordre intra objet

Début de méthode

Fin de méthode

Début de méthode jumelle

Fin de méthode jumelle

Nom de la méthode

Type d’événement (début, fin de méthode ; début, fin de méthode jumelle)

 

Ordre transactionnel

Lecture/écriture d’une variable d’instance privée

Nom de la variable

Type de l’événement (lecture ou écriture)

Tableau 5.1 : inventaire des relations d’ordre

La dernière partie de ce mémoire met à la disposition des développeurs d’applications JAVA réparties des classes spécialisées dans l’observation. Dès la phase de codage, ces outils donnent la possibilité à l’utilisateur d’insérer, dans le code source du programme observé, des sondes logicielles qui génèrent, au moment de son exécution, des traces. Celles-ci contiennent les séquences des ordres observés. D’une manière post-mortem, elles sont analysées pour connaître le comportement de l’application.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PARTIE 3 : EXPERIMENTATION.

 

 

 

CHAPITRE 6 : LA MISE EN œuvre DE L’OBSERVATION.

 

1. INTRODUCTION.

Cette étude a pour objectif de réaliser une boîte à outils logiciel destinée au déboguage post-mortem de toute application JAVA travaillant dans un environnement CORBA. Pour cela une technique d’analyse dynamique fondée sur l’observation est mise au point. Dans un premier temps, à l’aide de traces, elle réalise une récolte logicielle d’événements de type relation d’ordre. Dans un second, elle effectue un traitement sur les traces pour visualiser le comportement du programme observé sous la forme d’un graphe de dépendances causales.

Après avoir cerné la problématique de ce chapitre, des outils spécifiques sont présentés. Ils permettent la génération de traces, l’analyse lexicales de celles-ci et la visualisation d’arbres.

    1. LE BUT RECHERCHE.

 

Des logiciels spécifiques sont proposés au programmeur pour l’aider, lors de la phase de mise au point, à déverminer une application JAVA développée dans le cadre d’une approche objets répartis.

Cette technique d’observation, destinée à visualiser le comportement des objets et des programmes développés, est particulièrement utile au programmeur. Lorsque l’application s’est exécutée sans problème notable, elle lui permet de vérifier a posteriori que l’activité du logiciel a été conforme à son cahier des charges. Si l’application s’arrête accidentellement, arrêt " bavard " à la suite de la levée d’une exception facilement exploitable par le programmeur ou arrêt " muet ", très difficile à analyser par le développeur, à la suite par exemple d’un interblocage lié à un mauvais référencement d’objet, elle lui offre le moyen de pouvoir déterminer les conditions de ce blocage accidentel. Elle permet de répondre à la question suivante : à quel moment et dans quelles conditions cela s’est-il passé ?

Des outils logiciels spécifiques sont destinés à tracer et visualiser les relations d’ordre que nous venons de mettre en évidence dans le chapitre 5. Ils permettent de suivre le comportement des objets et des programmes à déverminer. Dans un premier temps, nous avons mis en évidence les interactions entre les objets ainsi que la création d’objets. Dans un second temps, l’accès à des variables critiques et la synchronisation entre fils d’exécution pourront être contrôlés.

Les événements liés aux objets qui conditionnent les changements d’états sont caractéristiques de relations d’ordre partiel. Les ordres étudiés peuvent être classés en deux catégories, les ordres programme et les ordres d’interaction. Les ordres programmes concernent l’exécution d’instructions séquentielles JAVA ou la création d’objets ou encore de fils d’activités. Les ordres d’interaction mettent en évidence les appels ou les invocations de méthodes ainsi que leurs exécutions distantes.

Le tableau 6.1 ci-joint récapitule les relations d’ordres et les événements tracés ainsi que les éléments enregistrés les caractérisant. Il est à remarquer que le type d’événement qui correspond à la destruction d’un objet n’est pas pris en compte dans cette étude. En effet, le procédé de ramasse miettes permet de désallouer automatiquement les espaces mémoires qui ne sont plus utilisés (voir chapitre 2 § 1.3). La suppression d’un objet JAVA de la mémoire n’est donc pas gérée par le programmeur mais par la machine virtuelle ce qui rend difficile la mise en évidence de ce phénomène au niveau d’un programme JAVA.

Ordre tracé

Type d’événement tracé

Ordre programme

Début d’exécution d’un programme JAVA

Ordre programme

Terminaison d’un programme JAVA

Ordre programme

Création d’un objet

Ordre d’interaction

Invocation d’une méthode

Ordre d’interaction

Retour d’une invocation de méthode

Ordre d’interaction

Début d’une méthode

Ordre d’interaction

Fin d’une méthode

Tableau 6.1 : Ordres locaux et ordres d’appel

 

1.2. LES MOYENS UTILISES.

Trois outils sont utilisés pour mener à bien cette expérimentation. La librairie Trace a été développée spécifiquement pour cette étude. Deux autres, JLex et Tree, ont été d’une part, téléchargées à partir de sites du réseau Internet et d’autre part, personnalisées pour le besoin de ce projet.

La librairie JAVA de nom est composée principalement d’une classe de nom Trace_impl.java qui gère la production de traces. Ce type d’objet enregistre, d’une façon séquentielle dans des fichiers au format ASCII qui sont les fichiers trace, les informations concernant les relations d’ordre des objets et programmes JAVA observés. Cette librairie comporte également les programmes qui analysent les traces précédentes, AnalyseTraces et AppletAnalyse.

La librairie JLex [Berk 97] contient un analyseur lexical, développé en JAVA, qui permet d’analyser les traces. Grâce à un moteur fourni en ligne (Main.java) et à des fichiers de spécifications lexicales, un analyseur lexical local, JlexOdr, puis un analyseur réparti, JlexOdr_impl, ont été mis au point dans le cadre de ce projet. Ceux-ci lisent les fichiers traces définis précédemment, y extraient les entités lexicales qui représentent les relations d’ordre et les sauvegardent temporairement dans une structure. Cette dernière est utilisée par les programmes d’analyse pour visualiser les relations d’ordre.

Quant à Tree [Koch 95], elle contient des objets JAVA qui permettent de visualiser des arbres. Cette librairie, utilisée par le programme d’analyse AppletAnalyse, permet de visualiser à l’écran les relations d’ordre sous la forme d’un arbre déroulant.

Ces outils permettent de créer l’architecture " objet observé/objet trace " décrite dans la figure 6.1. On associe par utilisation un objet de type trace à tout objet et à tout programme JAVA que l’on souhaite déboguer. Chaque objet trace dispose d’un fichier trace et d’un analyseur lexical.

Figure 6.1 : schématisation d’un objet tracé à l’aide d’un objet de type Trace

 

1.3. L’UTILISATION DES OUTILS.

L’utilisation des outils qui sont fondés sur l’architecture présentée dans le paragraphe précédent, se réalise en trois étapes.

Phase 1 : instrumentation du programme source. Mise en place, dans le programme source, d’instructions spécifiques nécessaires pour la génération de traces. Des sondes logicielles, gérées par les objets trace, enregistrent les événements suivants : début d’exécution d’un programme JAVA, terminaison d’un programme JAVA, création d’un objet, début d’une méthode, fin d’une méthode, invocation d’une méthode et retour de l’invocation d’une méthode.

Phase 2 : compilation et lancement du programme instrumenté pour l’écriture des fichiers trace. A tout objet ou programme JAVA observé est associé un fichier d’extension *.trc. Celui-ci recueille les informations de type relation d’ordre générées par les sondes logicielles installées lors de la phase précédente. Il est à remarquer, premièrement, que les fichiers trace sont distribués sur les différentes machines du réseau qui abritent les programmes et les objets JAVA observés : le fichier *.trc reste sur la machine où se trouve l’objet ou le programme instrumenté. Deuxièmement, il est à noter que le fichier trace peut contenir d’autres informations, comme par exemple des commentaires, que l’on peut, lors de la phase un, diriger dans le fichier trace, mais qui ne seront pas prises en compte lors de la phase d’analyse.

Phase 3 : analyse de la trace afin de visualiser l’activité des objets du programme instrumenté. L’analyseur lit les unes après les autres toutes les traces générées lors de la phase précédente. Cela se passe par le balayage de tous les objets trace distribués. Grâce à l’analyseur lexical JLexOdr, il détecte dans les traces les relations d’ordre, les sauvegarde temporairement dans une structure de données et les sélectionne sur le plan temporel. Ensuite, il les sauvegarde dans un fichier texte, programme AnalyseTraces et fichier Analyse.trc, ou il les affiche sous la forme d’un arbre, programme AppletAnalyse.

REMARQUES IMPORTANTES SUR L’UTILISATION DES OUTILS.

1. Il est important de noter que le programme d’analyse doit être lancé à la fin de l’exécution du programme à analyser alors que celui-ci dispose de tous ces objets encore présents en mémoire. En effet, l’architecture " objet observé /objet trace " doit être présente en mémoire pour que les programmes d’analyse puissent utiliser les analyseurs lexicaux et les traces associés aux objets trace. Elle est distribuée et l’analyse d’un programme observé se promène d’objet trace en objet trace.

2. Compte tenu de la structure de l’horloge vectorielle utilisée dans le projet, le nombre maximum de programmes et d’objets observés est de 50.

Les paragraphes qui suivent détaillent, dans un premier temps, la construction des traces et décrivent, dans un second temps, le fonctionnement des analyseurs. Enfin, les résultats de l’analyse sont présentés.

 

2. LA CONSTRUCTION DES TRACES.

La construction des traces passe d’une part, par la mise en évidence des relations d’ordres, d’autre part par l’estampillage temporel des événements et enfin, par l’utilisation d’objets spécialisés, les objets trace.

2.1. LA CONSTRUCTION DE L’ORDRE.

La mise en évidence des relations d’ordre nécessite l’installation, dans le programme à observer, de sondes logicielles qui sont enregistrées sous la forme de fonctions d’ordre. Une fonction d’ordre caractérise une relation d’ordre. Son nom symbolise l’ordre d’interaction ou l’ordre programme enregistré. Ses arguments consignent les caractéristiques de l’événement. Les fonctions d’ordre utilisées dans ce projet sont spécifiées dans le tableau 6.2.

Type d’événement tracé

Nom de la fonction d’ordre

Arguments

Début d’exécution d’un programme JAVA

DebutE

Nom du programme en cours d'exécution

Nom du programme appelant

Terminaison d’un programme JAVA

FinE

Nom du programme en fin d'exécution

Nom du programme appelant

Création d’un objet

Creat_obj

Nom de l'objet tracé

Invocation d’une méthode

Iappel

Nom de la méthode invoquée

Nom de la méthode appelante ou du programme appelant

Référence de l'objet appelant

Retour d’une invocation de méthode

Retour_Iappel

Nom de la méthode invoquée

Nom de la méthode appelante ou du programme appelant

Référence de l'objet appelant

Début d’une méthode

DebutM

Nom de la méthode qui débute 

Nom de la méthode appelante ou du programme appelant

Référence de l'objet appelant 

Fin d’une méthode

FinM

Nom de la méthode qui se termine 

Nom de la méthode appelante ou du programme appelant

Référence de l'objet appelant 

 

Tableau 6.2 : fonctions d’ordre utilisées

 

En adoptant cette représentation, l’exécution de deux programmes JAVA P1 et P2 que l’on souhaite observer, est représentée, dans la figure 6.2, par deux fils d’exécution. Dans un premier temps, les programmes P1 et P2 débutent (1). Dans un second temps P2, crée un objet O2. Puis, l’objet O1 du programme P1 invoque la méthode distante M de O2 (6). Dans un troisième temps, la méthode M de l’objet O2 débute (4), s’exécute et se termine (5). Ensuite, le programme appelant reprend son exécution à l’issu du retour de l’invocation de la méthode distante (7). Enfin, les programmes P1 et P2 finissent leurs exécutions (2).

 

Figure 6.2 : mise en évidence des ordres d’appel à l’aide des fonctions d’ordre

 

 

2.2. L’ESTAMPILLAGE TEMPORELLE.

2.2.1. L’horloge logique implémentée.

Une horloge logique de Fidge/Mattern est le moyen d’estampiller d’une manière temporelle un événement (voir chapitre 4, § 2.2.4). Cette estampille temporelle est implantée sous la forme d’un vecteur de dimension 50 (nombre maximum de programmes et d’objets observés). Elle consigne la date d’occurrence d’un événement de type relation d’ordre. A chaque objet ou programme observé est associé d’une part, un numéro unique et, d’autre part, une composante de l’horloge logique dont l’indice est égal à ce numéro. De part ce mécanisme et du fait de la dimension de l’horloge vectorielle, le nombre maximum de programmes et d’objets observés ne doit pas dépasser 50.

Chaque composante de l’horloge vectorielle est implantée comme un compteur. L’initialisation à zéro de l’horloge vectorielle se réalise lors de la création de l’objet trace.

Soient deux programmes Pi et Pj qui instancient respectivement deux objets Oi et Oj. L’objet Oi invoque une méthode distante de Oj. En limitant pour simplifier l’exemple à des horloges vectorielles de dimension deux, le mécanisme de datation, réalisé dans les programmes d’analyse, fonctionne de la manière suivante (voir figure 6.3).

Figure 6.3 : principe de la gestion de l’horloge vectorielle.

Trois cas de figures sont à envisager : invocation de méthodes distantes, retour d’invocation de méthodes distantes et autres possibilités.

 

2.2.2. Invocation de méthodes distantes.

Soit deux objets Oi et Oj, avec Oi qui invoque une méthode de Oj.

Un exemple d’invocation est présenté dans la figure 6.3.

 

2.2.3. Retour d’invocation de méthodes distantes.

Soit deux objets Oi et Oj, avec Oi qui termine l’invocation d’une méthode de Oj.

Un exemple de retour d’invocation est présenté dans la figure 6.3.

 

2.2.4. Autres cas.

Lors d’un événement de type ordre programme (DebutE, FinE, Creat_obj) ou de type ordre d’interaction différent de Iappel ou FinM, Oi incrémente la ième composante de son horloge vectorielle.

 

2.3. DEFINITION DES TRACES.

2.3.1. INTRODUCTION.

La gestion des traces d’un objet ou programme instrumenté est réalisée par son objet trace qui dispose de données et de méthodes spécifiques et d’un fichier de sortie standard, le fichier trace.

Le fichier trace d’un objet observé sauvegarde les événements de type ordre programme et ordre d’interaction. Les relations d’ordre partiel sont stockées dans la trace sous la forme de chaînes de caractères ASCII qui obéissent à une syntaxe spécifique de type fonction d’ordre.

Ces règles lexicales constituent le langage de trace. Les unités lexicales représentent des fonctions d’ordre. Elles apparaissent dans le fichier trace sous la forme suivante : leurs noms suivis, entre parenthèses, d’arguments qui les caractérisent. Les méthodes fonction d’ordre des objets trace, qui génèrent les fonctions d’ordre, sont décrites dans le paragraphe 2.3.2.4.

 

2.3.2. LA GESTION DES TRACES.

La gestion des objets trace se réalise à l’aide de la classe JAVA Trace_impl.java qui a été spécialement développée à cet effet.

L’objet observé est représenté dans la figure 6.1. On associe, par utilisation, un objet de type trace à tout objet ou programme JAVA que l’on souhaite tracer.

Ceci se réalise en déclarant, dans les données de l’objet ou dans le corps du programme à observer, un objet trace qui appartient à la classe Trace_impl.java. Celui-ci est constitué de données et de méthodes qui caractérisent respectivement l’objet instrumenté et son comportement.

Ces méthodes sont installées dans le code source lors de la première phase, phase d’instrumentation. Elles sont exécutées lors de la deuxième phase, phase d’exécution, pour qu’elles génèrent des traces sous la forme de fonctions d’ordre.

 

 

2.3.2.1. LE FICHIER IDL D’UN OBJET TRACE.

Le fichier IDL complet d’un objet de type Trace est donné en ANNEXE F. L’interface Trace présentée ci-dessous ne comporte que les méthodes de type fonction d’ordre qui permettent de générer les traces.

interface Trace {

//---

// TRACE.println : écriture d’une chaîne de caractères

//---

void println(in string Texte);

//--- // TRACE.GetRef_obj : recuperation de la reference d'un l'objet TRACE cree //---void GetRef_obj(in string Nom_objet);

//---

// METHODES DE TYPE FONCTION D’ORDRE

//---//---

// TRACE. DebutE : notification du commencement d’une programme JAVA

//---

void DebutE(in string Nom_programme,in string Nom_programme_appelant);//---

// TRACE.FinE : notification de la fin d’une programme JAVA

//---

void FinE(in string Nom_programme,in string Nom_programme_appelant);

//---

// TRACE. Creat_obj : notification de la création d’un objet

//---

void Creat_obj(in string Nom_objet);//---

// TRACE.DebutM : début d’une méthode

//---

void DebutM(in string Nom_methode,

in string Nom_methode_appelante,

in string Ref_objet_appelant);

//---

// TRACE.FinM : fin d’une méthode

//---void FinM(in string Nom_methode,

in string Nom_methode_appelante,

in string Ref_objet_appelant);

//---

// TRACE.Iappel : invocation d’une méthode //---

void Iappel(in string Nom_methode,

in string Nom_methode_appelante,

in string Ref_objet_invoque,

in Trace Objet_invoque);

//---

//TRACE.Retour_Iappel : retour d’une invocation d’une méthode

//---

void Retour_Iappel(in string Nom_methode,

in string Nom_methode_appelante,in string Ref_objet_invoque,

in Trace Objet_invoque);

};

 

2.3.2.2. LES DONNEES D’UN OBJET TRACE.

Les données associées à un objet trace sont de trois ordres. 

Premièrement, il existe des données qui caractérisent d’une manière statique l’objet tracé : son nom (variable NomObj qui contient le nom de la variable le référençant), sa référence par rapport à l’ORB (variable Reference), son numéro d’objet (variable Numero qui est géré manuellement au moment de l’intanciation de l’objet trace), l’adresse de la machine où il est chargé en mémoire (variable NomMachine) et le nom de son créateur (variable NomCreateur qui est une chaîne de caractères définie au moment de l’instanciation de l’objet trace).

Deuxièmement, l’objet trace dispose de données qui définissent d’une manière dynamique l’objet tracé. En effet, tout objet trace dispose d’un fichier d’extension *.trc (NomFichier) qui enregistre au fil de l’eau les événements de type ordre programme ou ordre d’interaction sous la forme de fonctions d’ordre. Celles-ci sont estampillées à l’aide de l’horloge logique (V_horloge) et sauvegardées dans le fichier trace. Pour une trace donnée, cette technique permet de visualiser, d’une manière séquentielle, le comportement d’un l’objet observé.

Enfin, l’objet trace utilise un analyseur lexical. JlexOdr, objet réparti, dispose de sa méthode d’accès appelée jlexOdr qui peut être invoquée de tout programme client à l’aide de l’interface jlex.idl de l’ORB. Lors de la phase d’analyse, il extrait des traces les entités lexicales de type fonctions d’ordre.

Les données contenues dans un objet de type trace sont répertoriées dans le tableau 6.3 ci-dessous.

 

Type de données

Nom de la variable

Type

Signification de la variable

 

NomObj

String

Nom de l'objet tracé

 

Reference

String

Référence IOR de l'objet TRACE vis à vis de l'ORB

Statique

Numero

int

Numérotation de l’objet trace

 

NomMachine

String

Variable contenant le nom de la machine où s'exécute l'objet et le numéro de port de celle-ci.

 

NomCreateur

String

Nom du créateur de l’objet

Dynamique

NomFichier

String

Nom du fichier de trace

 

V_horloge

Vecteur

Horloge vectorielle de dimension 50

 

Analyseur

AnalyseurOdr

JLexOdr

Analyseur lexical pour analyser les fonctions d'ordre

Tableau 6.3 : les variables d’un objet de type trace

Remarque sur la référence objet de type IOR.

Une référence objet peut être convertie en une chaîne de caractères de type IOR et sauvegardée sous cette forme dans un fichier. La chaîne de caractères ainsi conservée peut être de nouveau convertie en référence objet par l’ORB qui l’a produite.

Pour cela, CORBA définit deux fonctions, object-to-string et string-to-object, pour la manipulation des références objet. Elles peuvent être mises en œuvre de la manière suivante. Les programmes serveurs utilisent la première fonction pour sauvegarder, dans des fichiers, les références de ses objets sous la forme de chaînes de caractères. Les programmes client, quant à eux, invoquent la deuxième fonction pour convertir les chaînes de caractères en références objet.

La fonction object-to-string est utilisée dans cette étude pour sauvegarder dans la trace la référence de l’objet trace associé.

2.3.2.3. LES CONSTRUCTEURS D’UN OBJET TRACE.

Les constructeurs contenus dans la classe Trace_impl.java permettent d’instancier et d’initialiser les objets de type trace. Les constructeurs se complètent de proche en proche ce qui permet, par exemple, de définir le constructeur de rang n à l’aide du constructeur de rang n-1. Ainsi, au niveau de la programmation des constructions, le constructeur de rang n peut appeler d’une façon élégante le constructeur de rang n-1 ce qui évite, au rang n, de réécrire le code des constructeurs de rangs inférieurs.

2.3.2.3.1. Constructeur d’ordre zéro.

public Trace_impl() Le constructeur d’ordre zéro ne prend aucun argument et initialise les données de l’objet trace.

2.3.2.3.2. Constructeur d’ordre un.

public Trace_impl(int Numero)

Avec la variable Numero, le programmeur attribue manuellement un numéro unique à l’objet trace. Cette variable Numero est utilisée par l’horloge vectorielle (voir § 2.2).

Le constructeur d’ordre un affecte un numéro d’identification qui doit être unique à l’objet observé et à l’objet trace associé.

2.3.2.3.3. Constructeur d’ordre deux.

public Trace_impl(String NomFich,int Numero)

Avec NomFich, le nom du fichier trace associé à l’objet trace.

Le constructeur d’ordre deux associe à l’objet trace un fichier de sortie standard, le fichier trace, où il génère ses traces.

2.3.2.3.4. Constructeur d’ordre trois.

public Trace_impl(String NomFich, String Nom_createur,int Numero)

Avec Nom_Createur, le nom du créateur de l’objet observé.

Le constructeur d’ordre trois sauvegarde dans l’objet trace, le nom du créateur de l’objet observé.

2.3.2.3.5. Exemple.

Considérons le programme client.java qui est donné en ANNEXE A. Ce programme est tracé à l’aide d’un objet de type trace qui se nomme TClient. Cet objet est généré de la façon suivante.

Trace_impl TClient = new Trace_impl("client.trc","client.java",0);

Grâce à cette instanciation, l’objet TClient contient les données suivantes : le numéro de l’objet trace est 0, le nom du fichier trace qui lui est associé est client.trc et le nom de son créateur est le programme client.java.

 

2.3.2.4. LES METHODES DE TYPE FONCTION D’ORDRE D’UN OBJET TRACE.

Les méthodes de type fonction d’ordre d’un objet trace sont définies dans l’interface Trace du fichier IDL (voir § 2.3.2.1). Elles sont répertoriées dans les tableaux 6.4 et 6.5 par leurs noms et leurs arguments et elles y sont décrites. A chaque objet observé est associé un objet trace qui est intancié à l’aide du constructeur d’ordre trois décrit dans les paragraphes précédents. Les méthodes de l’objet trace, présentées dans les tableaux ci-dessous, peuvent être alors invoquées, depuis le code de l’objet observé, pour tracer les événements décrits dans la dernière colonne. L’invocation d’une méthode de type fonction d’ordre conduit à l’écriture dans le fichier trace de la fonction d’ordre associée. La syntaxe des fonctions d’ordre est décrite dans le paragraphe suivant.

No

Type d’événements tracé

Nom de la méthode de trace

Arguments

Description

1

Début d’exécution d’un programme JAVA

DebutE

(String Nom_programme,

String Nom_programme_appelant)

Notification du commencement d’un programme JAVA. Elle prend en paramètres : Nom_programme qui est le nom du programme en cours d'exécution; Nom_programme_appelant qui est le nom du programme appelant .

2

Terminaison d’un programme JAVA

FinE

(String Nom_programme,

String Nom_programme_appelant)

Notification de la fin d’un programme JAVA. Elle prend en paramètres : Nom_programme qui est le nom du programme en fin d'exécution; Nom_programme_appelant qui est le nom du programme appelant.

3

Création d’un objet

Creat_Obj

(String Nom_objet)

Notification de la création d’un objet. Elle prend en paramètres : Nom_objet qui est le nom de l'objet tracé.

 

Tableau 6.4. Les différentes méthodes pour tracer les ordres programme

 

No

Type d’événements tracé

Nom de la méthode de trace

Arguments

Description

4

Début d’une méthode

DebutM

(String Nom_methode,

String Nom_methode_appelante,

String Ref_objet_appelant)

Méthode appelée en début d’exécution de méthode, qui permet d’en signaler le commencement. Elle prend en paramètres : Nom_methode qui est nom de la méthode qui débute ; Nom_methode_appelante qui est le nom de la méthode appelante ou du programme appelant; Ref_objet_appelant qui est la référence IOR de l'objet appelant.

5

Fin d’une méthode

FinM

(String Nom_methode,

String Nom_methode_appelante,

String Ref_objet_appelant)

Elle signale la fin d’exécution d’une méthode. Elle prend en paramètres : Nom_methode qui est le nom de la méthode qui termine ; Nom_methode_appelante qui est le nom de la méthode appelante ou du programme appelant; Ref_objet_appelant qui est la référence IOR de l’objet appelant.

6

Invocation d’une méthode

Iappel

(String Nom_methode,

String Nom_methode_appelante,

String Ref_objet_invoque,

Trace Objet_appelant)

Notification d’un appel de méthode distante. Elle prend en paramètres : Nom_methode qui est le nom de la méthode invoquée; Nom_methode_appelante qui est le nom de la méthode appelante ou du programme appelant; Ref_objet_invoque qui est la référence IOR de l'objet invoqué ; Objet_appelant qui est la référence de l'objet appelant.

7

Retour d’une invocation d’une méthode

Retour_Iappel

(String Nom_methode,

String Nom_methode_appelante,

String Ref_objet_invoque,

Trace Objet_appelant)

Notification d’un retour d’appel de méthode distante. Elle prend en paramètres : Nom_methode qui est nom de la méthode invoquée; Nom_methode_appelante qui est le nom de la méthode appelante ou du programme appelant; Ref_objet_invoque qui est la référence IOR de l'objet invoqué ; Objet_appelant qui est la référence de l'objet appelant.

 

Tableau 6.5. Les différentes méthodes pour tracer les ordres d’interaction

2.3.3. LE CONTENU DES FICHIERS TRACE.

Les méthodes de type fonction d’ordre, décrites ci-dessus, tracent les fonctions d’ordre qui sont présentées dans les paragraphes suivants.

2.3.3.1. Les macro-expressions des fichiers trace.

Les expressions générées dans les traces sont les fonctions d’ordre. Les méthodes d’un objet de type trace (voir 2.1.2) génèrent les fonctions d’ordre décrites dans les paragraphes ci-dessous. Afin de les rendre plus lisibles, les fonctions d’ordre sont précédées d’informations en langage naturel qui décrivent l’événement tracé (voir ANNEXE G pour un exemple de fichier trace).

Pour décrire les arguments des fonctions d’ordre, des macro-expressions spécifiques sont utilisées dans le langage de trace. Elles sont définies dans le tableau 6.6 ci-dessous.

Macro expression

Expression régulière

Signification

ALPHA

[A-Za-z]

Caractère de type alpha

DIGIT

[0-9]

Caractère de type numérique

NOMBRE

({DIGIT}({DIGIT})*)

Nombre composé de digit

NOM_FICHIER

({ALPHA}({ALPHA}|{DIGIT}|_|".")*)

Nom de fichier

ALPHADIGIT

({ALPHA}({ALPHA}|{DIGIT}|_)*|{DIGIT}({ALPHA}|{DIGIT}|_)*)

Chaîne de caractères alphanumérique

NF_OU_AD

({ALPHADIGIT}|{NOM_FICHIER})

Nom de fichier ou chaîne de caractères alphanumérique

IOR

(IOR:{ALPHADIGIT})

Référence objet de type IOR

VECTEUR

("["( {NOMBRE} ",")*{NOMBRE}"]")

Vecteur temporel dont les composants sont des nombres

 

Tableau 6.6 : définition des macro-expressions

 

Remarques sur les expressions régulières.

(: les parenthèses sont des caractères spéciaux qui sont utilisés pour réaliser des regroupements dans les expressions régulières.

A – B : suite de caractères.

 : le caractère étoile est un caractère spécial qui représente zéro ou plusieurs répétitions de l’expression régulière qui précède ce symbole.

2.3.3.2. La fonction d’ordre " DebutE ".

La fonction d’ordre DebutE trace le début d’exécution d’un programme JAVA.

Elle est composée de quatre arguments :

- argument 1 : V_horloge ; horloge vectorielle ;

- argument 2 : Nom_programme ; nom du programme en cours d'exécution ;

- argument 3 : Nom_programme_appelant ; nom du programme appelant ;

- argument 4 : Ref_objet_trace ; référence IOR de l'objet trace.

Elle apparaît dans le fichier trace avec la syntaxe suivante.

DebutE({VECTEUR} V_horloge,

{NOM_FICHIER} Nom_programme,

{NOM_FICHIER} Nom_programme_appelant,

{IOR} Ref_objet_trace )

2.3.3.3. La fonction d’ordre " FinE ".

La fonction d’ordre FinE trace la fin d’exécution d’un programme JAVA.

Elle est composée de quatre arguments :

- argument 1 : V_horloge ; horloge vectorielle ;

- argument 2 : Nom_programme ; le nom du programme en fin d'exécution ;

- argument 3 : Nom_programme_appelant ; nom du programme appelant ;

- argument 4 : Ref_objet_trace ; référence IOR de l'objet trace.

Elle apparaît dans le fichier trace avec la syntaxe suivante.

FinE( {VECTEUR} V_horloge,

{NOM_FICHIER} Nom_programme,

{NOM_FICHIER} Nom_programme_appelant,

{IOR} Ref_objet_trace)

2.3.3.4. La fonction d’ordre " Creat_obj " .

La fonction d’ordre Creat_obj trace la création d’un objet JAVA.

Elle est composée de trois arguments :

- argument 1 : V_horloge ; horloge vectorielle ;

- argument 2 : Nom_objet ; le nom de l'objet trace ;

- argument 3 : Ref_objet_trace ; référence IOR de l'objet trace.

Elle apparaît dans le fichier trace avec la syntaxe suivante.

Creat_obj({VECTEUR} V_horloge, {ALPHADIGIT} Nom_objet, {IOR} Ref_objet_trace )

 

2.3.3.5. La fonction " GetRef_obj ".

La fonction GetRef_obj permet de récupérer dans le fichier trace le nom et la référence de l’objet trace associé. Elle n’est pas une fonction d’ordre mais est utilisée par l’analyseur, lors de la phase d’analyse, pour vérifier d’une part, que le fichier analysé est bien un fichier de type trace et d’autre part, pour récupérer la référence IOR de l’objet trace associé au programme analysé.

Elle est composée de trois arguments :

- argument 1 : V_horloge ; horloge vectorielle ;

- argument 2 : Nom_objet ; le nom de l'objet ou du programme trace ;

- argument 3 : Ref_objet_trace ; référence IOR de l'objet trace.

Elle apparaît dans le fichier trace avec la syntaxe suivante.

GetRef_obj({VECTEUR} V_horloge, {NF_OU_AD} Nom_objet, {IOR} Ref_objet_trace )

2.3.3.6. La fonction d’ordre " DebutM ".

La fonction d’ordre DebutM trace le début d’exécution d’une méthode JAVA.

Elle est composée de cinq arguments :

- argument 1 : V_horloge ; horloge vectorielle ;

- argument 2 : Nom_methode ; nom de la méthode qui débute ;

- argument 3 : Nom_methode_appelante ; nom de la méthode appelante ou du programme appelant ;

- argument 4 : Ref_objet_appelant ; référence IOR de l'objet appelant ;

- argument 5 : Ref_objet_trace ; référence IOR de l'objet trace.

Remarque.

Lorsque l’appelant de la méthode observée est une autre méthode, l’argument 3 contient le nom de cette dernière. Par contre, si l’appelant est un programme JAVA, l’argument 3 représente le nom de celui-ci.

Elle apparaît dans le fichier trace avec la syntaxe suivante.

DebutM( {VECTEUR} V_horloge,

{ALPHADIGIT} Nom_methode,

{NF_OU_AD} Nom_methode_appelante,

{IOR} Ref_objet_appelant,

{IOR} Ref_objet_trace)

2.3.3.7. La fonction d’ordre " FinM ".

La fonction d’ordre FinM trace la fin d’exécution d’une méthode JAVA.

Elle est composée de cinq arguments :

- argument 1 : V_horloge ; horloge vectorielle ;

- argument 2 : Nom_methode ; nom de la méthode qui se termine ;

- argument 3 : Nom_methode_appelante ; nom de la méthode appelante ou du programme appelant ;

- argument 4 : Ref_objet_appelant ; référence IOR de l'objet appelant ;

- argument 5 : Ref_objet_trace ; référence IOR de l'objet trace.

Elle apparaît dans le fichier trace avec la syntaxe suivante.

FinM({VECTEUR} V_horloge,

{ALPHADIGIT} Nom_methode,

{NF_OU_AD} Nom_methode_appelante,

{IOR} Ref_objet_appelant,

{IOR} Ref_objet_trace)

2.3.3.8. La fonction d’ordre " Iappel".

La fonction d’ordre Iappel trace l’invocation d’une méthode distante.

Elle est composée de cinq arguments :

- argument 1 : V_horloge ; horloge vectorielle ;

- argument 2 : Nom_methode ; nom de la méthode invoquée ;

- argument 3 : Nom_methode_appelante ; nom de la méthode appelante ou du programme appelant ;

- argument 4 : Ref_objet_invoque ; référence IOR de l'objet invoqué ;

- argument 5 : Ref_objet_trace ; référence de IOR l'objet trace.

Elle apparaît dans le fichier trace avec la syntaxe suivante.

Iappel({VECTEUR} V_horloge,

{ALPHADIGIT} Nom_methode,

{NF_OU_AD} Nom_methode_appelante,

{IOR} Ref_objet_invoque,

{IOR} Ref_objet_trace)

2.3.3.9. La fonction d’ordre " Retour_Iappel ".

La fonction d’ordre Retour_Iappel trace le retour d’invocation d’une méthode distante.

Elle est composée de cinq arguments :

- argument 1 : V_horloge ; horloge vectorielle ;

- argument 2 : Nom_methode ; nom de la méthode invoquée ;

- argument 3 : Nom_methode_appelante ; nom de la méthode appelante ou du programme appelant ;

- argument 4 : Ref_objet_invoque ; référence IOR de l'objet invoqué ;

- argument 5 : Ref_objet_trace ; référence IOR de l'objet trace.

Elle apparaît dans le fichier trace avec la syntaxe suivante.

Retour_Iappel({VECTEUR} V_horloge,

{ALPHADIGIT} Nom_methode,

{NF_OU_AD} Nom_methode_appelante,

{IOR} Ref_objet_invoque, {IOR} Ref_objet_trace)

 

2.3.4. EXEMPLE DE FICHIER TRACE.

Reprenons l’exemple de la figure 6.3. Pour simplifier, prenons les deux hypothèses suivantes. Le programme P1 et l’objet O1 partagent le même objet trace de référence supposée égale à IOR:00ab15. De plus, il en est de même pour P2 et 02 qui disposent d’un objet trace de référence IOR:00bc16. Dans ces conditions, nous obtenons les deux fichiers trace représentés dans les figures 6.4 et 6.5 suivantes.

Figure 6.5 : fichier trace de P2 et O2

La technique de production de traces a été décrite dans ce paragraphe. Elle permet d’enregistrer des événements liés à la relation d’ordre définie dans le chapitre 5. Abordons maintenant la description d’autres outils qui sont nécessaires pour exploiter les fichiers trace.

 

3. ANALYSE DES TRACES ET OBSERVATION.

La phase d’analyse permet d’exploiter les traces décrites dans les paragraphes précédentes. Les deux moteurs d’analyse développés dans le cadre de ce mémoire ont besoin d’outils spécifiques qui sont décrits dans ce paragraphe.

Via le réseau Internet, deux outils, en accès libre pour une utilisation non commerciale, ont été téléchargés. Il s’agit de JLex [Berk 97] qui est un analyseur lexical et d’un visualiseur d’arbres [Koch 95]. Ces deux produits écrits en JAVA sont fournis sous la forme de librairies. Pour analyser les fichiers trace produits lors de la phase précédente, plusieurs compilateurs de type JLex doivent être générés. De plus, une classe du visualiseur d’arbres est modifiée pour qu’il puisse afficher à l’écran des événements de type relation d’ordre.

3.1. L’ANALYSEUR LEXICAL JLEX.

 

3.1.1. Introduction à JLex.

3.1.1.1.Présentation de JLex.

JLex est un produit universitaire développé par Elliot Joel Berk au département informatique de l’université de Princeton, Etats-Unis.

Cet analyseur lexical est utilisé comme un système de recherche d’informations. Il permet de spécifier et de concevoir des programmes qui exécutent des actions déclenchées par des modèles contenus dans des chaînes de caractères. Il se rapproche du langage de programmation dirigée par le modèle appelé Lex. Dans ce langage, les modèles sont spécifiés par des expressions régulières. Un compilateur pour Lex peut engendrer un reconnaisseur efficace, par automates finis, d’expressions régulières.

3.1.1.2. Le moteur du compilateur.

Le compilateur de l’analyseur lexical JLex se nomme Main.java.

Ce dernier programme, une fois compilé, permet de générer des analyseurs lexicaux, sous la forme d’un fichier source, à partir des fichiers de spécification de type *.lex.

3.1.1.3. Les fichiers de spécification *.lex.

La spécification d’un analyseur lexical est définie dans un fichier d’extension *.lex. Celui-ci est composé de trois parties séparées par les symboles %%. La première partie est constituée du code utilisateur et de variables spécifiques. La seconde définit les macro-expressions et les expressions régulières de l’analyseur. Enfin, la dernière précise les actions associées à la reconnaissance d’une unité lexicale.

3.1.1.4. Exemple d’un fichier de spécification.

L’exemple présenté ci-dessous est le fichier de spécification de l’analyseur JlexTps qui permet d’extraire les nombres d’une chaîne de caractères.

Le code ci-joint permet de compléter une librairie qui a pour nom JLex. La partie 1 contient le code utilisateur et crée une classe publique de nom JlexTps dont la fonction d’accès à cet analyseur est jlexTps. La partie 2 contient la définition des macro-expressions et de l’expression régulière, qui représente un nombre entier, reconnues par l’analyseur JlexTps. La partie 3 décrit les actions associées à la reconnaissance d’une unité lexicale. Dans cet exemple, seule la reconnaissance d’une unité lexicale de type NOMBRE déclenche l’instanciation d’un objet de type StringToken qui contient le nombre reconnu sous la forme d’une chaîne de caractères.

 

package JLex;

import JAVA.lang.System;

%%

%{

//---

// PARTIE 1 : CODE UTILISATEUR

//---

%}

//---

// Déclaration d’un état lexical

//---

%state COMMENT

//---

// L’analyseur lexical se nomme JlexTps

//---

%class JlexTps

//---

// La fonction d’accès à l’analyseur est jlexTps

//---

%function jlexTps

//---

// La classe JlexTps est publique

//---

%public

 

//---

// PARTIE 2 : DEFINITION DES macro-expressionS

// et DES expressions régulières

//---

//---

// macro-expressionS

//---

ALPHA=[A-Za-z]

DIGIT=[0-9]

NONNEWLINE_WHITE_SPACE_CHAR=[\ \t\b\012]

WHITE_SPACE_CHAR=[\n\ \t\b\012]

//---

// expressions régulières

//---

NOMBRE=({DIGIT}({DIGIT})*)

%%

//---

// PARTIE 3 : ACTIONS ASSOCIEES

// A LA RECONNAISSANCE D’UNE UNITE LEXICALE

//---

<YYINITIAL> "'" { }

<YYINITIAL> "," { }

<YYINITIAL> ":" { }

<YYINITIAL> ";" { }

<YYINITIAL> "(" { }

<YYINITIAL> ")" { }

<YYINITIAL> "[" { }

<YYINITIAL> "]" { }

<YYINITIAL> "{" { }

<YYINITIAL> "}" { }

<YYINITIAL> "." { }

<YYINITIAL> "+" { }

<YYINITIAL> "-" { }

<YYINITIAL> "*" { }

<YYINITIAL> "/" { }

<YYINITIAL> "=" { }

<YYINITIAL> "<>" { }

<YYINITIAL> "<" { }

<YYINITIAL> "<=" { }

<YYINITIAL> ">" { }

<YYINITIAL> ">=" { }

<YYINITIAL> "&" { }

<YYINITIAL> "|" { }

<YYINITIAL> ":=" { }

<YYINITIAL> {NONNEWLINE_WHITE_SPACE_CHAR}+ { }

<YYINITIAL,COMMENT> \n { }

<YYINITIAL> "/*" { }

<COMMENT> "/*" { }

<COMMENT> "*/" { }

<COMMENT> "//" { }

<YYINITIAL> {NOMBRE}+ {

return (new StringToken(yytext()));}

<YYINITIAL,COMMENT> . {

System.out.println("Caractere illegal : <" + yytext() + ">");

Utility.error(Utility.E_UNMATCHED);

}

Remarques sur les expressions régulières.

() : les parenthèses sont des caractères spéciaux qui sont utilisés pour réaliser des regroupements dans les expressions régulières.

A – B : suite de caractères.

: le caractère étoile est un caractère spécial qui représente zéro ou plusieurs répétitions de l’expression régulière qui précède ce symbole.

\b : caractère Backspace.

\n : caractère nouvelle ligne.

\t : caractère de tabulation.

 

3.1.2. Utilisation de JLex.

Au moment de l’utilisation de JLex, les fichiers trace associés aux objets observés ont été produits. Ils sont répartis et contiennent des fonctions d’ordre qui ont été générées par l’invocation des méthodes des objets trace (voir § 2.3.3). De même, les analyseurs lexicaux sont distribués dans le réseau. Tout programme ou objet JAVA observé dispose d’un objet JlexOdr qui analyse son fichier trace qui se trouve sur la même machine qui implante l’entité instrumentée (voir figure 6.1).

L’analyseur AnalyseTraces ou AppletAnalyse utilise cette architecture pour d’une part, analyser toutes les traces produites lors de l’exécution du programme observé et d’autre part, visualiser les résultats. Il prend en argument le fichier trace du programme à analyser, invoque l’objet trace et l’analyseur lexical associés. Ce dernier détecte et sauvegarde temporairement, dans un objet de type OrdreToken, la première entité lexicale de type fonction d’ordre rencontrée. L’analyseur la visualise et passe à la fonction d’ordre suivante. Cette dernière peut se trouver soit dans la même trace, soit dans une trace différente. En effet, si la fonction d’ordre analysée est de type invocation d’une méthode distante (Iappel) ou fin d’une méthode distante (FinM), l’analyseur change d’objet trace, donc d’analyseur lexical et de fichier trace. Ainsi, de proche en proche, l’analyseur parcourt toutes les traces générées par le programme observé.

Ainsi, dans cette étude, Jlex permet de rechercher dans les traces (fichiers *.trc) les fonctions d’ordre et de les sauvegarder temporairement dans un objet de type OrdreToken. Pour cela, des analyseurs lexicaux spécifiques sont utilisés. La détection d’unités lexicales de type fonction d’ordre obéit à des règles lexicales contenues dans les macro-expressions et dans les expressions régulières qui sont définies dans le fichier JLexOdr.lex.

3.1.2.1. La structure de sauvegarde des tokens.

L’analyse d’un fichier trace se réalise en deux temps.

Dans un premier temps, la reconnaissance, grâce à l’analyseur lexical JlexOdr, d’une unité lexicale de type fonction d’ordre déclenche sa sauvegarde dans un objet de type OrdreToken décrit dans le tableau 6.7. A l’issue de cette opération tous les champs de cette structure sont complétés par la valeur, le nom, les arguments de la fonction d’ordre et le contenu de son horloge vectorielle.

Dans un second temps, les valeurs des fonctions d’ordre sont soit écrites dans un fichier de sortie, analyseur AnalyseTraces, soit visualisées à l’écran sous la forme d’un arbre d’appel, analyseur AppletAnalyse.

Ce procédé en deux temps se termine lorsque le programme d’analyse a parcouru toutes les traces générées par l’application observée.

Champs

Type

Signification

Valeur

String

Valeur totale de la fonction d’ordre

Nom_ordre

String

Nom de la fonction d’ordre

Arg1

String

Argument 1 de la fonction d’ordre.

Arg2

String

Argument 2 de la fonction d’ordre.

Arg3

String

Argument 3 de la fonction d’ordre.

Arg4

String

Argument 4 de la fonction d’ordre.

Arg5

String

Argument 5 de la fonction d’ordre.

V_horloge

Int[50]

Horloge vectorielle.

Tableau 6.7 : sauvegarde d’une fonction d’ordre dans un objet de type OrdreToken

3.1.2.2. Description des différents analyseurs lexicaux utilisés.

Le programme d’analyse dispose, associé à chaque fichier de trace, d’un analyseur lexical JlexOdr, qui en extrait les entités lexicales de type fonction d’ordre. Celui-ci sauvegarde dans le champ Valeur de l’objet OrdreToken l’entité lexicale détectée.

JlexOdr utilise alors deux autres analyseurs lexicaux, JlexStr et JlexTps, pour compléter les champs de la structure OrdreToken. Le premier, JlexStr, travaille sur le contenu du champ Valeur et en extrait les différentes composantes de la fonction d’ordre :  nom de la fonction d’ordre et arguments de la fonction d’ordre. Le deuxième, JlexTps, travaille sur le contenu du champ Arg1 qui contient l’horloge vectorielle sous la forme d’une chaîne de caractères. Il l’a transforme en un tableau composé des éléments du vecteur d’horloge et sauvegardé dans la structure V_horloge de l’objet OrdreToken (voir tableau 6.7).

Les analyseurs lexicaux utilisés sont décrits dans le tableau 6.8.

Nom

Nom du fichier de spécification

Nom des classes

Nom de la fonction d’accès

Recherche

Action : sauvegarde dans l’objet OrdreToken

JlexOdr

JlexOdr.lex

JlexOdr_impl.class

JlexOdr.class

Jlexodr()

Dans un flot d’entrée les tokens de type fonction d’ordre

Du token dans le champ Valeur

JLexStr

JlexStr.lex

JlexStr_impl.class

JlexStr.class

JlexStr()

Dans une chaîne de caractères contenant une fonction les tokens de correspondant aux macro-expressions définies dans le § 2.3.3.1.

Des tokens dans les champs suivants : Nom_ordre, Arg1, Arg2, Arg3, Arg4, Arg5.

JLexTps

 

JlexTps.lex

JlexTps_impl.class

JlexTps.class

JlexTps()

Dans une chaîne de caractères contenant une horloge vectorielle de tokens de type NOMBRE, les composantes du vecteur.

Des tokens dans les composantes du vecteur temporel V_horloge[I] avec 0 £ I < 50.

 

Tableau 6.8 : présentation des analyseurs lexicaux utilisés

3.1.2.3. La compilation d’un analyseur lexical JLex.

Prenons par exemple la compilation de l’analyseur JlexOdr qui permet de reconnaître les fonctions d’ordre. Elle se réalise en trois étapes :

1. la compilation du moteur Jlex par l’utilisation du fichier source Main.java ;

2. l’interprétation du fichier de spécifications lexicales JlexOdr.lex ;

3. la compilation de l’analyseur lexical généré lors de la phase précédente.

Le fichier Main.class s’obtient par la compilation du moteur Main.java (javac Main.java).

L’écriture et l’interprétation du fichier de spécification obéissent aux règles suivantes. Dans un premier temps, le fichier de spécification est développé selon les règles précisées dans le paragraphe 3.1.1.3. Puis, le programme Main est lancé en lui donnant comme argument le fichier de spécification : java Main JlexOdr.lex. Il produit alors en sortie le fichier JlexOdr.lex.java qui est renommé en JlexOdr.java afin qu’il puisse être compilé sans problème lié de nom.

Dès lors, il suffit de compiler le fichier source de l’analyseur lexical JlexOdr.java (javac JlexOdr.java) pour obtenir la classe, le fichier JlexOdr.class, qui correspond au byte code de l’analyseur lexical.

De plus, si nous désirons gérer les analyseurs lexicaux distribués qui sont utilisés dans notre architecture de traces, la dernière étape doit être complétée par les opérations suivantes.

Le code de l’analyseur JlexOdr doit être partagé par tous les objets distribués que sont les analyseurs lexicaux de type JlexOdr associés aux objets observés. Pour cela, tout objet de type JlexOdr est déclaré dans le fichier d’interface jlex.idl. Ce fichier IDL comporte en particulier l’interface des objets de type OrdreToken, qui sauvegardent temporairement les entités lexicales analysées, et l’interface des objets de type JlexOdr. Dans cette dernière interface, la méthode jlexOdr, qui permet l’accès à l’analyseur lexical, est déclarée pour que les programmes d’analyse, AnalyseTraces et AppletAnalyse, puissent l’invoquer sur des objets répartis de type JlexOdr.

Les interfaces OrdreToken et JlexOdr extraient du fichier jlex.idl sont données dans le code ci-dessous.

interface OrdreToken {

void putNom_ordre(in string NomOrdre) ;

string getNom_ordre();

void putArg1(in string Arg1);

string getArg1();

void putArg2(in string Arg2);

string getArg2();

void putArg3(in string Arg3);

string getArg3();

void putArg4(in string Arg4) ;

string getArg4();

void putArg5(in string Arg5);

string getArg5();

void putV_horloge(in monVecteur::Vecteur V_horlogeNew);

monVecteur::Vecteur getV_horloge();

};

interface JLexOdr {

OrdreToken jlexOdr();

};

Afin de générer les souches client et les souches serveur de ces objets, le fichier jlex.idl est compilé à l’aide de la commande suivante : jidl jlex.idl.

Ensuite, il suffit de renommer la classe JlexOdr.java en JlexOdr_impl.java, de la faire hériter de la classe _JlexOdrImplBase, squelette serveur statique associé à l’objet JLexOdr, de modifier son code source à cause de ce changement de nom et de la compiler à l’aide de la commande javac JlexOdr_impl.java.

Dans le fichier JlexOdr_impl.java, la classe JLexOdr_impl doit donc être déclarée de la manière suivante

public class JLexOdr_impl extends _JLexOdrImplBase {

//---

// Code

//---

}

3.1.2.4. Les macro-expressions reconnues.

Comme définies dans la partie 2.3.3.1, les macro-expressions suivantes sont reconnues par l’analyseur lexical JLexOdr.

ALPHA=[A-Za-z]

DIGIT=[0-9]

NOMBRE=({DIGIT}({DIGIT})*)

NOM_FICHIER=({ALPHA}({ALPHA}|{DIGIT}|_|".")*)

ALPHADIGIT=({ALPHA}({ALPHA}|{DIGIT}|_)*|{DIGIT}({ALPHA}|{DIGIT}|_)*)

NF_OU_AD=({ALPHADIGIT}|{NOM_FICHIER})

IOR=(IOR:{ALPHADIGIT})

VECTEUR=("["( {NOMBRE} ",")*{NOMBRE}"]")

3.1.2.5. Les expressions régulières reconnues.

Les expressions régulières reconnues sont celles générées lors de la trace (voir § 2.3.3).

3.1.2.5.1. La fonction d’ordre " DebutE ".

La fonction d’ordre DebutE trace le début d’exécution d’un programme JAVA.

L’expression régulière associée est la suivante.

DebutE"("{VECTEUR}","{NOM_FICHIER}","{NOM_FICHIER}","{IOR}")"

 

 

 

3.1.2.5.2.La fonction d’ordre " FinE ".

La fonction d’ordre FinE trace la fin d’exécution d’un programme JAVA.

L’expression régulière associée est la suivante.

FinE"(" {VECTEUR}","{NOM_FICHIER}","{NOM_FICHIER}","{IOR}")"

3.1.2.5.3. La fonction d’ordre " Creat_obj " .

La fonction d’ordre Creat_obj trace la création d’un objet JAVA.

L’expression régulière associée est la suivante.

Creat_obj"("{VECTEUR}","{ALPHADIGIT}","{IOR}")"

3.1.2.5.4. La fonction " GetRef_obj ".

La fonction d’ordre GetRef_obj n’est pas une fonction d’ordre mais une fonction utilitaire qui permet, au moment de l’analyse, d’une part de vérifier, lors de la phase d’analyse, que le fichier analysé est bien un fichier trace et d’autre part, de sauvegarder dans le fichier trace le nom et la référence de l’objet.

L’expression régulière associée est la suivante.

GetRef_obj"("{VECTEUR}",{NF_OU_AD}","{IOR}")"

3.1.2.5.5. La fonction d’ordre " DebutM ".

La fonction d’ordre DebutM trace le début d’exécution d’une méthode JAVA.

Remarque.

Lorsque l’appelant de la méthode observée est une autre méthode l’argument 4 contient le nom de cette dernière. Par contre, si l’appelant est un programme JAVA, l’argument 4 est le nom de celui-ci.

L’expression régulière associée est la suivante.

DebutM"("{VECTEUR}","{ALPHADIGIT}","{NF_OU_AD}","{IOR}","{IOR}")"

3.1.2.5.6. La fonction d’ordre " FinM ".

La fonction d’ordre FinM trace la fin d’exécution d’une méthode JAVA.

L’expression régulière associée est la suivante.

FinM"("{VECTEUR}","{ALPHADIGIT}","{NF_OU_AD}","{IOR}","{IOR}")"

3.1.2.5.7. La fonction d’ordre " Iappel".

La fonction d’ordre Iappel trace l’invocation d’une méthode distante.

L’expression régulière associée est la suivante.

Iappel"("{VECTEUR}","{ALPHADIGIT}","{NF_OU_AD}","{IOR}","{IOR}")"

3.1.2.5.8. La fonction d’ordre " Retour_Iappel ".

La fonction d’ordre Retour_Iappel trace le retour d’invocation d’une méthode distante.

L’expression régulière associée est la suivante.

Retour_Iappel"("{VECTEUR}","{ALPHADIGIT}","{NF_OU_AD}","{IOR}","{IOR}")"

 

3.2. LE VISUALISEUR D’ARBRES.

3.2.1. PRESENTATION DE LA LIBRAIRIE JAVA Tree.

Après avoir enregistré les traces, leur analyse, à l’aide du programme AppletAnalyse, conduit à la visualisation du programme observé sous la forme d’un arbre de dépendances causales (voir figure 6.6 et ANNEXE I).

Le visualiseur d’arbres [Koch 95] est inclus dans la librairie JAVA Tree. Il est composé de sept classes qui permettent de visualiser des structures de données sous la forme d’arbres à l’écran. La classe TreeNode a été modifiée d’une part, pour que ses nœuds représentent des relations d’ordre de type programme ou d’interaction et d’autre part, pour qu’elle soit exploitable par l’analyseur AppletAnalyse.

Les classes de la librairie JAVA Tree contiennent plusieurs types de structures de données. Tree est le type arbre. Il est composé de nœuds et de feuilles qui sont de type TreeNode. Un arbre est au moins constitué d’un nœud qui est la racine.

Un nœud est caractérisé par son " nœud père " qui le positionne dans l’arbre. La racine, par exemple, a un " nœud père " qui a la valeur null.

La classe TreeTool est utilisée pour modifier un arbre : toute modification dans la structure d’un arbre est propagée aux objets de type TreeToolObserver qui permettent de le visualiser.

Pour la visualisation graphique de l’arbre, les classes TreeViewPanel, ScrollableTreeViewPanel et ScollerInterface ont été développées.

 

 

 

 

 

 

 

 

 

Figure 6.6. Arbre de dépendances causales

 

3.2.2. MODIFICATION DE LA CLASSE TreeNode.

Seule la classe TreeNode de la bibliothèque JAVA Tree est modifiée. Des données supplémentaires lui sont ajoutées. De plus, son constructeur est modifié.

La classe TreeNode dispose d’un nouvel objet nommé NoeudPere et de type TreeNode. Ainsi, chaque nœud nouvellement créé a la connaissance de son nœud père.

public TreeNode NoeudPere= null;

Deuxièmement, tout objet de type TreeNode contient de nouveaux champs de type String. Pour donner des informations sur l’ordre tracé, ils contiennent quatre lignes de texte qui apparaissent dans chaque nœud.

public String ligne1= null;

public String ligne2= null;

public String ligne3= null;

public String ligne4= null;

De plus, le constructeur de la classe TreeNode est modifié. Il passe de zéro à cinq arguments. Ceux-ci permettent d’affecter les variables présentées dans les deux paragraphes précédents. La valeur du " nœud père " qui est associé au nœud instancié est sauvegardé dans la variable NoeudPere. Les variables ligne1, 2, 3 et 4 sauvegardent quatre lignes de texte, composées chacune de 80 caractères, que l’on souhaite visualiser dans le nœud nouvellement créé.

Enfin, les nœuds des arbres sont représentés par des rectangles dont la taille est fixée à 550 pixels de long pour 80 pixels de haut.

Les modifications apportées au code source originel apparaissent en caractère gras dans le programme ci-dessous.

public TreeNode(TreeNode NodePere,

String Ligne1,

String Ligne2,

String Ligne3,

String Ligne4) {

info = new String(""); // root

children = new Vector(3);

is_selected = false;

is_open = true;

// paranoia setting :

x = 0 ;

y = 0 ;

//---

// Dimentionnement du rectangle visualisant le nœud

//---

wid = 550;

hei = 80;

//---

// Mise en place du noeud pere

//---

NoeudPere=NodePere;

//---

// Mise en place du texte dans le noeud cree

//---

ligne1 = new String(Ligne1);

ligne2 = new String(Ligne2);

ligne3 = new String(Ligne3);

ligne4 = new String(Ligne4);

}

 

3.2.3. UTILISATION DE LA LIBRAIRIE JAVA Tree.

Les classes de la librairie JAVA Tree sont utilisées par le programme AppletAnalyse de la manière suivante. Cette applet utilise les interfaces de la classe TreeToolObserver qui est le visualiseur d’arbres.

Dans un premier temps, elle initialise l’arbre (tree), la racine (root), le nœud sélectionné (selected), l’outil permettant la modification de l’arbre (tool) et la fenêtre de visualisation (treepanel).

public class AppletAnalyse extends Applet implements TreeToolObserver {

//---

// Declaration et initialisation des variables de l'arbre

//---

Tree tree = null;

TreeNode root = null;

TreeNode selected = null;

TreeTool tool = null;

ScrollableTreeViewPanel treepanel = null;

//---

// Code

//---

}

 

Dans un deuxième temps, le constructeur de l’objet AppletAnalyse crée la racine de l’arbre, initialisée à vide, puis l’arbre.

public AppletAnalyse() {

//---

System.out.println("Creation de la racine");

//---

root = new TreeNode(null," "," "," "," ");

//---

System.out.println("Creation de l'arbre");

//---

tree = new Tree(root);

tool = new TreeTool(tree);

tool.addObserver(this);

}

Dans un troisième temps, l’analyseur prend en entrée un fichier de traces conforme aux règles lexicales définies dans le paragraphe 3.1.2.5 et écrit dans le nœud racine, sur trois lignes, le texte suivant : "Arbre des dépendances causales" "realise a partir du fichier trace" "<nom du fichier trace>". Ce nœud devient alors le nœud père (NoeudPereCrt) du prochain nœud qui est généré à la suite de la détection et de la sauvegarde, dans la structure Fct_ordre_token de type OrdreToken, de la fonction d’ordre suivante.

//---

System.out.println("Ecriture du noeud de la racine");

//---

root.ligne1="Arbre des dépendances causales";

root.ligne2="realise a partir du fichier trace";

root.ligne3=Fichier;

//---

System.out.println("La racine est le noeud Père courant");

//---

NoeudPereCrt=root;

//---

System.out.println("Recuperation du premier token");

// grace a l'analyseur lexical de

// l'objet trace de TracePtr

//---

Fct_ordre_token=TracePtr.getAnalyseurOdr().jlexOdr();

L’objet Fct_ordre_token contient toutes informations utiles pour instancier les variables ligne1 à 4 qui sont affichées dans le nouveau nœud de nom NoeudNv. Celui-ci s’attache dans l’arbre au nœud " père courant " grâce à la commande addNode de l’objet tool. Ensuite, l’analyseur passe à l’entité lexicale suivante et change de nœud " père courant ".

De cette manière, l’analyse des fonctions d’ordre de type DebutE, DebutM et Iappel est programmée conformément au code ci-dessous.

NoeudNv=new TreeNode(NoeudPereCrt,ligne1,ligne2,ligne3,ligne4);

tool.addNode(NoeudPereCrt,NoeudNv);

//---

// Passage au token suivant

//---

Fct_ordre_token=TracePtr.getAnalyseurOdr().jlexOdr();

//---

// Changement de noeud pere courant

// avant de passer au token suivant

//---

NoeudPereCrt=NoeudNv;

Par contre, l’analyse des fonctions d’ordre de type FinE, FinM et Retour_Iappel obéit à une logique légèrement différente.

En effet, lors du changement de nœud " père courant ", le programme d’analyse doit se positionner sur le nœud " père courant " qui était en cours au moment de l’analyse des fonctions respectives de type DebutE, DebutM et Iappel. Cette technique permet de créer des arbres équilibrés au niveau des événements DebutE, FinE, DebutM, FinM, Iappel et Retour_Iappel. Dans le cadre de ce mémoire, un arbre est dit équilibré lorsque les sous-arbres représentant ces ordres sont binaires. Pour cela, ces derniers doivent être associés deux à deux de la façon suivante : DebutE et FinE d’une part, DebutM et FinM d’autre part et enfin Iappel et Retour_Iappel.

De plus, avant de monter d’un niveau en direction de la racine, l’analyseur doit vérifier que le nœud " père courant " n'est pas la racine. Dans le cas contraire, il faut d’une part, conserver le nœud " père courant " afin de ne pas générer une exception de type " pointeur nul " et d’autre part avertir l’utilisateur que l’arbre n’est pas équilibré suite à une erreur d’instrumentation. Dans ce cas, le dernier nœud qui apparaît à l’écran comporte le ou les messages suivants ce qui constitue un premier niveau de diagnostique : " Au moins une instrumentation de type DebutE a ete oubliee. " ou "Au moins une instrumentation de type Iappel a ete oubliee. " ou enfin " Au moins une instrumentation de type DebutM a ete oubliee. ".

Dans cette logique, la programmation de l’analyse d’une fonction d’ordre de type FinE, FinM ou Retour_Iappel est conforme au code ci-dessous. Celui-ci concerne le traitement lié à l’analyse d’une fonction d’ordre de type FinE.

if (NoeudPereCrt != root) NoeudPereCrt=NoeudPereCrt.NoeudPere;

else

{

ArbreEquilibre=false;

ManqueDebutE=true;

System.out.println("ATTENTION: arbre non equilibre!");

System.out.println(" Revoir l'instrumentation!");

System.out.println(" Une instrumentation de type DebutE a ete oubliee");

System.out.println(" ");

}

tool.addNode(NoeudPereCrt,new TreeNode(NoeudPereCrt, ligne1,

ligne2,

ligne3,

ligne4));

//---

// Passage au token suivant

//---

Fct_ordre_token=TracePtr.getAnalyseurOdr().jlexOdr();

 

Après avoir présenté les outils destinés à la génération de traces, à leur analyse lexicale et à leur visualisation à l’écran sous la forme d’arbres, la technique d’analyse est détaillée dans le chapitre suivant pour aboutir aux résultats de l’expérimentation.

 

 

CHAPITRE 7 : MISE EN œuvre DE L’ANALYSE.

Ce chapitre met en œuvre sur un exemple de parcours d’un graphe de courtiers les outils d’analyse décrits dans le chapitre précédent.

Le programme que l’on souhaite observer est, dans un premier temps, instrumenté. Dans un second temps, il est exécuté pour qu’il produise des traces. Dans un troisième temps, alors que l’exécution du programme est terminée et que tous ses objets observés sont encore présents en mémoire, le programme d’analyse est lancé pour visualiser le graphe de dépendances causales des événements tracés. Le dernier paragraphe de ce chapitre commente les résultats obtenus lors de cette étude.

 

1. INSTRUMENTATION D’UN PROGRAMME SOURCE.

 

1.1. PRINCIPE DE FONCTIONNEMENT.

L’instrumentation d’un programme source consiste dans notre cas en l’ajout de code qui permet la création de l’architecture " objet observé/objet trace " décrite dans le chapitre précédent. Celle-ci permet de connaître parfaitement, d’une part, les objets observés aux moyens de leurs données statiques et, d’autre part, leur comportement à l’aide des informations de type ordre programme ou de type ordre d’interaction contenues dans les fichiers trace.

Tout objet que l’on désire tracer doit disposer, par simple déclaration, d’une variable privé TRACE de type trace. Celle-ci dispose d’un constructeur, de données et de méthodes spécifiques qui gèrent la production de traces.

Au commencement de tout programme ou à la création de tout objet étudié, un objet trace est créé au moyen d’un constructeur spécifique. Celui-ci est composé de trois arguments : le nom du fichier trace qui va enregistrer les événements de type relation d’ordre, le nom du créateur de l’objet ou du programme et le numéro de l’objet trace, qui est utilisé par l’horloge vectorielle. Les sondes logicielles ajoutées au programme originel permettent, d’une part, de sauvegarder dans l’objet trace les données statiques des objets tracés (nom, référence et numéro de l’objet, adresse de la machine où il s’exécute) et, d’autre part, d’écrire dans les fichiers trace le comportement de l’objet sous la forme de fonctions d’ordre générées par les méthodes des objets trace.

 

1.2. DESCRIPTION DU PROGRAMME INSTRUMENTE.

Les exemples d’instrumentation présentés dans les paragraphes ci-dessous sont tirés de l’application " Réseau de courtier " qui a été développée et utilisée, dans le cadre de ce projet, pour la mise au point de cette technique d’analyse post-mortem de traces dans un environnement de programmation JAVA/CORBA.

Nous avons choisi cet exemple car il met en jeu un ensemble d’objets répartis. Ceux-ci réalisent une structure de contrôle de type récursion distribuée. La requête d’un client, qui demande un service au premier courtier du réseau, est récursive. Si ce courtier ne peut assurer ce service, elle se propage de proche en proche aux courtiers voisins. Cet exemple de part la distribution du réseau de courtiers et du nombre important d’objets mise en œuvre est difficile à mettre au point. C’est pour cette raison qu’il a été choisi afin d’expérimenter notre outil de traces.

 

1.2.1. Introduction.

L’un des services les plus importants dans un système réparti à objets tel que CORBA est la localisation des objets. Deux services dans CORBA le permettent. Le premier correspond au service de nommage. Il assure la localisation d'un objet à partir de son nom. C'est à dire qu'à partir du nom de l'objet, il fournit la référence de l'objet recherché. C'est l'équivalent des pages blanches de l'annuaire. Un second service, appelé courtier ou trader, permet de retrouver une référence d'objet à partir de la description d'un objet. Dans la requête d'interrogation on ne précisera pas ici explicitement le nom de l'objet, mais le type d'objet recherché avec également la qualité du service demandée. Il s'agit de l'équivalent des pages jaunes de l'annuaire. On peut, par exemple, rechercher un objet offrant le service d'impression le plus proche ou le plus rapide. Le courtier recherchera alors dans sa base d'information l'objet ainsi que la référence qui correspond le mieux à la description du service demandé.

 

1.2.2. Le courtier.

Un courtier est donc un objet qui fournit des services de recherche d'objets pour d'autres objets. Les fournisseurs de service ou exportateurs enregistrent leurs services auprès du courtier. Les importateurs ou clients utilisent le courtier pour rechercher les services qui correspondent à leurs besoins. Le client peut ensuite invoquer directement le serveur en utilisant la référence d'objet qui lui a été donnée. Les courtiers permettent donc de découvrir dynamiquement des services et de faciliter la liaison tardive à ces services.

Le Service du courtier

Lorsqu’un nouveau fournisseur de service s'enregistre auprès du trader il fournit les informations suivantes :

- Une référence d'objet : cela correspond à la référence que les clients utilisent pour se connecter au service et pour invoquer les opérations.

- Le nom du type de service : un type de service inclut des informations sur les noms des opérations (ou méthodes) auquel le service pourra répondre avec le type des paramètres et des résultats. Il inclut également différentes propriétés du service et le contexte du courtier.

- Les propriétés du service : les propriétés décrivent les capacités d'un service. Il s'agit d'une association entre un nom et une valeur. On indiquera par exemple la propriété "vitesse d'exécution" pour un service d'impression et celle-ci peut avoir les valeurs "rapide", "moyenne", "lente" ou "indéfinie". Un exportateur spécifie les valeurs des propriétés du service qu'il enregistre auprès du courtier. Une propriété peut être obligatoire ou optionnelle. L'exportateur doit spécifier les valeurs des propriétés obligatoires.

 

1.2.3. Interconnexion des courtiers.

L'ensemble des objets d'un système réparti ne peut pas être géré par un seul courtier et stocké dans une seule base d'information, à la fois pour des raisons de tolérance aux pannes, mais aussi pour des raisons de dimension des bases d'information. On souhaite donc gérer de manière répartie l'ensemble des bases comprenant les références d'objet, le type du service rendu et les propriétés du service. L'information sera alors distribuée mais également redondante. On aura donc un ensemble de courtiers capables de répondre aux sollicitations des clients. Les courtiers seront reliés entre eux de manière à offrir un service plus complet à leurs clients. On parle alors du graphe de courtage. Lorsqu'un courtier se joint au réseau des courtiers, il permet donc l'élargissement de l'offre à l'ensemble des clients du réseau de courtage.

Interrogation par un client d'un ensemble de courtiers répartis dans le réseau.

L'interrogation d'un client est, dans ce cas, traitée non pas par un seul courtier, mais par un ensemble de courtiers. Le client s'adresse à un courtier, le plus proche par exemple. Ce courtier essaie de traiter la requête. Si le résultat n'est pas satisfaisant, le courtier peut alors décider de propager la requête vers l'ensemble des courtiers auxquels il est lié. Le même traitement sera alors effectué sur les courtiers recevant la requête : ils pourront décider de propager la requête vers les courtiers auxquels ils sont liés. Il faudra faire attention aux cycles dans le graphe et mettre en place une règle de poursuite de la requête. Au retour les résultats sont collectés progressivement. Chaque courtier ayant participé à la requête fournit sa réponse au courtier l'ayant appelé. Enfin, le courtier initial donne la réponse au client dès qu'il a eu le retour de toutes les requêtes émises.

Le réseau de courtiers est représenté dans la figure 7.1 ci-dessous. Les courtiers sont numérotés de 1 à 7. Leurs voisins respectifs sont au nombre maximum de trois, numérotés de 1 à 3 (voir fichier reseau.java en ANNEXE E).

Figure 7.1 : le réseau de courtiers

 

1.2.4. Développement du réseau de courtiers.

 

1.2.4.1. Les programmes courtiers.

Tout programme courtier instancie un objet de type courtier qui appartient à la classe JAVA Courtier_impl (voir ANNEXE C). Celle-ci possède trois méthodes principales export, query et QUERY.

La méthode export permet au serveur d’enregistrer, dans la base de données du courtier associé, la nature de ses services : la référence de l’objet serveur exportant le service (ObjServer), le nom du service (Nom_service), le nom de la propriété (Nom_propr), la valeur de celle-ci (Valeur). Elle peut être représentée de la manière suivante :

export (ObjServer,Nom_service, Nom_propr, Valeur).

La méthode query permet au client de retrouver, pour un service dans un courtier donné, la référence d’un objet serveur qui propose ce service. Elle peut être représentée de la façon suivante : query (Nom_service, Nom_propr, Valeur).

La méthode QUERY donne la possibilité au client de retrouver pour un service et dans un réseau de courtiers donnés, la référence d’un objet serveur qui propose ce service. Elle peut être représentée de la façon suivante : QUERY(Nom_service, Nom_propr, Valeur). Cette méthode invoquée sur un courtier donné appelle la méthode query de celui-ci. Si le service n’est pas trouvé, la méthode QUERY est invoquée de nouveau sur ses voisins si ceux-ci n’ont pas encore été visités. Ces invocations se réalisent d’une façon séquentielle en commençant par son voisin un, puis par son deuxième et son troisième si ces deux derniers existent. Lorsque le service demandé n’est pas trouvé dans le réseau de courtiers, la méthode QUERY retourne au client une référence d’objet de valeur null, sinon elle lui renvoie la référence de l’objet serveur qui propose ce service.

1.2.4.2. Les programmes serveur.

Un programme serveur peut créer plusieurs objets de type Serveur_impl qui disposent tous du service " temps machine " suivant : une méthode permet de récupérer sous la forme d’une chaîne de caractères la date courante du système.

Tout programme serveur instancie un objet de type serveur qui appartient à la classe JAVA Serveur_impl. Il propose le service " temps machine " qui a pour valeur " S207_1". L’objet de type serveur enregistre ce service dans la base de données de son courtier en invoquant la méthode export de ce dernier objet :

export(Obj1_ServNPtr, "temps","machine","S207_1").

Seul un serveur dispose d’un objet de service " temps machine " supplémentaire dont la valeur est " S207_2 ". Cet objet s’enregistre auprès de son courtier en invoquant la méthode export de ce dernier : export(Obj2_ServNPtr,"temps","machine","S207_2").

 

1.2.4.3. Le programme client.

Le programme client interroge le réseau de courtiers pour savoir si le service " temps machine S207_2 " existe sur le réseau. Pour cela il invoque la méthode QUERY du courtier 1 de la manière suivante : QUERY("temps","machine","S207_2").

Ainsi, le programme client visite, compte tenu de l’architecture du réseau de courtiers (voir figure 6.6), les courtiers 1, 2, 3 et 7. L’objet de service " temps machine S207_2 " est trouvé dans la base de données du courtier 7. Sa référence est récupérée par le programme client qui peut alors invoquer sa méthode getDate() pour récupérer la date et l’heure courantes (voir fichier client.java en ANNEXE A).

L’application " Réseau de courtiers " est une application JAVA lourde à gérer à cause du nombre important de programmes (15) et d’objets (15) utilisés :

De part cette complexité, il est intéressant d’en réaliser l’analyse qui débute par l’instrumentation de ses programmes sources.

1.3. MISE EN PLACE DE L’INSTRUMENTATION.

A tout objet ou programme JAVA observé est associé un objet de type trace qui permet de sauvegarder ses caractéristiques statiques et dynamiques.

Les paragraphes qui suivent abordent les points suivants.

Tout objet tracé est enrichi, par l’utilisation d’un objet trace dans son constructeur, de données spécifiques et de fonctions spécifiques qui construisent l’architecture " objet observé/objet trace " décrite dans le chapitre 6.

Tout programme à tracer est instrumenté à l’aide d’une instruction qui permet l’instanciation d’un objet trace. Elle se place au début du code du programme que l’on souhaite observer.

D’autres instructions sont installées dans le source. Elles sauvegardent dans les fichiers traces l’adresse et le port de la machine où s’exécute le programme ou l’objet observés. L’invocation de la fonction GetRef_obj de l’objet trace, associée à d’autres instructions, sauvegarde la référence de l’objet trace. Elles se placent après avoir instrumenté le programme JAVA avec l’objet de type trace.

Enfin les méthodes de type fonction d’ordre sont invoquées par les objets trace pour écrire les fonctions d’ordre dans les traces.

Compte tenu de l’initialisation et de l’utilisation des variables de l’objet trace qui découlent des différentes phases de l’instrumentation d’un programme, il est important de procéder à l’installation des sondes logicielles d’un programme JAVA en suivant strictement l’ordre initial décrit ci-dessous.

Premièrement : instrumentation du programme JAVA avec l’objet de type trace.

Deuxièmement : récupération du nom et du port de la machine.

Troisièmement : récupération de la référence de l’objet trace créé.

 

 

Les exemples donnés dans les paragraphes ci-dessous se retrouvent dans les fichiers et les annexes précisés dans le tableau 7.1 ci-joint.

 

Numéro du paragraphe

Nom du fichier

Annexe

1.3.2

Courtier_impl

C

1.3.3

Client .java

A

1.3.4

Client .java

A

1.3.5

Client .java

A

1.3.6

Client .java

A

1.3.7

Courtage1.java

D

1.3.8

Courtier_impl

C

1.3.9

Client.java

A

1.3.10

Client .java

A

1.3.11

Client .java

A

Tableau 7.1 : tableau d’exemples de fichiers instrumentés

 

1.3.1. Vérifications importantes.

Il faut s’assurer que tout programme ou objet observé reste présent en mémoire à l’issue de l’exécution de l’application. En effet, l’interpréteur de JAVA ne peut être déchargé de la mémoire sans en supprimer les objets qu’il administre. Ainsi, il faut vérifier que tout programme instrumenté ne se termine pas par l’instruction System.exit(0) qui force l'arrêt de l'interpréteur et le décharge de la mémoire.

Un contre exemple est donné dans l’ANNEXE E. Le programme reseau.java qui met en place le réseau de courtier et qui n’est pas instrumenté, se termine par l’instruction System.exit(0). Son interpréteur est alors déchargé pour libérer la mémoire.

 

1.3.2. Instrumentations des objets.

L’instrumentation d’un objet lui apporte données, constructeurs et fonctions spécifiques. Celles-ci doivent être déclarées dans le fichier IDL de l’objet (voir ANNEXE B).

1.3.2.1. Données spécifiques.

Les objets instrumentés doivent disposer de la donnée spécifique suivante qui correspond à son objet trace.

//---

//TRACE : definition de l'objet Trace de l'objet Courtier

//---

private Trace TRACE;

1.3.2.2. Constructeur spécifique.

L’objet instrumenté doit disposer d’un constructeur dont les trois arguments sont les suivants : nom du fichier trace associé à l’objet tracé, nom du créateur de l’objet, numéro de l’objet trace utilisé par l’horloge vectorielle. Le constructeur de l’objet observé est ainsi modifié en y insérant l’instanciation d’un objet de type trace. Ainsi tout objet observé nouvellement créé génère automatiquement un objet trace.

public Courtier_impl(String Nom_fichier_trace, String Nom_createur, int Numero){

//---

//TRACE : initialisation de l'objet Trace de l'objet tracé

//---

Trace_impl ObjetTrace=new Trace_impl(Nom_fichier_trace, Nom_createur, Numero);

putTRACE(ObjetTrace);

} ;

1.3.2.3. Fonctions spécifiques.

Les objets instrumentés doivent disposer des fonctions spécifiques suivantes.

//---

// TRACE : manipulation de l'objet Trace

//---

//---

// Lecture de l'objet TRACE

//---

Trace getTRACE();

//---

// Ecriture de l'objet TRACE

//---

void putTRACE(in Trace ObjetTrace);

//---

// GetRef_obj : recuperation de la reference de l'objet TRACE associe a l'objet tracé

//---

void GetRef_obj(in string Nom_objet);

//---

// Ecriture de la variable TRACE.Reference associee a l'objet tracé

//---

void putRef(in string Ref_obj);

//---

// Lecture de la variable TRACE.Reference associee a l'objet tracé

//---

string getRef();

//---

// Ecriture de la variable TRACE.NomMachine associee a l'objet tracé

//---

void putNomMachine(in string Nom_Machine);

 

1.3.3. Instrumentation d’un programme JAVA avec un objet de type trace.

1.3.3.1. Instanciation.

Trace_impl TClient = new Trace_impl("client.trc","client.java",0);

Avec :

Les objets de trace ont pour type Trace. Ils sont instanciés au moyen d’un constructeur qui dispose de trois arguments : le nom du fichier trace, le nom de l’objet tracé et le numéro de l’objet trace. On utilise le constructeur de dimension trois car d’une part, les deux premiers arguments sont nécessaires pour identifier l’objet tracé et sa trace, et, d’autre part, le troisième est obligatoirement utilisé pour la mise en œuvre de l’horloge vectorielle.

1.3.3.2. Remarque importante.

Il est nécessaire de placer cette instruction d’instanciation après avoir créé l’ORB et le BOA par l’utilisation des instructions suivantes.

ORB orb = ORB.init( args, new JAVA.util.Properties() ); BOA boa = orb.BOA_init( args, new JAVA.util.Properties() );

En effet, l’objet Tclient, de type Trace, hérite du squelette serveur _TraceImplBase qui est accédé via le BOA de l’ORB.

1.3.4. Récupération du nom et du port de la machine.

1.3.4.1. Instrumentation.

com.ooc.CORBA.BOA OBboa = (com.ooc.CORBA.BOA)boa;

String Machine=" ";

Machine=Machine.concat(OBboa.host());

Machine=Machine.concat(", port");

Machine=Machine.concat(" " + OBboa.port());

TClient.putNomMachine(Machine);

1.3.4.2. Remarque importante.

Il est nécessaire de placer ces instruction après avoir instancié l’ORB et le BOA par l’utilisation des instruction suivantes.

ORB orb = ORB.init( args, new JAVA.util.Properties() ); BOA boa = orb.BOA_init( args, new JAVA.util.Properties() );

1.3.4.3. Sauvegarde dans l’objet trace.

Un objet BOA est référencé dans le but de récupérer et de sauvegarder l’adresse de la machine et son numéro de port. Pour la sauvegarde, la méthode putNomMachine qui travaille sur la variable NomMachine est utilisée.

1.3.5. Récupération de la référence IOR d’un objet trace.

1.3.5.1. Instrumentation.

String RefTClient = orb.object_to_string(TClient);TClient.putRef(RefTClient);TClient.GetRef_obj("client.java");

L’exemple d’instrumentation présentée ci-dessus permet de récupérer la référence IOR de l’objet Tclient qui trace le fonctionnment du programme JAVA client.java.

1.3.5.2. Sauvegarde dans l’objet trace.

La référence IOR de l’objet trace est récupérée grâce à la fonction object_to_string. Elle est sauvegardée dans la variable Reference de l’objet tace. La fonction GetRef_obj sauvegarde également le nom de l’objet tracé dans la variable NomObj.

1.3.5.3. Ecriture dans le fichier trace.

La méthode GetRef_obj permet d’écrite dans la fichier trace le nom de l’objet tracé et sa référence. Elle prend comme argument le nom du programme ou de l’objet tracé.

 

1.3.6. Début d'exécution d'un programme JAVA.

1.3.6.1. Instrumentation.

TClient.DebutE("client.java","client.java");

Avec :

1.3.6.2. Ecriture dans le fichier trace.

La méthode DebutE trace le début d’exécution d’un programme JAVA. Elle se place au début du code du programme observé après y avoir installé les trois sondes logicielles précédentes.

Elle prend en paramètres le nom du programme en cours d'exécution et le nom du programme appelant.

 

1.3.7. Creation d'objet.

1.3.7.1. Instrumentation.

TCourtage1.Creat_obj("Courtier1Ptr");

Courtier_impl Courtier1Ptr = new Courtier_impl("Courtier1Ptr.trc","courtage1.java",2);

Avec Courtier1Ptr le nom de l'objet tracé.

Pour le constructeur :

Cette opération, à l’aide des constructeurs de type courtier et trace, permet d’associer un objet trace à tout objet observé et nouvellement créé.

Pour que cette instrumentation soit valide, il ne faut pas oublier que les constructeurs des objets observés doivent être modifiés pour qu’ils disposent des trois arguments suivants : le nom du fichier trace, le nom de l’objet tracé et le numéro de l’objet trace. Le constructeur de l’objet observé est modifié en y insérant l’instanciation d’un objet de type trace. De ce fait, tout objet observé nouvellement créé génère automatiquement un objet trace (voir § 1.3.2).

L’instrumentation précédente doit être suivie des instructions suivantes pour d’une part, récupérer l’adresse de la machine hôte et d’autre part, enregistrer le nom et la référence IOR de l’objet trace.

 

Courtier1Ptr.putNomMachine(Machine);

String RefTCourtier1Ptr = orb.object_to_string(Courtier1Ptr.getTRACE());Courtier1Ptr.putRef(RefTCourtier1Ptr);Courtier1Ptr.GetRef_obj("Courtier1Ptr");

1.3.7.2. Ecriture dans le fichier trace.

La méthode Creat_obj trace la création d’un objet JAVA. Elle prend en paramètre le nom de l'objet tracé.

Remarque : il n’est pas nécessaire que la méthode fonction d’ordre Creat_obj prenne en argument le nom du créateur de l’objet. En effet, cette information est enregistrée, à l’aide de l’instruction suivante, dans l’objet trace. Elle pourra être référencée grâce à l’argument 3 de la fonction d’ordre qui contient la référence de l'objet trace. 

 

1.3.8. Instrumentation d’une méthode.

1.3.8.1. Exemple d’instrumentation.

public Server QUERY(Trace TRACE,

String Nom_methode_appelante,

String Ref_objet_appelant,

String NomService,

String NomPropriete,

String Valeur)

{//---// Début de la méthode QUERY

//---

TRACE.DebutM("QUERY",Nom_methode_appelante,Ref_objet_appelant);

//---

// Code de la méthode QUERY

//---

//---// Fin de la méthode QUERY

//---

TRACE.FinM("QUERY",Nom_methode_appelante,Ref_objet_appelant);

return(RsltServerPtr);};

1.3.8.2. Modification du nombre d’arguments de la méthode instrumentée.

Une méthode instrumentée voit son nombre d’arguments augmenté de trois : la référence de l’objet trace, le nom de la méthode ou du programme appelant et la référence IOR de l’objet appelant (voir la méthode QUERY de l’exemple précédent). Cette dernière référence n’est pas la référence IOR de l’objet appelant au sens strict mais la référence IOR de son objet trace. Le premier argument, la référence de l’objet trace associé au programme ou à la méthode observé, est utilisé pour invoquer les méthodes DebutM et FinM de cet objet. Les deux autres arguments sont utilisés par ces deux méthodes pour connaître la méthode ou le programme et la référence IOR de l’objet qui est à l’origine de l’invocation.

Pour un objet distribué, la modification du nombre d’arguments de la méthode instrumentée doit être reportée dans son fichier IDL (voir ANNEXE B pour un objet de type courtier).

1.3.8.3. Début de méthode.

Ecriture dans le fichier trace.

La méthode DebutM trace le début de l’exécution d’une méthode JAVA.

Elle prend en paramètres : le nom de la méthode, le nom de la méthode appelante ou du programme appelant et la référence IOR de l'objet appelant. 

1.3.8.4.Fin de méthode.

Ecriture dans le fichier trace.

La méthode FinM trace la fin d’exécution d’une méthode JAVA.

Elles prend en paramètres : le nom de la méthode, le nom de la méthode appelante ou du programme appelant et la référence IOR de l'objet appelant. 

 

1.3.9. Invocation et retour d’invocation d'une méthode.

1.3.9.1. Instrumentation du programme.

//---

// Invocation de la méthode QUERY

//---

TClient.Iappel("QUERY","client.java",Courtier1Ptr.getRef(),Courtier1Ptr.getTRACE());

Obj1_Serv2Ptr=Courtier1Ptr.QUERY(Courtier1Ptr.getTRACE(),

"client.java",

TClient.getRef),

"temps",

"machine",

"S207_2");//---// Retour d’invocation de la methode QUERY//---

TClient.Retour_Iappel("QUERY","client.java",Courtier1Ptr.getRef(),Courtier1Ptr.getTRACE());

1.3.9.2. Ecriture dans le fichier trace.

La méthode d’ordre Iappel trace l’invocation d’une méthode distante.

La méthode d’ordre Retour_Iappel trace le retour d’invocation d’une méthode distante.

Elles prennent en paramètres : le nom de la méthode invoquée, le nom de la méthode ou du programme appelant et enfin, la référence IOR et la référence de l’objet trace de l'objet invoqué. Cette dernière référence est utilisée pour modifier le vecteur d’horloge de l’objet invoqué.

 

1.3.10. Commentaires.

1.3.10.1. Instrumentation.

TClient.println("CLIENT : Invocation des methodes distantes de la classes monCourtier");

1.3.10.2. Ecriture dans le fichier trace.

La méthode println permet d’écrire dans la trace un commentaire passé en argument. La chaîne de caractères ainsi générée n’est pas prise en compte lors de la phase d’analyse.

 

1.3.11. Terminaison d'un programme JAVA.

1.3.11.1. Instrumentation.

TClient.FinE("client.java","client.java");

Avec :

1.3.11.2. Ecriture dans le fichier trace.

La méthode FinE trace la fin d’exécution d’un programme JAVA. Elle se place à la fin du code du programme observé après avoir installé, en début de programme, la sonde logicielle qui correspond à la fonction d’ordre DebutE.

Elle prend en paramètres : le nom du programme en fin d'exécution et le nom du programme appelant.

 

1.3.12. REMARQUE IMPORTANTE.

Afin de réaliser une instrumentation qui aboutisse à un arbre équilibré des ordres programme et d’interaction, il faut que les méthodes de type fonction d’ordre soient implantées par paires : DebutE et FinE ; DebutM et FinM et enfin Iappel et Retour_Iappel.

Si cette règle n’est pas respectée, l’analyseur AppletAnalyse, qui visualise l’arbre de dépendances causales du programme observé, génère une erreur, non bloquante, qui informe l’utilisateur que l’arbre n’est pas équilibré suite à un problème d’instrumentation (voir 3.2 du chapitre 6).

 

 

1.4. EXEMPLES D’INSTRUMENTATION.

 

1.4.1. Exemple 1.

Prenons l’exemple de la mise en évidence des événements " début d’exécution" et " fin d’exécution " d’un programme JAVA.

Soit P.java le programme JAVA que l’on souhaite tracer.

1.4.1.1. Instrumentation du programme source.

Premièrement, le source du programme P.java est instrumenté pour créer un objet trace TP qui suive son activité. Deuxièmement, les événements DebutE et FinE, de type ordre programme, sont enregistrés sous la forme de fonctions d’ordre dans le fichier trace P.trc .

Dans un premier temps, l’instruction suivante est ajoutée au début du code source du programme P :

Trace_impl TP = new Trace_impl("P.trc","P.java",0) ;

Dans un second temps, le code source de P est instrumenté par l’appel de la méthode DebutE de l’objet trace TP. La sonde logicielle suivante est à ajouter au début du programme P :

TP.DebutE(P.java,P.java) ;

Dans un troisième temps, le code source programme P est modifié par l’appel de la méthode FinE de l’objet trace TP. La sonde logicielle suivante est à ajouter à la fin du programme P :

TP.FinE(P.java,P.java) ;

1.4.1.2. La trace générée.

Ainsi, au moment de l’exécution du programme P, l’invocation de la méthode DebutE de l’objet TP déclenche l’écriture, dans le fichier trace P.trc, de la fonction d’ordre DebutE suivante :

DebutE([1,0,0,0,0,..,0],P.java ,P ;JAVA ,IOR :0000011fsd..0f112) ;

Cette fonction d’ordre est composée des éléments suivants.

DebutE : nom de la fonction d’ordre .

Cette fonction d’ordre dispose de quatre arguments :

[1,0,0,0,0,..,0]= V_horloge.getStrV_horloge() : valeur de l’horloge vectorielle de dimension 50 ;

P.java= Nom_programme : nom du programme en cours d’exécution ;

P.java= Nom_programme_appelant : nom du programme appelant ;

IOR :0000011fsd..0f112=getRef() : référence IOR de l’objet trace.

De même, à la fin du code du programme P, l’invocation de la méthode FinE de l’objet TP déclenche l’écriture, dans le fichier trace P.trc, de la fonction d’ordre FinE suivante :

FinE([1,0,0,0,0,..,0],P.java ,P ;JAVA ,IOR :0000011fsd..0f112) ;

Ainsi, l’instrumentation du programme P conduit à l’obtention du fichier trace représenté dans la figure 7.2.

Figure 7.2 : fichier trace P.trc

1.4.2. Exemple 2.

Reprenons l’exemple du chapitre 6, figure 6.2, en le complétant par des informations liées à l’instrumentation. Pour les programmes P1 et P2 et l’objet O2, que l’on désire déboguer, le but est de créer autant d’objets trace que de programmes ou d’objets observés. La création des objets trace est visualisée dans la figure 7.3 par des rectangles qui contiennent des constructeurs de type objet trace.

1.4.2.1. La création des objets trace.

Ainsi, au commencement de l’exécution des programmes P1 et P2 deux objets de trace, TP1 et TP2, sont instanciés (rectangles A et B). Les objets TP1 et TP2 sauvegardent les données statiques et dynamiques des programmes P1 et P2, qui seront exploitées lors de la phase 3 de l’analyse. De la même façon, lors de la création de l’objet O2, un objet trace TO2 est créé afin de suivre le comportement de celui-ci (rectangle C).

L’objet TO2, par exemple, est créé au moyen d’un constructeur de type trace qui génère une trace O2.trc, sauvegarde le nom de son créateur, le programme P2, et son numéro d’objet, de valeur deux.

Figure 7.3 : instrumentation des programmes P1 et P2 et de l’objet O1

 

1.4.2.2. Les traces générées.

L’instrumentation des programmes P1 et P2 et de l’objet O1 conduit, lors de l’exécution de P1 et de P2, à la création des trois fichiers trace présentés dans la figure 7.4.

Figure 7.4 : fichiers trace obtenus suite à l’instrumentation des programmes P1 et P2 et de l’objet O1

 

Après avoir instrumenté, conformément à ce paragraphe, le programme " Réseau de courtiers ", il suffit de le lancer pour qu’il produise des traces. La partie suivante expose la technique pour exécuter, au moyen de trois fichiers de commandes, cette application distribuée.

 

2. LANCEMENT DU PROGRAMME COURTIER.

Le lancement du programme Courtier se réalise en trois temps, par l’intermédiaire de trois fichiers batch écrits en cshell. Dans un premier temps, les courtiers sont lancés. Dans un deuxième temps, les programmes serveur sont exécutés. Enfin, dans un troisième temps, le client peut envoyer sa requête au réseau de courtiers.

    1. LANCEMENT DES COURTIERS.
    2. Le lancement des programmes courtier se fait par l’intermédiaire du fichier lancer_court. Les programmes courtier s’exécutent en arrière plan sous la forme de processus UNIX. Il faut lancer ce programme en premier pour mettre en place les objets courtier.

      Le lancement des différents serveurs s’effectue de la façon suivante.

      JAVA monCourtier.courtage1&

      JAVA monCourtier.courtage2&

      JAVA monCourtier.courtage3&

      JAVA monCourtier.courtage4&

      JAVA monCourtier.courtage5&

      JAVA monCourtier.courtage6&

      JAVA monCourtier.courtage7&

       

    3. LANCEMENT DES SERVEURS.
    4. Le lancement des programmes serveur se fait par l’intermédiaire du fichier lancer_serv. Sous la forme de processus UNIX lancé en arrière plan, le réseau de courtiers est mis en place à l’aide du programme reseau et les programmes serveur s’exécutent. Il faut exécuter lancer_serv après le programme lancer_court et alors que tous les courtiers sont prêts.

      Le lancement des différents serveurs s’effectue de la manière suivante.

      JAVA monCourtier.server1&

      JAVA monCourtier.server2&

      JAVA monCourtier.server3&

      JAVA monCourtier.server4&

      JAVA monCourtier.server5&

      JAVA monCourtier.server6&

      JAVA monCourtier.server7&

      La mise en place du reseau entre les courtiers se réalise en lançant le programme reseau.

      JAVA monCourtier.reseau&

       

       

    5. LANCEMENT DU CLIENT.

Le lancement du programme client se fait par l’intermédiaire du fichier lancer_client.

Il faut l’exécuter après avoir lancé les courtiers (lancer_court) et les serveurs (lancer_serv) et attendre que tous les courtiers et les serveurs soient prêts.

Le programme client est un processus UNIX qui s'exécute en arrière plan et qui est lancé de la manière suivante.

JAVA monCourtier.client&

 

3. ANALYSE DES TRACES.

 

3.1. INTRODUCTION.

Les programmes d’analyse des traces ont pour nom AnalyseTraces.java et AppletAnalyse.java. Ils permettent de réaliser un traitement des traces générées par un programme préalablement instrumenté et exécuté.

Il est important, avant de lancer l’un des programmes d’analyse, de vérifier les points suivants.

L’analyseur prend en argument le fichier trace du programme que l’utilisateur souhaite observer. De ce fichier et à l’aide de l’analyseur lexical JLex, il extrait et analyse les unités lexicales de type fonction d’ordre qui sont ensuite soit sauvegardées en langage naturel dans le fichier résultat, Analyse.trc (programme AnalyseTraces.java), soit affichées sous la forme d’un arbre représentant les ordres programme et les ordres d’interaction tracés (programme AppletAnalyse). Cette technique d’analyse est représentée dans la figure 7.5.

Figure 7.5 : Schéma de l’analyse des traces

 

3.2. DESCRIPTION DE LA TECHNIQUE D’ANALYSE.

Soit P un programme JAVA à observer. Après l’avoir instrumenté et exécuté, le lancement de l’analyse se réalise par l’exécution du programme d’analyse en lui passant comme argument le fichier trace (P.trc).

Lorsque l’analyseur détecte dans ce fichier trace, au moyen de l’analyseur lexical JlexOdr, une unité lexicale de type fonction d’ordre, il réalise les opérations suivantes. Dans un premier temps, il la sauvegarde temporairement dans une structure de données puis l’écrit dans le fichier résultat Analyse.trc (programme AnalyseTraces) ou la visualise dans un arbre (programme AppletAnalyse) et ensuite, passe à la fonction d’ordre suivante.

Si la fonction d’ordre est une invocation d’une méthode distante, l’analyseur change d’objet de trace et ne retourne à l’objet initial qu’à la fin de la méthode invoquée. Ainsi, de proche en proche cette technique d’analyse parcourt toutes les traces générées lors de l’exécution du programme P étudié.

Les différentes étapes de cette analyse, numérotées de 1 à 8, sont représentées dans la figure 7.6 ci-dessous. Celle-ci représente deux objets, Objet I et Objet J, instrumentés qui disposent chacun d’un objet et d’un fichier de type trace. Par un souci de clarté les analyseurs lexicaux associés aux objets traces ne sont pas représentés.

Figure 7.6 : analyse d’une trace suite à l’invocation d’une méthode distante.

 

3.2.1. Le commencement de l’analyse.

La première entité lexicale lue par le moteur d’analyse, à l’aide d’un analyseur lexical local, est la fonction d’ordre GetRef_obj. Si cette fonction n’est pas trouvée en première instance, l’analyseur considère que le fichier proposé n’est pas un fichier trace et arrête l’analyse. Dans le cas contraire, l’analyse des arguments de la fonction d’ordre lui permet de récupérer la référence IOR de l’objet trace associé au programme P. L’analyseur transforme alors cette chaîne de caractères en référence objet. Celle-ci lui permet ensuite d’invoquer l’analyseur lexical de l’objet trace de P. L’analyse proprement dite du fichier trace peut alors commencer.

Le moteur d’analyse rencontre de nouveau, à l’aide cette fois ci de l’analyseur lexical de l’objet trace de P, la fonction d’ordre GetRef_obj (1) et recherche l’unité lexicale suivante. Il détecte cette dernière, la sauvegarde dans un objet de type OrdreToken, l’écrit en langage naturel, selon la liste des textes prévus à cet effet, dans le fichier Analyse.trc ou l’affiche à l’écran sous la forme d’un nouveau nœud de l’arbre. Ce procédé se répète jusqu’à ce qu’il trouve une fonction d’ordre de type  Iappel  (2).

 

3.2.2. Le traitement d’une token de type " Iappel ".

Lorsque le programme détecte une fonction d’ordre de ce type, il effectue, dans un premier temps, le traitement propre à l’analyse de toute fonction d’ordre : sauvegarde dans un objet de type OrdreToken, écriture en langage naturel dans le fichier Analyse.trc ou affichage à l’écran d’un nouveau nœud de l’arbre. Puis, il change d’objet trace et se positionne correctement dans le fichier trace de celui-ci.

3.2.2.1. Le changement d’objet trace.

Lorsque l’entité lexicale lue est de type Iappel (2), l’analyseur détecte que le programme effectue l’invocation d’une méthode qui se trouve sur un objet distant. Notons Iappel(I,K) cet événement où I représente l’objet Oi et K, le kième événement de la fonction d’ordre de l’objet Oi.

La lecture de l’argument quatre de cette fonction d’ordre lui permet de connaître la référence de l’objet trace invoque (3). Il change alors d’objet trace : il passe de la trace de l’objet qui effectue l’invocation à celle de l’objet invoqué.

3.2.2.2. Le positionnement de l’analyseur dans le fichier trace.

Suite à la détection de l’invocation d’une méthode distante, l’analyseur parcourt la trace de l’objet invoqué (parcours de la trace de l’état (4) à l’état (5)) pour trouver, grâce à l’analyse des horloges vectorielles, un événement immédiatement postérieur à l’événement Iappel(I,K). Soit FctOdr(J,N), le nième événement fonction d’ordre de l’objet 2. L’analyseur parcourt de 1 à N le fichier trace jusqu’à obtenir la condition suivante :

Ui.(Iappel(I,K)à VecteurH) < Ui. .(Iappel(J,N)à VecteurH)

Avec :

Iappel(I,K)à VecteurH : l’horloge vectorielle associée à l’événement Iappel(I,K) ;

Iappel(J,N)à VecteurH : l’horloge vectorielle associée à l’événement Iappel(J,N) ;

Ui : le vecteur unitaire dont la i ème composante est égale à 1 ;

" . " : le produit scalaire entre deux vecteurs.

3.2.3. Le traitement d’un token de type " FinM ".

Comme pour le paragraphe précédent, lorsque l’analyseur rencontre une fonction d’ordre de type FinM (6), il la traite et peut changer de trace si cet événement survient à la suite de l’exécution d’une méthode distante.

Dans ce cas, il passe de la trace de l’objet invoqué à celle de l’objet qui a effectué l’invocation grâce la référence IOR de l’objet appelant, contenue dans l’argument 4 de la fonction d’ordre FinM (7). Il traite alors l’unité lexicale suivante qui est une fonction d’ordre de type Retour_Iappel (8).

 

4. LES RESULTATS DE L’ANALYSE.

Le procédé décrit précédemment s’arrête après que toutes les traces générées par le programme étudié ont été parcourues. Le résultat de l’analyse se trouve alors soit dans le fichier texte Analyse.trc (voir ANNEXE H) qui contient les relations d’ordre observées, programme AnalyseTraces, soit à l’écran sous la forme d’un arbre des ordres programme et des ordres d’interaction, programme AppletAnalyse (voir figure 7.7 et ANNEXE I).

 

Figure 7.7. arbre des ordres programme et des ordres d’interaction

Un événement de type relation d’ordre apparaît d’une manière séquentielle. Il est décrit en langage naturel, avec le nom de la fonction d’ordre associée, la machine où il se produit et les données le caractérisant (voir annexes H et la figure 7.7).

Les performances sont mesurées pour le programme " Réseau de courtiers " qui s’exécute d’une manière répartie sur deux plates-formes dont les caractéristiques sont les suivantes. Sur le plan matériel, deux ordinateurs personnels sont munis d’un microprocesseur Pentium et d’une mémoire de 64 Mo. An niveau logiciel, ils fonctionnent avec le système d’exploitation LINUX, disposent, à l’aide d’un montage NFS, de la version 1.1.2. de JAVA et de la version 2.0b2 d’OmniBroker. Les temps d’exécution sont mesurés grâce à la commande time et les tailles d’occupation mémoire sont déterminées avec l’utilitaire qps version 1.3 de Mathias Engdegard (men@nada.kth.se). Sur l’une des deux machines les programmes courtier et client sont lancés. Sur l’autre, les programmes serveurs sont exécutés.

D’une part, à cause du nombre important d’objets et de programmes gérés par l’applicatif " Réseau de courtiers ", d’autre part, du fait que tout programme JAVA est interprété et qu’enfin, cette application répartie fonctionne au travers d’un réseau, son temps d’exécution est relativement long : 40 secondes au total. De plus, elle occupe en mémoire près de 93 Mo, dont 6 Mo pour un programme courtier (7 x 6), 6,5 Mo pour un programme serveur (7 x 6,5) et 5,5 Ko pour le programme client. Une première machine supporte les programmes courtier et les programmes client, soit 7 objets courtier, une deuxième machine implante les programmes serveur, soit 8 objets serveur. Les temps d’exécution du programme client sont représentés dans les tableaux 7.2 et 7.3 de la manière suivante : Minutes:Secondes.1/100.

 

Mesures 1

Mesures 2

Mesures 3

Mesures 4

Mesures 5

 

Lancer_client

0:02.96

0:03.19

0:03.21

0:03.14

0:03.03

 
 

Mesures 6

Mesures 7

Mesures 8

Mesures 9

Mesures 10

 

Lancer_client

0:02.91

0:02.92

0:03.28

0:03.17

0:02.95

 

Moyenne

         

0 :03.76

Tableau 7.2. Mesures des temps d’exécution du programme client

L’observation du " Réseau de courtiers " amplifie les temps d’exécution et l’occupation mémoire.

Une fois l’application instrumentée, son nombre d’objets passe de 15 à 45 (30 + 15). En effet tous ses objets et programmes, au nombre de 30, sont observés à l’aide de 30 objets trace. Ils se répartissent de la manière suivante : 21 (7 + 2 x 7) pour les programmes courtier, 23 (7 + 2 x 8) pour les programmes serveur et 1 pour le programme client. Ainsi, l’une des machines abrite 22 objets et l’autre 23.

De plus, à cause d’accès disque répétés et dus à l’écriture des relations d’ordre dans les fichiers trace, son temps d’exécution augmente fortement. Par exemple, le temps d’exécution du programme client est multiplié par plus de cent. Il passe en moyenne de 0,06 à 7 minutes (voir tableau 7.3).

 

Mesures 1

Mesures 2

Mesures 3

Mesures 4

Mesures 5

 

Lancer_client

6:26.37

7:44.60

7:35.25

7:19.00

7:50.69

 
 

Mesures 6

Mesures 7

Mesures 8

Mesures 9

Mesures 10

 

Lancer_client

7:01.37

6:28.49

6 :18.63

6:45.16

6:27.48

 

Moyenne

         

6 :59.07

Tableau 7.3. Mesures des temps d’exécution du programme client instrumenté

La taille d’occupation mémoire croit de 20%. Elle atteint la valeur de 112,5 Mo, dont 7 Mo (7 x 7) pour un programme courtier, 8 Mo pour un programme serveur (7 x 8), et 7,5 Mo pour le programme client.

Après avoir présenté l’expérimentation réalisée sur le programme distribué " Réseau de courtiers ", examinons dans le chapitre suivant une évolution possible de notre outil d’analyse de traces vers des ordres liés à la concurrence entre les objets répartis.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PARTIE 4 : LES PERSPECTIVES DE DEVELOPPEMENT.

 

CHAPITRE 8 : LES EVOLUTIONS ENVISAGEES.

 

1. INTRODUCTION.

Dans le chapitre précédent, nous avons mis au point une technique de traces destinée aux applications distribuées à l’aide d’un bus logiciel conforme à CORBA. Ce mécanisme d’observation prend en compte des événements liés, d’une part, aux ordres programmes, début, fin d’exécution d’un programme JAVA et création d’objet JAVA, et d’autre part, aux ordres d’interaction : invocation, début, fin et retour d’invocation d’une méthode.

Cet outil de mise au point est adapté pour toute application répartie qui réalise des exécutions qui ne comporte pas de parallélismes forts. En effet, il crée un ordre total entre les événements qui obéissent à la relation d’ordre définie dans le chapitre 5 mais ne gère pas, par exemple, une exécution entrelacée d’une même méthode. De même, l’ordre transactionnel lié à l’accès aux variables en exclusion mutuelle et l’ordre intra-objet qui visualise la concurrence pour l’accès à une méthode n’ont pas été, faute de temps, implantés.

Pourtant, il est intéressant d’envisager la mise en évidence de ces ordres pour l’évolution possible de cet outil. Le but de cette partie est de donner quelques pistes techniques pour le faire évoluer vers l’observation d’objets concurrents.

Le présent chapitre est composé des paragraphes suivants. La visualisation de nouveaux événements à tracer nécessite, d’une part, l’ajout de méthodes aux objets de type trace et d’autre part, la modification de l’analyseur lexical qui doit pouvoir analyser de nouvelles fonctions d’ordre. Enfin, de nouvelles sondes logicielles sont à implanter au niveau du code source afin de pouvoir tracer les événements présentés ci-dessous.

 

2. D’AUTRES TYPES D’EVENEMENTS A TRACER.

De nouveaux mécanismes d’observation doivent prendre en compte les événements liés, d’une part, aux ordres locaux, début et fin d’exécution d’un processus léger JAVA, d’autre part aux ordres transactionnels, lecture/écriture d’une variable critique et enfin ceux associés aux ordres d’interaction : début, fin d’une méthode jumelle et synchronisation entre des fils d’exécution.

Relation d’ordre tracée

Type d’évènements tracé

Ordre local

Création d’un fil d’activité (thread)

Ordre local

Début d’exécution d’un fil d’activité

Ordre local

Terminaison d’un fil de contrôle

Ordre transactionnel

Opération d’écriture sur une variable d’instance d’un objet

Ordre transactionnel

Opération de lecture sur une variable d’instance privée d’un objet

Ordre intra-objet

Début d’une méthode jumelle

Ordre intra-objet

Fin d’une méthode jumelle

Ordre intra-objet

Attente d’un message de synchronisation

Ordre intra-objet

Envoi d’un message de synchronisation

Tableau 8.1. Ordres programme et ordres d’interaction

3. LES MODIFICATIONs APPORTEES AUX OBJETS DE TYPE TRACE.

Les objets trace doivent implanter de nouvelles méthodes de type fonction d’ordre afin de tracer les événements présentés dans le paragraphe précédent. Elles sont présentées dans les deux tableaux ci-dessous.

No

Type d’événements tracé

Nom de la méthode de trace

Arguments

Description

8

Début d’une méthode jumelle

DebutMJ

(String Nom_methode,

String Nom_methode_appelante, String Ref_objet_appelant)

Méthode appelée en début d’exécution de méthode jumelle, qui permet d’en signaler le commencement. Elle prend en paramètres : Nom_methode qui est nom de la méthode qui débute ; Nom_methode_appelante qui est le nom de la méthode appelante ou du programme appelant ; Ref_objet_appelant qui est la référence IOR de l'objet appelant. 

9

Fin d’une méthode jumelle

FinMJ

(String Nom_methode,

String Nom_methode_appelante, String Ref_objet_appelant)

Elle signale la fin d’exécution d’une méthode jumelle. Elle prend en paramètres : Nom_methode qui est le nom de la méthode qui se termine ; Nom_methode_appelante qui est le nom de la méthode appelante ou du programme appelant.

10

Opération d’écriture sur une variable d’instance privée d’un objet

Evar

(Vecteur V_ecriture (String Nom_variable),

String Nom_méthode)

Elle notifie une opération de lecture sur la variable Nom_variable. Les autres paramètres sont : V_lecture qui est le vecteur contenant l’estampille de la dernière opération d’écriture sur cette variable ; Nom_méthode qui est le nom de la méthode ou du programme qui manipule cette variable.

11

Opération de lecture sur une variable d’instance privée d’un objet

Lvar

(Vecteur V_lecture(String Nom_variable),

String Nom_méthode)

Elle notifie une opération d’écriture sur la variable Nom_variable. Les autres paramètres sont : V_lecture qui est le vecteur contenant l’estampille de la dernière opération de lecture sur cette variable ; Nom_méthode qui est le nom de la méthode ou du programme qui manipule cette variable.

Tableau 8.2. Les différentes méthodes pour tracer les ordres programme et d’interaction

 

No

Type d’événements tracé

Méthode

Arguments

Description

12

Création d’un fil d’activité (thread)

Creat_thread

(String Nom_thread)

Notification de la création d’un fil d’activité. Elle prend en argument le nom du processus créé.

13

Début d’exécution d’un fil d’activité

DebutF

(String Nom_thread)

Notification du lancement de l’exécution d’un fil d’activité à l’aide de sa méthode start(). Elle prend en paramètre Nom_thread, le nom du fil d’activité qui commence à s’exécuter.

14

Terminaison d’un fil de contrôle

FinF

(String Nom_thread)

Notification de l’arrêt de l’exécution d’un fil d’activité à l’aide de sa méthode stop(). Elle prend en paramètre Nom_thread, le nom du fil d’exécution qui se termine.

15

Attente de la mort d’un thread donné à l’aide de la méthode join()

Tjoin

(String Nom_methode_appelante, String Ref_thread_appele)

Notification de la mort d’un thread donné à l’aide de la méthode join(). Elle prend en paramètres : Nom_methode_appelante qui est le nom de la méthode appelante ; Ref_thread_appele qui est la référence IOR du processus léger dont la fin est attendue par la méthode appelante.

16

Attente d’un message de synchronisation

Twait

(String Nom_methode,

String Nom_methode_appelante,

String Ref_objet_appelant)

Notification de l’invocation de la méthode de synchronisation wait(). Elle prend en paramètres : Nom_methode, le nom de la méthode qui invoque la méthode wait() ; Nom_methode_appelante qui est le nom de la méthode appelante ; Ref_objet_appelant qui est la référence IOR de l’objet appelant.

17

Envoi d’un message de synchronisation

Tnotify

(String Nom_methode,

String Nom_methode_appelante,

String Ref_objet_appelant)

Notification de l’invocation de la méthode de synchronisation notify() ou notifyAll(). Elle prend en paramètres : Nom_methode, le nom de la méthode qui invoque la méthode wait() ; Nom_methode_appelante qui est le nom de la méthode appelante ; Ref_objet_appelant qui est la référence IOR de l’objet appelant.

 

Tableau 8.3. Les différentes méthodes pour tracer les ordres programme et d’interaction (suite)

4. DES SONDES LOGICIELLES SUPPLEMENTAIRES.

Afin de produire les traces des événements supplémentaires, décrits précédemment, les sondes logicielles présentées dans les paragraphes suivants peuvent être mises en œuvre. Les exemples utilisés sont tirés de l’application " Producteur/Consommateur " donnée en ANNEXE J.

4.1. TRACE DES METHODES SUPPORTANT UNE SYNCHRONISATION.

Il s’agit typiquement de tracer les files d’attente des méthodes possédant le modificateur synchronized du programme JAVA. Dans notre exemple, la méthode get est libre de toute contrainte liée à la concurrence et c’est la méthode jumelle, JUM_get, qui est, elle, synchronisée. Cela permet de voir les états des files d’attente sur l’exclusion mutuelle.

public int get(Trace TRACE, String Nom_methode_appelante,String Ref_objet_appelant) {

//---

// TRACE.DebutM : début de la methode get

//---

TRACE.DebutM("get",

String Nom_methode_appelante,

String Ref_objet_appelant);

//---

// Appel de la méthode jumelle de JUM_get

//---

this.JUM_get(TRACE, Nom_methode_appelante, Ref_objet_appelant) ;

//---

// TRACE.FinM : fin de la methode get

//---

TRACE.FinM("get",String Nom_methode_appelante,String Ref_objet_appelant);

}

 

public synchronized int JUM_get(Trace TRACE,

String Nom_methode_appelante,

String Ref_objet_appelant) {

//---

// TRACE.DebutMJ : début de la methode jumelle de get, JUM_get

//---

TRACE.DebutMJ("JUM_get",

String Nom_methode_appelante,

String Ref_objet_appelant);

//---

// Code de la méthode

//---

//---

// TRACE.FinMJ : fin de la méthode jumelle de get, JUM_get

//---

TRACE.FinMJ("JUM_get",

String Nom_methode_appelante,

String Ref_objet_appelant);

return contents;

}

 

4.2. TRACES DES ACCES AUX DONNEES PARTAGEES.

L’objectif poursuivi est de s’assurer que nos instructions de trace correspondent bien à des opérations effectives sur les données tracées [Placide 95a].

Dans ce cas, il faut gérer un vecteur pour connaître les dépendances transactionnelles pour toute variable partagée. Pour des opérations de lecture ou d’écriture sur une variable les dépendances transitionnelles sont les suivantes :

Ainsi, chaque nouvelle opération d’écriture définie une nouvelle période dans la vie d’une variable partagée. En effet, lorsqu’une opération d’écriture est observée, les dépendances créées par les opérations précédentes sont prises en compte par transitivité. Compte tenu de cette remarque, les informations suivantes sont nécessaires et suffisantes pour tracer les accès à toute variable partagée :

Dans notre exemple, un objet de type CubbyHole dispose de deux méthodes pour manipuler la donnée partagée contents dont nous souhaitons tracer l’accès. La méthode JUM_put affecte une valeur à la variable contents. La méthode JUM_get récupère cette valeur. Les accès à la variable contents peuvent être observés à l’aide de sondes logicielles qui apparaissent en caractères gras dans le source ci-dessous.

public synchronized int JUM_get(Trace TRACE,

String Nom_methode_appelante,

String Ref_objet_appelant) {

//---

// TRACE.DebutMJ : début de la methode jumelle de get, JUM_get

//---

TRACE.DebutMJ("JUM_get",

String Nom_methode_appelante,

String Ref_objet_appelant);

//---

// Code de la méthode

//---

//---

// TRACE.FinMJ : fin de la méthode jumelle de get, JUM_get

//---

TRACE.FinMJ("JUM_get",

String Nom_methode_appelante,

String Ref_objet_appelant);

//---

//TRACE.Lvar : accès en lecture de la variable contents

//---

TRACE.Lvar(Vecteur( "contents"),"JUM_get") ;

return contents;

}

public synchronized int JUM_put(Trace TRACE,

String Nom_methode_appelante,

String Ref_objet_appelant,

int value) {

//---

// TRACE.DebutMJ : début de la methode JUM_put

//---

TRACE.DebutMJ("JUM_put",

String Nom_methode_appelante,

String Ref_objet_appelant);

//---

// Code de la méthode

//---

//---

//TRACE.Evar : accès en écriture de la variable contents

//---

TRACE.Evar(Vecteur( "contents"),"JUM_get") ;

contents = value;

available = true;

notifyAll;

//---

// TRACE.FinMJ : fin de la methode JUM_put

//---

TRACE.FinMJ("JUM_put",

String Nom_methode_appelante,

String Ref_objet_appelant);

}

 

4.3. TRACE DE L’ACTIVITE D’UN PROCESSUS LEGER.

Le but de cette trace est de notifier le lancement et l’arrêt de l’exécution d’un fil d’activité. Pour cela, on peut instrumenter les méthodes start(), stop() ou join() de la classe Thread ou Appletimplementation.

Un fil d’exécution peut être instrumenté pour tracer sa création, son lancement et sa synchronisation éventuelle avec d’autres processus légers.

 

4.3.1. INSTRUMENTATION DU PROCESSUS LEGER.

L’instrumentation d’un processus léger se réalise de la même façon que pour un objet (voir § 1.3.2 du chapitre 7). Elle lui apporte des données, des constructeurs et des fonctions spécifiques.

Un fil de contrôle instrumenté doit disposer de la donnée TRACE qui correspond à son objet trace. De plus, il doit disposer d’un constructeur dont les trois arguments sont les suivants : nom du fichier trace associé au fil d’activité tracé, nom du créateur du processus léger et son numéro qui est utilisé par l’horloge vectorielle. Le constructeur du fil de contrôle observé est ainsi modifié en y insérant l’instanciation d’un objet de type trace. Ainsi tout thread observé, nouvellement créé, génère automatiquement un objet trace. Enfin, les processus légers instrumentés doivent disposer de fonctions spécifiques données dans le 1.3.2.3 du chapitre 7.

Le processus léger de type producteur, donné en ANNEXE J, peut être instrumenté de la manière suivante.

public class Producer extends Thread {

private CubbyHole cubbyhole;

private int number;

//---

//TRACE : definition de l'objet Trace de l'objet Courtier

//---

private Trace TRACE;

//---

//TRACE : fonctions manipulant l’objet Trace

//---

//---

// Code des fonctions manipulant l’objet Trace

//---

//---

// TRACE : modification du constructeur

//---

public Producer(String Nom_fichier_trace,

String Nom_createur,

int Numero,

CubbyHole c,

int number) {

cubbyhole = c;

this.number = number;

//---

//TRACE : initialisation de l'objet Trace de l'objet Courtier

//---

Trace_impl ObjetTrace=new Trace_impl(Nom_fichier_trace, Nom_createur, Numero);

putTRACE(ObjetTrace);

}

public void run() {

//---

// Code de la methode run()

//---

}

}

}

 

4.3.2. CREATION, LANCEMENT ET FIN D’UN PROCESSUS LEGER.

Le programme JAVA ProducerConsumerTest.java de l’ANNEXE J lance un processus léger de type producteur et un autre de type consommateur. Les sondes logicielles qui permettent l’observation de la création, du lancement et de la terminaison du fil d’activité producteur, instrumenté dans le paragraphe précédent, apparaissent en caractères gras dans les codes source suivants.

Programme ProducerConsumerTest.java

public class ProducerConsumerTest {

public static void main(String[] args) {

ORB orb = ORB.init( args, new java.util.Properties() );

BOA boa = orb.BOA_init( args, new java.util.Properties() );

//---

// TRACE : generation de l'objet de trace

// associe au programme ProducerConsumerTest

//---

Trace_impl TProducerConsumerTest = newTrace_impl("ProducerConsumerTest.trc",

"ProducerConsumerTest.java",

0);

//---

// Code

//---

//---

// TRACE.Creat_thread : creation du thread p1 de type producteur

//---

TProducerConsumerTest.Creat_thread("p1");

Producer p1 = new Producer("p1.trc","ProducerConsumerTest",2,c, 1);

//---

// TRACE.putNomMachine : recuperation du nom et du port de la machine

//---

p1.putNomMachine(Machine);

//---

//---

// TRACE.GetRef_obj : recuperation de la reference de l'objet Trace cree

//---

String Ref_p1_Ptr = orb.object_to_string(p1.getTRACE());

p1.putRef(Ref_p1_Ptr);

p1.GetRef_obj("p1");

//---

//---

// TRACE.DebutF : lancement de l’exécution du thread p1

//---

TProducerConsumerTest.DebutF("p1");p1.start();

//---

// Code

//---

}

Programme Producer.java

public class Producer extends Thread{

//---

// Code

//---

public void stop(){

//---

// TRACE.FinF : fin de l’exécution d’un fil d’activité

//---

TRACE.FinF(TRACE.getNomObj()) ;

}

}

 

4.4. TRACE DE LA SYNCHRONISATION ENTRE PROCESSUS LEGERS.

Ces instrumentations sont destinées à la notification des invocations des méthodes de synchronisation telles que wait(), notify(), notifyAll() ou join().

Dans l’exemple proposé, deux sondes logicielles permettent de tracer les méthodes wait() et notifyAll() de la méthode synchronisée JUM_put. Elles apparaissent en gras dans le code source suivant.

public synchronized void JUM_put(Trace TRACE,

String Nom_methode_appelante,

String Ref_objet_appelant,

int value) {

//---

// TRACE.DebutMJ : début de la methode put

//---

TRACE.DebutMJ("JUM_put",

String Nom_methode_appelante,

String Ref_objet_appelant);

while (available == true) {

try {

//---

// TRACE.Twait : trace de la methode wait

//---

TRACE.Twait("JUM_put",

String Nom_methode_appelante,

String Ref_objet_appelant);

wait();

} catch (InterruptedException e) { }

}

contents = value;

available = true;

//---

// TRACE.Tnotify : trace de la methode notify

//---

TRACE.Tnotify.("JUM_put",

String Nom_methode_appelante,

String Ref_objet_appelant);

NotifyAll();

//---

// TRACE.FinMJ : fin de la methode JUM_put

//---

TRACE.FinMJ("JUM_put",

String Nom_methode_appelante,

String Ref_objet_appelant);

}

 

5. LA MODIFICATION DE L’ANALYSEUR LEXICAL.

 

Les fonctions d’ordre suivantes, liées aux nouveaux ordres tracés, doivent être reconnues par l’analyseur lexical JLex. La grammaire de JlexOdr doit être enrichie des expressions régulières suivantes.

 

5.1. LA FONCTION D’ORDRE " DebutMJ ".

Elle est composée des arguments suivants.

DebutMJ"("{VECTEUR}","{ALPHADIGIT}","{NF_OU_AD}","{IOR}","{IOR}")"

 

5.2. LA FONCTION D’ORDRE " FinMJ ".

Elle est composée des arguments suivants.

FinMJ"("{VECTEUR}","{ALPHADIGIT}","{NF_OU_AD}","{IOR}","{IOR}")"

 

5.3. LA FONCTION D’ORDRE " Evar ".

Elle est composée des arguments suivants.

Evar"("{VECTEUR}","{VECTEUR}","{NF_OU_AD}","{IOR}")

 

5.4. LA FONCTION D’ORDRE " Lvar ".

Elle est composée des arguments suivants.

Lvar"("{VECTEUR}","{VECTEUR}","{NF_OU_AD}","{IOR}")"

 

5.5. LA FONCTION D’ORDRE " Creat_thread ".

Elle est composée des arguments suivants :

Creat_thread"("{ALPHADIGIT}","{IOR}")"

 

5.6. LA FONCTION D’ORDRE " DebutF ".

Elle est composée des arguments suivants.

DebutF"("{VECTEUR}","{ALPHADIGIT}","{IOR}")"

5.7. LA FONCTION D’ORDRE " FinF ".

Elle est composée des arguments suivants.

FinF "("{VECTEUR}","{ALPHADIGIT}","{IOR}")"

 

5.8. LA FONCTION D’ORDRE " Tjoin ".

Elle est composée des arguments suivants.

FinF "("{VECTEUR}","{ALPHADIGIT}","{IOR}","{IOR}")"

 

5.9. LA FONCTION D’ORDRE " Twait ".

Elle est composée des arguments suivants.

Twait"("{VECTEUR}","{NF_OU_AD}","{NF_OU_AD}","{IOR}","{IOR}")"

 

5.10. LA FONCTION D’ORDRE " Tnotify ".

Elle est composée des arguments suivants.

Tnotify"("{VECTEUR}","{NF_OU_AD}","{NF_OU_AD}","{IOR}","{IOR}")"

Tous ces éléments supplémentaires permettront de tracer un arbre d’état global et un graphe des dépendances transactionnelles. Les perspectives suivantes peuvent s’orienter vers le développement d’un langage d’interrogation de traces qui vérifie les propriétés telles que la vivacité sur une exécution.

 

 

CONCLUSION.

 

1.L’OBJECTIF.

JAVA connaît, avec le développement du World Wide Web, une utilisation de plus en plus accrue. Il renferme tous les paradigmes d’un langage orienté objets répartis et est relativement facile à utiliser. Ainsi, ce langage de programmation est promis à un bel avenir dans le développement d’applications distribuées. CORBA apporte un modèle uniforme de structuration des ressources ou services. Les propriétés de l’approche orientée objet de CORBA permettent de concevoir des ressources modulaires, structurées, encapsulées et réutilisables. L’association de JAVA et CORBA permet de créer des objets JAVA distribués qui atteignent un haut niveau de coopération entre eux mais qui sont difficilement observables.

Le but de cette étude est de proposer, au développeur, un outil spécialisé pour étudier, d’une façon post-mortem, les relations d’ordre d’une application distribuée qui est développée dans un environnement JAVA/CORBA.

Dans un univers à objets répartis, nous avons repris la définition d’une relation de causalité fondée sur l’ordre programme et l’ordre d’interaction [Coste et al 98]. Elle nous a permis, d’une part, d’identifier les événements à enregistrer et d’autre part, de reconstituer un ordre partiel, entre les événements, qui est visualisé à l’aide de graphes de dépendances causales.

 

2. REALISATION.

Une librairie JAVA spécialisée, Trace, qui contient les classes nécessaires à la gestion des traces, a été développée. L’analyse des traces d’un programme observé s’appuie également sur deux autres librairies, JLex et Tree, qui ont été modifiées et personnalisées pour les besoins de cette étude.

Les outils de type Trace sont utilisés par le développeur pour instrumenter, dans un premier temps, l’application observée. Dans un second temps, celle-ci est exécutée. Dès qu’elle se termine et alors que tous les objets observés sont encore présents en mémoire, il lance l’analyse des traces générées par les objets et programmes instrumentés.

Une technique d’analyse a été mise au point. L’analyse des traces peut se réaliser au moyen de deux programmes. AnalyseTraces écrit dans le fichier Analyse.trc les ordres programme et les ordres d’interaction de l’application observée. AppletAnalyse affiche à l’écran le graphe de dépendances des objets et programmes débogués.

Cette technique de visualisation post-mortem des relations d’ordre d’une application JAVA/CORBA a été mise en œuvre à l’aide d’un compilateur JAVA 1.1, du bus logiciel OmniBroker version 2.0b2 conforme à la norme CORBA et sur une architecture distribuée qui est composée de plates-formes de type ordinateur personnel sous LINUX. Une application orientée objets répartis, le " Réseau de courtiers ", a été développée puis instrumentée pour étudier la viabilité de cette technique.

 

 

3. BILAN.

Cet outil de déboguage post-mortem permet de visualiser une exécution au travers d’une trace et d’arriver à une bonne compréhension du comportement du programme observé. Néanmoins, il a besoin de gérer une grande quantité d’informations. De plus, la génération et la sauvegarde des traces provoquent un changement de comportement du programme observé : son temps d’exécution augmente fortement.

Sur le plan performance, dans un environnement réparti d’ordinateurs personnels munis du système d’exploitation LINUX, il est à remarquer que le programme d’analyse AppletAnalyse est près de quatre fois plus rapide que le moteur d’analyse AnalyseTrace. En effet, le premier s’exécute en 0,8 minutes alors que le second donne le résultat de l’analyse au bout de 3 minutes.

Cette différence de temps d’exécution entre ces deux applications JAVA s’explique de la façon suivante. La première, AppletAnalyse, envoie directement à l’écran le résultat de l’analyse sous la forme d’un arbre. Par contre, AnalyseTrace effectue des écritures successives dans le fichier Analyse.trc réalisant ainsi des accès disques multiples qui la pénalisent au niveau de son temps d’exécution.

Cette étude comparative montre clairement que de simples opérations d’écriture dans un fichier augmentent fortement le temps d’exécution d’un programme observé grâce à une technique de traces. Ce phénomène croit d’une façon exponentielle lorsque tout programme ou objet d’une application en phase de mise au point, dispose d’un fichier trace comme cela est le cas dans notre étude.

A part les réserves liées à l’augmentation du temps d’exécution et à l’occupation mémoire dues respectivement et principalement d’une part, à la technique de traces et à la nature interprétée du langage JAVA et d’autre part, à l’ORB utilisé, les outils développés dans ce mémoire peuvent être utilisés sans encombre pour visualiser les relations d’ordre d’applications JAVA développées dans un environnement réparti conforme à CORBA.

 

3. PERSPECTIVES.

Notons tout d’abord que l’instrumentation du code source est lourde et est une source d’erreurs potentielles. Une amélioration pourrait conduire à l’insertion automatique des sondes logicielles dans le code source des programmes et des objets instrumentés.

Ensuite, la partie expérimentale de ce mémoire ne traite pas d’objets répartis et concurrents. La notion d’exécutions parallèles et concurrentes pourrait être mise en place dans la version future de cet outil en y intégrant les ordres intra-objet et transactionnels.

 

 

Bibliographie

 

 

[Atkinson] C. Atkinson. " Object Oriented reuse concurrency and distribution ". ACM press, Addison Wesley.

[API JAVA] SUN Microsystems " Les bibliothèques du langage JAVA ".

http://deptinfo.cnam.fr:8080/Ressources/JAVA/jdk-1.1/docs/api/packages.html

[Badri 96] M. Badri, L. Badri, " Tests fonctionnels des classes : une approche incrémentale basée sur le comportement des objets ", Génie Logiciel, no. 42, pp. 145-154, décembre 1996.

[Badri 95] M. Badri, L. Badri et S. Layachi. " Vers une stratégie des tests unitaires et d’intégration des classes dans les applications orientées objets ". Génie Logiciel, no 38, pp. 28-40, décembre 1995.

[Balter 91] R. Balter, J.P. Banâtre, S. Krakowiak (Editeurs). " Constructions des systèmes d’exploitation réparti. Collection Didactique, 1991.

[Bergdolt 97] F. Bergdolt, " De la spécification au déverminage : vérification dans un univers à objets répartis ". Mémoire d’ingénieur CNAM. Laboratoire CEDRIC, 1997.

[Bergdolt 97a] F. Bergdolt, L. Duchien, G. Florin and L. Seinturier. " From specification to the Debug . A distributed Application Trace Construction and its Use in a Distributed Oriened Object Environnenent ". 2nd European Research Seminar on Advances in Distributed Systems (ERSADS’97), Zinal, Switzerland.March 97.

[Berk 97] E. J. Berk, " L’analyseur lexical JLex ", octobre 1997.

http://www.cs.princeton.edu/~appel/modern/JAVA/JLex/

[Bonnet 97] L. Bonnet, L. Duchien, G. Florin and L. Seinturier. " Some specification steps of spanning tree algorithm with an Object-Oriented approach ". In proceedings of the 1st IFIP Workshop on Formal Methods for open Object Based Distributed Systems (FMOODS’ 96), pages 115-131. Chapman & Hall, 97.

[Carmichael 94] A. Carmichael. " Object Developpement Methods ". Advances in Object Technology. SIGS Books Inc., 588 Broadway, Suite 604, NY 10012, USA, mai 1994.

[Chaumette 94] S. Chaumette. " Neutral debugging using grafts ". Information and Software Technology, page 465-470.

[Clavel 97] G. Clavel, N. Mirouze, E. Pichon et M. Soukal. " JAVA. La synthèse ". InterEdition, 1997.

[Coste et al 98] C. Coste, L. Duchien, G. Florin et L. Seinturier. " Les Relations d’Ordre Partiel dans un Environnement Coopératif : une Approche pour Définir des Protocoles de Communication sur Groupe ". Mai 1998. Soumise à la revue " Calculateurs parallèles ".

[Duchien 98] L. Duchien, L. Seinturier. " Interopérabilité dans les Environnements à Objets distribués : CORBA ". Polycopie de Cours CNAM, 1998.

[Farinone 97] J.-M. Farinone. " Une journée JAVA ".

http://cedric.cnam.fr/~farinone/JAVA2810

[Fidge 88] C.J. Fidge. " Partial orders for parallel debugging ". In proceedings of the A.C.M. Workshop on Parallel and Distributed Debugging, page 183-194, 1988.

[Fowler 90] J. Fowler and W. Zwaenepoel. " Causal distributed breakpoints ". In Proceedings of the Tenht Intenational Conference on Distributed Computing Systems, pages 134-141, Paris, mai 1990.

[Geib 1997] J.-M. Geib, C. Gransart et P. Merle. 

" CORBA Des concepts à la pratique ". InterEditions, 1997.

[Guerraoui 95] R. Guerraoui. " Les langages concurrents à objets ". TSI 95.

[Hoare 74] C.A.R. Hoare. " Monitors : an operating system structuring concept ". Communication of the ACM, 17 (10), pages 549-557, octobre 1974.

[JAVA 90] SUN Microsystems. " JAVA Tutorial "

http://JAVA.sun.com/docs/books/tutorial/index.html

[Koch 95] T. Koch. " Tree : un visualiseur d’arbres ",1995.

http://orgwis.gmd.de/~koch/JAVA/Applets/TreeTool/

[Lamport 78] L. Lamport. " Time, clocks and the ordering of events in a distributed system ". Communication of the ACT, Vol. 21, pages 558-565, juillet 1978.

[Letondal 92] C. Letondal." Observation et mise au point d’applications parallèles ". Rapport de recherche du CNAM, Paris, octobre 1992.

[Mattern 89] F. Mattern, M. Cosnard, P. Quinton, M. Raynal and Y. Robert. " Virtual time and global states of distributed systems ". In Parallel and Distributed Algorithms, pages 215-226, North-Holland, octobre 1988

[Meyer 88] B. Meyer. " Object-Oriented Software Construction ". PrenticeHall, 1988.

[Miller 88] B.P. Miller, Helmbold. " Breakpoints and halting in distributed programs ". In Proceeding of Eight International Conference on Distributed Computing Systems, pages 326-323, 1988.

[OMG 97] The Commun Object Broker : Architecture and Specification,

Revision 2.0, Document de l’OMG du 25/02/97.

[OMG-CORBA] La norme CORBA de l’OMG.

http://www.omg.org/CORBA_Primer.html

[OMG-IDL 97] IDL/JAVA language Mapping,

Document de l’OMG du 01/03/97.

[OOC] Object Oriented Concepts Inc., OmniBroker

http://ooc.ooc.com.ob

[Orfali 94] R. Orfali, D. Harkey, J. Edwards. " Client/Serveur guide de survie " - Edition International Thomson Publishing, 1994 .

[Orfali 96] R. Orfali, D. Harkey, J. Edwards. " The Essential Distributed Objects Survival Guide " - Edition WILEY, 1996 .

[Orfali 97] R. Orfali, D. Harkey, J. Edwards. " Client and Server programming with CORBA and JAVA " - Edition WILEY, 1997 .

[Placide 95] P. Placide. " Mise au point d’applications réparties, Outil de visualisation ". Mémoire de DEA, Laboratoire CEDRIC, 1995.

[Placide 95a] P. Placide, L. Duchien, G. Florin and L. Seinturier. " A consistent Global State Algorithm to Debug Distributed Object-Oriented Applications ". In Proceeding of the 2nd International Workshop on Automated and Algorithmic Debugging (AADEBUG’95). May 1995. Extented version of [PLACIDE 95b].

[Placide 95b] P. Placide, L. Duchien, G. Florin and L. Seinturier. " Debugging of distributed object oriented applications ". In Proceeding of the European Research Seminar on Advances in Distributed Systems (ERSADS’95), pages 200-207, April 1995.

[Prun 98] D. Prun. " Méthodologie de conception de composants logiciels coopératifs : une approcche pour l’observation, la mise au point et la maintenace évolutive d’applications réparties ". Thèse de doctorat de l’université Paris 6, Paris, mars 1998.

[Roos 94] J.-F. Roos. " Réexécution de programmes parallèles ". Thèse de doctorat de l’Université des Sciences et Technologies de Lile. Laboratoire d’Informatique Fondamentale de Lille (LIFL-CNRS), 1994.

[Seinturier 96] L. Seinturier, L. Duchien et G. Florin. " Design and implementation of distributed oo application with meta-objet protocol ". Technical Report RRC-96-22 ; CNAM-CEDRIC, octobre 1996.

ftp.cnam.fr/pub/CNAM/cedric/tech_reports/RRC_96_22.ps.gz

[Seinturier 98] L. Seinturier. " Conception d’algorithmes répartis et de protocoles réseaux en approche objet ". Thèse de doctorat de Docteur du Conservatoire National des Arts et Métiers.

[Siegel 97] John Siegel. " CORBA Fundamentals and programming "

Edition WILEY,1997 .

[Vogel 97] A. Vogel and K. Duddy. " JAVA Programming with CORBA ". OMG, 1997.

[Wegner 87] P. Wegner. " Dimensions of object-based language design ". In proceeding of the 2nd Conference on Object-Oriented Programming : Systems, Languages and Applications (OOPSLA’87), Volume 22 of SIGPLAN Notices, pages 168-182. ACM Press, December 1997.

[Wollrath 96] A. Wollrath, R. Riggs and J. Waoldo. " A Distributed Object Model for the JAVA System ". In Proceeding of the second Conference on Object-Oriented Systems and Technologies (COOST), Toronto, Canada, June 17-21, 1996, pages 219-231. USENIX Association 1996.

 

 

 

 

 

 

 

 

 

 

 

 

 

ANNEXES

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PARTIE 1 : LE RESEAU DE COURTIERS.

 

 

 

 

ANNEXE A :

Programme client.java

// -----------------------------------------------------------------------------// Programme client// Recupere l'IOR du serveur et invoque des methodes des serveurs et du courtier// -----------------------------------------------------------------------------

package monCourtier;

// ----

// Inclusion des classes CORBA de base

// ----

import org.omg.CORBA.*;

import JAVA.io.*;

public class client

{

public static void main( String args[] )

{

//---

// Declaration des variables

// Elle est indispensable pour pouvoir generer les objets distribues

//---

String date11,date12,date21,date22,date31,date32;

String date41,date42,date51,date52,date61,date62,date71,date72;

//vis a vis du courtier

Courtier Courtier1Ptr, Courtier2Ptr,Courtier3Ptr,Courtier4Ptr,Courtier5Ptr,Courtier6Ptr, Courtier7Ptr;

//vis a vis du serveur

Server Obj1_Serv1Ptr, Obj1_Serv2Ptr, Obj1_Serv3Ptr,

Obj1_Serv4Ptr,Obj1_Serv5Ptr,Obj1_Serv6Ptr,Obj1_Serv7Ptr;

Server Obj2_Serv1Ptr, Obj2_Serv2Ptr, Obj2_Serv3Ptr,

Obj2_Serv4Ptr,Obj2_Serv5Ptr,Obj2_Serv6Ptr,Obj2_Serv7Ptr;

//---

ORB orb = ORB.init( args, new JAVA.util.Properties() );

BOA boa = orb.BOA_init( args, new JAVA.util.Properties() );

//---

// TRACE : generation de l'objet de trace

//---

Trace_impl TClient = new Trace_impl("client.trc","client.java",0);

//---

// TRACE.putNomMachine : recuperation du nom et du port de la machine

//---

com.ooc.CORBA.BOA OBboa = (com.ooc.CORBA.BOA)boa;

String Machine=" ";

Machine=Machine.concat(OBboa.host());

Machine=Machine.concat(", port");

Machine=Machine.concat(" " + OBboa.port());

TClient.putNomMachine(Machine);

//---

// TRACE.GetRef_obj : recuperation de la reference de l'objet Trace cree

//---

String RefTClient = orb.object_to_string(TClient);

TClient.putRef(RefTClient);

TClient.GetRef_obj("client.java");

//---

//---

// TRACE.DebutE : trace du debut d'execution d'un processus JAVA

//---

TClient.DebutE("client.java","client.java");

//---

//RECUPERATION DES IOR DES SERVEURS

//---

//---

// Pour le code complet voir fichier client.java

//---

//--- //RECUPERATION DES IOR DES COURTIERS //--- //---

// Pour le code complet voir fichier client.java

//---

//--- // INITIALISATION DES OBJETS

//---

//---

// Pour le code complet voir fichier client.java

//---

//--- //IMNVOCATION DE METHODES DISTANTES //--- // ---- TClient.println("CLIENT : Invocation des methodes distantes de la classes monCourtier");

// ----

//---

TClient.println("CLIENT : demande de service aux serveurs et au reseau de courtiers");

//---

//---

TClient.println("Initialisation du reseau de courtiers");

// Tous les courtiers sont positionnes a non visite (Visite=false)

//---

//---

// TRACE.Iappel : invocation d'une methode

//---

TClient.Iappel("InitReseau","client.java",Courtier1Ptr.getRef(),Courtier1Ptr.getTRACE());

//---

Courtier1Ptr.InitReseau(Courtier1Ptr.getTRACE(),"client.java",TClient.getRef(),Courtier1Ptr,Courtier2Ptr,Courtier3Ptr,Courtier4Ptr,Courtier5Ptr,Courtier6Ptr,Courtier7Ptr);

//---

// TRACE.Retour_Iappel : invocation d'une methode

//---

TClient.Retour_Iappel("InitReseau","client.java",Courtier1Ptr.getRef(),Courtier1Ptr.getTRACE());

//---

//---

//Visite du premier courtier

//---

//---

// TRACE.Iappel : invocation d'une methode

//---

TClient.Iappel("QUERY","client.java",Courtier1Ptr.getRef(),Courtier1Ptr.getTRACE());

//---

Obj1_Serv2Ptr=Courtier1Ptr.QUERY(Courtier1Ptr.getTRACE(),"client.java",TClient.getRef(),"temps","machine","S207_2");

//---

// TRACE.Retour_Iappel : invocation d'une methode

//---

TClient.Retour_Iappel("QUERY","client.java",Courtier1Ptr.getRef(),Courtier1Ptr.getTRACE());

//---

if (Obj1_Serv2Ptr==null)

{

System.out.println(" ");

System.out.println("Le service == temps,machine,S207_2 == n'existe pas dans le reseau de courtiers");

}

else {

//date21=Obj1_Serv2Ptr.getDate();

//---

// TRACE.Iappel : invocation d'une methode

//---

TClient.Iappel("getDate","client.java",Obj1_Serv2Ptr.getRef(),Obj1_Serv2Ptr.getTRACE());

//---

date21=Obj1_Serv2Ptr.getDate(Obj1_Serv2Ptr.getTRACE(),"client.java",TClient.getRef());

//---

// TRACE.Retour_Iappel : invocation d'une methode

//---

TClient.Retour_Iappel("getDate","client.java",Obj1_Serv2Ptr.getRef(),Obj1_Serv2Ptr.getTRACE());

//---

System.out.println(" ");

System.out.print("La date Systeme fournie par l'objet ==" + Obj1_Serv2Ptr.getNoObjet());

System.out.println("== du serveur ==" + Obj1_Serv2Ptr.getNoInterne() + "== est : ");

System.out.println(date21);

};

//---

// TRACE.FinE : trace de la terminaison d'un processus JAVA

//---

TClient.FinE("client.java","client.java");

}

}

 

ANNEXE B :

Fichier IDL d’un objet courtier

interface Courtier { //--- //TRACE : manipulation de l'objet Trace //---

//--- // Lecture de l'objet TRACE //--- Trace getTRACE();

//---

// Ecriture de l'objet TRACE

//---

void putTRACE(in Trace ObjetTrace);

//---

// Lecture de la variable TRACE.Reference associee a l'objet Courtier

//---

string getRef();

//---

// Ecriture de la variable TRACE.Reference associee a l'objet Courtier

//---

void putRef(in string Ref_obj);

//---

// TRACE.GetRef_obj : recuperation de la reference et initialisation d'un l'objet Courtier cree

//---

void GetRef_obj(in string Nom_objet);

//---

// Ecriture de la variable TRACE.Machine associee a l'objet Courtier

//---

void putNomMachine(in string Nom_Machine);

//---

//Fin TRACE

//---

Enregistre getPrmrEnregistre(); void ajouterEnregistre(in Enregistre NvllEnregistrePtr);

//void export(in Server ObjServer,in string NomService, in string NomPropriete, in string Valeur);

//TRACE : methode export tracee

void export( in Trace ObjTrace,

in string Nom_methode_appelante,

in string Ref_objet_appelant,

in Server ObjServer,

in string NomService,

in string NomPropriete, in string Valeur); //Server query( in Enregistre PrmrEnregistrePtr,

in string NomService,

in string NomPropriete,

in string Valeur);

//TRACE : methode export tracee

Server query( in Trace ObjTrace,

in string Nom_methode_appelante,

in string Ref_objet_appelant,

in Enregistre PrmrEnregistrePtr,

in string NomService,

in string NomPropriete,

in string Valeur);

//Server QUERY(in string NomService, in string NomPropriete, in string Valeur);

//TRACE : methode QUERY tracee

Server QUERY( in Trace ObjTrace,

in string Nom_methode_appelante,

in string Ref_objet_appelant,

in string NomService,

in string NomPropriete,in string Valeur) ; void putNbVoisin(in long Nombre); long getNbVoisin(); void putVoisin1(in Courtier Obj); void putVoisin2(in Courtier Obj); void putVoisin3(in Courtier Obj); Courtier getVoisin1();

Courtier getVoisin2();

Courtier getVoisin3();

//void putVisite(in boolean Bool);

//TRACE

void putVisite( in Trace TRACE,

in string Nom_methode_appelante,

in string Ref_objet_appelant,

in boolean Bool); boolean getVisite();

void putNoInterne(in long Nombre);

long getNoInterne();

void InitReseau(in Trace TRACE,

in string Nom_methode_appelante,

in string Ref_objet_appelant,

in Courtier Courtier1Ptr,

in Courtier Courtier2Ptr,

in Courtier Courtier3Ptr,

in Courtier Courtier4Ptr,

in Courtier Courtier5Ptr,

in Courtier Courtier6Ptr,

in Courtier Courtier7Ptr);

void putTrouve(in boolean Bool);

boolean getTrouve(); };

 

ANNEXE C :

Programme Courtier_impl

//----------------------------------------------------------------------

// E.J. le 27/11/97

// ---------------------------------------------------------------------

// Courtier_impl.java

// ---------------------------------------------------------------------

// Code des classes implantant les interfaces du fichier courtier.idl

// Implementation dess objets courtier

// ---------------------------------------------------------------------

// ----

// Inclusion des classes CORBA de base et

// des squelettes generes par le programme idl

// ----

package monCourtier;

import java.io.*;import java.util.*;import java.text.*;import maTrace.*;

//---

//Implementation de la classe Courtier

//---

public class Courtier_impl extends _CourtierImplBase

{

//---

//TRACE : definition de l'objet Trace de l'objet Courtier

//---

private Trace TRACE;

//---

//TRACE : manipulation de l'objet Trace

//---

public Trace getTRACE()

{

return(TRACE);

};

public void putTRACE(Trace ObjetTrace)

{

TRACE=ObjetTrace;

};

//---

// TRACE.GetRef_obj : recuperation de la reference de l'objet Trace

//---

public void GetRef_obj(String Nom_objet)

{

//---

// Incrementation du compteur de lignes

//---

TRACE.IncCompteur();

//---

// Ecriture dans le fichier trace

//---

TRACE.println(TRACE.getCompteur() + " GetRef_obj :");

TRACE.println(" la reference de l'objet ou du programme " + Nom_objet + " est ");

TRACE.println(" " + TRACE.getRef());

//---

// Incrementation de l'horloge vectorielle

//---

TRACE.getV_horloge().IncVecteur(TRACE.getNumero());

//---

// Ecriture de la fonction d'ordre

//---

TRACE.println("GetRef_obj(" + TRACE.getV_horloge().getStrV_horloge() + "," + Nom_objet + "," + TRACE.getRef() + ")");

TRACE.println(" ");

TRACE.println("L'objet s'execute sur l'hote " + TRACE.getNomMachine());

TRACE.println(" ");

TRACE.println("//=======================================================================");

TRACE.println(" ");

//---

// Sauvegarde du nom de l'objet

//---

TRACE.putNomObj(Nom_objet);

}

//---

// Ecriture de la variable TRACE.Reference associee a l'objet Courtier

//---

public void putRef(String Ref_obj)

{

TRACE.putRef(Ref_obj);

};

//---

// Lecture de la variable TRACE.Reference associee a l'objet Courtier

//---

public String getRef()

{

return TRACE.getRef();

};

//---

// Lecture de la variable TRACE.Machine associee a l'objet Courtier

//---

public void putNomMachine(String Nom_Machine)

{

TRACE.putNomMachine(Nom_Machine);

}

//---

//TRACE : fin des fonctions manipulant les objets Trace

//---

//---

//Constructeurs de la classe Courtier

//---

public Courtier_impl(){

//---

//V4 : mise en place du reseau de courtiers. Initialisation des 3 voisins

//--

CourtierVSN1=null;

CourtierVSN2=null;

CourtierVSN3=null;

};

public Courtier_impl(String Nom_fichier_trace, int Numero){

CourtierVSN1=null;

CourtierVSN2=null;

CourtierVSN3=null;

//---

//TRACE : initialisation de l'objet Trace de l'objet Courtier

//---

Trace_impl ObjetTrace=new Trace_impl(Nom_fichier_trace, Numero);

putTRACE(ObjetTrace);

};

public Courtier_impl(String Nom_fichier_trace, String Nom_createur, int Numero){

CourtierVSN1=null;

CourtierVSN2=null;

CourtierVSN3=null;

//---

//TRACE : initialisation de l'objet Trace de l'objet Courtier

//---

Trace_impl ObjetTrace=new Trace_impl(Nom_fichier_trace, Nom_createur, Numero);

putTRACE(ObjetTrace);

};

//---

//Un courtier est une liste chaine d'enregistrement de type Enregistre

//dont le premier element est PrmrEnregistrePtr

//---

//---

//Declaration des variables prives du courtier

//---

//---

// Voir le code source

//---//---//Declaration des méthodes du courtier//---

//---

// Voir le code source

//---//---//V5 : fonction QUERY //--//---//TRACE : ajout des parametres TRACE et nom_appelant

//---

public Server QUERY(Trace TRACE, String Nom_methode_appelante,String Ref_objet_appelant, String NomService, String NomPropriete, String Valeur)

{

//---

//TRACE

//---

TRACE.DebutM("QUERY",Nom_methode_appelante,Ref_objet_appelant);

//---

// Voir le code source

//---

//---//TRACE//---TRACE.FinM("QUERY",Nom_methode_appelante,Ref_objet_appelant);return(RsltServerPtr);};//---

// Voir le code source

//---

};}

 

ANNEXE D :

Programme courtage1.java

//----------------------------------------------------------------------

// E.J. le 27/11/97

// ----------------------------------------------------------------

// courtage1.java

// ----------------------------------------------------------------

// Programme de type courtier

// Instancie une implantation de l'interface monCourtier_courtage

// ----------------------------------------------------------------

package monCourtier;

// ----

// Inclusion des classes CORBA de base

// ----

import org.omg.CORBA.*;

import JAVA.io.*;

public class courtage1

{

public static void main( String args[] )

{

ORB orb = ORB.init( args, new JAVA.util.Properties() );

BOA boa = orb.BOA_init( args, new JAVA.util.Properties() );

//---

// generation de l'objet TRACE pour courtage1.java

//---

Trace_impl TCourtage1 = new Trace_impl("courtage1.trc","courtage1.java",1);

//---

// TRACE.putNomMachine : recuperation du nom et du port de la machine

//---

com.ooc.CORBA.BOA OBboa = (com.ooc.CORBA.BOA)boa;

String Machine=" ";

Machine=Machine.concat(OBboa.host());

Machine=Machine.concat(", port");

Machine=Machine.concat(" " + OBboa.port());

TCourtage1.putNomMachine(Machine);

//---

//---

// TRACE.GetRef_obj : recuperation de la reference de l'objet Trace cree

//---

String RefTCourtage1 = orb.object_to_string(TCourtage1);

TCourtage1.putRef(RefTCourtage1);

TCourtage1.GetRef_obj("courtage1.java");

//---

//---

// TRACE.DebutE : trace du debut d'execution d'un processus JAVA

//---

TCourtage1.DebutE("courtage1.java","courtage1.java");

//---

// ----

//Creation d'un objet Courtier1 et d'un objet TRACE

//Attention cette objet ne doit pas etre cree de nouveau au niveau du client :

//il y aurait alors deux agents courtier !

// ----

//---

// TRACE.Creat_obj : creation d'objet

//---

TCourtage1.Creat_obj("Courtier1Ptr");

Courtier_impl Courtier1Ptr = new Courtier_impl("Courtier1Ptr.trc","courtage1.java",2);

//---

// TRACE.putNomMachine : recuperation du nom et du port de la machine

//---

Courtier1Ptr.putNomMachine(Machine);

//---

//---

// TRACE.GetRef_obj : recuperation de la reference de l'objet Trace cree

//---

String RefTCourtier1Ptr = orb.object_to_string(Courtier1Ptr.getTRACE());

Courtier1Ptr.putRef(RefTCourtier1Ptr);

Courtier1Ptr.GetRef_obj("Courtier1Ptr");

//---

// ----

TCourtage1.println("Ecriture de l'IOR dans le fichier courtier1.ref");

// ----

try

{

String s = orb.object_to_string(Courtier1Ptr);

String refFile = "courtier1.ref";

PrintWriter out = new PrintWriter( new FileOutputStream(refFile) );

out.println(s);

out.flush();

}

catch( IOException ex )

{

System.err.println( "Ecriture dans "+ex.getMessage()+" impossible" );

System.exit(1);

}

//---

// V5 : mise en place du no interne du courtier1

//---

Courtier1Ptr.putNoInterne(1);

// ----

TCourtage1.println("Mise a disposition de l'objet courtier1");

// ----

System.out.println( "Courtier1 pret sur l'hote "+OBboa.host()+

", port "+OBboa.port() );

TCourtage1.println( "Courtier1 pret sur l'hote "+OBboa.host()+

", port "+OBboa.port() );

boa.impl_is_ready(null);

}

}

 

ANNEXE E :

Fichier reseau.java

// -----------------------------------------------------------------------------

// Programme reseau.java

// Recupere l'IOR des courtiers et mise en place du reseau de courtiers

// -----------------------------------------------------------------------------

package monCourtier;

// ----

// Inclusion des classes CORBA de base

// ----

import org.omg.CORBA.*;

import JAVA.io.*;

public class reseau

{

public static void main( String args[] )

{

// ----

System.out.println(" ");

System.out.println("PROCESSUS RESEAU : CREATION DU RESEAU DE COURTIERS");

// ----

//---

// Declaration des variables

// Elle est indispensable pour pouvoir generer les objets distribues

//---

//---

//vis a vis du courtier

//---

Courtier Courtier1Ptr, Courtier2Ptr,Courtier3Ptr,Courtier4Ptr,Courtier5Ptr,Courtier6Ptr,Courtier7Ptr;

ORB orb = ORB.init( args, new JAVA.util.Properties() );

BOA boa = orb.BOA_init( args, new JAVA.util.Properties() );

//---

//RECUPERATION DES IOR DES COURTIERS

//---

//---

// Voir le code source

//--- //--- //INITIALISATION DES OBJETS //---

//---

// Voir le code source

//--- //--- //INVOCATION DE METHODES DISTANTES

//---

//---

// Voir le code source

//--- // ---- // Invocation des methodes distantes de la classes monCourtier // ---- //--- // INITIALISATION DU RESEAU DE COURTIERS //---

//---

// Voir le code source

//--- //--- // MISE EN PLACE DU RESEAU DE COURTIERS //--- //--- // Mise en place des courtiers voisins du courtier1 //--- // Trois relations --> : est voisin de //--- Courtier1Ptr.putNbVoisin(3);

//---

// 1 --> 2

//---

Courtier1Ptr.putVoisin1(Courtier2Ptr);

//---

// 1 --> 7

//---

Courtier1Ptr.putVoisin2(Courtier7Ptr);

//--

// 1 --> 6

//---

Courtier1Ptr.putVoisin3(Courtier6Ptr);

////---

// Mise en place des courtiers voisins du courtier2

//---

// Une relations --> : est voisin de

//---

Courtier2Ptr.putNbVoisin(1);

//---

// 2 --> 3

Courtier2Ptr.putVoisin1(Courtier3Ptr);

//---

// Mise en place des courtiers voisins du courtier3

//---

// Zero relation --> : est voisin de

//---

Courtier3Ptr.putNbVoisin(0);

//---

// Mise en place des courtiers voisins du courtier4

//---

// Une relation --> : est voisin de

//---

Courtier4Ptr.putNbVoisin(1);

//---

// 4 --> 3

//---

Courtier4Ptr.putVoisin1(Courtier3Ptr);

//---

// Mise en place des courtiers voisins du courtier5

//---

// Une relations --> : est voisin de

//---

Courtier5Ptr.putNbVoisin(1);

//---

// 5 --> 4

//---

Courtier5Ptr.putVoisin1(Courtier4Ptr);

//---

// Mise en place des courtiers voisins du courtier6

//---

// Une relation --> : est voisin de

//---

Courtier6Ptr.putNbVoisin(1);

//---

// 6 --> 5

//---

Courtier6Ptr.putVoisin1(Courtier5Ptr);

//---

// Mise en place des courtiers voisins du courtier7

//---

// Deux relations --> : est voisin de"

//---

Courtier7Ptr.putNbVoisin(2);

//---

// 7 --> 3

//---

Courtier7Ptr.putVoisin1(Courtier3Ptr);

//---

// 7 --> 5

//---

Courtier7Ptr.putVoisin2(Courtier5Ptr);

//---

System.out.println("FIN DU PROCESSUS RESEAU");

//---

//---

// Forcer l'arret de l'interpreteur pour decharger ce programme de la memoire

// car ce programme n’est pas instrumenté //--- System.exit(0); }}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PARTIE 2 : LES OBJETS DE TYPE TRACE.

 

 

 

 

 

ANNEXE F :

Fichier IDL d’un objet trace.

interface Trace {

void IncCompteur();

void InitCompteur();

long getCompteur();

void IncIndexTrace();

void InitIndexTrace();

long getIndexTrace();

void putRef(in string Ref_obj);

string getRef();

void InitRef();

void putNomFichier(in string Nom);

string getNomFichier();

void InitNomFichier();

void InitNomObj();

void putNomObj(in string Nom_obj);

string getNomObj();

void putNomMachine(in string Nom_Machine);

string getNomMachine();

void InitNomMachine();

void putNomCreateur(in string Nom_Createur);

string getNomCreateur();

void InitNomCreateur();

JLexOdr getAnalyseurOdr();

void putAnalyseurOdr(in JLexOdr Nom_AnalyseurOdr);

void putNumero(in long NumeroNew);

long getNumero();

void putV_horloge(in Vecteur V_horlogeNew);

Vecteur getV_horloge();

void putV_horlogeCal(in Vecteur V_horlogeNew);

Vecteur getV_horlogeCal();

void println(in string Texte);

void DebutE(in string Nom_programme,in string Nom_programme_appelant);

void FinE(in string Nom_programme,in string Nom_programme_appelant);

void Creat_obj(in string Nom_objet);

void GetRef_obj(in string Nom_objet);

void DebutM(in string Nom_methode,in string Nom_methode_appelante,in string Ref_objet_appelant);

void FinM(in string Nom_methode,in string Nom_methode_appelante,in string Ref_objet_appelant);

void Iappel(in string Nom_methode,

in string Nom_methode_appelante,

in string Ref_objet_invoque,

in Trace Objet_invoque);

void Retour_Iappel(in string Nom_methode,

in string Nom_methode_appelante,

in string Ref_objet_invoque,

in Trace Objet_invoque);

};

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PARTIE 3 : RESULTATS.

 

 

 

 

ANNEXE G :

Exemple de fichier trace, client.trc

//=======================================================================//Fichier de trace : client.trc//=======================================================================// Nom du createur de l'objet : client.java

//=======================================================================

// Le numero de l'objet trace est : 0

//=======================================================================

1 GetRef_obj :

la reference de l'objet ou du programme client.java est

IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000

GetRef_obj([1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

L'objet s'execute sur l'hote mimosas.cnam.fr, port 1527

//=======================================================================

2 DebutE :

debut d'execution du programme JAVA client.java

appeler par le programme client.java

DebutE([2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],client.java,client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

Recuperation de l'IOR du serveur1 dans le fichier server1.ref

Recuperation de l'IOR du serveur2 dans le fichier server2.ref

Recuperation de l'IOR du serveur3 dans le fichier server3.ref

Recuperation de l'IOR du serveur4 dans le fichier server4.ref

Recuperation de l'IOR du serveur5 dans le fichier server5.ref

Recuperation de l'IOR du serveur6 dans le fichier server6.ref

Recuperation de l'IOR du serveur7 dans le fichier server7.ref

Recuperation de l'IOR du COURTIER1 dans le fichier courtier1.ref

Recuperation de l'IOR du COURTIER2 dans le fichier courtier2.ref

Recuperation de l'IOR du COURTIER1 dans le fichier courtier3.ref

Recuperation de l'IOR du COURTIER4 dans le fichier courtier4.ref

Recuperation de l'IOR du COURTIER5 dans le fichier courtier5.ref

Recuperation de l'IOR du COURTIER6 dans le fichier courtier6.ref

Recuperation de l'IOR du COURTIER7 dans le fichier courtier7.ref

CLIENT : Convertion du type des objets recuperes

Vis a vis de l'objet1 du serveur1

Vis a vis du l'objet2 serveur1

Vis a vis de l'objet1 du serveur2

Vis a vis du l'objet2 serveur2

Vis a vis de l'objet1 du serveur3

Vis a vis du l'objet2 serveur3

Vis a vis de l'objet1 du serveur4

Vis a vis du l'objet2 serveur4

Vis a vis de l'objet1 du serveur5

Vis a vis de l'objet1 du serveur6

Vis a vis du l'objet2 serveur6

Vis a vis de l'objet1 du serveur7

Vis a vis du l'objet2 serveur7

Vis a vis du courtier1

Vis a vis du courtier2

Vis a vis du courtier3

Vis a vis du courtier4

Vis a vis du courtier5

Vis a vis du courtier6

Vis a vis du courtier7

CLIENT : Invocation des methodes distantes de la classes monCourtier

CLIENT : demande de service aux serveurs et au reseau de courtiers

Initialisation du reseau de courtiers

3 Iappel :

invocation de la methode InitReseau

de l'objet :

IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b060000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003100

appelee par la methode (ou programme) client.java

Iappel([3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],InitReseau,client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b060000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003100,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

4 Retour_Iappel :

retour d'invocation de la methode InitReseau

de l'objet :

IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b060000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003100

appelee par la methode (ou programme) client.java

Retour_Iappel([4,0,24,0,6,0,6,0,6,0,6,0,6,0,9,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,12,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0],InitReseau,client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b060000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003100,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

5 Iappel :

invocation de la methode QUERY

de l'objet :

IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b060000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003100

appelee par la methode (ou programme) client.java

Iappel([5,0,24,0,6,0,6,0,6,0,6,0,6,0,9,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,12,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0],QUERY,client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b060000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003100,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

6 Retour_Iappel :

retour d'invocation de la methode QUERY

de l'objet :

IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b060000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003100

appelee par la methode (ou programme) client.java

Retour_Iappel([6,0,39,0,19,0,17,0,6,0,6,0,6,0,22,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,12,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0],QUERY,client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b060000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003100,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

7 Iappel :

invocation de la methode getDate

de l'objet :

IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b850000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003200

appelee par la methode (ou programme) client.java

Iappel([7,0,39,0,19,0,17,0,6,0,6,0,6,0,22,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,12,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0],getDate,client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b850000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003200,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

8 Retour_Iappel :

retour d'invocation de la methode getDate

de l'objet :

IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b850000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003200

appelee par la methode (ou programme) client.java

Retour_Iappel([8,0,39,0,19,0,17,0,6,0,6,0,6,0,22,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,12,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0],getDate,client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e30000000000000010000000000000046000100000000001070616e646f72652e636e616d2e6672002b850000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003200,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

9 FinE :

terminaison du programme JAVA client.java

appeler par le programme client.java

FinE([9,0,39,0,19,0,17,0,6,0,6,0,6,0,22,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,10,4,4,12,4,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0],client.java,client.java,IOR:000000000000001a49444c3a6d6f6e436f7572746965722f54726163653a312e3000000000000001000000000000004600010000000000106d696d6f7361732e636e616d2e66720005f70000000000264f422f49442b4e554d0049444c3a6d6f6e436f7572746965722f54726163653a312e30003000)

 

ANNEXE H :

Fichier résultat, Analyse.trc

//=======================================================================

//

// Analyse du fichier trace : client.trc

//

//=======================================================================

1 DebutE sur client.java pandore.cnam.fr, port 10888

debut d'execution du programme JAVA client.java

appele par le programme client.java

2 Iappel sur client.java pandore.cnam.fr, port 10888

invocation de la methode InitReseau

de l'objet Courtier1Ptr

appelee par la methode client.java

3 DebutM sur Courtier1Ptr pandore.cnam.fr, port 10885

debut methode InitReseau invoquee par la methode (ou programme) client.java

de l'objet client.java

4 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode putVisite

de l'objet Courtier1Ptr

appelee par la methode InitReseau

5 DebutM sur Courtier1Ptr pandore.cnam.fr, port 10885

debut methode putVisite invoquee par la methode (ou programme) InitReseau

de l'objet Courtier1Ptr

6 FinM sur Courtier1Ptr pandore.cnam.fr, port 10885

fin methode putVisite invoquee par la methode (ou programme)InitReseau

de l'objet Courtier1Ptr

7 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode putVisite

de l'objet Courtier1Ptr

appelee par la methode InitReseau

8 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode putVisite

de l'objet Courtier2Ptr

appelee par la methode InitReseau

9 DebutM sur Courtier2Ptr pandore.cnam.fr, port 10842

debut methode putVisite invoquee par la methode (ou programme) InitReseau

de l'objet Courtier1Ptr

10 FinM sur Courtier2Ptr pandore.cnam.fr, port 10842

fin methode putVisite invoquee par la methode (ou programme)InitReseau

de l'objet Courtier1Ptr

11 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode putVisite

de l'objet Courtier2Ptr

appelee par la methode InitReseau

12 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode putVisite

de l'objet Courtier3Ptr

appelee par la methode InitReseau

13 DebutM sur Courtier3Ptr pandore.cnam.fr, port 10886

debut methode putVisite invoquee par la methode (ou programme) InitReseau

de l'objet Courtier1Ptr

14 FinM sur Courtier3Ptr pandore.cnam.fr, port 10886

fin methode putVisite invoquee par la methode (ou programme)InitReseau

de l'objet Courtier1Ptr

15 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode putVisite

de l'objet Courtier3Ptr

appelee par la methode InitReseau

16 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode putVisite

de l'objet Courtier4Ptr

appelee par la methode InitReseau

17 DebutM sur Courtier4Ptr pandore.cnam.fr, port 10839

debut methode putVisite invoquee par la methode (ou programme) InitReseau

de l'objet Courtier1Ptr

18 FinM sur Courtier4Ptr pandore.cnam.fr, port 10839

fin methode putVisite invoquee par la methode (ou programme)InitReseau

de l'objet Courtier1Ptr

19 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode putVisite

de l'objet Courtier4Ptr

appelee par la methode InitReseau

20 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode putVisite

de l'objet Courtier5Ptr

appelee par la methode InitReseau

21 DebutM sur Courtier5Ptr pandore.cnam.fr, port 10840

debut methode putVisite invoquee par la methode (ou programme) InitReseau

de l'objet Courtier1Ptr

22 FinM sur Courtier5Ptr pandore.cnam.fr, port 10840

fin methode putVisite invoquee par la methode (ou programme)InitReseau

de l'objet Courtier1Ptr

23 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode putVisite

de l'objet Courtier5Ptr

appelee par la methode InitReseau

24 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode putVisite

de l'objet Courtier6Ptr

appelee par la methode InitReseau

25 DebutM sur Courtier6Ptr pandore.cnam.fr, port 10841

debut methode putVisite invoquee par la methode (ou programme) InitReseau

de l'objet Courtier1Ptr

26 FinM sur Courtier6Ptr pandore.cnam.fr, port 10841

fin methode putVisite invoquee par la methode (ou programme)InitReseau

de l'objet Courtier1Ptr

27 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode putVisite

de l'objet Courtier6Ptr

appelee par la methode InitReseau

28 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode putVisite

de l'objet Courtier7Ptr

appelee par la methode InitReseau

29 DebutM sur Courtier7Ptr pandore.cnam.fr, port 10887

debut methode putVisite invoquee par la methode (ou programme) InitReseau

de l'objet Courtier1Ptr

30 FinM sur Courtier7Ptr pandore.cnam.fr, port 10887

fin methode putVisite invoquee par la methode (ou programme)InitReseau

de l'objet Courtier1Ptr

31 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode putVisite

de l'objet Courtier7Ptr

appelee par la methode InitReseau

32 FinM sur Courtier1Ptr pandore.cnam.fr, port 10885

fin methode InitReseau invoquee par la methode (ou programme)client.java

de l'objet client.java

33 Retour_Iappel sur client.java pandore.cnam.fr, port 10888

retour d'invocation de la methode InitReseau

de l'objet Courtier1Ptr

appelee par la methode client.java

34 Iappel sur client.java pandore.cnam.fr, port 10888

invocation de la methode QUERY

de l'objet Courtier1Ptr

appelee par la methode client.java

35 DebutM sur Courtier1Ptr pandore.cnam.fr, port 10885

debut methode QUERY invoquee par la methode (ou programme) client.java

de l'objet client.java

36 DebutM sur Courtier1Ptr pandore.cnam.fr, port 10885

debut methode putVisite invoquee par la methode (ou programme) QUERY

de l'objet Courtier1Ptr

37 FinM sur Courtier1Ptr pandore.cnam.fr, port 10885

fin methode putVisite invoquee par la methode (ou programme)QUERY

de l'objet Courtier1Ptr

38 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode query

de l'objet Courtier1Ptr

appelee par la methode QUERY

39 DebutM sur Courtier1Ptr pandore.cnam.fr, port 10885

debut methode query invoquee par la methode (ou programme) QUERY

de l'objet Courtier1Ptr

40 FinM sur Courtier1Ptr pandore.cnam.fr, port 10885

fin methode query invoquee par la methode (ou programme)QUERY

de l'objet Courtier1Ptr

41 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode query

de l'objet Courtier1Ptr

appelee par la methode QUERY

42 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode QUERY

de l'objet Courtier2Ptr

appelee par la methode QUERY

43 DebutM sur Courtier2Ptr pandore.cnam.fr, port 10842

debut methode QUERY invoquee par la methode (ou programme) QUERY

de l'objet Courtier1Ptr

44 DebutM sur Courtier2Ptr pandore.cnam.fr, port 10842

debut methode putVisite invoquee par la methode (ou programme) QUERY

de l'objet Courtier2Ptr

45 FinM sur Courtier2Ptr pandore.cnam.fr, port 10842

fin methode putVisite invoquee par la methode (ou programme)QUERY

de l'objet Courtier2Ptr

46 Iappel sur Courtier2Ptr pandore.cnam.fr, port 10842

invocation de la methode query

de l'objet Courtier2Ptr

appelee par la methode QUERY

47 DebutM sur Courtier2Ptr pandore.cnam.fr, port 10842

debut methode query invoquee par la methode (ou programme) QUERY

de l'objet Courtier2Ptr

48 FinM sur Courtier2Ptr pandore.cnam.fr, port 10842

fin methode query invoquee par la methode (ou programme)QUERY

de l'objet Courtier2Ptr

49 Retour_Iappel sur Courtier2Ptr pandore.cnam.fr, port 10842

retour d'invocation de la methode query

de l'objet Courtier2Ptr

appelee par la methode QUERY

50 Iappel sur Courtier2Ptr pandore.cnam.fr, port 10842

invocation de la methode QUERY

de l'objet Courtier3Ptr

appelee par la methode QUERY

51 DebutM sur Courtier3Ptr pandore.cnam.fr, port 10886

debut methode QUERY invoquee par la methode (ou programme) QUERY

de l'objet Courtier2Ptr

52 DebutM sur Courtier3Ptr pandore.cnam.fr, port 10886

debut methode putVisite invoquee par la methode (ou programme) QUERY

de l'objet Courtier3Ptr

53 FinM sur Courtier3Ptr pandore.cnam.fr, port 10886

fin methode putVisite invoquee par la methode (ou programme)QUERY

de l'objet Courtier3Ptr

54 Iappel sur Courtier3Ptr pandore.cnam.fr, port 10886

invocation de la methode query

de l'objet Courtier3Ptr

appelee par la methode QUERY

55 DebutM sur Courtier3Ptr pandore.cnam.fr, port 10886

debut methode query invoquee par la methode (ou programme) QUERY

de l'objet Courtier3Ptr

56 FinM sur Courtier3Ptr pandore.cnam.fr, port 10886

fin methode query invoquee par la methode (ou programme)QUERY

de l'objet Courtier3Ptr

57 Retour_Iappel sur Courtier3Ptr pandore.cnam.fr, port 10886

retour d'invocation de la methode query

de l'objet Courtier3Ptr

appelee par la methode QUERY

58 FinM sur Courtier3Ptr pandore.cnam.fr, port 10886

fin methode QUERY invoquee par la methode (ou programme)QUERY

de l'objet Courtier2Ptr

59 Retour_Iappel sur Courtier2Ptr pandore.cnam.fr, port 10842

retour d'invocation de la methode QUERY

de l'objet Courtier3Ptr

appelee par la methode QUERY

60 FinM sur Courtier2Ptr pandore.cnam.fr, port 10842

fin methode QUERY invoquee par la methode (ou programme)QUERY

de l'objet Courtier1Ptr

61 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode QUERY

de l'objet Courtier2Ptr

appelee par la methode QUERY

62 Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

invocation de la methode QUERY

de l'objet Courtier7Ptr

appelee par la methode QUERY

63 DebutM sur Courtier7Ptr pandore.cnam.fr, port 10887

debut methode QUERY invoquee par la methode (ou programme) QUERY

de l'objet Courtier1Ptr

64 DebutM sur Courtier7Ptr pandore.cnam.fr, port 10887

debut methode putVisite invoquee par la methode (ou programme) QUERY

de l'objet Courtier7Ptr

65 FinM sur Courtier7Ptr pandore.cnam.fr, port 10887

fin methode putVisite invoquee par la methode (ou programme)QUERY

de l'objet Courtier7Ptr

66 Iappel sur Courtier7Ptr pandore.cnam.fr, port 10887

invocation de la methode query

de l'objet Courtier7Ptr

appelee par la methode QUERY

67 DebutM sur Courtier7Ptr pandore.cnam.fr, port 10887

debut methode query invoquee par la methode (ou programme) QUERY

de l'objet Courtier7Ptr

68 DebutM sur Courtier7Ptr pandore.cnam.fr, port 10887

debut methode query invoquee par la methode (ou programme) query

de l'objet Courtier7Ptr

69 FinM sur Courtier7Ptr pandore.cnam.fr, port 10887

fin methode query invoquee par la methode (ou programme)query

de l'objet Courtier7Ptr

70 FinM sur Courtier7Ptr pandore.cnam.fr, port 10887

fin methode query invoquee par la methode (ou programme)QUERY

de l'objet Courtier7Ptr

71 Retour_Iappel sur Courtier7Ptr pandore.cnam.fr, port 10887

retour d'invocation de la methode query

de l'objet Courtier7Ptr

appelee par la methode QUERY

72 FinM sur Courtier7Ptr pandore.cnam.fr, port 10887

fin methode QUERY invoquee par la methode (ou programme)QUERY

de l'objet Courtier1Ptr

73 Retour_Iappel sur Courtier1Ptr pandore.cnam.fr, port 10885

retour d'invocation de la methode QUERY

de l'objet Courtier7Ptr

appelee par la methode QUERY

74 FinM sur Courtier1Ptr pandore.cnam.fr, port 10885

fin methode QUERY invoquee par la methode (ou programme)client.java

de l'objet client.java

75 Retour_Iappel sur client.java pandore.cnam.fr, port 10888

retour d'invocation de la methode QUERY

de l'objet Courtier1Ptr

appelee par la methode client.java

76 Iappel sur client.java pandore.cnam.fr, port 10888

invocation de la methode getDate

de l'objet Obj2_Serv7Ptr

appelee par la methode client.java

77 DebutM sur Obj2_Serv7Ptr jasmin.cnam.fr, port 16167

debut methode getDate invoquee par la methode (ou programme) client.java

de l'objet client.java

78 FinM sur Obj2_Serv7Ptr jasmin.cnam.fr, port 16167

fin methode getDate invoquee par la methode (ou programme)client.java

de l'objet client.java

79 Retour_Iappel sur client.java pandore.cnam.fr, port 10888

retour d'invocation de la methode getDate

de l'objet Obj2_Serv7Ptr

appelee par la methode client.java

80 FinE sur client.java pandore.cnam.fr, port 10888

terminaison du programme JAVA client.java

appele par le programme client.java

 

 

ANNEXE I.

Exemple de visualisation des relations d’ordre

au moyen d’un arbre.

Copie d’écran n°1.

 

 

 

Copie d’écran N°2.

En cliquant sur le nœud 0 de l’arbre précédent, nous obtenons l’arbre suivant.

 

Copie d’écran n°3.

 

En cliquant sur le nœud 000 de l’arbre précédent, nous obtenons l’arbre suivant.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

PARTIE 4 : EXEMPLE D’UNE APPLICATION MULTITHREADEE.

 

 

 

ANNEXE J :

L’application producteur/consommateur.

La classe ProducerConsumer.java

public class ProducerConsumerTest {

public static void main(String[] args) {

CubbyHole c = new CubbyHole();

Producer p1 = new Producer(c, 1);

Consumer c1 = new Consumer(c, 1);

p1.start();

c1.start();

}

}

La classe CubbyHole.java

public class CubbyHole {

private int contents;

private boolean available = false;

public synchronized int get() {

while (available == false) {

try {

wait();

} catch (InterruptedException e) { }

}

available = false;

notifyAll();

return contents;

}

public synchronized void put(int value) {

while (available == true) {

try {

wait();

} catch (InterruptedException e) { }

}

contents = value;

available = true;

notifyAll();

}

}

La classe Producer.java

public class Producer extends Thread {

private CubbyHole cubbyhole;

private int number;

public Producer(CubbyHole c, int number) {

cubbyhole = c;

this.number = number;

}

public void run() {

for (int i = 0; i < 10; i++) {

cubbyhole.put(i);

System.out.println("Producer #" + this.number + " put: " + i);

try {

sleep((int)(Math.random() * 100));

} catch (InterruptedException e) { }

}

}

}

La classe Consumer.java

public class Consumer extends Thread {

private CubbyHole cubbyhole;

private int number;

public Consumer(CubbyHole c, int number) {

cubbyhole = c;

this.number = number;

}

public void run() {

int value = 0;

for (int i = 0; i < 10; i++) {

value = cubbyhole.get();

System.out.println("Consumer #" + this.number + " got: " + value);

}

}

}

 

 

LISTE DES SIGLES

 

API : Application Programming Interface.

BOA : Basic Object Adaptor.

COM : Component Object Model.

CORBA : Common Object Request Broker Architectur.

COS : Compliant Naming Service.

DCE : Distributed Computing Environment.

DCOM : Distributed Component Object Model.

DII : Dynamic Invocation Interface.

DSI : Dynamic Skeleton Interfaces.

GIOP : General Inter-ORB Protocol.

GUI : Graphic User Interfaces.

HTML : Hyper Text Markup Language.

IDL : Interface Definition Language.

IIOP : Internet Inter ORB Protocol.

IR : Interface Repository.

LAN : Local Area Network.

OLE/COM : Object Linking & Embedding/Common ou Component Object Model.

OMA : Object Management Architecture.

OMG : Object Management Group.

ORB : Object Request Broker.

OSF : Open Software Fondation.

RMI : Remote Method Invocation.

RPC : Remote Procedure Call.

SII : Static Invocation Interface.

SIS : Static IDL Skeletons.

TCI/IP : Transmission Control Protocol/Internet Protocol.

WAN : Wide Area Network.

 

 

 Copyright © CNAM 1999 - Tous droits réservés.