| /******************************************************************************* |
| * Copyright (c) 2015 É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 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Cédric Biancheri - Initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.incubator.internal.virtual.machine.analysis.core.fused.handlers; |
| |
| 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.model.HostThread; |
| import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelAnalysisEventLayout; |
| import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelTrace; |
| import org.eclipse.tracecompass.incubator.internal.virtual.machine.analysis.core.fused.FusedAttributes; |
| import org.eclipse.tracecompass.incubator.internal.virtual.machine.analysis.core.model.VirtualCPU; |
| import org.eclipse.tracecompass.incubator.internal.virtual.machine.analysis.core.model.VirtualMachine; |
| import org.eclipse.tracecompass.incubator.internal.virtual.machine.analysis.core.model.lxc.LxcModel; |
| import org.eclipse.tracecompass.incubator.internal.virtual.machine.analysis.core.model.qemukvm.QemuKvmStrings; |
| import org.eclipse.tracecompass.incubator.internal.virtual.machine.analysis.core.model.qemukvm.QemuKvmVmModel; |
| import org.eclipse.tracecompass.incubator.internal.virtual.machine.analysis.core.virtual.resources.StateValues; |
| import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; |
| import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder; |
| import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; |
| 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.TmfTraceManager; |
| import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils; |
| import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment; |
| |
| import com.google.common.collect.ImmutableMap; |
| |
| /** |
| * State provider for the Fused Virtual Machine analysis. It is based on the |
| * version 16 of the kernel state provider. |
| * |
| * @author Cedric Biancheri |
| */ |
| public class FusedVirtualMachineStateProvider extends AbstractTmfStateProvider { |
| // ------------------------------------------------------------------------ |
| // Static fields |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * 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 = 2; |
| |
| // ------------------------------------------------------------------------ |
| // Fields |
| // ------------------------------------------------------------------------ |
| |
| private final Map<String, VMKernelEventHandler> fEventNames; |
| private final Map<ITmfTrace, LayoutHandler> fLayouts = new HashMap<>(); |
| private QemuKvmVmModel fKvmModel; |
| private LxcModel fContainerModel; |
| private int fCurrentThreadNode; // quark to current thread node |
| private boolean fAllRolesFound = false; |
| |
| // ------------------------------------------------------------------------ |
| // Layout handling class and methods |
| // ------------------------------------------------------------------------ |
| private final Map<IKernelAnalysisEventLayout, LayoutHandler> fMap = new HashMap<>(); |
| |
| private LayoutHandler getForLayout(IKernelAnalysisEventLayout layout, Map<String, VMKernelEventHandler> builder) { |
| LayoutHandler layoutHandler = fMap.get(layout); |
| if (layoutHandler == null) { |
| layoutHandler = new LayoutHandler(layout); |
| fMap.put(layout, layoutHandler); |
| addEventNames(builder, layout); |
| } |
| return layoutHandler; |
| } |
| |
| private class LayoutHandler { |
| |
| protected final IKernelAnalysisEventLayout fLayout; |
| protected final VMKernelEventHandler fSysEntryHandler; |
| protected final VMKernelEventHandler fSysExitHandler; |
| protected final VMKernelEventHandler fKvmEntryHandler; |
| protected final VMKernelEventHandler fKvmExitHandler; |
| protected final VMKernelEventHandler fKvmNestedVmExitInjectHandler; |
| protected final VMKernelEventHandler fKvmMmuGetPageHandler; |
| |
| public LayoutHandler(IKernelAnalysisEventLayout layout) { |
| fLayout = layout; |
| fSysEntryHandler = new SysEntryHandler(layout, FusedVirtualMachineStateProvider.this); |
| fSysExitHandler = new SysExitHandler(layout, FusedVirtualMachineStateProvider.this); |
| fKvmEntryHandler = new KvmEntryHandler(layout, FusedVirtualMachineStateProvider.this); |
| fKvmExitHandler = new KvmExitHandler(layout, FusedVirtualMachineStateProvider.this); |
| fKvmMmuGetPageHandler = new KvmMmuGetPageHandler(layout, FusedVirtualMachineStateProvider.this); |
| fKvmNestedVmExitInjectHandler = new KvmNestedVmExitInjectHandler(layout, FusedVirtualMachineStateProvider.this); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Constructor |
| // ------------------------------------------------------------------------ |
| |
| /** |
| * Instantiate a new state provider plugin. |
| * |
| * @param experiment |
| * The experiment that will be analyzed. |
| */ |
| public FusedVirtualMachineStateProvider(TmfExperiment experiment) { |
| super(experiment, "Virtual Machine State Provider"); //$NON-NLS-1$ |
| |
| Map<String, VMKernelEventHandler> builder = new HashMap<>(); |
| |
| for (ITmfTrace trace : TmfTraceManager.getTraceSet(experiment)) { |
| if (trace instanceof IKernelTrace) { |
| IKernelAnalysisEventLayout layout = ((IKernelTrace) trace).getKernelEventLayout(); |
| fLayouts.put(trace, getForLayout(layout, builder)); |
| } |
| } |
| fEventNames = ImmutableMap.copyOf(builder); |
| fKvmModel = QemuKvmVmModel.get(experiment); |
| fContainerModel = new LxcModel(); |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Event names management |
| // ------------------------------------------------------------------------ |
| |
| private void addEventNames(Map<String, VMKernelEventHandler> builder, IKernelAnalysisEventLayout layout) { |
| |
| builder.put(layout.eventIrqHandlerEntry(), new IrqEntryHandler(layout, this)); |
| builder.put(layout.eventIrqHandlerExit(), new IrqExitHandler(layout, this)); |
| builder.put(layout.eventSoftIrqEntry(), new SoftIrqEntryHandler(layout, this)); |
| builder.put(layout.eventSoftIrqExit(), new SoftIrqExitHandler(layout, this)); |
| builder.put(layout.eventSoftIrqRaise(), new SoftIrqRaiseHandler(layout, this)); |
| builder.put(layout.eventSchedSwitch(), new SchedSwitchHandler(layout, this)); |
| builder.put(layout.eventSchedPiSetprio(), new PiSetprioHandler(layout, this)); |
| builder.put(layout.eventSchedProcessFork(), new ProcessForkContainerHandler(layout, this)); |
| builder.put(layout.eventSchedProcessExit(), new ProcessExitHandler(layout, this)); |
| builder.put(layout.eventSchedProcessFree(), new ProcessFreeHandler(layout, this)); |
| |
| final String eventStatedumpProcessState = layout.eventStatedumpProcessState(); |
| if (eventStatedumpProcessState != null) { |
| builder.put(eventStatedumpProcessState, new StateDumpContainerHandler(layout, this)); |
| } |
| |
| for (String eventSchedWakeup : layout.eventsSchedWakeup()) { |
| builder.put(eventSchedWakeup, new SchedWakeupHandler(layout, this)); |
| } |
| } |
| |
| // ------------------------------------------------------------------------ |
| // IStateChangeInput |
| // ------------------------------------------------------------------------ |
| |
| @Override |
| public TmfExperiment getTrace() { |
| ITmfTrace trace = super.getTrace(); |
| if (trace instanceof TmfExperiment) { |
| return (TmfExperiment) trace; |
| } |
| throw new IllegalStateException("FusedVirtualMachineStateProvider: The associated trace should be an experiment"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public int getVersion() { |
| return VERSION; |
| } |
| |
| @Override |
| public FusedVirtualMachineStateProvider getNewInstance() { |
| return new FusedVirtualMachineStateProvider(getTrace()); |
| } |
| |
| @Override |
| protected void eventHandle(@Nullable ITmfEvent event) { |
| if (event == null) { |
| return; |
| } |
| |
| Integer cpu = TmfTraceUtils.resolveIntEventAspectOfClassForEvent(event.getTrace(), TmfCpuAspect.class, event); |
| if (cpu == null) { |
| /* We couldn't find any CPU information, ignore this event */ |
| return; |
| } |
| |
| VirtualMachine host = null; |
| // FIXME: Add a test with 2 sets of machines connected by network and see where it fails |
| if (!allRolesFound()) { |
| host = getCurrentMachineAndAdd(event); |
| } else { |
| host = getCurrentMachine(event); |
| } |
| |
| String traceHost = event.getTrace().getHostId(); |
| LayoutHandler layoutHandler = fLayouts.get(event.getTrace()); |
| if (layoutHandler == null) { |
| return; |
| } |
| /* |
| * Have the hypervisor models handle the event first. |
| */ |
| fKvmModel.handleEvent(event, layoutHandler.fLayout); |
| |
| /* |
| * Continue even if host is unknown if the event is required for |
| * container analysis |
| */ |
| // if (host == null && |
| // !fContainerModel.getRequiredEvents().contains(event.getName()) && |
| // !allRolesFound()) { |
| // return; |
| // } |
| |
| // What is this condition? |
| if (!fContainerModel.getRequiredEvents(layoutHandler.fLayout).contains(event.getName()) && !allRolesFound()) { |
| return; |
| } |
| |
| Integer currentVCpu = -1; |
| if (host != null) { |
| /* Associate the cpu to its machine */ |
| VirtualCPU.getVirtualCPU(host, cpu.longValue()); |
| if (host.isGuest()) { |
| /* |
| * If the event is from a vm we have to find on which physical |
| * cpu it is running. |
| */ |
| currentVCpu = cpu; |
| cpu = getPhysicalCPU(host, cpu); |
| if (cpu == null) { |
| return; |
| } |
| } |
| } |
| |
| final String eventName = event.getName(); |
| final long ts = event.getTimestamp().getValue(); |
| |
| final ITmfStateSystemBuilder ss = checkNotNull(getStateSystemBuilder()); |
| |
| /* |
| * Do this block only all machines have their roles |
| */ |
| if (allRolesFound()) { |
| /* Shortcut for the "current CPU" attribute node */ |
| int currentCPUNode = ss.getQuarkRelativeAndAdd(getNodeCPUs(ss), String.valueOf(cpu)); |
| |
| /* |
| * Add in the state system the state of the cpu (in or out vm). |
| */ |
| int quarkCondition = ss.getQuarkRelativeAndAdd(currentCPUNode, FusedAttributes.CONDITION); |
| Integer valueCondition = StateValues.CONDITION_UNKNOWN; |
| int quarkMachines = FusedVMEventHandlerUtils.getMachinesNode(ss); |
| int machineHostQuark = ss.getQuarkRelativeAndAdd(quarkMachines, traceHost); |
| // if (inVM) { |
| if (host != null && host.isGuest()) { |
| valueCondition = StateValues.CONDITION_IN_VM; |
| int quarkVCpu = ss.getQuarkRelativeAndAdd(currentCPUNode, FusedAttributes.VIRTUAL_CPU); |
| ss.modifyAttribute(ts, currentVCpu, quarkVCpu); |
| |
| /* |
| * This part is used to remember how many cpus a machine has |
| */ |
| // if (host != null && host.isGuest()) { |
| ss.getQuarkRelativeAndAdd(machineHostQuark, FusedAttributes.CPUS, currentVCpu.toString()); |
| // } |
| /* Remember that this VM is using this pcpu. */ |
| int quarkPCPUs = FusedVMEventHandlerUtils.getMachinepCPUsNode(ss, traceHost); |
| ss.getQuarkRelativeAndAdd(quarkPCPUs, cpu.toString()); |
| } else { |
| /* |
| * We still need to check here if we are a guest because the |
| * guest's trace can be longer than the host's and we might be |
| * in a vm even if inVM == false // |
| */ |
| // if (host != null && host.isGuest()) { |
| // ss.getQuarkRelativeAndAdd(machineNameQuark, Attributes.CPUS, |
| // currentVCpu.toString()); |
| // } else { |
| ss.getQuarkRelativeAndAdd(quarkMachines, traceHost, FusedAttributes.CPUS, cpu.toString()); |
| // } |
| valueCondition = StateValues.CONDITION_OUT_VM; |
| } |
| /* |
| * Add the role of the machine in the state system |
| */ |
| setMachinesRoles(ss); |
| setMachinesParents(ss); |
| |
| /* |
| * Set the condition value in the state system (in or out vm) |
| */ |
| if (host != null && host.isHost() && !host.isGuest()) { |
| ss.modifyAttribute(ts, valueCondition, quarkCondition); |
| } |
| |
| /* |
| * Shortcut for the "current thread" attribute node. It requires |
| * querying the current CPU's current thread. |
| */ |
| int quark = ss.getQuarkRelativeAndAdd(currentCPUNode, FusedAttributes.CURRENT_THREAD); |
| |
| Object value = ss.queryOngoing(quark); |
| int thread = value instanceof Integer ? (int) value : -1; |
| |
| fCurrentThreadNode = ss.getQuarkRelativeAndAdd(getNodeThreads(ss, traceHost), String.valueOf(thread)); |
| |
| /* Set the name of the machine running on the cpu */ |
| quark = ss.getQuarkRelativeAndAdd(currentCPUNode, FusedAttributes.MACHINE_NAME); |
| value = event.getTrace().getHostId(); |
| if (host != null && host.isHost() && !host.isGuest()) { |
| ss.modifyAttribute(ts, value, quark); |
| } |
| } |
| /* |
| * Feed event to the history system if it's known to cause a state |
| * transition. |
| */ |
| VMKernelEventHandler handler = fEventNames.get(eventName); |
| // TODO: maybe put the other handlers also in fEventNames |
| if (handler == null) { |
| IKernelAnalysisEventLayout layout = layoutHandler.fLayout; |
| if (isSyscallExit(eventName, layout)) { |
| handler = layoutHandler.fSysExitHandler; |
| } else if (isSyscallEntry(eventName, layout)) { |
| handler = layoutHandler.fSysEntryHandler; |
| } else if (isKvmEntry(eventName)) { |
| handler = layoutHandler.fKvmEntryHandler; |
| } else if (isKvmExit(eventName)) { |
| handler = layoutHandler.fKvmExitHandler; |
| } else if (isKvmMmuGetPage(eventName)) { |
| handler = layoutHandler.fKvmMmuGetPageHandler; |
| } else if (isKvmNestedVmExitInject(eventName)) { |
| handler = layoutHandler.fKvmNestedVmExitInjectHandler; |
| } |
| } |
| if (handler != null) { |
| handler.handleEvent(ss, event); |
| } |
| |
| } |
| |
| // ------------------------------------------------------------------------ |
| // Convenience methods for commonly-used attribute tree locations |
| // Either private or package private so they can be used by the handlers |
| // ------------------------------------------------------------------------ |
| |
| private static int getNodeCPUs(ITmfStateSystemBuilder ssb) { |
| return ssb.getQuarkAbsoluteAndAdd(FusedAttributes.CPUS); |
| } |
| |
| private static int getNodeThreads(ITmfStateSystemBuilder ssb, String machineName) { |
| return ssb.getQuarkAbsoluteAndAdd(FusedAttributes.THREADS, machineName); |
| } |
| |
| static int getCurrentThreadNode(Integer cpuNumber, ITmfStateSystemBuilder ss) { |
| /* |
| * Shortcut for the "current thread" attribute node. It requires |
| * querying the current CPU's current thread. |
| */ |
| int quark = ss.getQuarkRelativeAndAdd(FusedVMEventHandlerUtils.getCurrentCPUNode(cpuNumber, ss), FusedAttributes.CURRENT_THREAD); |
| Object value = ss.queryOngoing(quark); |
| int thread = (value instanceof Integer) ? (int) value : -1; |
| quark = ss.getQuarkRelativeAndAdd(FusedVMEventHandlerUtils.getCurrentCPUNode(cpuNumber, ss), FusedAttributes.MACHINE_NAME); |
| String machineName = (String) ss.queryOngoing(quark); |
| return ss.getQuarkRelativeAndAdd(getNodeThreads(ss, machineName), String.valueOf(thread)); |
| } |
| |
| private static boolean isSyscallEntry(String eventName, IKernelAnalysisEventLayout layout) { |
| return (eventName.startsWith(layout.eventSyscallEntryPrefix()) |
| || eventName.startsWith(layout.eventCompatSyscallEntryPrefix())); |
| } |
| |
| private static boolean isSyscallExit(String eventName, IKernelAnalysisEventLayout layout) { |
| return (eventName.startsWith(layout.eventSyscallExitPrefix()) || |
| eventName.startsWith(layout.eventCompatSyscallExitPrefix())); |
| } |
| |
| int getCurrentThreadNode() { |
| return fCurrentThreadNode; |
| } |
| |
| @Nullable |
| Integer getPhysicalCPU(VirtualMachine host, Integer cpu) { |
| VirtualCPU vcpu = VirtualCPU.getVirtualCPU(host, cpu.longValue()); |
| Long physCpu = fKvmModel.getPhysicalCpuFromVcpu(host, vcpu); |
| if (physCpu == null) { |
| return null; |
| } |
| /* Replace the vcpu value by the physical one. */ |
| return physCpu.intValue(); |
| } |
| |
| @Nullable |
| VirtualMachine getCurrentMachineAndAdd(ITmfEvent event) { |
| return fKvmModel.getCurrentMachine(event); |
| } |
| |
| @Nullable |
| VirtualMachine getCurrentMachine(ITmfEvent event) { |
| return getKnownMachines().get(event.getTrace().getHostId()); |
| } |
| |
| @Nullable |
| VirtualMachine getCurrentContainer(ITmfEvent event) { |
| return fContainerModel.getCurrentMachine(event); |
| } |
| |
| @Nullable |
| VirtualMachine getVmFromHostThread(HostThread ht) { |
| return fKvmModel.getVmFromHostThread(ht); |
| } |
| |
| @Nullable |
| HostThread getHostThreadFromVCpu(VirtualCPU virtualCPU) { |
| return fKvmModel.getHostThreadFromVCpu(virtualCPU); |
| } |
| |
| @Nullable |
| VirtualCPU getVirtualCpu(HostThread ht) { |
| return fKvmModel.getVirtualCpu(ht); |
| } |
| |
| @Nullable |
| VirtualCPU getVCpuEnteringHypervisorMode(ITmfEvent event, HostThread ht, IKernelAnalysisEventLayout layout) { |
| return fKvmModel.getVCpuEnteringHypervisorMode(event, ht, layout); |
| } |
| |
| private static boolean isKvmEntry(String eventName) { |
| return eventName.equals(QemuKvmStrings.KVM_ENTRY) || eventName.equals(QemuKvmStrings.KVM_X86_ENTRY); |
| } |
| |
| private static boolean isKvmExit(String eventName) { |
| return eventName.equals(QemuKvmStrings.KVM_EXIT) || eventName.equals(QemuKvmStrings.KVM_X86_EXIT); |
| } |
| |
| private static boolean isKvmMmuGetPage(String eventName) { |
| return eventName.equals(QemuKvmStrings.KVM_MMU_GET_PAGE); |
| } |
| |
| private static boolean isKvmNestedVmExitInject(String eventName) { |
| return eventName.equals(QemuKvmStrings.KVM_NESTED_VMEXIT_INJECT); |
| } |
| |
| /** |
| * Return the known machines |
| * |
| * @return The known machines |
| */ |
| public Map<String, VirtualMachine> getKnownMachines() { |
| return fKvmModel.getKnownMachines(); |
| } |
| |
| /** |
| * Tell if all the roles of the machines were found |
| * |
| * TODO: What if we have UST traces? or traces that have no role in the |
| * experiment but that are welcome there, we shouldn't rely on this method |
| * |
| * @return true if all roles were found |
| */ |
| private boolean allRolesFound() { |
| if (fAllRolesFound) { |
| return fAllRolesFound; |
| } |
| int numberOfMachines = numberOfIdentifiedMachines(); |
| fAllRolesFound = getTrace().getTraces().size() == numberOfMachines; |
| return fAllRolesFound; |
| } |
| |
| private int numberOfIdentifiedMachines() { |
| int numberOfMachines = 0; |
| for (VirtualMachine machine : getKnownMachines().values()) { |
| if (machine.isHost() && !machine.isGuest()) { |
| numberOfMachines++; |
| numberOfMachines += numberOfIdentifiedMachinesRec(machine); |
| } |
| } |
| return numberOfMachines; |
| } |
| |
| private int numberOfIdentifiedMachinesRec(VirtualMachine machine) { |
| int nbMachines = 0; |
| for (VirtualMachine child : machine.getChildren()) { |
| child.setParent(machine); |
| nbMachines++; |
| nbMachines += numberOfIdentifiedMachinesRec(child); |
| } |
| return nbMachines; |
| } |
| |
| private void setMachinesRoles(ITmfStateSystemBuilder ss) { |
| Map<String, VirtualMachine> knownMachines = getKnownMachines(); |
| |
| for (VirtualMachine machine : knownMachines.values()) { |
| String machineHost = machine.getHostId(); |
| int machineQuark = ss.getQuarkAbsoluteAndAdd(FusedAttributes.HOSTS, machineHost); |
| // Update the trace name for this machine |
| int machineNameQuark = ss.optQuarkRelative(machineQuark, FusedAttributes.MACHINE_NAME); |
| if (machineNameQuark == ITmfStateSystem.INVALID_ATTRIBUTE) { |
| machineNameQuark = ss.getQuarkRelativeAndAdd(machineQuark, FusedAttributes.MACHINE_NAME); |
| ss.updateOngoingState(machine.getTraceName(), machineNameQuark); |
| } |
| // Set the type |
| Object machineType = ss.queryOngoing(machineQuark); |
| if (machineType instanceof Integer) { |
| continue; |
| } |
| ss.updateOngoingState(machine.getType(), machineQuark); |
| } |
| } |
| |
| private void setMachinesParents(ITmfStateSystemBuilder ss) { |
| Map<String, VirtualMachine> knownMachines = getKnownMachines(); |
| for (VirtualMachine machine : knownMachines.values()) { |
| String machineName = machine.getHostId(); |
| int parentQuark = ss.getQuarkAbsoluteAndAdd(FusedAttributes.HOSTS, machineName, FusedAttributes.PARENT); |
| Object parent = ss.queryOngoing(parentQuark); |
| if (parent != null) { |
| return; |
| } |
| VirtualMachine parentMachine = machine.getParent(); |
| if (parentMachine != null) { |
| ss.modifyAttribute(getStartTime(), parentMachine.getHostId(), parentQuark); |
| } |
| } |
| } |
| } |