Position paper: Instrumentation aspects require symmetric join points Lutz Dominick Siemens AG, Corporate Technology, ZT SE 2 D-81730 Munich, Germany Lutz.Dominick@mchp.siemens.de March 2000 Publication granted for ECOOP 2000 purposes Abstract This paper presents some of the experiences made with AOP and AspectJ. They got employed for supporting an existing tool for testing and monitoring of distributed systems (TOMDIS). Reusable aspects were defined to attach the Java instrumentation library to application code efficiently and at minimal costs. Some instrumentation requirements, the aspects and their implementation in AspectJ are presented. Not all requirements have been fulfilled by the aspect implementations because outgoing calls cannot be intercepted in AspectJ. This is regarded a lack of symmetry for join point definitions, limitations are presented and discussed. Introduction The goal of attaching a test and monitoring tool to an application is to make calls visible. The tool then can show where a call started and who was called. Details like method and class name and call parameters might be available also. The goal of using aspects and AspectJ [4] is to achieve efficient, flexible and reversible instrumentation at minimal costs. Code instrumentation chooses what components and calls need to be watched and connects an instrumentation library to the application code. At run-time, the tool then can be fed with information about what s going on in the application. The tool can display this information in a variety of views, ranging from lists to detailed interaction diagrams. An instrumentation library generates tool compliant output, defines the steps to follow during instrumentation and what information has to be made available at run-time. The library gets attached to an application by interception techniques. One option is adding the necessary calls to the source code directly. The user mostly doesn t want to monitor all calls, but chooses those specific layers or selected parts of the system that e.g. play a role in a test case or use case. He has concise knowledge about the source code as a prerequisite to making the decision what gets instrumented. It is important to reduce monitoring overhead because the behavior of the system gets changed with every additional library call. This can be looked at as sender side filtering in contrast to applying filter rules to mass data that has already been generated. The definitions of aspects [5], [6] and join points [2] fit well with source code instrumentation. They offer a way for efficient instrumentation. The paper describes some of the requirements for code instrumentation and their relation to aspects and AspectJ, shows two aspect implementations in AspectJ, and discusses results and limitations. The focus is on what can be achieved when applying AspectJ to the core problem. The code shown compiles with AspectJ. 0.4 beta 7. Later versions up to 0.6 beta 2 have been tried also (esp. for the around feature) but they are not used here due to a couple of open issues. Requirements for Code Instrumentation Aspects
Code instrumentation requires dumping e.g. method name, parameters and object identity, among other information. The requirements for the aspects are the following:?? The application code gets connected to the instrumentation library.?? Both the start point and the end point of a call has to be monitored.?? Additional information has to be made available, like generation of unique ids for tasks and objects and ids for calls that then let the tool draw a real interaction diagram. These requirements normally tangle the code and don't interfere with application functionality and its software architecture. These observations are helpful because:?? Instrumentation is a crosscutting concern of the application according to the above definition.?? One can conclude that a 'glue' aspect might solve the core part of the problem.?? It is likely that aspects can cover the remaining activities for code instrumentation. Basic library calls for (semi-)automated code instrumentation are:?? Open and close the connection to the TOMDIS java library.?? Register and deregister the network node, process, tasks and objects.?? Dump start and end of an invocation of selected methods with the above library. These activities are captured in two aspects. The first aspect 'TMTEnvAspect' deals with open/close of the library and setting up some environment like the id generator and id propagation. The second one ' TMTTaskAspect ' instruments all involved parts of the application and therefore comprises all steps for monitoring method invocations and the (de)registration of tasks and objects. This is sufficient because only a small set of features of the instrumentation library is required for this evaluation. The Environment Aspect The TMTEnvAspect defines start and stop of instrumentation and provides all common services. It comprises the following activities.?? Define start and stop action and when they occur.?? Provide common data like computer name and process name.?? Provide id generation and mediation. The code for TMTEnvAspect that is presented here contains the parts for in-process monitoring. When adapting the aspect code to an application, the user needs to redefine the bold identifiers at the beginning of the aspect. They describe what class and methods are used as startup and shutdown hooks. aspect TMTEnvAspect { //-- define start and stop action -------------------------------------- crosscut participants(): SymbolTest; crosscut loadaction(): new(..) & participants(); crosscut closeaction(): void close(..) & participants(); //-- handle outermost events ------------------------------------------- static advice loadaction() { before { EventTracer.deviceRegister(TMTEnvAspect.getLayerName(), TMTEnvAspect.getComputerId(), TMTEnvAspect.getComputerName(), -1, -1); EventTracer.processRegister(TMTEnvAspect.getLayerName(), TMTEnvAspect.getProcId(), TMTEnvAspect.getComputerId(), TMTEnvAspect.getProcessName(), -1, -1); static advice closeaction() { after { EventTracer.deviceUnregister(TMTEnvAspect.getLayerName(), TMTEnvAspect.getComputerId(), -1, -1, true); EventTracer.close();
//-- class properties of this aspect ----------------------------------- //-- Common identifiers ------------------------------------------------ static public int getcomputerid() { return computerid; static public int computerid = getnewtaskid(); static public int getprocid() { return procid; static public int procid = getnewtaskid(); static public String getcomputername() { return computername; static public String computername = "Notebook"; static public String getprocessname() { return processname; static public String processname = "Proc"; static public String getlayername() { return layername; static public String layername = "Sym"; //-- Id generator ------------------------------------------------------- static public synchronized int getnewtaskid() { return newtaskid++; static public int newtaskid = 100; //-- Id propagation ----------------------------------------------------- static public int getpeerid() { return peerid; static public void setpeerid( int p ) { peerid = p; static public int peerid = 0; The loadaction registers the computer device and the process. Subsequent registration calls require the id of the process to be registered. The closeaction deregisters the device and all dependent processes, tasks and objects recursively and shuts down the instrumentation library. The remaining code contains common read-only information and id handling. The latter consists of an id generation and a simple id propagation, both are kept as simple as possible here. This part of the aspect code doesn t refer to the application in no way, it is part of the aspect architecture only and reflects only requirements for monitoring. The load and close actions get connected to but not adapted to the application code. The AspectJ weaver intercepts the specified methods without exploring the properties of the join points. The aspect implementation fulfills the requirements. The Task Aspect The TMTTaskAspect defines the participants for the test case or use case and the join points and their instrumentation. It comprises the following activities.?? Define the participants and how they get modeled in the tool's event set.?? Enhance the participants with code for proper id handling.?? Register and deregister the participants according to the use case.?? Define the join points and provide weaves that attach them to the TOMDIS library. The code for TMTTaskAspect that is presented here contains the parts for in-process monitoring and reflects its application to a small example from JDK1.2.2 that has received some redesign. The join points dump the minimal information possible, e.g. they don t need to dump call parameters. When adapting the aspect code to an application, the user needs to redefine the crosscuts at the beginning of the aspect. This is sufficient because all weaves get adapted to their join points automatically. aspect TMTTaskAspect { crosscut tasks(): EnterAddressAspect ChoiceAspect; crosscut services(): SymbolTest SymbolCanvas; crosscut rpcclient(): (void configure() & SymbolTest) (void testit() & SymbolTest) (* actionperformed(..) & EnterAddressAspect) (* itemstatechanged(itemevent e) & ChoiceAspect) (* setfont(..) & SymbolCanvas); crosscut rpcserver(): * *(..) & services(); crosscut introaction(): services(); crosscut loadaction(): new(..); //-- Id propapation ----------------------------------------------------- introduction introaction() { public int getpeerid() { return peerid;
public void setpeerid( int p ) { peerid = p; public int peerid = 0; //-- task registration ------------------------------------------------ introduction tasks() { public int taskid = TMTEnvAspect.getNewTaskId(); static advice loadaction() & tasks() { after { EventTracer.taskRegister(TMTEnvAspect.getLayerName(), taskid, TMTEnvAspect.getProcId(), thisjoinpoint.classname, -1, -1); //-- service registration ---------------------------------------------- introduction services() { public int taskid = TMTEnvAspect.getNewTaskId(); static advice loadaction() & services() { after { EventTracer.serviceProviderRegister(TMTEnvAspect.getLayerName(), taskid, TMTEnvAspect.getProcId(), thisjoinpoint.classname, -1, -1); //-- handle task/provider specific events SERVER ----------------------- static advice rpcserver() { before { thisobject.setpeerid( TMTEnvAspect.getPeerId() ); EventTracer.rpcServerBegin(TMTEnvAspect.getLayerName(), thisobject.taskid, thisobject.getpeerid(), thisjoinpoint.methodname, submitparams, -1, -1); finally { MessageTripleList returnparams = null; EventTracer.rpcServerEnd(TMTEnvAspect.getLayerName(), thisobject.taskid, thisobject.getpeerid(), thisjoinpoint.methodname, submitparams, thisjoinpoint.methodname, returnparams, -1, -1); //-- handle task/provider specific events CLIENT ----------------------- static advice rpcclient() { before { TMTEnvAspect.setPeerId( thisobject.taskid ); EventTracer.rpcClientBegin(TMTEnvAspect.getLayerName(), thisobject.taskid, -1, thisjoinpoint.methodname, submitparams, -1, -1); finally { MessageTripleList returnparams = null; EventTracer.rpcClientEnd(TMTEnvAspect.getLayerName(), thisobject.taskid, -1, thisjoinpoint.methodname, submitparams, thisjoinpoint.methodname, returnparams, -1, -1); The model within tool TOMDIS consists of device, process, task and service where device contains process that in turn contains tasks and services. Actually, it is of no importance if something is a task or a service, both are only views on a thing, but some of the library calls are allowed only for services. The first part of the aspect defines the participants and the join points and sorts all of them into the appropriate category (task, service, rpcclient, rpcserver). The code presents a mixture of specifying join points explicitly and making use of wildcards. The introduction weaves deal with id handling. The task or service requires an id to register itself. At run-time, services store the id of the caller to allow the tool to show nested calls correctly. Advice weaves attach the library to the participant for registration and deregistration. Here, both actions are bound to the life-cycle of the object and the process. in a static and uniform way. This can be achieved by means of keywords in AspectJ that explore the current join point.
It is a great advantage that the aspect depends on the join point definitions in the crosscuts and that all advice weaves get adapted to application code automatically. Further more, rpcclients and rpcservers get modeled without referring to each other explicitly. The solution presented here is reusable and efficient. But it is not possible to fulfill all requirements with the current language features in AspectJ. Interception of method invocation traces only the end point of a call. It is not possible to intercept and trace the start point of a call. That means that the interaction diagram and the recorded timestamps for the start point are inexact. Because of this problem, the following workaround is used:?? When a method gets called, a start point of a call is dumped with the tool, assuming that this method might call nested methods that have received instrumentation.?? Then the actual method is called.?? After the method call an end point is dumped, assuming that some nested method was called and has returned to the intercepted method. This workaround fails if the intercepted method calls more than one nested method. When there is no other intermediate object or library in between, other workarounds like pre-instrumented libraries fail also. Timestamps are incorrect in either case. Monitoring an Example Application Before the problems are discussed, two screenshots show what has been achieved so far. The above aspects are a step forward and helpful. The instrumented application created a log file with all information. The following screen dump shows the hierarchy created by the registration calls. Two of the participants are aspects that got instrumented using aspects again. When running the sample application, the following interaction diagram is shown by tool TOMDIS.
The boxes on the lines in the sequence chart view are the join points that are defined in TMTTaskAspect. Time stamps have been created by the library calls but they are omitted here. The diagram shows all interaction and registration events that have been relevant. Discussion The second aspect implementation actually points to two issues. The first one is the interception of the start point of a call. The second one is adaptability and generality of weaves. When monitoring calls between objects and components, start and end point need to be traceable to show the exact behavior of a system. Tracing the end point is referred to defining an operation level join point. The start point is only its symmetric counterpart. The end point does not require knowing where the call came from. The start point doesn't require knowing where the call goes to. So this is very different from having introduced plain statement level join points that can cause problems [2]. In contrast, symmetry conveys the same benefits for both points. A method that makes multiple nested calls cannot be instrumented automatically without that symmetric behavior. Adaptability and generality of weaves: In new AspectJ versions, the aspect code from above cannot be used any more because keyword thisobject went away. The action code then has to be bound to the class type that has the join point. That lets the design grow in a way that we end up with almost one aspect per class or at least one advice weave per class. Although instrumentation is a cross cutting concern, its implementation suddenly depends on the number of classes and has to show all names of cross cut classes. Specifying all classes in a package using a wildcard symbol won't work. It is a great advantage if join points and actions on the join points can be specified in a more general way. Everything that is captured by the language doesn't have to be captured by aspect design. But this is not the only reason. The nature of a couple of known cross cutting concerns requires almost identical code for a known or unknown number of join points. The word 'almost' points to two things. If the aspect code has to be reusable, it has to be as general as possible. It reflects the nature of the cross cutting concern, not the one of the join points. At the same time, the code has to be adaptable to some extend because the join points can even be abstract. A sufficient large set of special keywords simplifies aspect designs and reduces the overall effort. Conclusion The two aspects have shown that it is possible to instrument an application using AspectJ efficiently. Together with the tool features, this can be of great help during development and test. Despite of the problems discussed, the approach is quite useful in the current version if the limitations are taken into account, when defining the join points and during the exploration of the results. The aspects employ static weaves and don t dump method parameters, both to support a quick evaluation and maximum performance. The impact on performance when introducing more and more tool features needs further investigation. References [1] Frank Buschmann, Regine Meunier, Hans Rohnert Peter Sommerlad, Michael Stal: A System of Patterns, Wiley and Sons Ltd, 1996 [2] Hardol Ossher, Peri Tarr: Operation-Level Composition: A Case in (Join) Point, Proceedings of ECOOP98 [3] Douglas C. Schmidt, Washington University of St. Louis, home page at http://www.siesta.cs.wustl.edu/~schmidt
[4] AspectJ download page: http://aspectj.org [5] Gregor Kiczales et al.: Aspect Oriented Programming: A Position paper from the Xerox PARC Aspect Oriented Programming Project, Xerox PARC, 1996 [6] Gregor Kiczales, John Lamping, Anurag Medbeker, Chris Maeda, Cristina Lopes, Jean-Marc Loingtier, John Irving: Aspect Oriented Programming, ECCOP97