/*******************************************************************************
* Copyright (c) 2012-2015 Ericsson and others.
* 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:
* Marc Khouzam (Ericsson) - initial API and implementation
* Marc Khouzam (Ericsson) - Updated to use /proc/cpuinfo for remote targets (Bug 374024)
* Marc Dumais (Ericsson) - Add CPU/core load information to the multicore visualizer (Bug 396268)
* Marc Dumais (Ericsson) - Bug 434889
* Teodor Madan (Freescale) - Activate multicore visualizer on non-linux hosts for remote case
* Marc Dumais (Ericsson) - Bug 464184
*
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.Immutable;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.AbstractDMContext;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.DataModelInitializedEvent;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMData;
import org.eclipse.cdt.dsf.debug.service.ICachingService;
import org.eclipse.cdt.dsf.debug.service.command.CommandCache;
import org.eclipse.cdt.dsf.debug.service.command.ICommand;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControl;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommandListener;
import org.eclipse.cdt.dsf.debug.service.command.ICommandResult;
import org.eclipse.cdt.dsf.debug.service.command.ICommandToken;
import org.eclipse.cdt.dsf.debug.service.command.IEventListener;
import org.eclipse.cdt.dsf.gdb.internal.CoreInfo;
import org.eclipse.cdt.dsf.gdb.internal.CoreList;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.internal.ProcStatCoreLoads;
import org.eclipse.cdt.dsf.gdb.internal.ProcStatParser;
import org.eclipse.cdt.dsf.gdb.internal.service.command.commands.MIMetaGetCPUInfo;
import org.eclipse.cdt.dsf.gdb.internal.service.command.output.MIMetaGetCPUInfoInfo;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.mi.service.IMICommandControl;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIListThreadGroupsInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIListThreadGroupsInfo.IThreadGroupInfo;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.internal.core.ICoreInfo;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.BundleContext;
/**
* This class implements the {@link IGDBHardwareAndOS} interface which gives access
* to hardware information about the target.
*
* @since 4.1
*/
public class GDBHardwareAndOS extends AbstractDsfService implements IGDBHardwareAndOS2, ICachingService {
static String sTempFolder;
static {
try {
sTempFolder = Files.createTempDirectory(GdbPlugin.PLUGIN_ID).toString() + '/';
} catch (IOException | IllegalArgumentException | UnsupportedOperationException e ) {
sTempFolder = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$
}
}
@Immutable
protected static class GDBCPUDMC extends AbstractDMContext
implements ICPUDMContext
{
/**
* String ID that is used to identify the thread in the GDB/MI protocol.
*/
private final String fId;
/**
*/
protected GDBCPUDMC(String sessionId, IHardwareTargetDMContext targetDmc, String id) {
super(sessionId, targetDmc == null ? new IDMContext[0] : new IDMContext[] { targetDmc });
fId = id;
}
@Override
public String getId(){
return fId;
}
@Override
public String toString() { return baseToString() + ".CPU[" + fId + "]"; } //$NON-NLS-1$ //$NON-NLS-2$
@Override
public boolean equals(Object obj) {
return baseEquals(obj) && ((GDBCPUDMC)obj).fId.equals(fId);
}
@Override
public int hashCode() { return baseHashCode() ^ fId.hashCode(); }
}
@Immutable
protected static class GDBCoreDMC extends AbstractDMContext
implements ICoreDMContext
{
private final String fId;
public GDBCoreDMC(String sessionId, ICPUDMContext CPUDmc, String id) {
super(sessionId, CPUDmc == null ? new IDMContext[0] : new IDMContext[] { CPUDmc });
fId = id;
}
@Override
public String getId(){ return fId; }
@Override
public String toString() { return baseToString() + ".core[" + fId + "]"; } //$NON-NLS-1$ //$NON-NLS-2$
@Override
public boolean equals(Object obj) {
return baseEquals(obj) &&
(((GDBCoreDMC)obj).fId == null ? fId == null : ((GDBCoreDMC)obj).fId.equals(fId));
}
@Override
public int hashCode() { return baseHashCode() ^ (fId == null ? 0 : fId.hashCode()); }
}
@Immutable
protected static class GDBCPUDMData implements ICPUDMData {
final int fNumCores;
public GDBCPUDMData(int num) {
fNumCores = num;
}
@Override
public int getNumCores() { return fNumCores; }
}
@Immutable
protected static class GDBCoreDMData implements ICoreDMData {
final String fPhysicalId;
public GDBCoreDMData(String id) {
fPhysicalId = id;
}
@Override
public String getPhysicalId() { return fPhysicalId; }
}
/**
* @since 4.2
*/
@Immutable
protected class GDBLoadInfo implements ILoadInfo {
private String fLoad;
private Map<String,String> fDetailedLoad;
public GDBLoadInfo(String load, Map<String,String> detailedLoad) {
fLoad = load;
fDetailedLoad = detailedLoad;
}
public GDBLoadInfo(String load) {
this(load, null);
}
@Override
public String getLoad() {
return fLoad;
}
@Override
public Map<String,String> getDetailedLoad() {
return fDetailedLoad;
}
}
// to save queued load info requests for later processing
private Map<IDMContext, DataRequestMonitor<ILoadInfo>> fLoadInfoRequestCache;
private IGDBControl fCommandControl;
private IGDBBackend fBackend;
private CommandFactory fCommandFactory;
// A command cache to cache the data gotten from /proc/cpuinfo
// Because we obtain the data differently for a local target
// than a remote target, we can't buffer an actual MI command,
// so instead, we use a MetaMICommand to "fetch the cpu info"
// Since the CPU info does not change, this cache does not need
// to be cleared.
private CommandCache fFetchCPUInfoCache;
// Track if the debug session has been fully initialized.
// Until then, we may not be connected to the remote target
// yet, and not be able to properly fetch the information we need.
// Bug 374293
private boolean fSessionInitializationComplete;
// used to keep track when we last computed the load
private long fLastCpuLoadRefresh = 0;
// to keep track if we are already seeking to get the load
private boolean fLoadRequestOngoing = false;
// Length of the measured sample in ms
private final static int LOAD_SAMPLE_DELAY = 250;
// To avoid bombarding the remote GDB server, we cache the measured load
// and serve it again if requested within a short period of time.
private ProcStatCoreLoads fCachedLoads = null;
// lifetime of the load cache, in ms
private final static int LOAD_CACHE_LIFETIME = 500;
public GDBHardwareAndOS(DsfSession session) {
super(session);
}
/**
* This method initializes this service.
*
* @param requestMonitor
* The request monitor indicating the operation is finished
*/
@Override
public void initialize(final RequestMonitor requestMonitor) {
super.initialize(new RequestMonitor(ImmediateExecutor.getInstance(), requestMonitor) {
@Override
protected void handleSuccess() {
doInitialize(requestMonitor);
}
});
}
/**
* This method initializes this service after our superclass's initialize()
* method succeeds.
*
* @param requestMonitor
* The call-back object to notify when this service's
* initialization is done.
*/
private void doInitialize(RequestMonitor requestMonitor) {
fSessionInitializationComplete = false;
fCommandControl = getServicesTracker().getService(IGDBControl.class);
fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory();
fBackend = getServicesTracker().getService(IGDBBackend.class);
// The cache does not go directly to the commandControl service.
// Instead is goes through a CPUInfoManager which will decide how to
// handle getting the required cpu info
fFetchCPUInfoCache = new CommandCache(getSession(), new CPUInfoManager());
fFetchCPUInfoCache.setContextAvailable(fCommandControl.getContext(), true);
fLoadInfoRequestCache = new HashMap<IDMContext, DataRequestMonitor<ILoadInfo>>();
getSession().addServiceEventListener(this, null);
// Register this service.
register(new String[] { IGDBHardwareAndOS.class.getName(),
IGDBHardwareAndOS2.class.getName(),
GDBHardwareAndOS.class.getName() },
new Hashtable<String, String>());
requestMonitor.done();
}
/**
* This method shuts down this service. It unregisters the service, stops
* receiving service events, and calls the superclass shutdown() method to
* finish the shutdown process.
*
* @return void
*/
@Override
public void shutdown(RequestMonitor requestMonitor) {
getSession().removeServiceEventListener(this);
fFetchCPUInfoCache.reset();
fLoadInfoRequestCache.clear();
unregister();
super.shutdown(requestMonitor);
}
/**
* @return The bundle context of the plug-in to which this service belongs.
*/
@Override
protected BundleContext getBundleContext() {
return GdbPlugin.getBundleContext();
}
protected boolean getSessionInitializationComplete() {
return fSessionInitializationComplete;
}
@Override
public void getCPUs(final IHardwareTargetDMContext dmc, final DataRequestMonitor<ICPUDMContext[]> rm) {
if (!fSessionInitializationComplete) {
// We are not ready to answer yet
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Debug session not initialized yet", null)); //$NON-NLS-1$
return;
}
if (supportsProcPseudoFS()) {
fFetchCPUInfoCache.execute(
new MIMetaGetCPUInfo(fCommandControl.getContext()),
new ImmediateDataRequestMonitor<MIMetaGetCPUInfoInfo>() {
@Override
protected void handleSuccess() {
rm.done(parseCoresInfoForCPUs(dmc, getData().getInfo()));
}
});
} else {
// No way to know the CPUs for Windows session.
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Operation not supported", null)); //$NON-NLS-1$
}
}
/**
* @return true if supports fetching OS info from /proc pseudo-filesystem
*/
private boolean supportsProcPseudoFS() {
if (Platform.getOS().equals(Platform.OS_LINUX))
return true;
// for non-linux platform, support only remote (linux? ) targets
if (SessionType.REMOTE == fBackend.getSessionType()) {
return true;
}
return false;
}
@Override
public void getCores(IDMContext dmc, final DataRequestMonitor<ICoreDMContext[]> rm) {
if (!fSessionInitializationComplete) {
// We are not ready to answer yet
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Debug session not initialized yet", null)); //$NON-NLS-1$
return;
}
if (dmc instanceof ICPUDMContext) {
// Get the cores under this particular CPU
final ICPUDMContext cpuDmc = (ICPUDMContext)dmc;
if (supportsProcPseudoFS()) {
fFetchCPUInfoCache.execute(
new MIMetaGetCPUInfo(fCommandControl.getContext()),
new ImmediateDataRequestMonitor<MIMetaGetCPUInfoInfo>() {
@Override
protected void handleSuccess() {
rm.done(parseCoresInfoForCores(cpuDmc, getData().getInfo()));
}
});
} else {
// No way to know the cores for Windows session.
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Operation not supported", null)); //$NON-NLS-1$
}
} else {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid DMC type", null)); //$NON-NLS-1$
}
}
/**
* Parse the CoreInfo and create the CPU Contexts for the hardwareTarget context.
*/
ICPUDMContext[] parseCoresInfoForCPUs(IHardwareTargetDMContext dmc, ICoreInfo[] coresInfo) {
Set<String> cpuIds = new HashSet<String>();
ICPUDMContext[] CPUs;
for (ICoreInfo core : coresInfo) {
cpuIds.add(core.getPhysicalId());
}
String[] cpuIdsArray = cpuIds.toArray(new String[cpuIds.size()]);
CPUs = new ICPUDMContext[cpuIdsArray.length];
for (int i = 0; i < cpuIdsArray.length; i++) {
CPUs[i] = createCPUContext(dmc, cpuIdsArray[i]);
}
return CPUs;
}
/**
* Parse the CoreInfo and create the Core Contexts for the specified CPU context.
*/
ICoreDMContext[] parseCoresInfoForCores(ICPUDMContext cpuDmc, ICoreInfo[] coresInfo) {
Vector<ICoreDMContext> coreDmcs = new Vector<ICoreDMContext>();
for (ICoreInfo core : coresInfo) {
if (core.getPhysicalId().equals(cpuDmc.getId())){
// This core belongs to the right CPU
coreDmcs.add(createCoreContext(cpuDmc, core.getId()));
}
}
return coreDmcs.toArray(new ICoreDMContext[coreDmcs.size()]);
}
@Override
public void getExecutionData(IDMContext dmc, DataRequestMonitor<IDMData> rm) {
if (dmc instanceof ICoreDMContext) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Not done yet", null)); //$NON-NLS-1$
} else if (dmc instanceof ICPUDMContext) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Not done yet", null)); //$NON-NLS-1$
} else {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid DMC type", null)); //$NON-NLS-1$
}
}
@Override
public ICPUDMContext createCPUContext(IHardwareTargetDMContext targetDmc, String CPUId) {
return new GDBCPUDMC(getSession().getId(), targetDmc, CPUId);
}
@Override
public ICoreDMContext createCoreContext(ICPUDMContext cpuDmc, String coreId) {
return new GDBCoreDMC(getSession().getId(), cpuDmc, coreId);
}
@DsfServiceEventHandler
public void eventDispatched(DataModelInitializedEvent e) {
// The launch sequence is complete, so we can start providing information.
// If we don't wait for this event, we may provide results before we are
// connected to the remote target which would be wrong.
// Bug 374293
fSessionInitializationComplete = true;
}
@Override
public void flushCache(IDMContext context) {
// Although the CPUInfo does not change,
// this allows us to have a way to forcibly clear the cache.
// We would need to call this method from the UI somehow.
fFetchCPUInfoCache.reset(context);
}
/**
* A commandControl that will decide what to do when needing to find the CPUInfo.
* The class is used together with a CommandCache an MIMetaCommands to fetch
* information we need.
*/
private class CPUInfoManager implements ICommandControl {
private final List<ICommandListener> fCommandProcessors = new ArrayList<ICommandListener>();
@Override
public <V extends ICommandResult> ICommandToken queueCommand(final ICommand<V> command, DataRequestMonitor<V> rm) {
final ICommandToken token = new ICommandToken() {
@Override
public ICommand<? extends ICommandResult> getCommand() {
return command;
}
};
// The class does not buffer commands itself, but sends them directly to the real
// MICommandControl service. Therefore, we must immediately tell our calling cache that the command
// has been sent, since we can never cancel it.
processCommandSent(token);
if (command instanceof MIMetaGetCPUInfo) {
@SuppressWarnings("unchecked")
final DataRequestMonitor<MIMetaGetCPUInfoInfo> drm = (DataRequestMonitor<MIMetaGetCPUInfoInfo>)rm;
final ICommandControlDMContext dmc = (ICommandControlDMContext)command.getContext();
if (fBackend.getSessionType() == SessionType.REMOTE) {
// Ask GDB to fetch /proc/cpuinfo from the remote target, and then we parse it.
String remoteFile = "/proc/cpuinfo"; //$NON-NLS-1$
final String localFile = sTempFolder + "proc.cpuinfo." + getSession().getId(); //$NON-NLS-1$
fCommandControl.queueCommand(
fCommandFactory.createCLIRemoteGet(dmc, remoteFile, localFile),
new ImmediateDataRequestMonitor<MIInfo>(rm) {
@Override
protected void handleSuccess() {
ICoreInfo[] info = new CoreList(localFile).getCoreList();
// Now that we processed the file, remove it to avoid polluting the file system
new File(localFile).delete();
drm.done(new MIMetaGetCPUInfoInfo(info));
processCommandDone(token, drm.getData());
}
@Override
protected void handleError() {
// On some older linux versions, gdbserver is not able to read from /proc
// because it is a pseudo filesystem.
// We need to find some other method of getting the info we need.
// For a remote session, we can use GDB's -list-thread-groups --available
// command, which shows on which cores a process is running. This does
// not necessarily give the exhaustive list of cores, but that is the best
// we have in this case.
//
// In this case, we don't have knowledge about CPUs, so we lump all cores
// into a single CPU.
fCommandControl.queueCommand(
fCommandFactory.createMIListThreadGroups(dmc, true),
new ImmediateDataRequestMonitor<MIListThreadGroupsInfo>(drm) {
@Override
protected void handleSuccess() {
// First extract the string id for every core GDB reports
Set<String> coreIds = new HashSet<String>();
IThreadGroupInfo[] groups = getData().getGroupList();
for (IThreadGroupInfo group : groups) {
coreIds.addAll(Arrays.asList(group.getCores()));
}
// Now create the context for each distinct core
//
// We don't have CPU info in this case so let's put them all under a single CPU
final String defaultCPUId = "0"; //$NON-NLS-1$
ICoreInfo[] info = new ICoreInfo[coreIds.size()];
int i = 0;
for (String id : coreIds) {
info[i++] = new CoreInfo(id, defaultCPUId);
}
drm.done(new MIMetaGetCPUInfoInfo(info));
processCommandDone(token, drm.getData());
}
});
}
});
} else {
// For a local session, parse /proc/cpuinfo directly.
ICoreInfo[] info = new CoreList("/proc/cpuinfo").getCoreList(); //$NON-NLS-1$
drm.done(new MIMetaGetCPUInfoInfo(info));
processCommandDone(token, drm.getData());
}
} else {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Unexpected Meta command", null)); //$NON-NLS-1$
rm.done();
}
return token;
}
// Need to support these as they are used by the commandCache
@Override
public void addCommandListener(ICommandListener processor) { fCommandProcessors.add(processor); }
@Override
public void removeCommandListener(ICommandListener processor) { fCommandProcessors.remove(processor); }
private void processCommandSent(ICommandToken token) {
for (ICommandListener processor : fCommandProcessors) {
processor.commandSent(token);
}
}
private void processCommandDone(ICommandToken token, ICommandResult result) {
for (ICommandListener processor : fCommandProcessors) {
processor.commandDone(token, result);
}
}
@Override
public void addEventListener(IEventListener processor) { assert false : "Not supported"; } //$NON-NLS-1$
@Override
public void removeEventListener(IEventListener processor) { assert false : "Not supported"; } //$NON-NLS-1$
@Override
public void removeCommand(ICommandToken token) { assert false : "Not supported"; } //$NON-NLS-1$
}
/**
* @since 4.2
*/
@Override
public boolean isAvailable() {
return getSessionInitializationComplete();
}
/**
* @since 4.2
*/
@Override
public void getResourceClasses(IDMContext dmc,
DataRequestMonitor<IResourceClass[]> rm) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Operation not supported", null)); //$NON-NLS-1$
}
/**
* @since 4.2
*/
@Override
public void getResourcesInformation(IDMContext dmc, String resourceClassId,
DataRequestMonitor<IResourcesInformation> rm) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Operation not supported", null)); //$NON-NLS-1$
}
/**
* This method processes "load info" requests. The load is computed using a
* sampling method; two readings of a local or remote /proc/stat file are done
* with a delay in between. Then the load is computed from the two samples,
* for all CPUs/cores known in the system.
*
* Because of the method used, it's possible that fast variations in CPU usage will
* be missed. However longer load trends should be reflected in the results.
*
* To avoid generating too much load in the remote case, there is a cache that will
* return the already computed load, if requested multiple times in a short period.
* There is also a mechanism to queue subsequent requests if one is ongoing. Upon
* completion of the ongoing request, any queued request is answered with the load
* that was just computed.
*
* @since 4.2
*/
@Override
public void getLoadInfo(final IDMContext context, final DataRequestMonitor<ILoadInfo> rm) {
if (!(context instanceof ICoreDMContext) && !(context instanceof ICPUDMContext)) {
// we only support getting the load for a CPU or a core
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Load information not supported for this context type", null)); //$NON-NLS-1$
return;
}
// The measurement interval should be of a minimum length to be meaningful
assert (LOAD_SAMPLE_DELAY >= 100);
// so the cache is useful
assert (LOAD_CACHE_LIFETIME >= LOAD_SAMPLE_DELAY);
// This way of computing the CPU load is only applicable to Linux
if (!supportsProcPseudoFS()) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Operation not supported", null)); //$NON-NLS-1$
return;
}
// Is a request is already ongoing?
if(fLoadRequestOngoing) {
// queue current new request
fLoadInfoRequestCache.put(context, rm);
return;
}
// no request ongoing, so proceed
fLoadRequestOngoing = true;
// caching mechanism to keep things sane, even if the views(s)
// request load information very often.
long currentTime = System.currentTimeMillis();
// time to fetch fresh load information?
if (fLastCpuLoadRefresh + LOAD_CACHE_LIFETIME < currentTime) {
fLastCpuLoadRefresh = currentTime;
}
else {
// not time yet... re-use cached load data
processLoads(context, rm, fCachedLoads);
fLoadRequestOngoing = false;
return;
}
final ProcStatParser procStatParser = new ProcStatParser();
final ICommandControlDMContext dmc = DMContexts.getAncestorOfType(context, ICommandControlDMContext.class);
final String statFile = "/proc/stat"; //$NON-NLS-1$
final String localFile = sTempFolder + "proc.stat." + getSession().getId(); //$NON-NLS-1$
// Remote debugging? We will ask GDB to get us the /proc/stat file from target, twice, with a delay between.
if (fBackend.getSessionType() == SessionType.REMOTE) {
fCommandControl.queueCommand(
fCommandFactory.createCLIRemoteGet(dmc, statFile, localFile),
new ImmediateDataRequestMonitor<MIInfo>(rm) {
@Override
protected void handleCompleted() {
if (! isSuccess()) {
fLoadRequestOngoing = false;
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Can't get load info for CPU", null)); //$NON-NLS-1$
return;
}
// Success - parse first set of stat counters
try {
procStatParser.parseStatFile(localFile);
} catch (Exception e) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Can't get load info for CPU", null)); //$NON-NLS-1$
fLoadRequestOngoing = false;
return;
}
// delete temp file
new File(localFile).delete();
getExecutor().schedule(new Runnable() {
@Override
public void run() {
fCommandControl.queueCommand(
fCommandFactory.createCLIRemoteGet(dmc, statFile, localFile),
new ImmediateDataRequestMonitor<MIInfo>(rm) {
@Override
protected void handleCompleted() {
if (! isSuccess()) {
fLoadRequestOngoing = false;
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Can't get load info for CPU", null)); //$NON-NLS-1$
return;
}
// Success - parse the second set of stat counters and compute loads
try {
procStatParser.parseStatFile(localFile);
} catch (Exception e) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Can't get load info for CPU", null)); //$NON-NLS-1$
fLoadRequestOngoing = false;
return;
}
// delete temp file
new File(localFile).delete();
// Compute load
fCachedLoads = procStatParser.getCpuLoad();
processLoads(context, rm, fCachedLoads);
// done with request
fLoadRequestOngoing = false;
// process any queued request
for(Entry<IDMContext, DataRequestMonitor<ILoadInfo>> e : fLoadInfoRequestCache.entrySet()) {
processLoads(e.getKey(), e.getValue(), fCachedLoads);
}
fLoadInfoRequestCache.clear();
}
});
}
}, LOAD_SAMPLE_DELAY, TimeUnit.MILLISECONDS);
}
});
// Local debugging? Then we can read /proc/stat directly
} else {
// Read /proc/stat file for the first time
try {
procStatParser.parseStatFile(statFile);
} catch (Exception e) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Can't get load info for CPU", null)); //$NON-NLS-1$
fLoadRequestOngoing = false;
return;
}
// Read /proc/stat file again after a delay
getExecutor().schedule(new Runnable() {
@Override
public void run() {
try {
procStatParser.parseStatFile(statFile);
} catch (Exception e) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Can't get load info for CPU", null)); //$NON-NLS-1$
fLoadRequestOngoing = false;
return;
}
// compute load
fCachedLoads = procStatParser.getCpuLoad();
processLoads(context, rm, fCachedLoads);
// done with request
fLoadRequestOngoing = false;
// process any queued request
for(Entry<IDMContext, DataRequestMonitor<ILoadInfo>> e : fLoadInfoRequestCache.entrySet()) {
processLoads(e.getKey(), e.getValue(), fCachedLoads);
}
fLoadInfoRequestCache.clear();
}
}, LOAD_SAMPLE_DELAY, TimeUnit.MILLISECONDS);
}
}
/**
* For a given "load info" request, this method processes the load obtained from the
* proc stat parser and creates/sends the response.
* @param context
* @param rm
* @param loads
*/
private void processLoads(final IDMContext context, final DataRequestMonitor<ILoadInfo> rm, final ProcStatCoreLoads loads) {
// problem with fetching load info
if (loads == null) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Can't get load info", null)); //$NON-NLS-1$
return;
}
// core context?
if (context instanceof ICoreDMContext) {
String coreId = ((ICoreDMContext) context).getId();
// Integer precision sufficient for our purpose
float load = loads.getLoad(coreId);
rm.done(new GDBLoadInfo(Integer.toString((int)load)));
}
else if (context instanceof ICPUDMContext) {
// get the list of cores in that CPU
getCores(context,
new ImmediateDataRequestMonitor<ICoreDMContext[]>() {
@Override
protected void handleCompleted() {
ICoreDMContext[] coreContexts = getData();
if (!isSuccess() || coreContexts == null || coreContexts.length < 1) {
// Unable to get any core data
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Can't get load info for CPU", null)); //$NON-NLS-1$
return;
}
int i = 0;
float load = 0.0f;
// compute the average load of cores in that CPU
for (ICoreDMContext coreCtx : coreContexts) {
String coreId = coreCtx.getId();
load += loads.getLoad(coreId);
i++;
}
load /= i;
rm.done(new GDBLoadInfo(Integer.toString((int)load)));
}
}
);
}
}
}