/**
* Copyright (c) 2014 Richard Warburton (richard.warburton@gmail.com)
* <p>
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
**/
package com.insightfullogic.honest_profiler.core.parser;
import org.slf4j.Logger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import static com.insightfullogic.honest_profiler.core.parser.LogParser.AmountRead.*;
public class LogParser
{
// These names match the names reported by the forte quality kit(AKA AsyncGetCallTrace)
// enum {
// ticks_no_Java_frame = 0,
// ticks_no_class_load = -1,
// ticks_GC_active = -2,
// ticks_unknown_not_Java = -3,
// ticks_not_walkable_not_Java = -4,
// ticks_unknown_Java = -5,
// ticks_not_walkable_Java = -6,
// ticks_unknown_state = -7,
// ticks_thread_exit = -8,
// ticks_deopt = -9,
// ticks_safepoint = -10
// };
private static final String[] AGCT_ERRORS = {"NoJavaFramesErr0",
"NoClassLoadErr1",
"GcActiveErr2",
"UnknownNotJava3",
"NotWalkableNotJavaErr4",
"UnknownJavaErr5",
"NotWalkableJavaErr6",
"UnknownStateErr7",
"ThreadExitErr8",
"DeoptErr9",
"SafepointErr10"};
private static final int NOT_WRITTEN = 0;
private static final int TRACE_START = 1;
private static final int TRACE_WITH_TIME = 11;
private static final int STACK_FRAME_BCI_ONLY = 2;
private static final int STACK_FRAME_FULL = 21;
private static final int NEW_METHOD = 3;
private static final int THREAD_META = 4;
private final LogEventListener listener;
private final Logger logger;
public static enum AmountRead
{
COMPLETE_RECORD, PARTIAL_RECORD, NOTHING
}
public LogParser(final Logger logger, final LogEventListener listener)
{
this.listener = listener;
this.logger = logger;
// report the different errors as methods
for (long errId = 0; errId < AGCT_ERRORS.length; errId++)
{
// we use negative jmethodIds, these are invalid addresses and should not collide with jmethodIds supplied by JVM
new Method(-errId - 1, "", "-AGCT-", AGCT_ERRORS[(int) errId]).accept(listener);
}
}
public AmountRead readRecord(ByteBuffer input)
{
int initialPosition = input.position();
if (!input.hasRemaining())
{
return NOTHING;
}
byte type = input.get();
try
{
switch (type)
{
case NOT_WRITTEN:
// go back one byte since we've just read a 0
input.position(input.position() - 1);
return NOTHING;
case TRACE_START:
readTraceStart(input, false);
return COMPLETE_RECORD;
case TRACE_WITH_TIME:
readTraceStart(input, true);
return COMPLETE_RECORD;
case STACK_FRAME_BCI_ONLY:
readStackFrameBciOnly(input);
return COMPLETE_RECORD;
case STACK_FRAME_FULL:
readStackFrameFull(input);
return COMPLETE_RECORD;
case NEW_METHOD:
readNewMethod(input);
return COMPLETE_RECORD;
case THREAD_META:
readNewThreadMeta(input);
return COMPLETE_RECORD;
}
}
catch (BufferUnderflowException e)
{
// If you've underflowed the buffer,
// then you need to wait for more data to be written.
input.position(initialPosition);
return PARTIAL_RECORD;
}
// Should never get here
return NOTHING;
}
public void endOfLog()
{
listener.endOfLog();
}
private void readNewMethod(ByteBuffer input)
{
Method newMethod = new Method(input.getLong(), readString(input), readString(input), readString(input));
newMethod.accept(listener);
}
private String readString(ByteBuffer input)
{
int size = input.getInt();
char[] buffer = new char[size];
// conversion from c style characters to Java.
for (int i = 0; i < size; i++)
{
buffer[i] = (char) input.get();
}
return new String(buffer);
}
private void readStackFrameBciOnly(ByteBuffer input)
{
int bci = input.getInt();
long methodId = input.getLong();
StackFrame stackFrame = new StackFrame(bci, methodId);
stackFrame.accept(listener);
}
private void readStackFrameFull(ByteBuffer input)
{
int bci = input.getInt();
int lineNumber = input.getInt();
long methodId = input.getLong();
StackFrame stackFrame = new StackFrame(bci, lineNumber, methodId);
stackFrame.accept(listener);
}
private void readTraceStart(ByteBuffer input, boolean withTime)
{
int numberOfFrames = input.getInt();
long threadId = input.getLong();
long timeSec = 0L;
long timeNano = 0L;
if (withTime) {
timeSec = input.getLong();
timeNano = input.getLong();
}
// number of frames <= 0 -> error, so log a mock stack frame reflecting the error. Logging errors as frames makes
// more sense when collecting profiles.
if (numberOfFrames <= 0)
{
// if this is an unknown error add a new method for it
if (-numberOfFrames >= AGCT_ERRORS.length)
{
new Method(numberOfFrames - 1, "", "AGCT", "UnknownErrCode"+(-numberOfFrames)).accept(listener);
}
// we choose to report errors via frames, so pretend there's a single frame in the trace
new TraceStart(1, threadId, timeSec, timeNano).accept(listener);
// we shift the err code by -1 to avoid using the valid NULL jmethodId
new StackFrame(-1, numberOfFrames - 1).accept(listener);
}
else
{
TraceStart traceStart = new TraceStart(numberOfFrames, threadId, timeSec, timeNano);
traceStart.accept(listener);
}
}
private void readNewThreadMeta(ByteBuffer input) {
long threadId = input.getLong();
String threadName = readString(input);
ThreadMeta threadMeta = new ThreadMeta(threadId, threadName);
threadMeta.accept(listener);
}
}