/******************************************************************************* * Copyright (c) 2009 the CHISEL group and contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Del Myers - initial API and implementation *******************************************************************************/ package ca.uvic.chisel.javasketch.persistence.internal; import static ca.uvic.chisel.javasketch.persistence.internal.logs.TraceLogEvent.METHOD_ENTERED; import static ca.uvic.chisel.javasketch.persistence.internal.logs.TraceLogEvent.METHOD_EXITED; import static ca.uvic.chisel.javasketch.persistence.internal.logs.TraceLogEvent.PAUSED; import static ca.uvic.chisel.javasketch.persistence.internal.logs.TraceLogEvent.RESUMED; import static ca.uvic.chisel.javasketch.persistence.internal.logs.TraceLogEvent.THREAD_INIT; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.URISyntaxException; import java.sql.SQLException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IDebugEventSetListener; import ca.uvic.chisel.javasketch.IProgramSketch; import ca.uvic.chisel.javasketch.SketchPlugin; import ca.uvic.chisel.javasketch.data.SketchDataPlugin; import ca.uvic.chisel.javasketch.data.internal.DataUtils; import ca.uvic.chisel.javasketch.data.internal.WriteDataUtils; import ca.uvic.chisel.javasketch.data.model.imple.internal.TraceImpl; import ca.uvic.chisel.javasketch.internal.DBProgramSketch; import ca.uvic.chisel.javasketch.launching.ITraceClient; import ca.uvic.chisel.javasketch.persistence.internal.logs.MethodEnterEvent; import ca.uvic.chisel.javasketch.persistence.internal.logs.MethodEvent; import ca.uvic.chisel.javasketch.persistence.internal.logs.MethodExitEvent; import ca.uvic.chisel.javasketch.persistence.internal.logs.PauseEvent; import ca.uvic.chisel.javasketch.persistence.internal.logs.ResumeEvent; import ca.uvic.chisel.javasketch.persistence.internal.logs.ThreadInitEvent; import ca.uvic.chisel.javasketch.persistence.internal.logs.TraceLog; import ca.uvic.chisel.javasketch.persistence.internal.logs.TraceLogEvent; /** * A simple job that persists all the trace files into a database. It is assumed * that the sketch is completed, and not currently running. If it is running, * then this job will quit with an error. * * @author Del Myers * */ public class PersistTraceJob extends Job { private final String KEY_FILTER_DECORATION = "filter"; private final String filtered = "fil"; private final String filterStart = "fils"; private final String triggered = "trig"; private final String triggerEnd = "trige"; private String[] inclusionFilters = null; private String[] exclusionFilters = null; private class TracePauseResumeListener implements IDebugEventSetListener { /* (non-Javadoc) * @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(org.eclipse.debug.core.DebugEvent[]) */ @Override public void handleDebugEvents(DebugEvent[] events) { for (DebugEvent event : events) { if (event.getSource().equals(client)) { if (event.getKind() == DebugEvent.TERMINATE) { synchronized (this) { notifyAll(); } } else if (event.getKind() == DebugEvent.MODEL_SPECIFIC) { if (event.getDetail() == ITraceClient.TRACE_PAUSED) { synchronized (this) { notifyAll(); } } } } } } } private static enum ModelEventType { Thread, Call, Arrival, Activation, Reply, Return, Throw, Catch } /** * Used for storing an internal "call stack" for each thread. This way, the returns * and catches can be resolved while processing the events. * @author Del Myers * */ private class TraceModelEvent { public final ModelEventType eventType; public final long modelId; private HashMap<String, Object> decorations; public TraceModelEvent(ModelEventType eventType, long modelId) { this.eventType = eventType; this.modelId = modelId; decorations = new HashMap<String, Object>(); } public void decorate(String key, Object value) { decorations.put(key, value); } public Object getDecoration(String key) { return decorations.get(key); } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return eventType.name() + ":" + modelId; } } private class ThreadLogReader { private LinkedList<TraceModelEvent> eventStack; private LinkedList<MethodEnterEvent> callEventStack; private TraceLog log; private long mark; private boolean finished; public int accessorLineNumber = -1; private boolean failed = false; //the time of the first fail, to calculate whether we should give up private long failure = 0; public ThreadLogReader(File file) throws IOException { log = new TraceLog(file); eventStack = new LinkedList<TraceModelEvent>(); callEventStack = new LinkedList<MethodEnterEvent>(); } public void mark() { this.mark = log.getReadLocation(); } public long getMark() { return mark; } public long getReadLocation() { return log.getReadLocation(); } public long getTraceLength() { return log.getTraceLength(); } /** * Process the next event in the log, if one can be read. Returns false * if no event was read, or the end of the log has been reached. If blockOnRead * is set, then the reader will block until the next event can be read, or an * end of file is reached. Otherwise, it will only proccess an event given that * one is available. * @param blockOnRead * @return * @throws CoreException * @throws SQLException */ public boolean processNextEvent(boolean blockOnRead) throws CoreException { if (log.isComplete() || (!blockOnRead && !log.available())) return false; try { TraceLogEvent event = null; try { event = log.nextEvent(); } catch (IOException ioe) { closeThread(eventStack); log.close(); return false; } if (event != null) { failed = false; failure = 0; processEvent(eventStack, callEventStack, event, this); } else { if (isClientFinished()) { //failed read, save it and quit if (!failed) { failed = true; failure = System.currentTimeMillis(); } else { if ((System.currentTimeMillis() - failure) > 10000) { //give up to 10 seconds to try again, or quit. closeThread(eventStack); log.close(); } } } return false; } } catch (Exception e) { try { log.close(); } catch (IOException e1) { throw new CoreException(SketchPlugin.getDefault().createStatus(e1)); } throw new CoreException(SketchPlugin.getDefault().createStatus(e)); } return true; } public void cancel() throws IOException { log.close(); } public boolean isFinished() { return log.isComplete() || finished; } /** * @throws SQLException * @throws CoreException * */ public void finish() throws CoreException, SQLException { closeThread(eventStack); try { log.close(); } catch (IOException e) { throw new CoreException(SketchPlugin.getDefault().createStatus(e)); } finished = true; } } private IProgramSketch sketch; private WriteDataUtils queryUtils; private ITraceClient client; public PersistTraceJob(IProgramSketch sketch) { super("Storing Trace Data: " + sketch.getProcessName()); this.sketch = sketch; setRule(sketch.getRule()); setPriority(LONG); } @Override protected IStatus run(IProgressMonitor monitor) { //first clear the sketch try { IProgramSketch active = SketchPlugin.getDefault().getActiveSketch(); if (active != null && sketch.equals(active)) { SketchPlugin.getDefault().setActiveSketch(null); } ((DBProgramSketch)sketch).reset(); } catch (CoreException e1) { return e1.getStatus(); } int totalWork = 7500000; int indexWork = 2500000; double workDone = 0; monitor.beginTask("Storing Trace Data", totalWork + indexWork); // create a database to store everything in if (queryUtils != null) { try { queryUtils.close(); } catch (SQLException e) { return new Status(Status.ERROR, SketchDataPlugin.PLUGIN_ID, "Error closing database connection", e); } } TracePauseResumeListener pauseResumeListener = new TracePauseResumeListener(); DebugPlugin.getDefault().addDebugEventListener(pauseResumeListener); HashMap<File, ThreadLogReader> readers = new HashMap<File, ThreadLogReader>(); try { queryUtils = (WriteDataUtils) ((TraceImpl)sketch.getTraceData()).getDataUtils(); queryUtils.getConnection().setAutoCommit(false); boolean done = false; client = sketch.getTracer(); long events = 0; long totalFileLength = -1; //initialize the readers. getNewTracedThreads(readers); while (!done) { getNewTracedThreads(readers); boolean clientFinished = isClientFinished(); ThreadLogReader[] readerArray = readers.values().toArray(new ThreadLogReader[0]); //calculate the total file length so that we can get an //accurate amount of work that has been completed. if (clientFinished && totalFileLength < 0) { totalFileLength = 0; long readLength = 0; for (ThreadLogReader r: readerArray) { totalFileLength += r.getTraceLength(); if (r.isFinished()) { readLength += r.getTraceLength(); } else { readLength += r.getReadLocation(); } } if (readLength > 0) { workDone = ((double)readLength)/totalFileLength; } //get the monitor caught up. monitor.worked((int)(workDone*totalWork)); } boolean isComplete = true; for (ThreadLogReader reader : readerArray) { //mark the last spot read. reader.mark(); if (monitor.isCanceled()) { reader.cancel(); break; } if (clientFinished) { //process each reader until it is complete because //it is slightly faster than processing one event //at a time while (reader.processNextEvent(clientFinished)) { events++; monitor.subTask("(" + events + ") events"); long mark = reader.getMark(); long amountRead = reader.getReadLocation()-mark; double worked = ((double)amountRead/totalFileLength)*totalWork; if (worked > 0) { //mark for the next read, and update the monitor reader.mark(); monitor.worked((int)worked); } //don't force the issue if (monitor.isCanceled()) { break; } } } else { if (client != null && !(client.isPaused() || client.isTerminated())) { //wait a bit, and reset synchronized (pauseResumeListener) { pauseResumeListener.wait(5000); break; } } else { //process one event from each reader, only if //the trace is paused if (reader.processNextEvent(clientFinished)) { events++; monitor.subTask("(" + events + ") events"); } } } isComplete &= reader.isFinished(); } done = (monitor.isCanceled() || isComplete & clientFinished); } //store the views. monitor.subTask("Indexing methods and types..."); for (ThreadLogReader reader : readers.values()) { reader.finish(); } queryUtils.commit(); queryUtils.storeViews(); queryUtils.compact(); queryUtils.getConnection().setAutoCommit(true); monitor.worked(indexWork); } catch (InterruptedException e){ Thread.interrupted(); monitor.setCanceled(true); clearData(); } catch (CoreException e) { monitor.setCanceled(true); clearData(); return e.getStatus(); } catch (Exception e) { e.printStackTrace(); return new Status(Status.ERROR, SketchPlugin.PLUGIN_ID, "Fatal Error Running Persist Job", e); } finally { for (ThreadLogReader reader : readers.values()) { if (!reader.log.isComplete()) { try { reader.log.close(); } catch (IOException e) { SketchPlugin.getDefault().log(e); } } } DebugPlugin.getDefault().removeDebugEventListener(pauseResumeListener); } if (monitor.isCanceled()) { clearData(); return new Status(IStatus.WARNING, SketchPlugin.PLUGIN_ID, "Unable to complete file processing. Database rolled-back."); } monitor.done(); return Status.OK_STATUS; } /** * @return */ protected boolean isClientFinished() { return (client == null || (client.isTerminated() && client.getLaunch().isTerminated())); } /** * @param readers2 * @return * @throws IOException */ private void getNewTracedThreads(HashMap<File, ThreadLogReader> readers) throws IOException { File[] files = getTraceFiles(); for (File file : files) { ThreadLogReader reader = readers.get(file); if (reader == null) { reader = new ThreadLogReader(file); readers.put(file, reader); } } } /** * */ private void clearData() { // TODO Auto-generated method stub } /* * Processes the file. * * @param file * @param monitor * @throws CoreException * private void process(String taskName, File file, IProgressMonitor monitor) throws CoreException { //source.getResourceSet().getR // read the file one line at a time, processing each event. try { TraceLog log = new TraceLog(file); // figure out how many lines are in the file int worked = 0; monitor.beginTask(taskName, (int)log.getTraceLength()); //this contains a list of the model ids for LinkedList<TraceModelEvent> eventStack = new LinkedList<TraceModelEvent>(); List<MethodEnterEvent> callEventStack = new LinkedList<MethodEnterEvent>(); TraceLogEvent event = null; long lastPosition = log.getReadLocation(); int eventCount = 0; while ((event = log.nextEvent()) != null) { processEvent(eventStack, callEventStack, event); if (monitor.isCanceled()) { break; } long position = log.getReadLocation(); monitor.worked((int)(position-lastPosition)); monitor.subTask(file.getName() + " (" +eventCount+" events)"); eventCount++; lastPosition = position; } closeThread(eventStack); monitor.worked((int)(file.length()-worked)); monitor.done(); queryUtils.commit(); } catch (IOException e) { throw new CoreException(new Status(Status.ERROR, SketchPlugin.PLUGIN_ID, "Error reading trace file", e)); } catch (SQLException e) { throw new CoreException(new Status(Status.ERROR, SketchPlugin.PLUGIN_ID, "Error accessing database", e)); } } */ /** * Finishes off the thread by adding a final reply to the top activation. * @param eventStack * @throws CoreException * @throws SQLException * */ private void closeThread(LinkedList<TraceModelEvent> eventStack) throws CoreException, SQLException { Long time = -1L; long longestTime = -1; //move up the call chain, sending a return for each call. while (eventStack.size() > 3) { TraceModelEvent parentActivation = eventStack.removeLast(); time = (Long)parentActivation.getDecoration("end"); if (time > longestTime) { longestTime = time; } TraceModelEvent parentArrival = eventStack.removeLast(); TraceModelEvent parentCall = eventStack.removeLast(); TraceModelEvent callingActivation = eventStack.getLast(); if (parentActivation.eventType != ModelEventType.Activation || parentArrival.eventType != ModelEventType.Arrival) { throw new CoreException(new Status(Status.ERROR, SketchPlugin.PLUGIN_ID, "Error creating return: malformed call stack")); } long replyID = queryUtils.nextMessageID(); long returnID = replyID + 1; long result = queryUtils.createMessage(DataUtils.MESSAGE_KIND_REPLY, parentActivation.modelId, returnID, time, -1, null); Integer line = (Integer) parentCall.getDecoration("line"); checkModel("reply", result, replyID); result = queryUtils.createMessage(DataUtils.MESSAGE_KIND_RETURN, callingActivation.modelId, replyID, time, (line != null) ? line : -1, null); checkModel("return", result, returnID); } if (eventStack.size() > 0) { // create the message for the final return. long replyID = queryUtils.nextMessageID(); TraceModelEvent parentActivation = eventStack.removeLast(); time = (Long)parentActivation.getDecoration("end"); if (time == null || time < longestTime) { time = longestTime; } long result = queryUtils.createMessage(DataUtils.MESSAGE_KIND_REPLY, parentActivation.modelId, null, time, -1, null); checkModel("reply", result, replyID); } } /** * @param event * @param threadLogReader * @throws IOException * @throws CoreException */ private void processEvent(LinkedList<TraceModelEvent> eventStack, List<MethodEnterEvent> callEventStack, TraceLogEvent event, ThreadLogReader reader) throws SQLException, CoreException { //if (true) return; switch (event.type) { case THREAD_INIT: processThread(eventStack, callEventStack, (ThreadInitEvent)event); break; case METHOD_ENTERED: processEntry(eventStack, callEventStack, (MethodEnterEvent)event, reader); break; case METHOD_EXITED: processExit(eventStack, callEventStack, (MethodExitEvent)event); break; case PAUSED: processPause(eventStack, callEventStack, (PauseEvent)event); break; case RESUMED: processResume(eventStack, callEventStack, (ResumeEvent)event, reader); break; } } /** * Checks the entry event to see if it should be filtered. If so, then the entry * is placed on a stack for later processing in case a later entry should trigger * it to no longer be filtered (we must track the calls from the originally filtered * call, to the new triggered call). * @param eventStack * @param event * @param reader * @throws CoreException * @throws SQLException */ private void processEntry(LinkedList<TraceModelEvent> eventStack, List<MethodEnterEvent> callEventStack, MethodEnterEvent event, ThreadLogReader reader) throws SQLException, CoreException { if (event.methodName.startsWith("access$")) { //just add the method to the stack, and ignore it callEventStack.add(event); return; } //check the top of the stack to see if it is an accessor. //if it is, then set this one's line number accordingly if (callEventStack.size() > 0) { MethodEnterEvent top = callEventStack.get(callEventStack.size()-1); if (top.methodName.startsWith("access$")) { event.lineNumber = top.lineNumber; } } if (isFiltered(event)) { if (callEventStack.size() > 0) { //check the top of the stack to see if filtering is //already occurring. If it is not, set this one as //the start of the filtering, and still process it MethodEnterEvent top = callEventStack.get(callEventStack.size()-1); String dec = top.getDecoration(KEY_FILTER_DECORATION); if (dec == null) { event.decorate(KEY_FILTER_DECORATION, filterStart); doProcessEntry(eventStack, event, reader); } else { event.decorate(KEY_FILTER_DECORATION, filtered); } } else { event.decorate(KEY_FILTER_DECORATION, filtered); //doProcessEntry(eventStack, event); } } else { //check the event on the top of call stack //if it was filtered, then we have to run backward, to the //root of the filtering and process each entry event //so that we have a path to the new triggered event if (callEventStack.size() > 0) { ListIterator<MethodEnterEvent> iterator = callEventStack.listIterator(callEventStack.size()); LinkedList<MethodEnterEvent> eventsToProcess = new LinkedList<MethodEnterEvent>(); while (iterator.hasPrevious()) { MethodEnterEvent top = iterator.previous(); String filterState = top.getDecoration(KEY_FILTER_DECORATION); if (filtered.equals(filterState)) { top.decorate(KEY_FILTER_DECORATION, triggered); eventsToProcess.addFirst(top); } else if (filterStart.equals(filterState)) { //it has already been processed, so mark it as the end //of the triggers, and don't process top.decorate(KEY_FILTER_DECORATION, triggerEnd); break; } else { //we don't want to process anything that was not just //previously filtered. break; } } while (eventsToProcess.size() > 0) { //process the events doProcessEntry(eventStack, eventsToProcess.removeFirst(), reader); } } //finally, process this event doProcessEntry(eventStack, event, reader); } //add the event to the top of the callstack callEventStack.add(event); } /** * @param event * @return */ private boolean isFiltered(MethodEvent event) { //if (true) return false; return !(isIncluded(event) && !isExcluded(event)); } /** * @param event * @return */ private boolean isExcluded(MethodEvent event) { String[] inclusion = getInclusionFilters(); String[] exclusion = getExclusionFilters(); if (inclusion.length == 0 && exclusion.length == 0) { return false; } else if (exclusion.length == 0) { return false; } String typeName = simplifyJavaType(event.className); String methodSig = typeName + "." + event.methodName + event.methodSignature; for (String p : exclusionFilters) { //check for the default package if (".*".equals(p)) { if (typeName.indexOf('.') < 0) { return true; } } else if (p.charAt(0) == '*') { p = p.substring(1); if (p.length() != 0) { if (p.charAt(p.length()-1) == '*') { p = p.substring(0, p.length()-1); if (methodSig.contains(p)) { return true; } } else { if (methodSig.endsWith(p)) { return true; } } } else { return true; } } else if (p.charAt(p.length()-1) == '*') { p = p.substring(0, p.length()-1); if (methodSig.startsWith(p)) { return true; } } else { if (methodSig.equals(p)) { return true; } } } return false; } /** * @param event * @return */ private boolean isIncluded(MethodEvent event) { String[] inclusion = getInclusionFilters(); String[] exclusion = getExclusionFilters(); if (inclusion.length == 0 && exclusion.length == 0) { return true; } else if (inclusion.length == 0) { return true; } String typeName = simplifyJavaType(event.className); String methodSig = typeName + "." + event.methodName + event.methodSignature; for (String p : inclusionFilters) { //check for the default package if (".*".equals(p)) { if (typeName.indexOf('.') < 0) { return true; } } else if (p.charAt(0) == '*') { p = p.substring(1); if (p.isEmpty()) return true; if (p.charAt(p.length()-1) == '*') { p = p.substring(0, p.length()-1); if (methodSig.contains(p)) { return true; } } else { if (methodSig.endsWith(p)) { return true; } } } else if (p.charAt(p.length()-1) == '*') { p = p.substring(0, p.length()-1); if (methodSig.startsWith(p)) { return true; } } else { if (methodSig.equals(p)) { return true; } } } return false; } /** * @return */ private String[] getExclusionFilters() { if (exclusionFilters == null) { //load the exclusion filters. exclusionFilters = getSketch().getFilterSettings().getResolvedExclusionFilters(); } return exclusionFilters; } /** * @return */ private String[] getInclusionFilters() { if (inclusionFilters == null) { //load the inclusion filters. inclusionFilters = getSketch().getFilterSettings().getResolvedInclusionFilters(); } return inclusionFilters; } private void processExit( LinkedList<TraceModelEvent> eventStack, List<MethodEnterEvent> callEventStack, MethodExitEvent event) throws CoreException, SQLException { //size should never be less than one on an exit process if (callEventStack.size() <= 0) return; MethodEnterEvent topCall = callEventStack.remove(callEventStack.size()-1); if (!topCall.methodName.startsWith("access$")) { String filteredState = topCall.getDecoration(KEY_FILTER_DECORATION); if (filteredState == null || !filteredState.equals(filtered)) { //process the exit doProcessExit(eventStack, event); } } } /** * @param eventStack * @param callEventStack * @param event * @throws SQLException * @throws CoreException */ private void processPause(LinkedList<TraceModelEvent> eventStack, List<MethodEnterEvent> callEventStack, PauseEvent event) throws CoreException, SQLException { //run through the stack frames, and send an exit event for each one if (event.stackTrace == null) { return; } int thread_id = (Integer)eventStack.getFirst().getDecoration("thread_id"); queryUtils.createEvent(event.time, "PAUSE IN THREAD " + thread_id); //clear the stack if (callEventStack.size() <= 0) return; ListIterator<MethodEnterEvent> iterator = callEventStack.listIterator(callEventStack.size()); //we have to generate the events before processing them in order to avoid a concurrent //modification. LinkedList<MethodExitEvent> exitEvents = new LinkedList<MethodExitEvent>(); while (iterator.hasPrevious()) { MethodEnterEvent topEvent = iterator.previous(); MethodExitEvent exitEvent = topEvent.simulateExit(event.time); exitEvents.addFirst(exitEvent); } while (exitEvents.size() > 0) { processExit(eventStack, callEventStack, exitEvents.removeLast()); } queryUtils.commit(); // for (String frame : stackFrames) { // int methodNameIndex = frame.indexOf(';') + 1; // int methodSigIndex = frame.indexOf('('); // int lineNumberIndex = frame.lastIndexOf('['); // int lineNumberEndIndex = frame.length(); // if (methodNameIndex <= 0 || lineNumberIndex <= 0) { // continue; // } // String className = frame.substring(0, methodNameIndex); // String methodName = frame.substring(methodNameIndex, methodSigIndex); // String methodSig = frame.substring(methodSigIndex, lineNumberIndex); // //line number isn't needed // HashMap<String, String> exitEvent = new HashMap<String, String>(); // exitEvent.put("event", "X"); // exitEvent.put(KEY_METHOD_CLASS, className.replace('/', '.')); // exitEvent.put(KEY_METHOD_NAME, methodName); // exitEvent.put(KEY_METHOD_SIGNATURE, methodSig.replace('/', '.')); // exitEvent.put(KEY_METHOD_TIME, event.get(KEY_METHOD_TIME)); // exitEvent.put(KEY_METHOD_RETURN, "?"); // processExit(eventStack, callEventStack, exitEvent); // } } /** * @param eventStack * @param callEventStack * @param event * @throws SQLException * @throws CoreException */ private void processResume(LinkedList<TraceModelEvent> eventStack, List<MethodEnterEvent> callEventStack, ResumeEvent event, ThreadLogReader reader) throws CoreException, SQLException { if (event.stackTrace == null) { return; } String stackFrames[] = event.stackTrace.split("\\n+"); int thread_id = (Integer)eventStack.getFirst().getDecoration("thread_id"); queryUtils.createEvent(event.time, "RESUME IN THREAD " + thread_id); //no need to process the last one on the frame: it will be the same as the next //entry call. for (int i = 0; i < stackFrames.length-1; i++) { String frame = stackFrames[i].trim(); int methodNameIndex = frame.indexOf(';') + 1; int methodSigIndex = frame.indexOf('('); int lineNumberIndex = frame.lastIndexOf('['); int lineNumberEndIndex = frame.length()-1; if (methodNameIndex <= 0 || lineNumberIndex <= 0) { continue; } try { String className = frame.substring(0, methodNameIndex); String methodName = frame.substring(methodNameIndex, methodSigIndex); String methodSig = frame.substring(methodSigIndex, lineNumberIndex); String lineString = frame.substring(lineNumberIndex+1, lineNumberEndIndex); int line = Integer.parseInt(lineString); //line number isn't needed MethodEnterEvent exitEvent = MethodEnterEvent.simulateEnter( className.replace('/','.'), methodName, methodSig.replace('/','.'), line, event.time); processEntry(eventStack, callEventStack, exitEvent, reader); } catch (StringIndexOutOfBoundsException e) { throw new CoreException(new Status(IStatus.ERROR, SketchPlugin.PLUGIN_ID, "Bad stack trace: \n " + e.getMessage(), e)); } } } /** * @param parent * @param event * @throws IOException * @throws CoreException * @throws SQLException */ private void doProcessExit(LinkedList<TraceModelEvent> eventStack, MethodExitEvent event) throws CoreException, SQLException { //some threads may start with a stack greater than //0 because they join in from a different thread //unfortunately, there is no graceful way to deal with this. if (eventStack.size() <= 3) return; TraceModelEvent parentActivation = eventStack.removeLast(); TraceModelEvent parentArrival = eventStack.removeLast(); TraceModelEvent parentCall = eventStack.removeLast(); TraceModelEvent callingActivation = eventStack.getLast(); if (parentActivation.eventType != ModelEventType.Activation || parentArrival.eventType != ModelEventType.Arrival || parentCall.eventType != ModelEventType.Call || callingActivation.eventType != ModelEventType.Activation) { throw new CoreException(new Status(Status.ERROR, SketchPlugin.PLUGIN_ID, "Error creating return: malformed call stack")); } // create the message for the return. long replyID = queryUtils.nextMessageID(); long returnID = replyID + 1; String replyString = DataUtils.MESSAGE_KIND_REPLY; String returnString = DataUtils.MESSAGE_KIND_RETURN; if (event.isException) { replyString = DataUtils.MESSAGE_KIND_THROW; returnString = DataUtils.MESSAGE_KIND_CATCH; } long result = queryUtils.createMessage(replyString, parentActivation.modelId, returnID, event.time, event.lineNumber, null); checkModel("reply", result, replyID); result = queryUtils.createMessage(returnString, callingActivation.modelId, replyID, event.time, (Integer)parentCall.getDecoration("line"), null); checkModel("return", result, returnID); //set the time for last activation callingActivation.decorate("end", event.time); //debugString = debugString.substring(1); TraceModelEvent firstEvent = eventStack.get(2); firstEvent.decorate("end", event.time); //System.out.println(debugString + "<" + "." + methodName); //TODO: add data for the return } /** * @param parent * @param event * @throws IOException * @throws CoreException */ private void doProcessEntry(LinkedList<TraceModelEvent> eventStack, MethodEnterEvent event, ThreadLogReader reader) throws SQLException, CoreException { //get the top of the call stack, to make sure that it is an activation TraceModelEvent parent = eventStack.getLast(); long thread_id = eventStack.getFirst().modelId; if (parent.eventType != ModelEventType.Activation) { throw new CoreException(new Status(Status.ERROR, SketchPlugin.PLUGIN_ID, "Error creating call: invalid parent")); } //use java type separators, and get rid of the language type identifiers for classes String this_type = simplifyJavaType(event.className); String type_name = simplifyJavaType(event.className); String signature = event.methodSignature.replace('/', '.'); String thisInstance = "?"; if (event.variableValues.length > 0) { //TODO process variables } //this method call may have come from an accessor.. get the line number //from it. // if (event.getDecoration(accessor) != null) // if (reader.accessorLineNumber != -1) { // if (event.lineNumber == -1) { // event.lineNumber = reader.accessorLineNumber; // } // //clear the accessor line number // reader.accessorLineNumber = -1; // } String sequence = ""; if (eventStack.size() > 3) { TraceModelEvent parentCall = eventStack.get(eventStack.size()-3); if (parentCall.eventType != ModelEventType.Call) { throw new CoreException(new Status(Status.ERROR, SketchPlugin.PLUGIN_ID, "Illegal format in trace file: missing parent call")); } sequence = (String) parentCall.getDecoration("sequence") + "."; } Long parentSize = (Long) parent.getDecoration("children"); if (parentSize == null) { parentSize = 0L; } parentSize = parentSize + 1; sequence = sequence + parentSize; //create the call long callID = queryUtils.nextMessageID(); long arrivalID = callID + 1; long result = queryUtils.createMessage(DataUtils.MESSAGE_KIND_CALL, parent.modelId, arrivalID, event.time, event.lineNumber, sequence); //make sure that the id matches checkModel("call", result, callID); //make sure to store the new number of children parent.decorate("children", parentSize); //create the arrival long activationID = queryUtils.nextActivationID(); result = queryUtils.createMessage(DataUtils.MESSAGE_KIND_ARRIVE, activationID, callID, event.time, -1, sequence); checkModel("arrival", result, arrivalID); //create the activation result = queryUtils.createActivation(arrivalID, type_name, event.methodName, signature, thread_id, this_type, thisInstance); checkModel("activation", result, activationID); //TODO: add data for the parameters //add to the call stack TraceModelEvent call = new TraceModelEvent(ModelEventType.Call, callID); call.decorate("line", event.lineNumber); call.decorate("time", event.time); call.decorate("method", event.methodName); if (sequence.length() > 40000) { throw new CoreException(new Status(IStatus.ERROR, SketchPlugin.PLUGIN_ID, "Call depth too large")); } call.decorate("sequence", sequence); eventStack.add(call); eventStack.add(new TraceModelEvent(ModelEventType.Arrival, arrivalID)); eventStack.add(new TraceModelEvent(ModelEventType.Activation, activationID)); eventStack.getLast().decorate("end", event.time); TraceModelEvent firstEvent = eventStack.get(2); firstEvent.decorate("end", event.time); //.out.println(debugString + ">" + type_name + "." + methodName); //debugString = debugString + " "; } /** * Converts names of classes as defined by the bytecode into simplified * Java names as they would appear in source code. * @param thisType * @return */ private String simplifyJavaType(String typeName) { typeName = typeName.replace('/', '.'); if (typeName.endsWith(";")) { if (typeName.length() > 2) { typeName = typeName.substring(1, typeName.length() -1); } } return typeName; } /** * @param callEventStack * @param parent * @param event * @throws CoreException * @throws IOException */ private void processThread(LinkedList<TraceModelEvent> eventStack, List<MethodEnterEvent> callEventStack, ThreadInitEvent event) throws SQLException, CoreException { //create the user class if needed // queryUtils.getOrCreateTraceClass("USER"); // long methodID = queryUtils.getOrCreateMethod("USER", "start", "()V"); long arrivalID = queryUtils.nextMessageID(); long threadID = queryUtils.createThread(event.threadID+"", event.threadName, arrivalID); long activationID = queryUtils.nextActivationID(); long result = queryUtils.createMessage(DataUtils.MESSAGE_KIND_ARRIVE, activationID, null, 0, -1, ""); checkModel("message", result, arrivalID); result = queryUtils.createActivation(arrivalID, "USER", "start", "()V", threadID, "USER", "?"); checkModel("activation", result, activationID); //set up the call stack eventStack.addLast(new TraceModelEvent(ModelEventType.Thread, threadID)); eventStack.addLast(new TraceModelEvent(ModelEventType.Arrival, arrivalID)); eventStack.addLast(new TraceModelEvent(ModelEventType.Activation, activationID)); eventStack.getLast().decorate("end", 0L); Integer thread_id = event.threadID; eventStack.getFirst().decorate("thread_id", thread_id); } private void checkModel(String modelType, long id1, long id2) throws CoreException { if (id1 != id2) { throw new CoreException(new Status(Status.ERROR, SketchPlugin.PLUGIN_ID, "Mismatched id for " + modelType + ": " + id1 + "/" + id2)); } } /** * @return */ private File[] getTraceFiles() { File file; try { file = new File(sketch.getTracePath().toURI()); return file.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getName().endsWith(".trace"); } }); } catch (URISyntaxException e) { return new File[0]; } } /* * (non-Javadoc) * * @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object) */ @Override public boolean belongsTo(Object family) { return (IProgramSketch.class.equals(family)); } /** * @return */ public IProgramSketch getSketch() { return sketch; } }