/*
* Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* 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, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.tele;
import java.io.*;
import java.util.*;
import com.sun.max.tele.debug.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.heap.*;
/**
* Implements the (mostly) immutable history of Maxine VM states during a debugging sessions.
*/
public final class TeleVMState implements MaxVMState {
private static final List<TeleNativeThread> EMPTY_THREAD_LIST = Collections.emptyList();
private static final List<MaxThread> EMPTY_MAXTHREAD_LIST = Collections.emptyList();
private static final List<TeleBreakpointEvent> EMPTY_BREAKPOINTEVENT_LIST = Collections.emptyList();
private static final List<MaxBreakpointEvent> EMPTY_MAXBREAKPOINTEVENT_LIST = Collections.emptyList();
private static final List<MaxEntityMemoryRegion<? extends MaxEntity> > EMPTY_MAXMEMORYREGION_LIST = Collections.emptyList();
private final MaxInspectionMode inspectionMode;
private final MaxProcessState processState;
private final long serialID;
private final long epoch;
private final List<MaxEntityMemoryRegion<? extends MaxEntity> > memoryAllocations;
private final List<MaxThread> threads;
private final MaxThread singleStepThread;
private final List<MaxThread> threadsStarted;
private final List<MaxThread> threadsDied;
private final List<MaxBreakpointEvent> breakpointEvents;
private final MaxWatchpointEvent maxWatchpointEvent;
private final HeapPhase heapPhase;
private final boolean isInEviction;
private final TeleVMState previous;
/**
* @param inspectionMode TODO
* @param processState current state of the VM
* @param epoch current process epoch counter
* @param memoryAllocations memory regions the VM has allocated from the OS
* @param threads threads currently active in the VM
* @param singleStepThread thread just single-stepped, null if none
* @param threadsStarted threads created since the previous state
* @param threadsDied threads died since the previous state
* @param breakpointEvents information about threads currently at breakpoints, empty if none
* @param watchpointEvent information about a thread currently at a watchpoint, null if none
* @param heapPhase is possible heap phases in the VM
* @param isInEviction is the VM, when paused, in a code eviction
* @param previous previous state
*/
public TeleVMState(
MaxInspectionMode inspectionMode,
ProcessState processState,
long epoch,
List<MaxEntityMemoryRegion<? extends MaxEntity> > memoryAllocations,
Collection<TeleNativeThread> threads,
TeleNativeThread singleStepThread,
List<TeleNativeThread> threadsStarted,
List<TeleNativeThread> threadsDied,
List<TeleBreakpointEvent> breakpointEvents,
VmWatchpointEvent watchpointEvent,
HeapPhase heapPhase,
boolean isInEviction,
TeleVMState previous) {
this.inspectionMode = inspectionMode;
switch(processState) {
case RUNNING:
this.processState = MaxProcessState.RUNNING;
break;
case STOPPED:
this.processState = MaxProcessState.STOPPED;
break;
case TERMINATED:
this.processState = MaxProcessState.TERMINATED;
break;
default:
this.processState = MaxProcessState.UNKNOWN;
}
this.serialID = previous == null ? 0 : previous.serialID() + 1;
this.epoch = epoch;
// Reuse old list of memory regions if unchanged
if (previous != null && previous.memoryAllocations.equals(memoryAllocations)) {
this.memoryAllocations = previous.memoryAllocations;
} else {
this.memoryAllocations = Collections.unmodifiableList(memoryAllocations);
}
this.singleStepThread = singleStepThread;
if (threadsStarted.size() == 0) {
this.threadsStarted = EMPTY_MAXTHREAD_LIST;
} else {
this.threadsStarted = Collections.unmodifiableList(new ArrayList<MaxThread>(threadsStarted));
}
if (threadsDied.size() == 0) {
this.threadsDied = EMPTY_MAXTHREAD_LIST;
} else {
this.threadsDied = Collections.unmodifiableList(new ArrayList<MaxThread>(threadsDied));
}
if (breakpointEvents.isEmpty()) {
this.breakpointEvents = EMPTY_MAXBREAKPOINTEVENT_LIST;
} else {
this.breakpointEvents = Collections.unmodifiableList(new ArrayList<MaxBreakpointEvent>(breakpointEvents));
}
this.maxWatchpointEvent = watchpointEvent;
this.heapPhase = heapPhase;
this.isInEviction = isInEviction;
this.previous = previous;
// Compute the current active thread list.
if (previous == null) {
// First state transition in the history.
this.threads = Collections.unmodifiableList(new ArrayList<MaxThread>(threadsStarted));
} else if (threadsStarted.size() + threadsDied.size() == 0) {
// No changes since predecessor; share the thread list. This is the most common case.
this.threads = previous.threads();
} else {
// There have been some thread changes; make a new (immutable) sequence for the new state
this.threads = Collections.unmodifiableList(new ArrayList<MaxThread>(threads));
}
}
public MaxProcessState processState() {
return processState;
}
public long serialID() {
return serialID;
}
public long epoch() {
return epoch;
}
public List<MaxEntityMemoryRegion<? extends MaxEntity> > memoryAllocations() {
return memoryAllocations;
}
public MaxEntityMemoryRegion<? extends MaxEntity> findMemoryRegion(Address address) {
for (MaxEntityMemoryRegion<? extends MaxEntity> memoryRegion : memoryAllocations) {
if (memoryRegion != null && memoryRegion.contains(address)) {
return memoryRegion;
}
}
return null;
}
public List<MaxThread> threads() {
return threads;
}
public MaxThread singleStepThread() {
return singleStepThread;
}
public List<MaxThread> threadsStarted() {
return threadsStarted;
}
public List<MaxThread> threadsDied() {
return threadsDied;
}
public List<MaxBreakpointEvent> breakpointEvents() {
return breakpointEvents;
}
public MaxWatchpointEvent watchpointEvent() {
return maxWatchpointEvent;
}
public HeapPhase heapPhase() {
return heapPhase;
}
public boolean isInEviction() {
return isInEviction;
}
public MaxVMState previous() {
return previous;
}
public boolean newerThan(MaxVMState maxVMState) {
return maxVMState == null || serialID > maxVMState.serialID();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(50);
sb.append(getClass().getSimpleName()).append("(");
sb.append(Long.toString(serialID)).append(", ");
sb.append(processState.toString()).append(", ");
sb.append("epoch=").append(Long.toString(epoch)).append(", ");
sb.append("phase=").append(heapPhase.label()).append(", ");
sb.append("prev=");
if (previous == null) {
sb.append("null");
} else {
sb.append(previous.processState().label());
}
sb.append(")");
return sb.toString();
}
public void writeSummary(PrintStream printStream) {
MaxVMState state = this;
while (state != null) {
final StringBuilder sb = new StringBuilder(100);
sb.append(Long.toString(state.serialID())).append(": ");
sb.append("proc=(").append(state.processState().label()).append(", epoch=").append(Long.toString(state.epoch())).append(") ");
sb.append("phase=").append(heapPhase.label()).append(" ");
sb.append("eviction=").append(state.isInEviction()).append(" ");
printStream.println(sb.toString());
if (state.singleStepThread() != null) {
printStream.println("\tsingle-stepped=" + state.singleStepThread().toShortString());
}
if (state.previous() != null && state.memoryAllocations() == state.previous().memoryAllocations()) {
printStream.println("\tmemory regions: <unchanged>");
} else {
printStream.println("\tmemory regions:");
for (MaxMemoryRegion memoryRegion : state.memoryAllocations()) {
printStream.println("\t\t" + memoryRegion.getClass().getName() + "(\"" + memoryRegion.regionName() + "\" @ 0x" + memoryRegion.start().toHexString() + ")");
}
}
if (state.previous() != null && state.threads() == state.previous().threads()) {
printStream.println("\tthreads active: <unchanged>");
} else if (state.threads().size() == 0) {
printStream.println("\tthreads active: <empty>");
} else {
printStream.println("\tthreads active:");
for (MaxThread thread : state.threads()) {
printStream.println("\t\t" + thread.toShortString());
}
}
if (state.threadsStarted().size() > 0) {
printStream.println("\tthreads newly started:");
for (MaxThread thread : state.threadsStarted()) {
printStream.println("\t\t" + thread.toShortString());
}
}
if (state.threadsDied().size() > 0) {
printStream.println("\tthreads newly died:");
for (MaxThread thread : state.threadsDied()) {
printStream.println("\t\t" + thread.toShortString());
}
}
if (state.breakpointEvents().size() > 0) {
printStream.println("\tbreakpoint events");
for (MaxBreakpointEvent breakpointEvent : state.breakpointEvents()) {
printStream.println("\t\t" + breakpointEvent.toString());
}
}
if (state.watchpointEvent() != null) {
printStream.println("\tthread at watchpoint=" + state.watchpointEvent());
}
state = state.previous();
}
}
/**
* Creates a null state that is designed to precede any real states in
* a state history.
*
* @param mode the mode of the inspection in which the history is being
* recorded.
* @return a null state with no predecessor, unknown process state, and no thread information.
*/
public static TeleVMState nullState(MaxInspectionMode mode) {
return new TeleVMState(
mode,
ProcessState.UNKNOWN,
-1L,
EMPTY_MAXMEMORYREGION_LIST,
EMPTY_THREAD_LIST,
(TeleNativeThread) null,
EMPTY_THREAD_LIST,
EMPTY_THREAD_LIST,
EMPTY_BREAKPOINTEVENT_LIST,
(VmWatchpointEvent) null, null, false, (TeleVMState) null);
}
}