/*******************************************************************************
* Copyright (c) 2014, 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 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
* Geneviève Bastien - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.vm.model.qemukvm;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
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.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.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventField;
import org.eclipse.tracecompass.tmf.core.event.TmfEventField;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfCpuAspect;
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.ImmutableSet;
/**
* The virtual machine model corresponding to the Qemu/KVM hypervisor. It uses
* the kvm_exit/kvm_entry events to identify entry to and exit from the
* hypervisor. It also requires vmsync_* events from both guests and hosts to
* identify which thread from a host belongs to which machine.
*
* @author Mohamad Gebai
*/
public class QemuKvmVmModel implements IVirtualMachineModel {
private static final String KVM = "kvm_"; //$NON-NLS-1$
/* Associate a host's thread to a virtual CPU */
private final Map<HostThread, VirtualCPU> fTidToVcpu = new HashMap<>();
/* Associate a host's thread to a virtual machine */
private final Map<HostThread, VirtualMachine> fTidToVm = new HashMap<>();
/* Maps a virtual machine name to a virtual machine */
private final Map<String, VirtualMachine> fKnownMachines = new HashMap<>();
private final TmfExperiment fExperiment;
private Map<IKernelAnalysisEventLayout, Set<String>> fRequiredEvents = new HashMap<>();
private static final ImmutableSet<String> VMSYNC_EVENTS = ImmutableSet.of(
QemuKvmStrings.VMSYNC_GH_GUEST,
QemuKvmStrings.VMSYNC_GH_HOST,
QemuKvmStrings.VMSYNC_HG_GUEST,
QemuKvmStrings.VMSYNC_HG_HOST);
/**
* Constructor
*
* @param exp
* The experiment this model applies to
*/
public QemuKvmVmModel(TmfExperiment exp) {
fExperiment = exp;
}
@Override
public @Nullable VirtualMachine getCurrentMachine(ITmfEvent event) {
final String hostId = event.getTrace().getHostId();
VirtualMachine machine = fKnownMachines.get(hostId);
if (machine != null) {
return machine;
}
/*
* TODO: This model wouldn't support a machine (same hostId) being both
* a guest and a host
*/
/*
* This code path would be used only at the beginning of the analysis.
* Once all the hostIds have been associated with a virtual machine, the
* machines are all cached and the method returns earlier
*/
/* Try to get the virtual machine from the event */
String eventName = event.getName();
if (eventName.startsWith(KVM)) {
/* Only the host machine has kvm_* events, so this is a host */
machine = VirtualMachine.newHostMachine(hostId);
} else if (eventName.equals(QemuKvmStrings.VMSYNC_GH_GUEST) || eventName.equals(QemuKvmStrings.VMSYNC_HG_GUEST)) {
/* Those events are only present in the guests */
TmfEventField field = (TmfEventField) event.getContent();
ITmfEventField data = field.getField(QemuKvmStrings.VM_UID_PAYLOAD);
if (data != null) {
machine = VirtualMachine.newGuestMachine((Long) data.getValue(), hostId);
}
}
if (machine != null) {
/* Associate the machine to the hostID here, for cached access later */
fKnownMachines.put(hostId, machine);
}
return machine;
}
@Override
public Set<String> getRequiredEvents(IKernelAnalysisEventLayout layout) {
Set<String> events = fRequiredEvents.get(layout);
if (events == null) {
events = new HashSet<>();
events.addAll(layout.eventsKVMEntry());
events.addAll(layout.eventsKVMExit());
events.addAll(VMSYNC_EVENTS);
fRequiredEvents.put(layout, events);
}
return events;
}
private @Nullable VirtualMachine findVmFromParent(ITmfEvent event, HostThread ht) {
/*
* Maybe the parent of the current thread has a VM associated, see if we
* can infer the VM for this thread
*/
KernelAnalysisModule module = getLttngKernelModuleFor(ht.getHost());
if (module == null) {
return null;
}
Integer ppid = KernelThreadInformationProvider.getParentPid(module, ht.getTid(), event.getTimestamp().getValue());
if (ppid == null) {
return null;
}
HostThread parentHt = new HostThread(ht.getHost(), ppid);
VirtualMachine vm = fTidToVm.get(parentHt);
if (vm == null) {
return null;
}
fTidToVm.put(ht, vm);
return vm;
}
@Override
public @Nullable VirtualCPU getVCpuExitingHypervisorMode(ITmfEvent event, HostThread ht, IKernelAnalysisEventLayout layout) {
final String eventName = event.getName();
/*
* The KVM_ENTRY event means we are entering a virtual CPU, so exiting
* hypervisor mode
*/
if (!layout.eventsKVMEntry().contains(eventName)) {
return null;
}
/*
* Are we entering the hypervisor and if so, which virtual CPU is
* concerned?
*/
VirtualMachine vm = fTidToVm.get(ht);
if (vm == null) {
vm = findVmFromParent(event, ht);
if (vm == null) {
return null;
}
}
/* Associate this thread with the virtual CPU that is going to be run */
final ITmfEventField content = event.getContent();
long vcpu_id = (Long) content.getField(QemuKvmStrings.VCPU_ID).getValue();
VirtualCPU virtualCPU = VirtualCPU.getVirtualCPU(vm, vcpu_id);
fTidToVcpu.put(ht, virtualCPU);
return virtualCPU;
}
@Override
public @Nullable VirtualCPU getVCpuEnteringHypervisorMode(ITmfEvent event, HostThread ht, IKernelAnalysisEventLayout layout) {
final String eventName = event.getName();
/*
* The KVM_EXIT event means we are exiting a virtual CPU, so entering
* hypervisor mode
*/
if (!layout.eventsKVMExit().contains(eventName)) {
return null;
}
return getVirtualCpu(ht);
}
@Override
public @Nullable VirtualCPU getVirtualCpu(HostThread ht) {
return fTidToVcpu.get(ht);
}
@Override
public void handleEvent(ITmfEvent event) {
/* Is the event handled by this model */
final String eventName = event.getName();
if (!eventName.equals(QemuKvmStrings.VMSYNC_GH_HOST)) {
return;
}
final ITmfEventField content = event.getContent();
final long ts = event.getTimestamp().toNanos();
final String hostId = event.getTrace().getHostId();
final Integer cpu = TmfTraceUtils.resolveIntEventAspectOfClassForEvent(event.getTrace(), TmfCpuAspect.class, event);
if (cpu == null) {
/* We couldn't find any CPU information, ignore this event */
return;
}
/* Find a virtual machine with the vm uid payload value */
ITmfEventField data = content.getField(QemuKvmStrings.VM_UID_PAYLOAD);
if (data == null) {
return;
}
long vmUid = (Long) data.getValue();
for (Entry<String, VirtualMachine> entry : fKnownMachines.entrySet()) {
if (entry.getValue().getVmUid() == vmUid) {
/*
* We found the VM being run, let's associate it with the thread
* ID
*/
KernelAnalysisModule module = getLttngKernelModuleFor(hostId);
if (module == null) {
break;
}
Integer tid = KernelThreadInformationProvider.getThreadOnCpu(module, cpu, ts);
if (tid == null) {
/*
* We do not know which process is running at this point. It
* may happen at the beginning of the trace.
*/
break;
}
HostThread ht = new HostThread(hostId, tid);
fTidToVm.put(ht, entry.getValue());
/*
* To make sure siblings are also associated with this VM, also
* add an entry for the parent TID
*/
Integer ppid = KernelThreadInformationProvider.getParentPid(module, tid, ts);
if (ppid != null) {
HostThread parentHt = new HostThread(hostId, ppid);
fTidToVm.put(parentHt, entry.getValue());
}
}
}
}
private @Nullable KernelAnalysisModule getLttngKernelModuleFor(String hostId) {
return TmfExperimentUtils.getAnalysisModuleOfClassForHost(fExperiment, hostId, KernelAnalysisModule.class);
}
}