/******************************************************************************* * Copyright (c) 2010 Eric Bodden. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eric Bodden - initial API and implementation ******************************************************************************/ package de.bodden.tamiflex.playout.rt; import static de.bodden.tamiflex.playout.rt.ShutdownStatus.hasShutDown; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.Socket; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class ReflLogger { //holds hashed names protected static Map<PersistedLogEntry,PersistedLogEntry> oldContainerMethodToEntries = new HashMap<PersistedLogEntry,PersistedLogEntry>(); //holds actual names protected static Map<String,Map<RuntimeLogEntry,RuntimeLogEntry>> containerMethodToEntries = new HashMap<String, Map<RuntimeLogEntry,RuntimeLogEntry>>(); //is initialized by the agent private static File logFile; //is initialized by the agent private static boolean doCount; //is initialized by the agent private static boolean useDeclaredTypes; //is initialized by the agent private static PrintWriter newLineWriter = new PrintWriter(new OutputStream() { @Override public void write(int b) throws IOException { //by default, do nothing } }); /** This field is used to guard against infinite recursion during logging. */ private static ThreadLocal<Integer> nestingDepth = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public static void enteringReflectionAPI() { nestingDepth.set(nestingDepth.get()+1); } private static void leavingReflectionAPI() { nestingDepth.set(nestingDepth.get()-1); } private static void logAndIncrementTargetClassEntry(String containerMethod, int lineNumber, Kind kind, String targetClass) { if(hasShutDown) return; TargetClassLogEntry newEntry = new TargetClassLogEntry(containerMethod, lineNumber, kind, targetClass); RuntimeLogEntry entry; synchronized (ReflLogger.class) { entry = pullOrCreateEntry(containerMethod, newEntry); if(doCount) entry.incrementCounter(); } } private static void logAndIncrementTargetMethodEntry(String containerMethod, int lineNumber, Kind kind, String declaringClass, String returnType, String name, boolean isAccessible, String... paramTypes) { if(hasShutDown) return; TargetMethodLogEntry newEntry = new TargetMethodLogEntry(containerMethod, lineNumber, kind, declaringClass, returnType, name, isAccessible, paramTypes); RuntimeLogEntry entry; synchronized (ReflLogger.class) { entry = pullOrCreateEntry(containerMethod, newEntry); if(doCount) entry.incrementCounter(); } } private static void logAndIncrementTargetArrayEntry(String containerMethod, int lineNumber, Kind kind, String componentType, int... dimensions) { if(hasShutDown) return; TargetArrayLogEntry newEntry = new TargetArrayLogEntry(containerMethod, lineNumber, kind, componentType, dimensions); RuntimeLogEntry entry; synchronized (ReflLogger.class) { entry = pullOrCreateEntry(containerMethod, newEntry); if(doCount) entry.incrementCounter(); } } private static void logAndIncrementTargetFieldEntry(String containerMethod, int lineNumber, Kind kind, String declaringClass, String fieldType, String name, boolean isAccessible) { if(hasShutDown) return; TargetFieldLogEntry newEntry = new TargetFieldLogEntry(containerMethod, lineNumber, kind, declaringClass, fieldType, name, isAccessible); RuntimeLogEntry entry; synchronized (ReflLogger.class) { entry = pullOrCreateEntry(containerMethod, newEntry); if(doCount) entry.incrementCounter(); } } private static RuntimeLogEntry pullOrCreateEntry(String containerMethod, RuntimeLogEntry newEntry) { Map<RuntimeLogEntry,RuntimeLogEntry> entries = containerMethodToEntries.get(containerMethod); if(entries==null) { entries = new HashMap<RuntimeLogEntry,RuntimeLogEntry>(); containerMethodToEntries.put(containerMethod, entries); } RuntimeLogEntry sameEntry = entries.get(newEntry); if(sameEntry==null) { //found a new entry sameEntry = newEntry; entries.put(newEntry,newEntry); newLineWriter.println(newEntry.toString()); newLineWriter.flush(); } return sameEntry; } public static void classMethodInvoke(Class<?> c, Kind classMethodKind) { if(isReentrant()) return; try { StackTraceElement frame = getInvokingFrame(); logAndIncrementTargetClassEntry(frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),classMethodKind,c.getName()); } finally { leavingReflectionAPI(); } } public static void classForName(String typeName) { if(isReentrant()) return; try { StackTraceElement frame = getInvokingFrame(); logAndIncrementTargetClassEntry(frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.ClassForName,handleArrayTypes(typeName)); } finally { leavingReflectionAPI(); } } public static void constructorMethodInvoke(Constructor<?> c, Kind constructorMethodKind) { if(isReentrant()) return; try { StackTraceElement frame = getInvokingFrame(); String[] paramTypes = classesToTypeNames(c.getParameterTypes()); logAndIncrementTargetMethodEntry(frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),constructorMethodKind,c.getDeclaringClass().getName(),"void","<init>", c.isAccessible(), paramTypes); } finally { leavingReflectionAPI(); } } private static String[] classesToTypeNames(Class<?>[] params) { String[] paramTypes = new String[params.length]; int i=0; for (Class<?> type : params) { paramTypes[i]=getTypeName(type); i++; } return paramTypes; } public static void methodMethodInvoke(Object receiver, Method m, Kind methodKind) { methodMethodInvoke(receiver, m, methodKind, null); } public static void methodMethodInvoke(Object receiver, Method m, Kind methodKind, Class<?> getMethodReceiverClass) { if(isReentrant()) return; StackTraceElement frame = getInvokingFrame(); //There appears to be a call to Method.getModifiers() issued by the //VM in order to call the program's main method. //For this call there is no calling context and hence no frame. //We here simply ignore this call, returning early in this case. if(frame==null) { leavingReflectionAPI(); return; } Class<?> receiverClass = methodKind!=Kind.MethodInvoke || Modifier.isStatic(m.getModifiers()) ? m.getDeclaringClass() : receiver.getClass(); try { //resolve virtual call Method resolved = null; Class<?> c = receiverClass; do { try { resolved = c.getDeclaredMethod(m.getName(), m.getParameterTypes()); } catch(NoSuchMethodException e) { c = c.getSuperclass(); } } while(resolved==null && c!=null); if(resolved==null) { Error error = new Error("Method not found : "+m+" in class "+receiverClass+" and super classes."); error.printStackTrace(); } String[] paramTypes = classesToTypeNames(resolved.getParameterTypes()); String className = resolved.getDeclaringClass().getName(); if (useDeclaredTypes) { if (methodKind==Kind.MethodInvoke && !Modifier.isStatic(m.getModifiers())) className = receiver.getClass().getName(); else if(methodKind==Kind.ClassGetMethod) { className = getMethodReceiverClass.getName(); } } logAndIncrementTargetMethodEntry(frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),methodKind,className,getTypeName(resolved.getReturnType()),resolved.getName(), m.isAccessible(), paramTypes); } catch (Exception e) { e.printStackTrace(); } finally { leavingReflectionAPI(); } } public static void arrayNewInstance(Class<?> componentType, int dimension) { arrayMultiNewInstance(componentType, dimension); } public static void arrayMultiNewInstance(Class<?> componentType, int... dimensions) { if(isReentrant()) return; try { StackTraceElement frame = getInvokingFrame(); logAndIncrementTargetArrayEntry( frame.getClassName()+"."+frame.getMethodName(), frame.getLineNumber(), Kind.ArrayNewInstance, getTypeName(componentType), dimensions); } catch (Exception e) { e.printStackTrace(); } finally { leavingReflectionAPI(); } } public static void fieldMethodInvoke(Field f, Kind fieldMethodKind) { fieldMethodInvoke(f, fieldMethodKind, null); } public static void fieldMethodInvoke(Field f, Kind fieldMethodKind, Class<?> getFieldReceiverClass) { if(isReentrant()) return; try { StackTraceElement frame = getInvokingFrame(); Class<?> fieldClass = (useDeclaredTypes && fieldMethodKind==Kind.ClassGetField) ? getFieldReceiverClass : f.getDeclaringClass(); logAndIncrementTargetFieldEntry( frame.getClassName()+"."+frame.getMethodName(), frame.getLineNumber(), fieldMethodKind, getTypeName(fieldClass), getTypeName(f.getType()), f.getName(), f.isAccessible()); } catch (Exception e) { e.printStackTrace(); } finally { leavingReflectionAPI(); } } private static boolean isReentrant() { //this method is called at every entry point to //the TamiFlex runtime; at this point we are entering the reflection API enteringReflectionAPI(); //check if we have a recursive call caused by TamiFlex itself; //this is the case if depth is >1 Integer depth = nestingDepth.get(); if(depth>1) { //by convention, when this method returns true, //we will be leaving the TamiFlex runtime (callers must //return immediately); hence we here flag that we leave the API leavingReflectionAPI(); return true; } else { return false; } } protected static String handleArrayTypes(String className) { int arrDepth = 0; for(int i=0;i<className.length();i++) { if(className.charAt(i)=='[') { arrDepth++; } else { break; } } className = className.substring(arrDepth); if(className.endsWith(";")) { //cut of leading "L" and trailing ";" className = className.substring(1,className.indexOf(';')); } if("B".equals(className)) className= byte.class.getName(); if("C".equals(className)) className= char.class.getName(); if("D".equals(className)) className= double.class.getName(); if("F".equals(className)) className= float.class.getName(); if("I".equals(className)) className = int.class.getName(); if("J".equals(className)) className = long.class.getName(); if("S".equals(className)) className = short.class.getName(); if("Z".equals(className)) className = boolean.class.getName(); if("V".equals(className)) className= void.class.getName(); for(int i=0; i<arrDepth; i++) { className += "[]"; } return className; } private static String getTypeName(Class<?> type) { //copied from java.lang.reflect.Field.getTypeName(Class) if (type.isArray()) { try { Class<?> cl = type; int dimensions = 0; while (cl.isArray()) { dimensions++; cl = cl.getComponentType(); } StringBuffer sb = new StringBuffer(); sb.append(cl.getName()); for (int i = 0; i < dimensions; i++) { sb.append("[]"); } return sb.toString(); } catch (Throwable e) { /*FALLTHRU*/ } } return type.getName(); } /** * Returns the stack frame two frames above any frame related * to this class. (The frame just above is the call we are tracing, * but we want to return the invoking frame, hence two frames above.) */ private static StackTraceElement getInvokingFrame() { StackTraceElement[] stackTrace = new Exception().getStackTrace(); StackTraceElement outerFrame = null; boolean outside = false; for (StackTraceElement frame : stackTrace) { String c = frame.getClassName(); if(!outside) { if (!c.equals(ReflLogger.class.getName())) { outside = true; continue; } } else { outerFrame = frame; break; } } return outerFrame; } public static synchronized void writeLogfileToDisk(boolean verbose, int newClasses) { Set<PersistedLogEntry> mergedEntries = mergeOldAndNewLog(verbose, newClasses); //printStatistics(); try { PrintWriter pw = new PrintWriter(logFile); List<String> lines = new ArrayList<String>(); for (PersistedLogEntry entry : mergedEntries) { lines.add(entry.toString()); } Collections.sort(lines); for (String line : lines) { pw.println(line); } pw.flush(); pw.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } } public static void setMustCount(boolean mustCount) { doCount = mustCount; } public static void setLogFile(File f) { logFile = f; //send path of log file over Socket (if connected) newLineWriter.println(f.getAbsolutePath()); } public static void setSocket(Socket s) throws IOException { newLineWriter = new PrintWriter(s.getOutputStream()); } public static void setuseDeclaredTypes(boolean on) { useDeclaredTypes = on; } //is called by the agent private static void initializeLogFile() { File f = logFile; if(f.exists() && f.canRead()) { FileInputStream fis = null; BufferedReader reader = null; try { fis = new FileInputStream(f); reader = new BufferedReader(new InputStreamReader(fis)); String line; while((line=reader.readLine())!=null) { String[] split = line.split(";",-1); Kind kind = Kind.kindForLabel(split[0]); String target = split[1]; String containerMethod = split[2]; int lineNumber = split[3].isEmpty()?-1:Integer.parseInt(split[3]); String metadata = split[4]; int count = (split.length<6||split[5].isEmpty()||!doCount)?0:Integer.parseInt(split[5]); PersistedLogEntry entry = new PersistedLogEntry(containerMethod, lineNumber, kind, target, metadata, count); oldContainerMethodToEntries.put(entry,entry); } } catch (IOException e) { e.printStackTrace(); } finally { try { if(reader!=null) reader.close(); if(fis!=null) fis.close(); } catch (IOException e) { } } } } private static Set<PersistedLogEntry> mergeOldAndNewLog(boolean verbose, int newClasses) { initializeLogFile(); Set<RuntimeLogEntry> newLogSet = new HashSet<RuntimeLogEntry>(); for(Map<RuntimeLogEntry,RuntimeLogEntry> values: containerMethodToEntries.values()) { newLogSet.addAll(values.keySet()); } Set<PersistedLogEntry> merged = new HashSet<PersistedLogEntry>(); for (RuntimeLogEntry newLogEntry : newLogSet) { PersistedLogEntry persistedEntry = newLogEntry.toPersistedEntry(); PersistedLogEntry correspondingOldEntry = oldContainerMethodToEntries.get(persistedEntry); if(correspondingOldEntry!=null) { PersistedLogEntry mergedEntry = PersistedLogEntry.merge(persistedEntry, correspondingOldEntry); merged.add(mergedEntry); } else { merged.add(persistedEntry); } } for (PersistedLogEntry oldLogEntry : oldContainerMethodToEntries.keySet()) { //if no corresponding merged entry contained yet, add the old one if(!merged.contains(oldLogEntry)) { merged.add(oldLogEntry); } } Set<PersistedLogEntry> newEntries = new HashSet<PersistedLogEntry>(merged); newEntries.removeAll(oldContainerMethodToEntries.keySet()); System.out.println("\n============================================================"); System.out.println("TamiFlex Play-Out Agent Version "+ReflLogger.class.getPackage().getImplementationVersion()); if(newEntries.isEmpty()) { System.out.println("Found no new log entries."); } else { System.out.println("Found "+newEntries.size()+" new log entries."); } if(newClasses>0) { System.out.println("Dumped "+newClasses+" new classes."); } else { System.out.println("Dumped no new classes."); } if(verbose) { System.out.println("New Entries: "); for (PersistedLogEntry logEntry : newEntries) { System.out.println(logEntry); } } System.out.println("Log file written to: "+logFile.getAbsolutePath()); System.out.println("============================================================"); return merged; } }