monitoring with AspectJ
Transcription
monitoring with AspectJ
monitoring with AspectJ CS 119 examples illustrating how to write monitors Overview • this and previous lecture should enable you to write monitors in AspectJ • we will see examples of AspectJ monitors • how to structure monitors • trace view of program executions – reference to the past – obligations for the future – state machines • giving an idea of how it is done without the use of special specification notation • motivate later subjects 2 recall trace view of execution • a formal view of an execution is to consider it as a sequence ¾ of program states: ¾ = s1 s2 s3 … sn • during program execution we are at any point in time in the present moment now where the past is known but the future is not known. past now future 3 past time properties • If A(x) happens now then B(y) must have happened in the past where R(x,y) B(y) past A(x) now future 4 future time properties • If A(x) happens now then B(y) must happen in the future where R(x,y) past A(x) B(y) now future 5 state machines • A(x) and B(x) should happen in an alternating manner A(x) 1 2 B(x) A(x) past B(x) A(x) now B(x) A(x) B(x) future 6 monitor keeps state § § = past events £ future obligations § past now future 7 two monitoring architectures • write checking logic inside aspect program aspect + logic § • write checking logic in separate class program aspect logic § 8 logic in separate class program aspect logic § • separating logic into a separate module makes specification writing modular. event2(…) event2(…) event3(…) logic § either: a method for each kind of event 9 logic in separate class program aspect logic § • separating logic into a separate module makes specification writing modular. dispatch(Event e) logic § or: a single event dispatch method 10 a file example class File { public static final int READ = 1; public static final int WRITE = 2; public File(String name, int mode){…} } public public public public void write(String text){…}; String read(){…}; void close() {…} String getName() {…} 11 a requirement A file should be accessed according to its mode READ or WRITE. A file cannot be accessed or closed unless it has been opened. File file = new File("data",File.WRITE); file.write("monitor"); file.write("this if you can"); file.write(file.read()); file.close(); file.close(); error error 12 first: logic inside aspect program aspect + logic § 13 § = File ! {READ,WRITE} 1 2 file1 ! 1 file2 ! 2 file3 ! 1 … Identity IdentityHashMap<File,Integer> modes = new IdentityHashMap(); 14 Identity Maps in Java • a normal hashmap does not work: – uses equals method to determine equality – and file objects may change wrt. equals HashMap<File,Integer> modes = new HashMap(); • necesarry to use identity hashmap: – uses == to determine file equality IdentityHashMap<File,Integer> modes = new IdentityHashMap(); 15 logic inside aspect public aspect FileMonitor { pointcut open(int mode) : call(File.new(String,int)) && args(..,mode); pointcut write(File file): call(void File.write(..)) && target(file); pointcut read(File file) : call(String File.read(..)) && target(file); pointcut close(File file): call(void File.close()) && target(file); … IdentityHashMap<File,Integer> modes = new IdentityHashMap(); after(int mode) returning (File file) : open(mode) { modes.put(file,mode); } } before(File file) : write(file) { Integer mode = modes.get(file); if(mode == null || mode != File.WRITE) error("illegal write to file " + file.getName()); } before(File file) : read(file) { Integer mode = modes.get(file); if(mode == null || mode != File.READ) error("illegal read from file " + file.getName()); } before(File file) : close(file) { if (modes.get(file) == null) error("attempt to close closed file " + file.getName()); modes.remove(file); } 16 a simple way of getting an informative error message void error(String str) { try { throw new Exception ("*** " + str); } catch (Exception e) { e.printStackTrace(); } } 17 error message in Eclipse 18 second: logic in separate class program aspect logic § • separating logic into a separate module makes specification writing modular. event2(…) event2(…) event3(…) logic § a method for each kind of event 19 logic in separate class class EngineM { IdentityHashMap<File,Integer> modes = new IdentityHashMap(); … void open(File file, int mode) { modes.put(file,mode); } void write(File file) { Integer mode = modes.get(file); if(mode == null || mode != File.WRITE) { error("illegal write to file " + file.getName()); } } void read(File file) { Integer mode = modes.get(file); if(mode == null || mode != File.READ) { error("illegal read from file " + file.getName()); } } } void close(File file) { if (modes.get(file) == null) error("attempt to close closed file " + file.getName()); modes.remove(file); } 20 aspect calls logic public aspect FileMonitor { pointcut open(int mode) : pointcut write(File file): pointcut read(File file) : pointcut close(File file): call(File.new(String,int)) && args(..,mode); call(void File.write(..)) && target(file); call(String File.read(..)) && target(file); call(void File.close()) && target(file); EngineM engine = new EngineM(); after(int mode) returning (File file) : open(mode) { engine.open(file,mode); } before(File file) : write(file) { engine.write(file); } before(File file) : read(file) { engine.read(file); } } before(File file) : close(file) { engine.close(file); } 21 using a single event dispatcher program aspect logic § dispatch(Event e) logic § 22 one dispatch function class EngineE { void dispatch(Event event) { … } … } class OpenEvent implements Event { class OpenEvent implements Event { public File file; class OpenEvent implements Event { public File file; public int mode; class OpenEvent implements Event { publicint File file; public mode; publicint File file; public mode; public OpenEvent(File public int mode; file, int mode) { public OpenEvent(File int mode) { this.file = file; file, public OpenEvent(File file, int mode) { this.file =mode; file; this.mode = public OpenEvent(File file, int mode) { this.file= =mode; file; this.mode } this.file= =mode; file; this.mode } } this.mode = mode; } } aspect FileMonitor { } pointcut open(int mode) : …; } } pointcut write(File file) : …; pointcut read(File file) : …; pointcut close(File file) : …; EngineE engine = new EngineE(); after(int mode) returning (File file) : open(mode) { engine.dispatch(new OpenEvent(file,mode)); } } … 23 parameterized temporal operators – define two temporal operators: • Response(o.Q,o.R) : whenever method Q is called on object o, eventually method R will be called on o. • Request(o.P,o.Q) : whenever method Q is called on o, in the past method P must have been called on o. Request(P,Q) P past Response(Q,R) Q R now future 24 file example again assume existence of a method: File.open() R1: A file should eventually be closed once opened. R2: A file cannot be closed unless it has been opened. File file1 = new File(”data1",File.WRITE); file1.open(); file1.write("monitor this”);] // missing close of file1 File file2 = new File(”data2",File.WRITE); // missing open of file2 file2.close(); terminate(); error error 25 abstract Response property abstract aspect Response { abstract pointcut firstMethod(Object o); abstract pointcut secondMethod(Object o); abstract pointcut shutDown(); IdentityHashSet obligations = new IdentityHashSet(); before(Object o) : firstMethod(o) { obligations.add(o); } before(Object o) : secondMethod(o) { obligations.remove(o); } } before() : shutDown() { Iterator it = obligations.iterator(); while(it.hasNext()) { error("Matching closing method not found on object: " + it.next()); } } 26 abstract Request property abstract aspect Request { abstract pointcut firstMethod(Object o); abstract pointcut secondMethod(Object o); IdentityHashSet history = new IdentityHashSet(); before(Object o) : firstMethod(o) { history.add(o); } } before(Object o) : secondMethod(o) { if (!history.contains(o)) { error("Matching opening method not found on object " + o); } else { history.remove(o); } } 27 concrete Response and Request properties R1 and R2 aspect R1 extends Response { pointcut firstMethod(Object o) : call(void File.open()) && target(o); pointcut secondMethod(Object o) : call(void File.close()) && target(o); pointcut shutDown() : call(void Test.terminate()); } aspect R2 extends Request { pointcut firstMethod(Object o) : call(void File.open()) && target(o); pointcut secondMethod(Object o) : call(void File.close()) && target(o); pointcut shutDown() : call(void Test.terminate()); } 28 29 timing properties • inside monitor: record milliseconds used with – System.currentTimeMillis() : “the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.” – requires a new event to occur • use a “real” Timer object that spawns a thread which times out by itself. This does not require a new event to trigger. For example javax.swing.Timer. • timestamp events in some other way in case they come from “outside” the application. 30 abstract timed Response property abstract aspect TimedResponse { abstract pointcut firstMethod(Object o); abstract pointcut secondMethod(Object o); abstract pointcut shutDown(); abstract boolean timeok(long time); IdentityHashMap obligations = new IdentityHashMap(); before(Object o) : firstMethod(o) { obligations.put(o,System.currentTimeMillis()); } before(Object o) : secondMethod(o) { long secondTime = System.currentTimeMillis(); Long firstTime = (Long)obligations.get(o); if (firstTime != null) { long time = secondTime - firstTime; if (!timeok(time)) error("time " + time + " violates time constraint for " + o); obligations.remove(o); } } } before() : shutDown() {… check for emptiness as before …} 31 back to the file example TR1: After a file has been opened it should be closed within 5 seconds. ¤(o.firstMethod ! }5 o.secondMethod) aspect TR1 extends TimedResponse { pointcut firstMethod(Object o) : call(void File.open()) && target(o); pointcut secondMethod(Object o) : call(void File.close()) && target(o); pointcut shutDown() : call(void Test.terminate()); } boolean timeok(long time) { return time <= 5000; } 32 monitoring best programming practices • generic properties we would want to hold for any program • often concerns the use of various data types • such properties are no different than domain specific properties • some can be detected with static analysis • however a dynamic analysis is relatively easy to program, whereas a static analysis either would not be possible or would require a substantial amount of work to implement 33 properties of Java library APIs R1: There should be no two calls to next() without a call to hasNext() in between, on the same iterator. 34 use of iterators There should be no two calls to Iterator.next() without a call to Iterator.hasNext() in between, on same iterator Vector<String> words = new Vector(); readWords(words); Iterator it = words.iterator(); while(it.hasNext()) { String w1 = (String)it.next(); String w2 = (String)it.next(); storeCorrespondence(w1,w2); if (it.hasNext()) System.out.println("there is more!"); } error 35 slightly stronger property expressed as a state machine hasNext DoHasNext DoNext next 36 we need a state machine per iterator it1 ! it2 ! it3 ! 37 the state design pattern • allow an object to alter its behavior when its internal state changes. The object will appear to change its class. • an object-oriented state machine Gamma, Erich; Richard Helm, Ralph Johnson, John M. Vlissides (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley 38 the state design pattern spelled out • define a "context" class to present a single interface to the outside world. • define a State abstract base class. • represent the different "states" of the state machine as derived classes of the State base class. • define state-specific behavior in the appropriate State derived classes. • maintain a pointer to the current "state" in the "context" class. • to change the state of the state machine, change the current "state" pointer. 39 class Machine { State state = State.doHasNext; void hasNext() { state = state.hasNext(); } void next() { state = state.next(); } } the state machines class State { static final State doNext = new DoNext(); static final State doHasNext = new DoHasNext(); State hasNext(){ System.out.println("*** warning: hasNext called unnecessarily"); return this; } } State next(){ System.out.println("*** error: next called illegally"); return this; } class DoHasNext extends State { State hasNext() { return doNext; } } class DoNext extends State { State next() { return doHasNext; } } 40 the aspect aspect HasNextPolicy { WeakIdentityHashMap monitors = new WeakIdentityHashMap(); pointcut createiter(): call(* java.util.Collection+.iterator()); pointcut hasNext(Iterator it): call(* java.util.Iterator+.hasNext()) && target(it); pointcut next(Iterator it): call(* java.util.Iterator+.next()) && target(it); weak & identity after() returning (Iterator it): createiter() { monitors.put(it,new Machine()); } before(Iterator it): hasNext(it) { ((Machine)monitors.get(it)).hasNext(); } } before(Iterator it): next(it) { ((Machine)monitors.get(it)).next(); } 41 weak identity hash maps program aspect + logic • hasmap still an identity hashmap (using `==‘) • a normal (identity) hashmap keeps a mapping until it is explicitly deleted with `Map.remove()’. • this becomes a problem since the monitor will then accumulate bindings between iterators and state machines. The garbage collector cannot collect the iterators when they are no longer used by the monitored application. • a weak collection releases an object to the garbage collector when it is no longer used by any other part of the program. 42 properties of Java library APIs R2: An enumeration should not be propagated after the underlying vector has been changed. 43 enumerators faster than iterators but less safe “I'd tried using Iterator and Enumeration to compare their performance on a Vector object containing 100 000 Strings. Enumeration was consistently about 50% faster”. - web blog But: an Iterator next() operation throws a: ConcurrentModificationException if it detects that the underlying collection has been modified while iteration is underway. Problem: Enumerator does not! 44 use of enumerators An enumeration should not be propagated after the undelying vector has been changed. Vector v = new Vector(); v.add(1); v.add(2); v.add(3); produces: Enumeration en = v.elements(); 1 3 4 while(en.hasMoreElements()) { Integer i = (Integer)en.nextElement(); if (i == 2) v.add(4); else System.out.println(i); } } error 45 three maps are needed • DsState: recording when a data structure was last updated (maps to unique object) • EnumState: recording the state of the data structure of an enumeration at creation time • EnumDs: recording what data structure corresponds to what enumeration DsState DsState = Ds ! State EnumState = Enum ! State EnumDs = Enum ! Ds ds state1 = EnumDs enum state1 EnumState 46 example monitored run … v.add(3); Enumeration en = v.elements(); while(en.hasMoreElements()) { Integer i = (Integer)en.nextElement(); if (i == 2) v.add(4); else System.out.println(i); } } 47 … v.add(3); Enumeration en = v.elements(); while(en.hasMoreElements()) { Integer i = (Integer)en.nextElement(); if (i == 2) v.add(4); else System.out.println(i); } } example monitored run ds state1 update 48 … v.add(3); Enumeration en = v.elements(); while(en.hasMoreElements()) { Integer i = (Integer)en.nextElement(); if (i == 2) v.add(4); else System.out.println(i); } } example monitored run update ds state1 enum state1 create 49 … v.add(3); Enumeration en = v.elements(); while(en.hasMoreElements()) { Integer i = (Integer)en.nextElement(); if (i == 2) v.add(4); else System.out.println(i); } } example monitored run update ds state2 enum state1 create update 50 … v.add(3); Enumeration en = v.elements(); while(en.hasMoreElements()) { Integer i = (Integer)en.nextElement(); if (i == 2) v.add(4); else System.out.println(i); } } example monitored run update ds state2 ≠ enum state1 create update next 51 aspect SafeEnum private Map private Map private Map { ds_state = new WeakIdentityHashMap(); enum_state = new WeakIdentityHashMap(); enum_ds = new WeakIdentityHashMap(); the checker private static class StateId {} pointcut vector_update() : call(* Vector.add*(..)) || call(* Vector.clear()) || call(* Vector.insertElementAt(..)) || call(* Vector.remove*(..)) || call(* Vector.retainAll(..)) || call(* Vector.set*(..)) && scope(); after(Vector ds) returning(Enumeration e) : call(Enumeration Vector.elements()) && target(ds) { enum_ds.put(e,ds); Object s = ds_state.get(ds); if (s != null) enum_state.put(e,s); } before(Enumeration e): call(Object Enumeration.nextElement()) && target(e) { if (ds_state.get(enum_ds.get(e)) != enum_state.get(e)) error("nextElement called on enumerator after update"); } } after(Vector ds) : vector_update() && target(ds) { ds_state.put(ds,new StateId()); } 52 properties of Java library APIs R3: An collection should not be modified while it is a member of a hashset (don’t change the hashcode). 53 don’t change hashcode A collection should not be modified while it is member of a hash set. In other words: don’t change the collection’s hashcode during this period. HashSet s {…} add remove Set<Collection<String>> s = new HashSet(); Collection<String> c = new ArrayList(); produces: false c.add("this is ok"); s.add(c); Collection c c.add("don't do this"); System.out.println(s.contains(c)); error modify 54 one map is needed • recording what sets a collection is stored in Map = Collection ! HashSet-set on n is t coll1 {hs1,hs } se 2 n he w n {hs ,hs ,hs } o i coll2 2 3 4 ct e l ol c te a {hs4} d p coll3 u ’t n o d pt m e - y 55 Set<Collection<String>> s = new HashSet(); Collection<String> c = new ArrayList(); example monitored run c.add("this is ok"); s.add(c); c.add("don't do this"); System.out.println(s.contains(c)); 56 Set<Collection<String>> s = new HashSet(); Collection<String> c = new ArrayList(); c.add("this is ok"); example monitored run s.add(c); c.add("don't do this"); System.out.println(s.contains(c)); c {s} add 57 Set<Collection<String>> s = new HashSet(); Collection<String> c = new ArrayList(); c.add("this is ok"); example monitored run s.add(c); c.add("don't do this"); System.out.println(s.contains(c)); c add {s} ≠ ? update 58 aspect HashSetPolicy { pointcut addCollection(HashSet s, Collection c): call(* Collection.add(Object)) && target(s) && args(c); pointcut removeCollection(HashSet s, Collection c): call(* Collection.remove(Object)) && target(s) && args(c); pointcut modifyCollection(Collection c): (call(* Collection.add(..)) || …) && target(c); WeakIdentityHashMap hashSets = new WeakIdentityHashMap(); after(HashSet s, Collection c) : addCollection(s,c) { if(hashSets.get(c)==null) hashSets.put(c,new WeakIdentityHashSet()); WeakIdentityHashSet sets = (WeakIdentityHashSet) hashSets.get(c); sets.add(s); } after(HashSet s, Collection c) : removeCollection(s,c) { WeakIdentityHashSet sets = (WeakIdentityHashSet)hashSets.get(c); if(sets!=null) sets.remove(s); } } before(Collection c): modifyCollection(c) { WeakIdentityHashSet sets = (WeakIdentityHashSet) hashSets.get(c); if(sets!=null) for (Object s : sets) error("*** Modified collection "+ c +" in " + s); } the checker 59 lock release policies Within one method invocation, locks should be acquired and released correctly, meaning … • release locks in same invocation they are taken • and either one of the following policies: – release exactly once L(x) L(x) U(x) U(x) set – release as many times as acquired (order unimportant) L(x) L(x) U(x) U(x) U(x) bag – release in reverse order L(x) L(y) U(y) U(x) U(x) U(y) stack 60 an example class Lock { String name; Lock(String name) { this.name = name; } synchronized void lock(){…} synchronized void unlock(){…} } public String toString() { return name; } assuming reverse order discipline void start() { a(); } void a() { l1.lock(); l2.lock(); l1.unlock(); l2.unlock(); l3.lock(); b(); } error error void b() { l3.unlock(); } 61 stack of lock histories Data = Level ! LockHist Data = LockHist-stack LockHist = Set | Bag | Stack assuming reverse order discipline (stack) 3 L1 L2 L3 4 L6 L7 7 L5 public interface LockHist { public void lock(Lock l); public boolean unlock(Lock l); public boolean isEmpty(); public void clear(); } 62 stack code class StackHist implements LockHist { Stack stack = new Stack(); public void lock(Lock l) { stack.push(l); } public boolean unlock(Lock l) { boolean success = !stack.isEmpty() && stack.peek() == l; if (success) stack.pop(); return success; } public boolean isEmpty() { return stack.isEmpty(); } } … 63 computing method invocation level ThreadLocal: this java.lang class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. aspect CflowDepth { pointcut anyfunc() : execution(* *(..)); static ThreadLocal cflowdepth = new ThreadLocal() {… 0 …}; before() : anyfunc() { Integer prev = (Integer) cflowdepth.get(); cflowdepth.set(new Integer(prev.intValue() + 1)); } } after() : anyfunc() { Integer prev = (Integer) cflowdepth.get(); cflowdepth.set(new Integer(prev.intValue() - 1)); } 64 aspect LockChecker { public static ThreadLocal depthMap = new ThreadLocal() {… emptymap …}; pointcut locking(Lock l) : call(* Lock.lock()) && target(l); pointcut unlocking(Lock l) : call(* Lock.unlock()) && target(l); } before(Lock l) : locking(l) { HashMap map = (HashMap) depthMap.get(); Integer depth = (Integer)CflowDepth.cflowdepth.get(); LockHist lockhist = (LockHist) map.get(depth); if (lockhist == null) { lockhist = new StackHist(); map.put(depth, lockhist); } lockhist.lock(l); new SetHist() } before(Lock l) : unlocking(l) { new BagHist() HashMap map = (HashMap) depthMap.get(); Integer depth = (Integer)CflowDepth.cflowdepth.get(); LockHist lockhist = (LockHist) map.get(depth); if (lockhist == null || !lockhist.unlock(l)) error("unlock op. not preceeded by lock op." + l); } after() : CflowDepth.anyfunc() { HashMap map = (HashMap) depthMap.get(); Integer depth = (Integer)CflowDepth.cflowdepth.get(); LockHist lockHist = (LockHist) map.get(depth); if (lockHist != null && !lockHist.isEmpty()) error("locks have not been released " + depth + lockHist); if (lockHist != null) lockHist.clear(); } declare precedence : CflowDepth, LockChecker; the checker 65 aspect precedence • if advice A occurs before advice B in same aspect then A has higher precedence than B • if the following declaration is given: declare precedence : A, B; then A has higher priority than B 66 aspect precedence before highest after Join point around highest lowest lowest Join point lowest Join point highest 67 end 68