/*******************************************************************************
* Copyright (c) 2014, 2016 École Polytechnique de Montréal
*
* 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:
* Mohamad Gebai - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.module;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.os.linux.core.kernel.KernelAnalysisModule;
import org.eclipse.tracecompass.analysis.os.linux.core.kernel.KernelThreadInformationProvider;
import org.eclipse.tracecompass.analysis.os.linux.core.model.HostThread;
import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelAnalysisEventLayout;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.Activator;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.VcpuStateValues;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.VmAttributes;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.model.IVirtualMachineModel;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.model.VirtualCPU;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.model.VirtualMachine;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.model.qemukvm.QemuKvmVmModel;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.trace.layout.LttngEventLayout;
import org.eclipse.tracecompass.lttng2.kernel.core.trace.LttngKernelTrace;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventField;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfCpuAspect;
import org.eclipse.tracecompass.tmf.core.statesystem.AbstractTmfStateProvider;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;
import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperimentUtils;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
/**
* This is the state provider which translates the virtual machine experiment
* events to a state system.
*
* Attribute tree:
*
* <pre>
* |- Virtual Machines
* | |- <Guest Host ID> -> Friendly name (trace name)
* | | |- <VCPU number>
* | | | |- Status -> <Status value>
* </pre>
*
* The status value of the VCPUs are either {@link VcpuStateValues#VCPU_IDLE},
* {@link VcpuStateValues#VCPU_UNKNOWN} or {@link VcpuStateValues#VCPU_RUNNING}.
* Those three values are ORed with flags {@link VcpuStateValues#VCPU_VMM}
* and/or {@link VcpuStateValues#VCPU_PREEMPT} to indicate respectively whether
* they are in hypervisor mode or preempted on the host.
*
* @author Mohamad Gebai
*/
public class VirtualMachineStateProvider extends AbstractTmfStateProvider {
/**
* Version number of this state provider. Please bump this if you modify the
* contents of the generated state history in some way.
*/
private static final int VERSION = 1;
private static final int SCHED_SWITCH_INDEX = 0;
/* TODO: An analysis should support many hypervisor models */
private IVirtualMachineModel fModel;
private final Table<ITmfTrace, String, @Nullable Integer> fEventNames;
private final Map<ITmfTrace, IKernelAnalysisEventLayout> fLayouts;
// ------------------------------------------------------------------------
// Constructor
// ------------------------------------------------------------------------
/**
* Constructor
*
* @param experiment
* The virtual machine experiment
*/
public VirtualMachineStateProvider(TmfExperiment experiment) {
super(experiment, "Virtual Machine State Provider"); //$NON-NLS-1$
fModel = new QemuKvmVmModel(experiment);
Table<ITmfTrace, String, @Nullable Integer> table = HashBasedTable.create();
fEventNames = table;
fLayouts = new HashMap<>();
}
// ------------------------------------------------------------------------
// Event names management
// ------------------------------------------------------------------------
private void buildEventNames(ITmfTrace trace) {
IKernelAnalysisEventLayout layout;
if (trace instanceof LttngKernelTrace) {
layout = ((LttngKernelTrace) trace).getKernelEventLayout();
} else {
/* Fall-back to the base LttngEventLayout */
layout = LttngEventLayout.getInstance();
}
fLayouts.put(trace, layout);
fEventNames.put(trace, layout.eventSchedSwitch(), SCHED_SWITCH_INDEX);
}
// ------------------------------------------------------------------------
// IStateChangeInput
// ------------------------------------------------------------------------
@Override
public TmfExperiment getTrace() {
ITmfTrace trace = super.getTrace();
if (trace instanceof TmfExperiment) {
return (TmfExperiment) trace;
}
throw new IllegalStateException("VirtualMachineStateProvider: The associated trace should be an experiment"); //$NON-NLS-1$
}
@Override
public int getVersion() {
return VERSION;
}
@Override
public VirtualMachineStateProvider getNewInstance() {
TmfExperiment trace = getTrace();
return new VirtualMachineStateProvider(trace);
}
@Override
protected void eventHandle(@Nullable ITmfEvent event) {
if (event == null) {
return;
}
/* Is the event managed by this analysis */
final String eventName = event.getName();
IKernelAnalysisEventLayout eventLayout = fLayouts.get(event.getTrace());
if (eventLayout == null) {
buildEventNames(event.getTrace());
eventLayout = fLayouts.get(event.getTrace());
if (eventLayout == null) {
return;
}
}
if (!eventName.equals(eventLayout.eventSchedSwitch()) &&
!fModel.getRequiredEvents(eventLayout).contains(eventName)) {
return;
}
ITmfStateSystemBuilder ss = checkNotNull(getStateSystemBuilder());
ITmfStateValue value;
final ITmfEventField content = event.getContent();
final long ts = event.getTimestamp().getValue();
final String hostId = event.getTrace().getHostId();
try {
/* Do we know this trace's role yet? */
VirtualMachine host = fModel.getCurrentMachine(event);
if (host == null) {
return;
}
/* Make sure guest traces are added to the state system */
if (host.isGuest()) {
/*
* If event from a guest OS, make sure the guest exists in the
* state system
*/
int vmQuark = -1;
try {
vmQuark = ss.getQuarkRelative(getNodeVirtualMachines(), host.getHostId());
} catch (AttributeNotFoundException e) {
/*
* We should enter this catch only once per machine, so it
* is not so costly to do compared with adding the trace's
* name for each guest event
*/
vmQuark = ss.getQuarkRelativeAndAdd(getNodeVirtualMachines(), host.getHostId());
TmfStateValue machineName = TmfStateValue.newValueString(event.getTrace().getName());
ss.modifyAttribute(ts, machineName, vmQuark);
}
}
/* Have the hypervisor models handle the event first */
fModel.handleEvent(event);
/* Handle the event here */
Integer idx = fEventNames.get(event.getTrace(), eventName);
int intval = (idx == null ? -1 : idx.intValue());
switch (intval) {
case SCHED_SWITCH_INDEX: // "sched_switch":
/*
* Fields: string prev_comm, int32 prev_tid, int32 prev_prio, int64
* prev_state, string next_comm, int32 next_tid, int32 next_prio
*/
{
int prevTid = ((Long) content.getField(eventLayout.fieldPrevTid()).getValue()).intValue();
int nextTid = ((Long) content.getField(eventLayout.fieldNextTid()).getValue()).intValue();
if (host.isGuest()) {
/* Get the event's CPU */
Integer cpu = TmfTraceUtils.resolveIntEventAspectOfClassForEvent(event.getTrace(), TmfCpuAspect.class, event);
if (cpu == null) {
/*
* We couldn't find any CPU information, ignore this
* event
*/
break;
}
/*
* If sched switch is from a guest, just update the status
* of the virtual CPU to either idle or running
*/
int curStatusQuark = ss.getQuarkRelativeAndAdd(getNodeVirtualMachines(), host.getHostId(),
cpu.toString(), VmAttributes.STATUS);
value = TmfStateValue.newValueInt(VcpuStateValues.VCPU_IDLE);
if (nextTid > 0) {
value = TmfStateValue.newValueInt(VcpuStateValues.VCPU_RUNNING);
}
ss.modifyAttribute(ts, value, curStatusQuark);
break;
}
/* Event is not from a guest */
/* Verify if the previous thread corresponds to a virtual CPU */
HostThread ht = new HostThread(hostId, prevTid);
VirtualCPU vcpu = fModel.getVirtualCpu(ht);
/*
* If previous thread is virtual CPU, update status of the
* virtual CPU to preempted
*/
if (vcpu != null) {
VirtualMachine vm = vcpu.getVm();
int curStatusQuark = ss.getQuarkRelativeAndAdd(getNodeVirtualMachines(), vm.getHostId(),
vcpu.getCpuId().toString(), VmAttributes.STATUS);
/* Add the preempted flag to the status */
value = ss.queryOngoingState(curStatusQuark);
if ((value.unboxInt() & VcpuStateValues.VCPU_IDLE) == 0) {
int newVal = Math.max(VcpuStateValues.VCPU_UNKNOWN, value.unboxInt());
value = TmfStateValue.newValueInt(newVal | VcpuStateValues.VCPU_PREEMPT);
ss.modifyAttribute(ts, value, curStatusQuark);
}
}
/* Verify if the next thread corresponds to a virtual CPU */
ht = new HostThread(hostId, nextTid);
vcpu = fModel.getVirtualCpu(ht);
/*
* If next thread is virtual CPU, update status of the virtual
* CPU the previous status
*/
if (vcpu != null) {
VirtualMachine vm = vcpu.getVm();
int curStatusQuark = ss.getQuarkRelativeAndAdd(getNodeVirtualMachines(), vm.getHostId(),
vcpu.getCpuId().toString(), VmAttributes.STATUS);
/* Remove the preempted flag from the status */
value = ss.queryOngoingState(curStatusQuark);
int newVal = Math.max(VcpuStateValues.VCPU_UNKNOWN, value.unboxInt());
value = TmfStateValue.newValueInt(newVal & ~VcpuStateValues.VCPU_PREEMPT);
ss.modifyAttribute(ts, value, curStatusQuark);
}
}
break;
default:
/* Other events not covered by the main switch */
{
HostThread ht = getCurrentHostThread(event, ts);
if (ht == null) {
break;
}
/*
* Are we entering the hypervisor mode and if so, which virtual
* CPU is concerned?
*/
VirtualCPU virtualCpu = fModel.getVCpuEnteringHypervisorMode(event, ht, eventLayout);
if (virtualCpu != null) {
/* Add the hypervisor flag to the status */
VirtualMachine vm = virtualCpu.getVm();
int curStatusQuark = ss.getQuarkRelativeAndAdd(getNodeVirtualMachines(), vm.getHostId(),
Long.toString(virtualCpu.getCpuId()), VmAttributes.STATUS);
value = ss.queryOngoingState(curStatusQuark);
if ((value.unboxInt() & VcpuStateValues.VCPU_IDLE) == 0) {
int newVal = Math.max(VcpuStateValues.VCPU_UNKNOWN, value.unboxInt());
value = TmfStateValue.newValueInt(newVal | VcpuStateValues.VCPU_VMM);
ss.modifyAttribute(ts, value, curStatusQuark);
}
}
/*
* Are we exiting the hypervisor mode and if so, which virtual
* CPU is concerned?
*/
virtualCpu = fModel.getVCpuExitingHypervisorMode(event, ht, eventLayout);
if (virtualCpu != null) {
/* Remove the hypervisor flag from the status */
VirtualMachine vm = virtualCpu.getVm();
int curStatusQuark = ss.getQuarkRelativeAndAdd(getNodeVirtualMachines(), vm.getHostId(),
Long.toString(virtualCpu.getCpuId()), VmAttributes.STATUS);
value = ss.queryOngoingState(curStatusQuark);
int newVal = Math.max(VcpuStateValues.VCPU_UNKNOWN, value.unboxInt());
value = TmfStateValue.newValueInt(newVal & ~VcpuStateValues.VCPU_VMM);
ss.modifyAttribute(ts, value, curStatusQuark);
}
}
break;
}
} catch (TimeRangeException | StateValueTypeException e) {
Activator.getDefault().logError("Error handling event in VirtualMachineStateProvider", e); //$NON-NLS-1$
}
}
// ------------------------------------------------------------------------
// Convenience methods for commonly-used attribute tree locations
// ------------------------------------------------------------------------
private int getNodeVirtualMachines() {
return checkNotNull(getStateSystemBuilder()).getQuarkAbsoluteAndAdd(VmAttributes.VIRTUAL_MACHINES);
}
private @Nullable HostThread getCurrentHostThread(ITmfEvent event, long ts) {
/* Get the LTTng kernel analysis for the host */
String hostId = event.getTrace().getHostId();
KernelAnalysisModule module = TmfExperimentUtils.getAnalysisModuleOfClassForHost(getTrace(), hostId, KernelAnalysisModule.class);
if (module == null) {
return null;
}
/* Get the CPU the event is running on */
Integer cpu = TmfTraceUtils.resolveIntEventAspectOfClassForEvent(event.getTrace(), TmfCpuAspect.class, event);
if (cpu == null) {
/* We couldn't find any CPU information, ignore this event */
return null;
}
Integer currentTid = KernelThreadInformationProvider.getThreadOnCpu(module, cpu, ts);
if (currentTid == null) {
return null;
}
return new HostThread(hostId, currentTid);
}
}