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}