/******************************************************************************* * 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.Method; import java.lang.reflect.Modifier; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; 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 PrintWriter newLineWriter = new PrintWriter(new OutputStream() { @Override public void write(int b) throws IOException { //by default, do nothing } }); private static PrintWriter yamlWriter; public static void initYamlWriter(PrintWriter yamlWriter) { ReflLogger.yamlWriter = yamlWriter; yamlWriter.println("-"); yamlWriter.println(" start: \""+new Date()+"\""); yamlWriter.println(" env: {"); for(Entry<Object,Object> entry: System.getProperties().entrySet()) { String key = (String) entry.getKey(); String val = (String) entry.getValue(); yamlWriter.println(" "+key+": \""+val+"\","); } yamlWriter.println("}"); try { InetAddress localHost = java.net.InetAddress.getLocalHost(); yamlWriter.println(" hostName: "+localHost.getHostName()); yamlWriter.println(" ipAddress: "+localHost.getHostAddress()); } catch (UnknownHostException e) { } yamlWriter.println(" logFile: \""+logFile.getAbsolutePath()+"\""); yamlWriter.println(" calls:"); } 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, String... paramTypes) { if(hasShutDown) return; TargetMethodLogEntry newEntry = new TargetMethodLogEntry(containerMethod, lineNumber, kind, declaringClass, returnType, name, paramTypes); 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(); } addToYamlLog(newEntry); return sameEntry; } private static void addToYamlLog(RuntimeLogEntry entry) { final String PAD = " "; yamlWriter.println(PAD+"-"); yamlWriter.println(PAD+PAD+"kind: "+entry.getKind()); yamlWriter.println(PAD+PAD+"sourceMethod: "+entry.getContainerMethod()); yamlWriter.println(PAD+PAD+"sourceLine: "+entry.getLineNumber()); PersistedLogEntry persistedEntry = entry.toPersistedEntry(); yamlWriter.println(PAD+PAD+"target: \""+persistedEntry.getTargetClassOrMethod()+"\""); String thread = Thread.currentThread().getName(); yamlWriter.println(PAD+PAD+"thread: "+thread); String stackTrace = getStackTraceForYaml(); yamlWriter.println(PAD+PAD+"stackTrace: "+stackTrace); } private static String getStackTraceForYaml() { StackTraceElement[] stackTrace = new Exception().getStackTrace(); StringBuilder builder = new StringBuilder(); builder.append("["); for (StackTraceElement frame : stackTrace) { String c = frame.getClassName(); if(!c.equals(ReflLogger.class.getName()) && !c.equals(Class.class.getName()) && !c.equals(Method.class.getName()) && !c.equals(Constructor.class.getName())) { builder.append("\""); builder.append(frame); builder.append("\""); builder.append(","); } } //delete trailing "," builder.deleteCharAt(builder.length()-1); builder.append("]"); return builder.toString(); } public static void classNewInstance(Class<?> c) { StackTraceElement frame = getInvokingFrame(); logAndIncrementTargetClassEntry(frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.ClassNewInstance,c.getName()); } public static void classForName(String typeName) { StackTraceElement frame = getInvokingFrame(); logAndIncrementTargetClassEntry(frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.ClassForName,handleArrayTypes(typeName)); } public static void constructorNewInstance(Constructor<?> c) { StackTraceElement frame = getInvokingFrame(); String[] paramTypes = classesToTypeNames(c.getParameterTypes()); logAndIncrementTargetMethodEntry(frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.ConstructorNewInstance,c.getDeclaringClass().getName(),"void","<init>",paramTypes); } 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 methodInvoke(Object receiver, Method m) { Class<?> receiverClass = 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(); } StackTraceElement frame = getInvokingFrame(); String[] paramTypes = classesToTypeNames(resolved.getParameterTypes()); logAndIncrementTargetMethodEntry(frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.MethodInvoke,resolved.getDeclaringClass().getName(),getTypeName(resolved.getReturnType()),resolved.getName(),paramTypes); } catch (Exception e) { e.printStackTrace(); } } 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= Character.class.getName(); if("D".equals(className)) className= Double.class.getName(); if("F".equals(className)) className= Float.class.getName(); if("I".equals(className)) className = Integer.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(); } private static StackTraceElement getInvokingFrame() { StackTraceElement[] stackTrace = new Exception().getStackTrace(); StackTraceElement outerFrame = null; for (StackTraceElement frame : stackTrace) { String c = frame.getClassName(); if(!c.equals(ReflLogger.class.getName()) && !c.equals(Class.class.getName()) && !c.equals(Method.class.getName()) && !c.equals(Constructor.class.getName())) { outerFrame = frame; break; } } return outerFrame; } public static synchronized void writeLogfileToDisk(boolean verbose) { Set<PersistedLogEntry> mergedEntries = mergeOldAndNewLog(verbose); //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(); } yamlWriter.println(" end: \""+new Date()+"\""); yamlWriter.close(); } public static void setMustCount(boolean mustCount) { doCount = mustCount; } public static void setLogFile(File f) { logFile = f; } public static void setSocket(Socket s) throws IOException { newLineWriter = new PrintWriter(s.getOutputStream()); } //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]); int count = (split.length<5||split[4].isEmpty()||!doCount)?0:Integer.parseInt(split[4]); PersistedLogEntry entry = new PersistedLogEntry(containerMethod, lineNumber, kind, target, 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) { 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.err.println("\n============================================="); System.err.println("TamiFlex Play-Out Agent Version "+ReflLogger.class.getPackage().getImplementationVersion()); if(newEntries.isEmpty()) { System.err.println("Found no new log entries."); } else { System.err.println("Found "+newEntries.size()+" new log entries."); } if(verbose) { System.err.println("New Entries: "); for (PersistedLogEntry logEntry : newEntries) { System.err.println(logEntry); } } System.err.println("Log file written to: "+logFile.getAbsolutePath()); return merged; } }