ALASCA — Architecture logicielles avancées pour les systèmes
Transcription
ALASCA — Architecture logicielles avancées pour les systèmes
Statistiques par proxy Statistiques par réflexion au chargement ALASCA — Architecture logicielles avancées pour les systèmes complexes auto-adaptables c 2014–2015 Jacques Malenfant Master informatique, spécialité STL – UFR 919 Ingénierie Université Pierre et Marie Curie [email protected] 1 / 16 Statistiques par proxy Statistiques par réflexion au chargement Plan 1 Statistiques par proxy 2 Statistiques par réflexion au chargement 2 / 16 Statistiques par proxy Statistiques par réflexion au chargement Énoncé du problème Définir une classe Statistics permettant de modifier des objets pour recueillir des statistiques sur le nombre d’appels aux différentes méthodes de toutes les interfaces implantées. La classe Statistics propose une méthode : public static Object toggleStatsOn(Object o) ; qui retourne pour l’objet o une nouvelle référence à utiliser pour appeler o mais qui assure que les statistiques seront collectées. L’objet retourné par cette méhode implante l’interface suivante : public interface ProvideStatistics { public String getCurrentStatistics() ; public void printCurrentStatistics() ; } La première méthode retournant une chaîne résumant les statistiques collectées et la seconde imprimant ces statistiques au terminal. La forme de cette chaîne devrait être la suivante : <signature de la méthode 1> : <nombre> appels ... <signature de la méthode n> : <nombre> appels 3 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java I public class Statistics { public static Object toggleStatsOn(Object o) { Object proxy = null ; ClassLoader cl = o.getClass().getClassLoader() ; Class<?>[] interfaces = o.getClass().getInterfaces() ; Class<?>[] newInterfaces = new Class[interfaces.length + 1] ; for (int i = 0; i < interfaces.length; i++) { newInterfaces[i] = interfaces[i] ; } newInterfaces[interfaces.length] = ProvideStatistics.class ; proxy = java.lang.reflect.Proxy.newProxyInstance( cl, newInterfaces, new StatisticsIH(o)); return proxy ; } } import java.lang.reflect.*; public class StatisticsIH implements InvocationHandler { protected Object target ; protected StatisticGatherer sg ; public StatisticsIH(Object target) { super(); this.target = target; 4 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java II this.sg = new StatisticGatherer() ; } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { String name = m.getName() ; if ("getCurrentStatistics".equals(name)) { return sg.getCurrentStatistics() ; } else if ("printCurrentStatistics".equals(name)) { sg.printCurrentStatistics() ; return null ; } else { sg.addCallToMethod(m.toGenericString()) ; return m.invoke(target, args) ; } } } import java.util.*; public class StatisticGatherer implements ProvideStatistics { protected Hashtable<String, Integer> methodCallStatistics ; public StatisticGatherer() { super(); this.methodCallStatistics = new Hashtable<String, Integer>() ; 5 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java III } public void addCallToMethod(String m) { int numberOfCalls = 0 ; if (methodCallStatistics.containsKey(m)) { numberOfCalls = methodCallStatistics.get(m) ; methodCallStatistics.remove(m) ; } methodCallStatistics.put(m, numberOfCalls + 1) ; } public String getCurrentStatistics() { String res = "" ; int maxLength = 0 ; for (Enumeration<String> e = methodCallStatistics.keys(); e.hasMoreElements(); ) { String element = (String) e.nextElement(); int l = element.length() ; if (l > maxLength) { maxLength = l ; } } for (Iterator<Entry<String,Integer>> iter = ((Set<Entry<String,Integer>>) methodCallStatistics.entrySet()).iterator(); iter.hasNext();) { 6 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java IV Entry<String,Integer> element = (Entry<String,Integer>) iter.next(); String signature = ((String)element.getKey()) ; for (int i = signature.length(); i < maxLength; i++) { signature = signature + " " ; } res = res + signature + " : " + element.getValue() + " calls\n" ; } return res ; } public void printCurrentStatistics() { System.out.println(this.getCurrentStatistics()) ; } } import fr.upmc.alasca.statistics.ProvideStatistics; import fr.upmc.alasca.statistics.Statistics; public class Test { public static void main(String[] args) { C c = new C() ; IC proxy = (IC)Statistics.toggleStatsOn(c) ; ProvideStatistics proxyStats = (ProvideStatistics)proxy ; for (int i = 0; i < 10 ; i++) { proxy.m1() ; 7 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java V } for (int i = 0; i < 5 ; i++) { proxy.m2() ; } proxyStats.printCurrentStatistics() ; } } > java fr.upmc.alasaca.statistics.tests.Test m1 ... // 10 fois m2 ... // 5 fois fr.upmc.alasca.statistics.tests.C2.m2() : 5 calls fr.upmc.alasca.statistics.tests.C2.m1() : 10 calls 8 / 16 Statistiques par proxy Statistiques par réflexion au chargement Plan 1 Statistiques par proxy 2 Statistiques par réflexion au chargement 9 / 16 Statistiques par proxy Statistiques par réflexion au chargement Énoncé du problème Éviter de forcer l’utilisateur àutiliser la référence retournée par toggleStatsOn pour que les statistiques soient accumulées (les appels hors de cette référence ne sont pas pris en compte). Utiliser la transformation de classe au chargement pour que toute classe qui est annotée par l’annotation suivante : public interface@ ProvideStatisticsAnnotation { } crée des objets permettant d’activer et de désactiver la prise de statistiques. On leur demandant d’implanter l’interface suivante : public interface ProvideStatistics2 extends ProvideStatistics { // Starts taking call statistics public Object toggleStatsOn() ; // Stops taking call statistics public void toggleStatsOff() ; // Resets to 0 all call statistics public Object resetStatistics() ; } 10 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java I import javassist.*; public class StatisticsTransformation { public static void main(String[] args) { Translator t = new StatisticsTranslator() ; ClassPool pool = ClassPool.getDefault() ; Loader cl = new Loader() ; String application = args[0] ; String[] newArgs = new String[args.length - 1] ; for (int i = 1; i < args.length; i++) { newArgs[i - 1] = args[i] ; } try { cl.addTranslator(pool, t) ; cl.run(application, newArgs) ; } catch (Throwable e) { e.printStackTrace(); } } } import javassist.*; public class StatisticsTranslator implements Translator { public void onLoad(ClassPool pool, String classname) 11 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java II throws NotFoundException, CannotCompileException { CtClass cc = pool.get(classname) ; if (this.hasStatisticsAnnotation(cc)) { System.out.println(cc.getName() + " modified.") ; pool.get("fr.upmc.alasca.statistics.ProvideStatistics2") ; pool.get("fr.upmc.alasca.statistics.StatisticGatherer2") ; cc.addField(CtField.make("fr.upmc.alasca.statistics.StatisticGatherer2 sg ;", CtConstructor[] constructors = cc.getConstructors() ; for (int i = 0; i < constructors.length; i++) { constructors[i].insertAfter( "{ this.sg = new fr.upmc.alasca.statistics.StatisticGatherer2() ; }") ; } // end for if (constructors.length == 0) { cc.addConstructor(CtNewConstructor.make( "public " + cc.getName() + "() {" + " this.sg = new fr.upmc.alasca.statistics.StatisticGatherer2() ; }", cc)) ; } cc.addMethod(CtNewMethod.make( "public String getCurrentStatistics() {" + " return this.sg.getCurrentStatistics() ; }", cc)) ; cc.addMethod(CtNewMethod.make( "public void printCurrentStatistics() {" + 12 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java III " this.sg.printCurrentStatistics() ; }", cc)) ; cc.addMethod(CtNewMethod.make( "public Object toggleStatsOn() {" + " this.sg.toggleStatsOn() ; return this ; }", cc)) ; cc.addMethod(CtNewMethod.make( "public void toggleStatsOff() { this.sg.toggleStatsOff() ; }", cc)) ; cc.addMethod(CtNewMethod.make( "public Object resetStatistics() {" + " this.sg.resetStatistics() ; return this ; }", cc)) ; CtClass[] interfaces = cc.getInterfaces() ; cc.addInterface( pool.get("fr.upmc.alasca.statistics.ProvideStatistics2")) ; for (int i = 0 ; i < interfaces.length ; i++) { CtMethod[] imethods = interfaces[i].getMethods() ; for (int j = 0 ; j < imethods.length ; j++) { try { CtMethod m = cc.getDeclaredMethod( imethods[j].getName(), imethods[j].getExceptionTypes()) ; m.insertBefore("this.sg.addCallToMethod(\"" + 13 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java IV m.getLongName() + "\") ;") ; } catch(NotFoundException e) { ; } } } } } protected boolean hasStatisticsAnnotation(CtClass cc) { Object[] annotations = new Object[0]; try { annotations = cc.getAnnotations(); } catch (ClassNotFoundException e) { e.printStackTrace(); } boolean res = false ; for (int i = 0; !res && i < annotations.length; i++) { if (annotations[i] instanceof ProvideStatisticsAnnotation) { res = true ; } } return res ; } 14 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java V public void start(ClassPool arg0) throws NotFoundException, CannotCompileException { } } import java.util.Hashtable; public class StatisticGatherer2 extends StatisticGatherer implements ProvideStatistics2 { protected boolean statsOn ; public StatisticGatherer2() { this.methodCallStatistics = null ; this.statsOn = false ; } public Object resetStatistics() { this.methodCallStatistics = new Hashtable<String, Integer>() ; this.statsOn = true ; return null ; } public void toggleStatsOff() { this.statsOn = false ; } public Object toggleStatsOn() { this.methodCallStatistics = new Hashtable<String, Integer>() ; this.statsOn = true ; return null ; } 15 / 16 Statistiques par proxy Statistiques par réflexion au chargement Code Java VI public boolean hasStatsOn() { return this.statsOn ; } @Override public void addCallToMethod(String m) { if (this.hasStatsOn()) { super.addCallToMethod(m); } } } > java fr.upmc.alasaca.statistics.StatisticsTransformation fr.upmc.alasaca.statistics.tests.test2 fr.upmc.alasca.statistics.tests.C2 modified. m1 ... // 10 fois m2 ... // 5 fois fr.upmc.alasca.statistics.tests.C2.m2() : 5 calls fr.upmc.alasca.statistics.tests.C2.m1() : 10 calls 16 / 16