os.linux: Add some util methods to check thread state
authorAlexandre Montplaisir <alexmonthy@efficios.com>
Wed, 16 Nov 2016 20:52:35 +0000 (15:52 -0500)
committerAlexandre Montplaisir <alexmonthy@efficios.com>
Fri, 25 Nov 2016 23:32:57 +0000 (18:32 -0500)
This can be used by view filters to show either only active
threads, or threads on a given subset of CPUs.

Change-Id: I3887868d3c6c97009f772c1088274eda2bfbdd65
Signed-off-by: Alexandre Montplaisir <alexmonthy@efficios.com>
analysis/org.eclipse.tracecompass.analysis.os.linux.core.tests/src/org/eclipse/tracecompass/analysis/os/linux/core/tests/kernel/KernelThreadInformationProviderTest.java
analysis/org.eclipse.tracecompass.analysis.os.linux.core/META-INF/MANIFEST.MF
analysis/org.eclipse.tracecompass.analysis.os.linux.core/src/org/eclipse/tracecompass/analysis/os/linux/core/kernel/KernelThreadInformationProvider.java

index b22ab40bc2419422df527fe7dbbb9f02d17556e4..f355dc78567e346e9e9c0cc6bf7aab63f2d3cc9f 100644 (file)
@@ -20,7 +20,9 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.NullProgressMonitor;
@@ -158,6 +160,55 @@ public class KernelThreadInformationProviderTest {
 
     }
 
+    /**
+     * Test the {@link KernelThreadInformationProvider#getThreadsOfCpus} method.
+     */
+    @Test
+    public void testGetThreadsOfCpus() {
+        KernelAnalysisModule module = checkNotNull(fModule);
+        final long start = 45L;
+        final long end = 65L;
+
+        /*
+         * The test trace considers all scheduled out processes to remain in the
+         * "wait for cpu" state. In this particular case, the util method means
+         * "has ever run on CPU X".
+         */
+
+        Set<Integer> tids = KernelThreadInformationProvider.getThreadsOfCpus(module, Collections.singleton(0L), start, end);
+        assertNotNull(tids);
+        /*
+         * Only threads 11 and 100 should be present, due to the sched_switch at
+         * t=35.
+         */
+        assertEquals(ImmutableSet.of(11, 100), tids);
+
+        tids = KernelThreadInformationProvider.getThreadsOfCpus(module, Collections.singleton(1L), start, end);
+        assertNotNull(tids);
+        /* Only threads 10, 20, 21, 30 get scheduled on CPU 1. */
+        assertEquals(ImmutableSet.of(10, 20, 21, 30), tids);
+    }
+
+    /**
+     * Test the {@link KernelThreadInformationProvider#getActiveThreadsForRange}
+     * method.
+     */
+    @Test
+    public void testIsThreadActiveRange() {
+        KernelAnalysisModule module = checkNotNull(fModule);
+        final long start = 45L;
+        final long end = 65L;
+
+        /* Thread 30 should be active in the range */
+        Set<Integer> tids = KernelThreadInformationProvider.getActiveThreadsForRange(module, start, end);
+        assertNotNull(tids);
+        assertTrue(tids.contains(30));
+
+        // TODO Check for non-active states too. Unfortunately the test trace
+        // wrongly considers all processes to be in a active (value=5) state all
+        // the time.
+    }
+
     /**
      * Test the
      * {@link KernelThreadInformationProvider#getParentPid(KernelAnalysisModule, Integer, long)}
index 4945e61630e6a271e3a20ad7f11acc46b3f75115..8655f4095d73d7c538d258c04a34caaa48b8b521 100644 (file)
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-Vendor: %Bundle-Vendor
-Bundle-Version: 2.1.0.qualifier
+Bundle-Version: 2.2.0.qualifier
 Bundle-Localization: plugin
 Bundle-SymbolicName: org.eclipse.tracecompass.analysis.os.linux.core;singleton:=true
 Bundle-Activator: org.eclipse.tracecompass.internal.analysis.os.linux.core.Activator
index dd4e20f6914034603852870cc41f42d1f8fd7c07..b1b80e3aa9348a044fd6df0fe0640caf1a05f12f 100644 (file)
@@ -14,9 +14,12 @@ package org.eclipse.tracecompass.analysis.os.linux.core.kernel;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.annotation.NonNull;
@@ -31,6 +34,8 @@ import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
 import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
 import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue.Type;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * Information provider utility class that retrieves thread-related information
  * from a Linux Kernel Analysis
@@ -75,6 +80,161 @@ public final class KernelThreadInformationProvider {
         return null;
     }
 
+    /**
+     * The the threads that have been scheduled on the given CPU(s), for the
+     * given time range. Threads with TID 0 (swapper threads) will never be
+     * included.
+     *
+     * @param module
+     *            The kernel analysis module to query
+     * @param cpus
+     *            The list of cpus
+     * @param rangeStart
+     *            The start of the time range
+     * @param rangeEnd
+     *            The end of the time range
+     * @return A set of all the thread IDs that are run on said CPUs on the time
+     *         range. Empty set if there is no thread on the CPUs in this time
+     *         range. Null if the information is not available.
+     * @since 2.2
+     */
+    public static @Nullable Set<Integer> getThreadsOfCpus(KernelAnalysisModule module, Collection<Long> cpus, long rangeStart, long rangeEnd) {
+        ITmfStateSystem ss = module.getStateSystem();
+        if (ss == null) {
+            return null;
+        }
+
+        Set<Long> uniqueCpus = (cpus instanceof Set ? (Set<Long>) cpus : ImmutableSet.copyOf(cpus));
+
+        int threadsQuark = ss.optQuarkAbsolute(Attributes.THREADS);
+        if (threadsQuark == ITmfStateSystem.INVALID_ATTRIBUTE) {
+            return null;
+        }
+
+        List<Integer> threadQuarks = ss.getSubAttributes(threadsQuark, false);
+        return threadQuarks.stream()
+                /*
+                 * Keep only the quarks of threads that are on at least one of the
+                 * wanted CPUs' run queue.
+                 */
+                .filter(threadQuark -> {
+                    int threadCurrentCpuQuark = ss.optQuarkRelative(threadQuark, Attributes.CURRENT_CPU_RQ);
+                    if (threadCurrentCpuQuark == ITmfStateSystem.INVALID_ATTRIBUTE) {
+                        return false;
+                    }
+
+                    Iterator<ITmfStateInterval> it = StateSystemUtils.getIteratorOverQuark(ss, threadCurrentCpuQuark, rangeStart, rangeEnd);
+                    while (it.hasNext()) {
+                        ITmfStateInterval interval = it.next();
+                        ITmfStateValue value = interval.getStateValue();
+                        if (!value.isNull() && uniqueCpus.contains(Long.valueOf(value.unboxLong()))) {
+                            return true;
+                        }
+                    }
+                    /* The thread was never on any of the requested CPUs. */
+                    return false;
+                })
+
+                /* Convert the thread quarks to their corresponding TIDs */
+                .map(ss::getAttributeName)
+                /* Ignore swapper threads */
+                .filter(attribName -> !attribName.startsWith(Attributes.THREAD_0_PREFIX))
+                .map(Integer::parseInt)
+                .collect(Collectors.toSet());
+    }
+
+    /**
+     * Predicate indicating if a thread state value is considered active or not.
+     */
+    private static final Predicate<ITmfStateValue> IS_STATE_VALUE_ACTIVE = stateValue -> {
+        if (stateValue.isNull() ||
+                stateValue.equals(StateValues.PROCESS_STATUS_UNKNOWN_VALUE) ||
+                stateValue.equals(StateValues.PROCESS_STATUS_WAIT_BLOCKED_VALUE) ||
+                stateValue.equals(StateValues.PROCESS_STATUS_WAIT_UNKNOWN_VALUE)) {
+            return false;
+        }
+        return true;
+    };
+
+    /**
+     * Return all the threads that are considered active in the given time
+     * range. Threads with TID 0 (swapper threads) will never be included.
+     *
+     * @param module
+     *            The kernel analysis module to query
+     * @param rangeStart
+     *            The start of the time range
+     * @param rangeEnd
+     *            The end of the time range
+     * @return A set of all the thread IDs that are considered active in the
+     *         time range. Empty set if there are none. Null if the information
+     *         is not available.
+     * @since 2.2
+     */
+    public static @Nullable Set<Integer> getActiveThreadsForRange(KernelAnalysisModule module, long rangeStart, long rangeEnd) {
+        ITmfStateSystem ss = module.getStateSystem();
+        if (ss == null) {
+            return null;
+        }
+
+        List<ITmfStateInterval> fullQueryAtStart;
+        int threadsQuark;
+        try {
+            fullQueryAtStart = ss.queryFullState(rangeStart);
+            threadsQuark = ss.getQuarkAbsolute(Attributes.THREADS);
+        } catch (AttributeNotFoundException | StateSystemDisposedException e) {
+            return null;
+        }
+
+
+        List<Integer> threadQuarks = ss.getSubAttributes(threadsQuark, false);
+        return threadQuarks.stream()
+                /*
+                 * Keep only the quarks of threads that are considered active at
+                 * some point in the time range.
+                 */
+                .filter(threadQuark -> {
+                    /*
+                     * If the thread was active at range start, we can already
+                     * consider it active.
+                     */
+                    ITmfStateInterval intervalAtStart = fullQueryAtStart.get(threadQuark);
+                    if (IS_STATE_VALUE_ACTIVE.test(intervalAtStart.getStateValue())) {
+                        return true;
+                    }
+
+                    /*
+                     * If it was inactive, and it remains in the exact same
+                     * state for the whole time range, we can conclude it is
+                     * inactive for the whole range.
+                     *
+                     * Note this will not catch cases where the threads goes
+                     * from one inactive state to another, this will be found
+                     * with the range query below.
+                     */
+                    if (intervalAtStart.getEndTime() >= rangeEnd) {
+                        return false;
+                    }
+
+                    Iterator<ITmfStateInterval> it = StateSystemUtils.getIteratorOverQuark(ss, threadQuark, rangeStart, rangeEnd);
+                    while (it.hasNext()) {
+                        ITmfStateInterval interval = it.next();
+                        if (IS_STATE_VALUE_ACTIVE.test(interval.getStateValue())) {
+                            return true;
+                        }
+                    }
+                    /* We haven't found an active state value in the whole range. */
+                    return false;
+                })
+
+                /* Convert the thread quarks to their corresponding TIDs */
+                .map(ss::getAttributeName)
+                /* Ignore swapper threads */
+                .filter(attribName -> !attribName.startsWith(Attributes.THREAD_0_PREFIX))
+                .map(Integer::parseInt)
+                .collect(Collectors.toSet());
+    }
+
     /**
      * Get the TIDs of the threads from an analysis
      *
This page took 0.02771 seconds and 5 git commands to generate.