/*
* Copyright (c) 2014, 2015, Dynatrace and/or its affiliates. All rights reserved.
*
* This file is part of the Lock Contention Tracing Subsystem for the HotSpot
* Virtual Machine, which is developed at Christian Doppler Laboratory on
* Monitoring and Evolution of Very-Large-Scale Software Systems. Please
* contact us at if you need additional information
* or have any questions.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work. If not, see .
*
*/
package sun.evtracing.processing;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import sun.evtracing.parser.GlobalSequenceOrderedEvent;
import sun.evtracing.parser.GroupEvent;
import sun.evtracing.parser.ThreadExitEvent;
import sun.evtracing.parser.ThreadNameChangeEvent;
import sun.evtracing.parser.ThreadParkBeginEvent;
import sun.evtracing.parser.ThreadParkEndEvent;
import sun.evtracing.parser.ThreadStartEvent;
import sun.evtracing.parser.ThreadState;
import sun.evtracing.parser.ThreadStateChangeEvent;
import sun.evtracing.parser.VMEndEvent;
import sun.evtracing.parser.metadata.JavaStack;
public class TraceEventJsonWriter extends AbstractTraceEventHandler {
private class ThreadData {
private long stateChangeTime = -1;
private ThreadState state;
private final Deque> nestingStack = new ArrayDeque<>();
public long stateChangeTime() {
assert stateChangeTime > 0;
return stateChangeTime;
}
public ThreadState state() {
return state;
}
public void setThreadState(long time, ThreadState ts) {
this.stateChangeTime = time;
this.state = ts;
}
public void resetThreadState() {
setThreadState(-1, null);
}
public int nestingLevel() {
return nestingStack.size();
}
public void addParkBeginEvent(ThreadParkBeginEvent event) {
int currentLevel = nestingStack.size();
assert event.nestingLevel() >= currentLevel;
if (event.nestingLevel() > currentLevel) {
for (int i = currentLevel; i < event.nestingLevel() - 1; i++) {
nestingStack.push(new ArrayList<>());
}
List events = new ArrayList<>();
events.add(event);
nestingStack.push(events);
} else if (event.nestingLevel() == currentLevel) {
addParkEvent(event);
}
}
public void addParkEndEvent(ThreadParkEndEvent event) {
addParkEvent(event);
}
public void addParkEvent(GlobalSequenceOrderedEvent event) {
if (nestingLevel() > 0) {
nestingStack.peek().add(event);
} else {
event.accept(parkHandler);
}
}
public long nestingFirstSeen() {
return nestingStack.peek().get(0).timestamp();
}
public void popNestingLevel() {
List events = nestingStack.pop();
if (nestingLevel() > 0) {
nestingStack.peek().addAll(events);
} else {
events.forEach(e -> e.accept(parkHandler));
}
}
}
private PrintStream ps;
private final Map threadData = new HashMap<>();
private final TraceEventHandler parkHandler = new AbstractTraceEventHandler() {
@Override
public void threadParkBegin(ThreadParkBeginEvent event) {
String name = event.clazz().metadata().prettyName(true);
JavaStack jstack = event.stack().metadata();
String stack;
if (jstack.isSet()) {
stack = StreamSupport.stream(jstack.spliterator(), false)
.map(frame -> "\"" + (frame.method().isSet() ? frame.method().prettyDescriptor(true) : "(unknown)") + "\"")
.collect(Collectors.joining(","));
} else {
stack = "";
}
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());
}
@Override
public void threadParkEnd(ThreadParkEndEvent event) {
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());
}
};
public TraceEventJsonWriter(File file) throws FileNotFoundException {
ps = new PrintStream(file);
ps.println("[");
ps.print("{\"name\":\"process_name\",\"ph\":\"M\",\"pid\":1,\"args\":{\"name\":\"java\"}}");
}
private ThreadData getOrCreateThreadData(long thread) {
return threadData.computeIfAbsent(thread, t -> new ThreadData());
}
@Override
public void threadStart(ThreadStartEvent event) {
ps.printf(",\n{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":1,\"tid\":%d,\"args\":{\"name\":\"%s\"}}", event.thread().identifier(), event.name());
}
@Override
public void threadNameChange(ThreadNameChangeEvent event) {
ps.printf(",\n{\"name\":\"thread_name\",\"ph\":\"M\",\"pid\":1,\"tid\":%d,\"args\":{\"name\":\"%s\"}}", event.affectedThread().identifier(), event.newName());
}
@Override
public void threadStateChange(ThreadStateChangeEvent event) {
ThreadData td = getOrCreateThreadData(event.affectedThread().identifier());
if (td.state() != null) {
printThreadState(event.timestamp(), event.affectedThread().identifier(), td);
}
if (!event.newState().toThreadState().equals(Thread.State.TERMINATED)) {
td.setThreadState(event.timestamp(), event.newState());
}
}
private void printThreadState(long time, long thread, ThreadData threadData) {
long startTime = threadData.stateChangeTime() / 1000;
long endTime = time / 1000;
if (endTime - startTime > 0) {
ps.printf(",\n{\"name\":\"%s\",\"cat\":\"state\",\"ph\":\"B\",\"ts\":%d,\"pid\":1,\"tid\":%d}", threadData.state().toString(), startTime, thread);
ps.printf(",\n{\"cat\":\"state\",\"ph\":\"E\",\"ts\":%d,\"pid\":1,\"tid\":%d}", endTime, thread);
}
}
@Override
public void threadExit(ThreadExitEvent event) {
ThreadData td = getOrCreateThreadData(event.thread().identifier());
if (td.state() != null) {
printThreadState(event.timestamp(), event.thread().identifier(), td);
td.resetThreadState();
}
}
@Override
public void endVM(VMEndEvent event) {
ps.println("\n]");
ps.close();
}
@Override
public void threadParkBegin(ThreadParkBeginEvent event) {
ThreadData td = getOrCreateThreadData(event.thread().identifier());
td.addParkBeginEvent(event);
}
@Override
public void threadParkEnd(ThreadParkEndEvent event) {
ThreadData td = getOrCreateThreadData(event.thread().identifier());
td.addParkEndEvent(event);
}
@Override
public void group(GroupEvent event) {
ThreadData td = getOrCreateThreadData(event.thread().identifier());
int nestingLevel = td.nestingLevel();
if (nestingLevel > 0) {
String name = event.clazz().metadata().prettyName(true);
long startTime = td.nestingFirstSeen();
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());
td.popNestingLevel();
}
}
}