/* * 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(); } } }