All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.tools.profiler.SafepointStackSampler Maven / Gradle / Ivy

/*
 * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.truffle.tools.profiler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.stream.Collectors;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.ThreadLocalAction;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.TruffleStackTraceElement;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.TruffleInstrument.Env;
import com.oracle.truffle.api.nodes.LanguageInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;

final class SafepointStackSampler {

    private static final Set> VISITOR_TAGS = new HashSet<>(Arrays.asList(StandardTags.RootTag.class));
    private volatile int stackLimit;
    private volatile SourceSectionFilter sourceSectionFilter;
    private final ConcurrentLinkedQueue stackVisitorCache = new ConcurrentLinkedQueue<>();
    private final AtomicReference cachedAction = new AtomicReference<>();
    private final ThreadLocal syntheticFrameThreadLocal = ThreadLocal.withInitial(() -> null);
    private volatile boolean overflowed;
    private volatile boolean includeAsyncStackTrace;
    private final AtomicLong sampleIndex = new AtomicLong(0);

    SafepointStackSampler(int stackLimit, SourceSectionFilter sourceSectionFilter) {
        this.stackLimit = stackLimit;
        this.sourceSectionFilter = sourceSectionFilter;
    }

    private StackVisitor fetchStackVisitor() {
        StackVisitor visitor = stackVisitorCache.poll();
        if (visitor == null) {
            visitor = new StackVisitor(stackLimit, sourceSectionFilter, includeAsyncStackTrace);
        }
        return visitor;
    }

    List sample(CPUSampler cpuSampler, Env env, Map contexts, boolean useSyntheticFrames, long timeout, TimeUnit timeoutUnit) {
        long startNanos = System.nanoTime();
        SampleAction action = cachedAction.getAndSet(null);
        if (action == null) {
            long index = sampleIndex.getAndIncrement();
            if (index < 0) {
                // handle overflow gracefully
                index = 0;
                sampleIndex.set(0);
            }
            action = new SampleAction(index);
        }
        action.useSyntheticFrames = useSyntheticFrames;

        long submitTime = System.nanoTime();
        Map> futures = new LinkedHashMap<>();
        for (TruffleContext context : contexts.keySet()) {
            if (!context.isClosed()) {
                try {
                    futures.put(context, env.submitThreadLocal(context, null, action));
                } catch (IllegalStateException e) {
                    // context may be closed while submitting
                }
            }
        }

        boolean incompleteSample = false;
        for (Map.Entry> futureEntry : futures.entrySet()) {
            TruffleContext context = futureEntry.getKey();
            Future future = futureEntry.getValue();
            long timeElapsed = System.nanoTime() - startNanos;
            long timeoutNanos = timeoutUnit.toNanos(timeout);
            if (!incompleteSample && timeElapsed < timeoutNanos) {
                try {
                    futureEntry.getValue().get(timeout, timeoutUnit);
                } catch (ExecutionException e) {
                    env.getLogger(getClass()).log(Level.SEVERE, "Sampling error", e);
                    incompleteSample = true;
                } catch (InterruptedException e) {
                    /*
                     * Closing the CPUSampler during shutdown of the instrument sends interrupt to
                     * the sampling thread, which is expected, and so we don't log it.
                     */
                    if (!cpuSampler.closed) {
                        env.getLogger(getClass()).log(Level.SEVERE, "Sampling interrupted", e);
                    }
                    incompleteSample = true;
                } catch (TimeoutException e) {
                    future.cancel(false);
                    contexts.get(context).missedSamples.incrementAndGet();
                    incompleteSample = true;
                }
            } else {
                future.cancel(false);
            }
        }
        // we compute the time to find out how accurate this sample is.
        List perThreadSamples = new ArrayList<>();
        for (CollectionResult result : action.getStacks()) {
            // time until the safepoint is executed from schedule
            StackSample sample = result.createSample(submitTime);
            if (sample.overflowed) {
                this.overflowed = true;
            }
            perThreadSamples.add(sample);
        }
        action.reset();
        cachedAction.set(action);

        assert perThreadSamples.stream().map(e -> e.thread).collect(Collectors.toSet()).size() == perThreadSamples.size();
        return perThreadSamples;
    }

    boolean hasOverflowed() {
        return overflowed;
    }

    public void pushSyntheticFrame(LanguageInfo language, String message) {
        // For synthetic frames we need iterate and create frames eagerly.
        long submitTime = System.nanoTime();
        StackVisitor visitor = fetchStackVisitor();
        visitor.iterateFrames();
        StackSample stackSample = visitor.createSample(submitTime);
        SyntheticFrame frame = new SyntheticFrame(syntheticFrameThreadLocal.get(), stackSample, language, message);
        syntheticFrameThreadLocal.set(frame);
    }

    public void popSyntheticFrame() {
        SyntheticFrame toPop = syntheticFrameThreadLocal.get();
        if (toPop != null) {
            syntheticFrameThreadLocal.set(toPop.parent);
        }
    }

    int getStackLimit() {
        return stackLimit;
    }

    void setStackLimit(int stackLimit) {
        this.stackLimit = stackLimit;
        this.stackVisitorCache.clear();
    }

    SourceSectionFilter getSourceSectionFilter() {
        return sourceSectionFilter;
    }

    void setSourceSectionFilter(SourceSectionFilter sourceSectionFilter) {
        this.sourceSectionFilter = sourceSectionFilter;
        this.stackVisitorCache.clear();
    }

    boolean isIncludeAsyncStackTrace() {
        return includeAsyncStackTrace;
    }

    void setIncludeAsyncStackTrace(boolean includeAsyncStackTrace) {
        this.includeAsyncStackTrace = includeAsyncStackTrace;
    }

    void resetSampling() {
        // Note: synchronized on CPUSampler instance in the caller.
        this.sampleIndex.set(0);
        this.cachedAction.set(null);
        this.overflowed = false;
        this.stackVisitorCache.clear();
    }

    static final class StackSample {

        final Thread thread;
        final List stack;
        final long biasNs;
        final long durationNs;
        final boolean overflowed;

        StackSample(Thread thread, List stack, long biasNs, long durationNs, boolean overflowed) {
            this.thread = thread;
            this.stack = stack;
            this.biasNs = biasNs;
            this.durationNs = durationNs;
            this.overflowed = overflowed;
        }
    }

    private class StackVisitor implements FrameInstanceVisitor, CollectionResult {

        // filled from top to bottom of stack, i.e. inner to outer frame
        private final CallTarget[] targets;
        private final int[] tiers;
        private final boolean[] roots;
        private final SourceSectionFilter sourceSectionFilter;
        private final boolean includeAsyncStackTrace;
        private Thread thread;
        /** Next frame index and the number of captured entries. */
        private int nextFrameIndex;
        private long startTime;
        private long endTime;
        private boolean overflowed;

        StackVisitor(int stackLimit, SourceSectionFilter sourceSectionFilter, boolean includeAsyncStackTrace) {
            assert stackLimit > 0;
            this.tiers = new int[stackLimit];
            this.roots = new boolean[stackLimit];
            this.targets = new CallTarget[stackLimit];
            this.sourceSectionFilter = sourceSectionFilter;
            this.includeAsyncStackTrace = includeAsyncStackTrace;
        }

        final void iterateFrames() {
            assert this.thread == null : "not cleaned";
            assert this.nextFrameIndex == 0 : "not cleaned";

            this.thread = Thread.currentThread();
            this.startTime = System.nanoTime();
            Truffle.getRuntime().iterateFrames(this);
            this.endTime = System.nanoTime();
        }

        public StackSample createSample(long submitTime) {
            long bias = this.startTime - submitTime;
            long overhead = this.endTime - this.startTime;
            StackSample sample = new StackSample(this.thread, this.createEntries(sourceSectionFilter),
                            bias, overhead, this.overflowed);
            assert sample.thread != null;
            this.resetAndReturn();
            return sample;
        }

        public FrameInstance visitFrame(FrameInstance frameInstance) {
            CallTarget callTarget = frameInstance.getCallTarget();
            int compilationTier = frameInstance.getCompilationTier();
            boolean compilationRoot = frameInstance.isCompilationRoot();
            boolean continueVisiting = addStackTraceEntry(callTarget, compilationTier, compilationRoot);
            if (continueVisiting && includeAsyncStackTrace) {
                continueVisiting = addAnyAsyncStackTraceEntries(callTarget, frameInstance.getFrame(FrameAccess.READ_ONLY));
            }
            if (continueVisiting) {
                return null;
            } else {
                overflowed = true;
                return frameInstance;
            }
        }

        private boolean addStackTraceEntry(CallTarget callTarget, int compilationTier, boolean compilationRoot) {
            assert !overflowed;
            tiers[nextFrameIndex] = compilationTier;
            roots[nextFrameIndex] = compilationRoot;
            targets[nextFrameIndex] = callTarget;
            nextFrameIndex++;
            return nextFrameIndex < targets.length; // continue?
        }

        private boolean addAnyAsyncStackTraceEntries(CallTarget callTarget, Frame frame) {
            assert !overflowed;
            // Try to mix in async stack trace elements.
            List asyncStackTrace = TruffleStackTrace.getAsynchronousStackTrace(callTarget, frame);
            if (asyncStackTrace != null && !asyncStackTrace.isEmpty()) {
                List nextAsyncStackTrace = asyncStackTrace;
                do {
                    asyncStackTrace = nextAsyncStackTrace;
                    nextAsyncStackTrace = null;
                    for (TruffleStackTraceElement element : asyncStackTrace) {
                        RootCallTarget asyncTarget = element.getTarget();
                        Frame asyncFrame = element.getFrame();
                        if (!addStackTraceEntry(asyncTarget, 0, true)) {
                            return false; // stop
                        }
                        // Include nested async stack frames at the end, e.g.
                        // a <- b <- resume c (async trace: c <- d <- resume e)
                        // c <- d <- resume e (async trace: e <- f <- start)
                        // e <- f <- start
                        // should be reconstructed as:
                        // a <- b <- c <- d <- e <- start
                        if (asyncFrame != null && nextAsyncStackTrace == null) {
                            List nestedAsyncStacktrace = TruffleStackTrace.getAsynchronousStackTrace(asyncTarget, asyncFrame);
                            if (nestedAsyncStacktrace != null && !nestedAsyncStacktrace.isEmpty()) {
                                nextAsyncStackTrace = nestedAsyncStacktrace;
                            }
                        }
                    }
                } while (nextAsyncStackTrace != null);
            }
            return true; // continue
        }

        void resetAndReturn() {
            Arrays.fill(tiers, 0, nextFrameIndex, 0);
            Arrays.fill(roots, 0, nextFrameIndex, false);
            Arrays.fill(targets, 0, nextFrameIndex, null);
            nextFrameIndex = 0;
            thread = null;
            overflowed = false;
            this.startTime = 0L;
            this.endTime = 0L;
            stackVisitorCache.add(this);
        }

        @SuppressWarnings("unused")
        List createEntries(SourceSectionFilter filter) {
            List entries = new ArrayList<>(nextFrameIndex);
            for (int i = 0; i < nextFrameIndex; i++) {
                CallTarget target = targets[i];
                RootNode root = ((RootCallTarget) target).getRootNode();
                SourceSection sourceSection = root.getSourceSection();
                if (filter.includes(root, sourceSection, null)) {
                    entries.add(new StackTraceEntry(VISITOR_TAGS, sourceSection, root, root, tiers[i], roots[i]));
                }
            }
            return entries;
        }
    }

    private class SampleAction extends ThreadLocalAction {

        final ConcurrentHashMap completed = new ConcurrentHashMap<>();
        boolean useSyntheticFrames = true;

        private long index;

        protected SampleAction(long index) {
            super(false, false);
            this.index = index;
        }

        @Override
        protected void perform(Access access) {
            if (completed.containsKey(access.getThread())) {
                return;
            }
            if (useSyntheticFrames) {
                SyntheticFrame syntheticFrame = syntheticFrameThreadLocal.get();
                if (syntheticFrame != null) {
                    completed.put(access.getThread(), syntheticFrame);
                    return;
                }
            }
            StackVisitor visitor = fetchStackVisitor();
            visitor.iterateFrames();
            completed.put(access.getThread(), visitor);
        }

        List getStacks() {
            return new ArrayList<>(completed.values());
        }

        @Override
        public String toString() {
            return "StackSampleAction[index=" + index + "]";
        }

        void reset() {
            completed.clear();
        }
    }

    private interface CollectionResult {

        StackSample createSample(long submitTime);

    }

    private static class SyntheticFrame implements CollectionResult {
        final SyntheticFrame parent;
        final StackSample stackSample;
        final LanguageInfo language;
        final String message;

        boolean syntheticFrameCreated;

        /**
         * Created on the interpreter thread, keep as fast as possible.
         */
        SyntheticFrame(SyntheticFrame parent, StackSample stackSample, LanguageInfo language, String message) {
            this.parent = parent;
            this.stackSample = stackSample;
            this.language = language;
            this.message = message;
        }

        public StackSample createSample(long submitTime) {
            if (!syntheticFrameCreated) {
                /*
                 * The synthetic frame is created lazily to avoid string concatenation on the main
                 * thread.
                 */
                stackSample.stack.add(0, new StackTraceEntry("<<" + language.getId() + ":" + message + ">>"));
                syntheticFrameCreated = true;
            }
            return stackSample;
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy