1 /*
   2  * Copyright (c) 2014, 2015, Dynatrace and/or its affiliates. All rights reserved.
   3  * 
   4  * This file is part of the Lock Contention Tracing Subsystem for the HotSpot
   5  * Virtual Machine, which is developed at Christian Doppler Laboratory on
   6  * Monitoring and Evolution of Very-Large-Scale Software Systems. Please
   7  * contact us at <http://mevss.jku.at/> if you need additional information
   8  * or have any questions.
   9  *
  10  * This code is free software; you can redistribute it and/or modify it
  11  * under the terms of the GNU General Public License version 2 only, as
  12  * published by the Free Software Foundation.
  13  *
  14  * This code is distributed in the hope that it will be useful, but WITHOUT
  15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  16  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  17  * version 2 for more details (a copy is included in the LICENSE file that
  18  * accompanied this code).
  19  *
  20  * You should have received a copy of the GNU General Public License version
  21  * 2 along with this work. If not, see <http://www.gnu.org/licenses/>.
  22  *
  23  */ 
  24 package sun.evtracing.processing;
  25 
  26 import java.io.File;
  27 import java.io.FileNotFoundException;
  28 import java.io.PrintStream;
  29 import java.util.ArrayDeque;
  30 import java.util.ArrayList;
  31 import java.util.Deque;
  32 import java.util.HashMap;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.stream.Collectors;
  36 import java.util.stream.StreamSupport;
  37 
  38 import sun.evtracing.parser.GlobalSequenceOrderedEvent;
  39 import sun.evtracing.parser.GroupEvent;
  40 import sun.evtracing.parser.ThreadExitEvent;
  41 import sun.evtracing.parser.ThreadNameChangeEvent;
  42 import sun.evtracing.parser.ThreadParkBeginEvent;
  43 import sun.evtracing.parser.ThreadParkEndEvent;
  44 import sun.evtracing.parser.ThreadStartEvent;
  45 import sun.evtracing.parser.ThreadState;
  46 import sun.evtracing.parser.ThreadStateChangeEvent;
  47 import sun.evtracing.parser.VMEndEvent;
  48 import sun.evtracing.parser.metadata.JavaStack;
  49 
  50 public class TraceEventJsonWriter extends AbstractTraceEventHandler {
  51         private class ThreadData {
  52                 private long stateChangeTime = -1;
  53                 private ThreadState state;
  54                 private final Deque<List<GlobalSequenceOrderedEvent>> nestingStack = new ArrayDeque<>();
  55 
  56                 public long stateChangeTime() {
  57                         assert stateChangeTime > 0;
  58                         return stateChangeTime;
  59                 }
  60 
  61                 public ThreadState state() {
  62                         return state;
  63                 }
  64 
  65                 public void setThreadState(long time, ThreadState ts) {
  66                         this.stateChangeTime = time;
  67                         this.state = ts;
  68                 }
  69 
  70                 public void resetThreadState() {
  71                         setThreadState(-1, null);
  72                 }
  73 
  74                 public int nestingLevel() {
  75                         return nestingStack.size();
  76                 }
  77 
  78                 public void addParkBeginEvent(ThreadParkBeginEvent event) {
  79                         int currentLevel = nestingStack.size();
  80                         assert event.nestingLevel() >= currentLevel;
  81                         if (event.nestingLevel() > currentLevel) {
  82                                 for (int i = currentLevel; i < event.nestingLevel() - 1; i++) {
  83                                         nestingStack.push(new ArrayList<>());
  84                                 }
  85                                 List<GlobalSequenceOrderedEvent> events = new ArrayList<>();
  86                                 events.add(event);
  87                                 nestingStack.push(events);
  88                         } else if (event.nestingLevel() == currentLevel) {
  89                                 addParkEvent(event);
  90                         }
  91                 }
  92 
  93                 public void addParkEndEvent(ThreadParkEndEvent event) {
  94                         addParkEvent(event);
  95                 }
  96 
  97                 public void addParkEvent(GlobalSequenceOrderedEvent event) {
  98                         if (nestingLevel() > 0) {
  99                                 nestingStack.peek().add(event);
 100                         } else {
 101                                 event.accept(parkHandler);
 102                         }
 103                 }
 104 
 105                 public long nestingFirstSeen() {
 106                         return nestingStack.peek().get(0).timestamp();
 107                 }
 108 
 109                 public void popNestingLevel() {
 110                         List<GlobalSequenceOrderedEvent> events = nestingStack.pop();
 111                         if (nestingLevel() > 0) {
 112                                 nestingStack.peek().addAll(events);
 113                         } else {
 114                                 events.forEach(e -> e.accept(parkHandler));
 115                         }
 116                 }
 117         }
 118 
 119         private PrintStream ps;
 120         private final Map<Long, ThreadData> threadData = new HashMap<>();
 121         private final TraceEventHandler parkHandler = new AbstractTraceEventHandler() {
 122                 @Override
 123                 public void threadParkBegin(ThreadParkBeginEvent event) {
 124                         String name = event.clazz().metadata().prettyName(true);
 125                         JavaStack jstack = event.stack().metadata();
 126                         String stack;
 127                         if (jstack.isSet()) {
 128                                 stack = StreamSupport.stream(jstack.spliterator(), false)
 129                                                         .map(frame -> "\"" + (frame.method().isSet() ? frame.method().prettyDescriptor(true) : "(unknown)") + "\"")
 130                                                         .collect(Collectors.joining(","));
 131                         } else {
 132                                 stack = "";
 133                         }
 134                         ps.printf(",\n{\"name\":\"%s\",\"cat\":\"park\",\"ph\":\"B\",\"ts\":%d,\"pid\":1,\"tid\":%d,\"args\":{\"Arg isAbsolute\":%s,\"Arg time\":%d,\"Identity Hash Code\":\"%s\",\"Stack\":[%s],\"Nesting Level\":%d,\"Seq Begin\":%d}}", name, event.timestamp() / 1000, event.thread().identifier(), event.isAbsolute(), event.parkTime(), Integer.toHexString(event.blockerObject()), stack, event.nestingLevel(), event.sequenceNumber());
 135                 }
 136 
 137                 @Override
 138                 public void threadParkEnd(ThreadParkEndEvent event) {
 139                         ps.printf(",\n{\"ph\":\"E\",\"ts\":%d,\"pid\":1,\"tid\":%d,\"args\":{\"Seq Unpark\":%d,\"Seq End\":%d,\"Return Code\":\"%s\"}}", event.timestamp() / 1000, event.thread().identifier(), event.unparkSequenceNumber(), event.sequenceNumber(), event.parkReturnCode().name());
 140                 }
 141         };
 142 
 143         public TraceEventJsonWriter(File file) throws FileNotFoundException {
 144                 ps = new PrintStream(file);
 145                 ps.println("[");
 146                 ps.print("{\"name\":\"process_name\",\"ph\":\"M\",\"pid\":1,\"args\":{\"name\":\"java\"}}");
 147         }
 148 
 149         private ThreadData getOrCreateThreadData(long thread) {
 150                 return threadData.computeIfAbsent(thread, t -> new ThreadData());
 151         }
 152 
 153         @Override
 154         public void threadStart(ThreadStartEvent event) {
 155                 ps.printf(",\n{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":1,\"tid\":%d,\"args\":{\"name\":\"%s\"}}", event.thread().identifier(), event.name());
 156         }
 157 
 158         @Override
 159         public void threadNameChange(ThreadNameChangeEvent event) {
 160                 ps.printf(",\n{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":1,\"tid\":%d,\"args\":{\"name\":\"%s\"}}", event.affectedThread().identifier(), event.newName());
 161         }
 162 
 163         @Override
 164         public void threadStateChange(ThreadStateChangeEvent event) {
 165                 ThreadData td = getOrCreateThreadData(event.affectedThread().identifier());
 166                 if (td.state() != null) {
 167                         printThreadState(event.timestamp(), event.affectedThread().identifier(), td);
 168                 }
 169                 if (!event.newState().toThreadState().equals(Thread.State.TERMINATED)) {
 170                         td.setThreadState(event.timestamp(), event.newState());
 171                 }
 172         }
 173 
 174         private void printThreadState(long time, long thread, ThreadData threadData) {
 175                 long startTime = threadData.stateChangeTime() / 1000;
 176                 long endTime = time / 1000;
 177                 if (endTime - startTime > 0) {
 178                         ps.printf(",\n{\"name\":\"%s\",\"cat\":\"state\",\"ph\":\"B\",\"ts\":%d,\"pid\":1,\"tid\":%d}", threadData.state().toString(), startTime, thread);
 179                         ps.printf(",\n{\"cat\":\"state\",\"ph\":\"E\",\"ts\":%d,\"pid\":1,\"tid\":%d}", endTime, thread);
 180                 }
 181         }
 182 
 183         @Override
 184         public void threadExit(ThreadExitEvent event) {
 185                 ThreadData td = getOrCreateThreadData(event.thread().identifier());
 186                 if (td.state() != null) {
 187                         printThreadState(event.timestamp(), event.thread().identifier(), td);
 188                         td.resetThreadState();
 189                 }
 190         }
 191 
 192         @Override
 193         public void endVM(VMEndEvent event) {
 194                 ps.println("\n]");
 195                 ps.close();
 196         }
 197 
 198         @Override
 199         public void threadParkBegin(ThreadParkBeginEvent event) {
 200                 ThreadData td = getOrCreateThreadData(event.thread().identifier());
 201                 td.addParkBeginEvent(event);
 202         }
 203 
 204         @Override
 205         public void threadParkEnd(ThreadParkEndEvent event) {
 206                 ThreadData td = getOrCreateThreadData(event.thread().identifier());
 207                 td.addParkEndEvent(event);
 208         }
 209 
 210         @Override
 211         public void group(GroupEvent event) {
 212                 ThreadData td = getOrCreateThreadData(event.thread().identifier());
 213                 int nestingLevel = td.nestingLevel();
 214                 if (nestingLevel > 0) {
 215                         String name = event.clazz().metadata().prettyName(true);
 216                         long startTime = td.nestingFirstSeen();
 217                         ps.printf(",\n{\"name\":\"%s\",\"cat\":\"group\",\"ph\":\"X\",\"ts\":%d,\"dur\":%d,\"pid\":1,\"tid\":%d,\"args\":{\"Identity Hash Code\":\"%s\",\"Nesting Level\":%d,\"Seq Begin Reference\":%d,\"Seq End\":%d}}", name, startTime / 1000, (event.timestamp() - startTime) / 1000, event.thread().identifier(), Integer.toHexString(event.obj()), nestingLevel, event.sequenceBeginReference(), event.sequenceNumber());
 218                         td.popNestingLevel();
 219                 }
 220         }
 221 
 222 }