/*
* Copyright (c) 2013-2017 Chris Newland.
* Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
* Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
*/
package org.adoptopenjdk.jitwatch.parser.hotspot;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_AT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_OPEN_ANGLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_OPEN_SQUARE_BRACKET;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.DEBUG_LOGGING;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.LOADED;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.SKIP_BODY_TAGS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.SKIP_HEADER_TAGS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_AT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_FILE_COLON;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_OPEN_ANGLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_SPACE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_CLOSE_CDATA;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_CODE_CACHE_FULL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_COMMAND;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_HOTSPOT_LOG_DONE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_NMETHOD;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_OPEN_CDATA;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_OPEN_CLOSE_CDATA;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_RELEASE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_START_COMPILE_THREAD;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_SWEEPER;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_TASK;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_TASK_QUEUED;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_TTY;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_VM_ARGUMENTS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_VM_VERSION;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_XML;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.List;
import java.util.Set;
import org.adoptopenjdk.jitwatch.core.IJITListener;
import org.adoptopenjdk.jitwatch.model.CodeCacheEvent.CodeCacheEventType;
import org.adoptopenjdk.jitwatch.model.NumberedLine;
import org.adoptopenjdk.jitwatch.model.Tag;
import org.adoptopenjdk.jitwatch.model.Task;
import org.adoptopenjdk.jitwatch.parser.AbstractLogParser;
import org.adoptopenjdk.jitwatch.util.StringUtil;
public class HotSpotLogParser extends AbstractLogParser
{
public HotSpotLogParser(IJITListener jitListener)
{
super(jitListener);
}
private void checkIfErrorDialogNeeded()
{
if (!hasParseError)
{
if (!hasTraceClassLoad)
{
hasParseError = true;
errorDialogTitle = "Missing VM Switch -XX:+TraceClassLoading";
errorDialogBody = "JITWatch requires the -XX:+TraceClassLoading VM switch to be used.\nPlease recreate your log file with this switch enabled.";
}
}
if (hasParseError)
{
errorListener.handleError(errorDialogTitle, errorDialogBody);
}
}
private void parseHeaderLines()
{
if (DEBUG_LOGGING)
{
logger.debug("parseHeaderLines()");
}
for (NumberedLine numberedLine : splitLog.getHeaderLines())
{
if (!skipLine(numberedLine.getLine(), SKIP_HEADER_TAGS))
{
Tag tag = tagProcessor.processLine(numberedLine.getLine());
processLineNumber = numberedLine.getLineNumber();
if (tag != null)
{
handleTag(tag);
}
}
}
}
@Override
protected void parseLogFile()
{
parseHeaderLines();
buildParsedClasspath();
buildClassModel();
parseLogCompilationLines();
parseAssemblyLines();
checkIfErrorDialogNeeded();
}
private void parseLogCompilationLines()
{
if (DEBUG_LOGGING)
{
logger.debug("parseLogCompilationLines()");
}
for (NumberedLine numberedLine : splitLog.getCompilationLines())
{
if (!skipLine(numberedLine.getLine(), SKIP_BODY_TAGS))
{
Tag tag = tagProcessor.processLine(numberedLine.getLine());
processLineNumber = numberedLine.getLineNumber();
if (tag != null)
{
handleTag(tag);
}
}
}
}
private void parseAssemblyLines()
{
if (DEBUG_LOGGING)
{
logger.debug("parseAssemblyLines()");
}
for (NumberedLine numberedLine : splitLog.getAssemblyLines())
{
processLineNumber = numberedLine.getLineNumber();
asmProcessor.handleLine(numberedLine.getLine());
}
asmProcessor.complete();
asmProcessor.attachAssemblyToMembers(model.getPackageManager());
asmProcessor.clear();
}
@Override
protected void splitLogFile(File hotspotLog)
{
reading = true;
try (BufferedReader reader = new BufferedReader(new FileReader(hotspotLog), 65536))
{
String currentLine = reader.readLine();
while (reading && currentLine != null)
{
try
{
String trimmedLine = currentLine.trim();
if (trimmedLine.length() > 0)
{
char firstChar = trimmedLine.charAt(0);
if (firstChar == C_OPEN_ANGLE || firstChar == C_OPEN_SQUARE_BRACKET || firstChar == C_AT)
{
currentLine = trimmedLine;
}
handleLogLine(currentLine);
}
}
catch (Exception ex)
{
logger.error("Exception handling: '{}'", currentLine, ex);
}
currentLine = reader.readLine();
}
}
catch (IOException ioe)
{
logger.error("Exception while splitting log file", ioe);
}
}
private boolean skipLine(final String line, final Set<String> skipSet)
{
boolean isSkip = false;
for (String skip : skipSet)
{
if (line.startsWith(skip))
{
isSkip = true;
break;
}
}
return isSkip;
}
private void handleLogLine(final String inCurrentLine)
{
String currentLine = inCurrentLine;
NumberedLine numberedLine = new NumberedLine(parseLineNumber++, currentLine);
if (TAG_TTY.equals(currentLine))
{
inHeader = false;
return;
}
else if (currentLine.startsWith(TAG_XML))
{
inHeader = true;
}
if (inHeader)
{
// HotSpot log header XML can have text nodes so consume all lines
splitLog.addHeaderLine(numberedLine);
}
else
{
if (currentLine.startsWith(TAG_OPEN_CDATA) || currentLine.startsWith(TAG_CLOSE_CDATA)
|| currentLine.startsWith(TAG_OPEN_CLOSE_CDATA))
{
// ignore, TagProcessor will recognise from <fragment> tag
}
else if (currentLine.startsWith(S_OPEN_ANGLE))
{
// After the header, XML nodes do not have text nodes
splitLog.addCompilationLine(numberedLine);
}
else if (currentLine.startsWith(LOADED))
{
splitLog.addClassLoaderLine(numberedLine);
}
else if (currentLine.startsWith(S_AT))
{
// possible PrintCompilation was enabled as well as
// LogCompilation?
// jmh does this with perf annotations
// Ignore this line
}
else
{
// need to cope with nmethod appearing on same line as last hlt
// 0x0000 hlt <nmethod compile_id= ....
int indexNMethod = currentLine.indexOf(S_OPEN_ANGLE + TAG_NMETHOD);
if (indexNMethod != -1)
{
if (DEBUG_LOGGING)
{
logger.debug("detected nmethod tag mangled with assembly");
}
String assembly = currentLine.substring(0, indexNMethod);
String remainder = currentLine.substring(indexNMethod);
numberedLine.setLine(assembly);
splitLog.addAssemblyLine(numberedLine);
handleLogLine(remainder);
}
else
{
splitLog.addAssemblyLine(numberedLine);
}
}
}
}
@Override
protected void handleTag(Tag tag)
{
String tagName = tag.getName();
switch (tagName)
{
case TAG_VM_VERSION:
handleVmVersion(tag);
break;
case TAG_TASK_QUEUED:
handleTagQueued(tag);
break;
case TAG_NMETHOD:
handleTagNMethod(tag);
break;
case TAG_TASK:
handleTagTask((Task) tag);
break;
case TAG_SWEEPER:
storeCodeCacheEvent(CodeCacheEventType.SWEEPER, tag);
break;
case TAG_CODE_CACHE_FULL:
storeCodeCacheEvent(CodeCacheEventType.CACHE_FULL, tag);
break;
case TAG_HOTSPOT_LOG_DONE:
model.setEndOfLog(tag);
break;
case TAG_START_COMPILE_THREAD:
handleStartCompileThread(tag);
break;
case TAG_VM_ARGUMENTS:
handleTagVmArguments(tag);
break;
default:
break;
}
}
private void handleVmVersion(Tag tag)
{
String release = tag.getNamedChildren(TAG_RELEASE).get(0).getTextContent();
model.setVmVersionRelease(release);
}
private void handleTagVmArguments(Tag tag)
{
List<Tag> tagCommandChildren = tag.getNamedChildren(TAG_COMMAND);
if (tagCommandChildren.size() > 0)
{
vmCommand = tagCommandChildren.get(0).getTextContent();
if (DEBUG_LOGGING)
{
logger.debug("VM Command: {}", vmCommand);
}
}
}
private void handleStartCompileThread(Tag tag)
{
model.getJITStats().incCompilerThreads();
}
private void buildParsedClasspath()
{
if (DEBUG_LOGGING)
{
logger.debug("buildParsedClasspath()");
}
for (NumberedLine numberedLine : splitLog.getClassLoaderLines())
{
buildParsedClasspath(numberedLine.getLine());
}
}
private void buildClassModel()
{
if (DEBUG_LOGGING)
{
logger.debug("buildClassModel()");
}
for (NumberedLine numberedLine : splitLog.getClassLoaderLines())
{
buildClassModel(numberedLine.getLine());
}
}
private void buildParsedClasspath(String inCurrentLine)
{
if (!hasTraceClassLoad)
{
hasTraceClassLoad = true;
}
final String FROM_SPACE = "from ";
String originalLocation = null;
int fromSpacePos = inCurrentLine.indexOf(FROM_SPACE);
if (fromSpacePos != -1)
{
originalLocation = inCurrentLine.substring(fromSpacePos + FROM_SPACE.length(), inCurrentLine.length() - 1);
}
if (originalLocation != null && originalLocation.startsWith(S_FILE_COLON))
{
originalLocation = originalLocation.substring(S_FILE_COLON.length());
try
{
originalLocation = URLDecoder.decode(originalLocation, "UTF-8");
}
catch (UnsupportedEncodingException e)
{
// ignore
}
getParsedClasspath().addClassLocation(originalLocation);
}
}
private void buildClassModel(String inCurrentLine)
{
String fqClassName = StringUtil.getSubstringBetween(inCurrentLine, LOADED, S_SPACE);
if (fqClassName != null)
{
addToClassModel(fqClassName);
}
}
}