001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.reef.wake.profiler; 020 021import net.sf.cglib.proxy.Enhancer; 022import net.sf.cglib.proxy.MethodInterceptor; 023import net.sf.cglib.proxy.MethodProxy; 024import org.apache.reef.tang.Aspect; 025import org.apache.reef.tang.InjectionFuture; 026import org.apache.reef.tang.types.ConstructorDef; 027import org.apache.reef.tang.util.MonotonicHashMap; 028import org.apache.reef.tang.util.ReflectionUtilities; 029 030import java.lang.reflect.Constructor; 031import java.lang.reflect.InvocationTargetException; 032import java.lang.reflect.Method; 033import java.lang.reflect.Modifier; 034import java.util.*; 035import java.util.concurrent.atomic.AtomicLong; 036import java.util.logging.Logger; 037 038public class WakeProfiler implements Aspect { 039 private static final Logger LOG = Logger.getLogger(WakeProfiler.class.toString()); 040 private final Map<Object, Vertex<?>> vertexObject = new MonotonicHashMap<>(); 041 private final Map<InjectionFuture<?>, Object> futures = new MonotonicHashMap<>(); 042 private final Map<Object, Stats> stats = new MonotonicHashMap<>(); 043 044 @Override 045 public Aspect createChildAspect() { 046 return this; 047 } 048 049 @SuppressWarnings("unchecked") 050 private <T> Vertex<T> getVertex(final T t) { 051 if (t instanceof Set) { 052 return (Vertex<T>) newSetVertex((Set<?>) t); 053 } else { 054 Vertex<T> v = (Vertex<T>) vertexObject.get(t); 055 // Add dummy vertices for objects that were bound volatile. 056 if (v == null) { 057 v = new Vertex<>(t); 058 vertexObject.put(t, v); 059 } 060 return v; 061 } 062 } 063 064 @SuppressWarnings("unchecked") 065 private <T> Vertex<T> getFuture(final InjectionFuture<T> future) { 066 return getVertex((T) futures.get(future)); 067 } 068 069 @SuppressWarnings("unchecked") 070 private <T> Vertex<?> newSetVertex(final Set<T> s) { 071 if (vertexObject.containsKey(s)) { 072 return (Vertex<Set<T>>) vertexObject.get(s); 073 } 074 if (s.size() > 1) { 075 LOG.fine("new set of size " + s.size()); 076 final Vertex<?>[] sArgs = new Vertex[s.size()]; 077 int k = 0; 078 for (final Object p : s) { 079 sArgs[k] = getVertex(p); 080 k++; 081 } 082 final Vertex<Set<T>> sv = new Vertex<>(s, null, sArgs); 083 vertexObject.put(s, sv); 084 return sv; 085 } else { 086 final Object p = s.iterator().next(); 087 final Vertex<?> w = getVertex(p); 088 // alias the singleton set to its member 089 vertexObject.put(s, w); 090 return w; 091 } 092 } 093 094 @SuppressWarnings("unchecked") 095 @Override 096 public <T> T inject(final ConstructorDef<T> constructorDef, final Constructor<T> constructor, final Object[] args) 097 throws InvocationTargetException, IllegalAccessException, IllegalArgumentException, InstantiationException { 098// LOG.info("inject" + constructor + "->" + args.length); 099 final Vertex<?>[] vArgs = new Vertex[args.length]; 100 for (int i = 0; i < args.length; i++) { 101 final Object o = args[i]; 102 final Vertex<?> v = getVertex(o); 103 if (o instanceof Set) { 104 LOG.fine("Got a set arg for " + constructorDef + " length " + ((Set<?>) o).size()); 105 } 106 vArgs[i] = v; 107 } 108 109 T ret; 110 final Class<T> clazz = constructor.getDeclaringClass(); 111 boolean isEventHandler = false; 112 for (final Method m : clazz.getDeclaredMethods()) { 113 if (m.getName().equals("onNext")) { // XXX hack: Interpose on "event handler in spirit" 114 isEventHandler = true; 115 } 116 } 117 if (isEventHandler) { 118 try { 119 if (Modifier.isFinal(clazz.getDeclaredMethod("onNext", Object.class).getModifiers())) { 120 throw new Exception(ReflectionUtilities.getFullName(clazz) + ".onNext() is final; cannot intercept it"); 121 } 122 final Stats s = new Stats(); 123 final Enhancer e = new Enhancer(); 124 e.setSuperclass(clazz); 125 e.setCallback(new MethodInterceptor() { 126 127 @Override 128 public Object intercept(final Object object, final Method method, final Object[] args, 129 final MethodProxy methodProxy) throws Throwable { 130 131 if (method.getName().equals("onNext")) { 132 final long start = System.nanoTime(); 133// LOG.info(object + "." + method.getName() + " called"); 134 final Object o = methodProxy.invokeSuper(object, args); 135 final long stop = System.nanoTime(); 136 137 s.getMessageCount().incrementAndGet(); 138 s.getSumLatency().addAndGet(stop - start); 139 140 return o; 141 142 } else { 143 return methodProxy.invokeSuper(object, args); 144 } 145 } 146 }); 147 ret = (T) e.create(constructor.getParameterTypes(), args); 148 stats.put(ret, s); 149 } catch (final Exception e) { 150 LOG.warning("Wake profiler could not intercept event handler: " + e.getMessage()); 151 ret = constructor.newInstance(args); 152 } 153 } else { 154 ret = constructor.newInstance(args); 155 } 156 final Vertex<T> v = new Vertex<T>(ret, constructorDef, vArgs); 157 vertexObject.put(ret, v); 158 return ret; 159 } 160 161 @Override 162 public <T> void injectionFutureInstantiated(final InjectionFuture<T> arg0, final T arg1) { 163 if (!futures.containsKey(arg0)) { 164 LOG.warning("adding future " + arg0 + " instance " + arg1); 165 futures.put(arg0, arg1); 166 getVertex(arg1); 167 } 168 } 169 170 private String jsonEscape(final String s) { 171 return s 172 .replaceAll("\\\\", "\\\\\\\\") 173 .replaceAll("\\\"", "\\\\\"") 174 .replaceAll("/", "\\\\/") 175 .replaceAll("\b", "\\\\b") 176 .replaceAll("\f", "\\\\f") 177 .replaceAll("\n", "\\\\n") 178 .replaceAll("\r", "\\\\r") 179 .replaceAll("\t", "\\\\t"); 180 181 } 182 183 private String join(final String sep, final List<String> tok) { 184 if (tok.size() == 0) { 185 return ""; 186 } 187 final StringBuffer sb = new StringBuffer(tok.get(0)); 188 for (int i = 1; i < tok.size(); i++) { 189 sb.append(sep + tok.get(i)); 190 } 191 return sb.toString(); 192 } 193 194 private boolean whitelist(final Object o) { 195 return true; 196 } 197 198 public String objectGraphToString() { 199 final List<Vertex<?>> vertices = new ArrayList<>(); 200 final Map<Vertex<?>, Integer> offVertex = new MonotonicHashMap<>(); 201 202 final StringBuffer sb = new StringBuffer("{\"nodes\":[\n"); 203 204 final List<String> nodes = new ArrayList<String>(); 205 final LinkedList<Vertex<?>> workQueue = new LinkedList<>(); 206 for (final Object o : vertexObject.keySet()) { 207 if (whitelist(o)) { 208 workQueue.add(getVertex(o)); 209 } 210 } 211 for (final Object o : futures.values()) { 212 if ((!vertexObject.containsKey(o)) && whitelist(o)) { 213 workQueue.add(getVertex(o)); 214 } 215 } 216 while (!workQueue.isEmpty()) { 217 final Vertex<?> v = workQueue.removeFirst(); 218 LOG.warning("Work queue " + v); 219 220 final Object o = v.getObject(); 221 final String s; 222 final String tooltip; 223 if (o instanceof InjectionFuture) { 224 s = null; 225 tooltip = null; 226 } else if (o instanceof String) { 227 s = "\"" + ((String) o) + "\""; 228 tooltip = null; 229 } else if (o instanceof Number) { 230 s = o.toString(); 231 tooltip = null; 232 } else if (o instanceof Set) { 233 LOG.warning("Set of size " + ((Set<?>) o).size() + " with " + v.getOutEdges().length + " out edges"); 234 s = "{...}"; 235 tooltip = null; 236//// } else if(false && (o instanceof EventHandler || o instanceof Stage)) { 237//// s = jsonEscape(v.getObject().toString()); 238 } else { 239 final Stats stat = stats.get(o); 240 if (stat != null) { 241 final long cnt = stat.getMessageCount().get(); 242 final long lat = stat.getSumLatency().get(); 243 tooltip = ",\"count\":" + cnt + ",\"latency\":\"" + (((double) lat) / (((double) cnt) * 1000000.0) + "\""); 244 // quote the latency, since it might be nan 245 } else { 246 tooltip = null; 247 } 248 s = removeEnhancements(o.getClass().getSimpleName()); 249 } 250 if (s != null) { 251 offVertex.put(v, vertices.size()); 252 vertices.add(v); 253 if (tooltip == null) { 254 nodes.add("{\"name\":\"" + jsonEscape(s) + "\"}"); 255 } else { 256 nodes.add("{\"name\":\"" + jsonEscape(s) + "\"" + tooltip + "}"); 257 } 258 259 } 260 } 261 sb.append(join(",\n", nodes)); 262 sb.append("],\n\"links\":["); 263 final List<String> links = new ArrayList<>(); 264 for (final Vertex<?> v : vertices) { 265 for (final Vertex<?> w : v.getOutEdges()) { 266 LOG.fine("pointing object" + v.getObject()); 267 LOG.fine("pointed to object " + w.getObject()); 268 if (w.getObject() instanceof InjectionFuture) { 269 final Vertex<?> futureTarget = getFuture((InjectionFuture<?>) w.getObject()); //futures.get(w.getObject()); 270 final Integer off = offVertex.get(futureTarget); 271 LOG.fine("future target " + futureTarget + " off = " + off); 272 if (off != null) { 273 links.add("{\"target\":" + offVertex.get(v) + ",\"source\":" + off + ",\"value\":" + 1.0 + 274 ",\"back\":true}"); 275 } 276 } else { 277 final Integer off = offVertex.get(w); 278 if (off != null) { 279 final Stats s = stats.get(w.getObject()); 280 if (s != null) { 281 links.add("{\"source\":" + offVertex.get(v) + ",\"target\":" + off + ",\"value\":" + 282 (s.getMessageCount().get() + 3.0) + "}"); 283 } else { 284 links.add("{\"source\":" + offVertex.get(v) + ",\"target\":" + off + ",\"value\":" + 1.0 + "}"); 285 } 286 } 287 } 288 } 289 } 290 sb.append(join(",\n", links)); 291 sb.append("]}"); 292 LOG.info("JSON: " + sb.toString()); 293 return sb.toString(); 294 } 295 296 private String removeEnhancements(final String simpleName) { 297 return simpleName.replaceAll("\\$\\$.+$", ""); 298 } 299 300 private final class Stats { 301 private AtomicLong messageCount = new AtomicLong(0); 302 private AtomicLong sumLatency = new AtomicLong(0); 303 304 AtomicLong getMessageCount() { 305 return messageCount; 306 } 307 308 AtomicLong getSumLatency() { 309 return sumLatency; 310 } 311 } 312 313}