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

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

/*
 * Copyright (c) 2017, 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 com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.StandardTags.RootTag;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.instrumentation.TruffleInstrument.Env;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.vm.PolyglotEngine;
import com.oracle.truffle.tools.profiler.impl.CPUSamplerInstrument;
import com.oracle.truffle.tools.profiler.impl.ProfilerToolFactory;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Implementation of a sampling based profiler for
 * {@linkplain com.oracle.truffle.api.TruffleLanguage Truffle languages} built on top of the
 * {@linkplain TruffleInstrument Truffle instrumentation framework}.
 * 

* The sampler keeps a shadow stack during execution. This shadow stack is sampled at regular * intervals, i.e. the state of the stack is copied and saved into trees of {@linkplain ProfilerNode * nodes}, which represent the profile of the execution. *

* Usage example: {@link CPUSamplerSnippets#example} * * @since 0.30 */ public final class CPUSampler implements Closeable { /** * Wrapper for information on how many times an element was seen on the shadow stack. Used as a * template parameter of {@link ProfilerNode}. Differentiates between an execution in compiled * code and in the interpreter. * * @since 0.30 */ public static final class Payload { Payload() { } int compiledHitCount; int interpretedHitCount; int selfCompiledHitCount; int selfInterpretedHitCount; final List selfHitTimes = new ArrayList<>(); /** * @return The number of times the element was found bellow the top of the shadow stack as * compiled code * @since 0.30 */ public int getCompiledHitCount() { return compiledHitCount; } /** * @return The number of times the element was found bellow the top of the shadow stack as * interpreted code * @since 0.30 */ public int getInterpretedHitCount() { return interpretedHitCount; } /** * @return The number of times the element was found on the top of the shadow stack as * compiled code * @since 0.30 */ public int getSelfCompiledHitCount() { return selfCompiledHitCount; } /** * @return The number of times the element was found on the top of the shadow stack as * interpreted code * @since 0.30 */ public int getSelfInterpretedHitCount() { return selfInterpretedHitCount; } /** * @return Total number of times the element was found on the top of the shadow stack * @since 0.30 */ public int getSelfHitCount() { return selfCompiledHitCount + selfInterpretedHitCount; } /** * @return Total number of times the element was found bellow the top of the shadow stack * @since 0.30 */ public int getHitCount() { return compiledHitCount + interpretedHitCount; } /** * @return An immutable list of time stamps for the times that the element was on the top of * the stack * @since 0.30 */ public List getSelfHitTimes() { return Collections.unmodifiableList(selfHitTimes); } } /** * Describes the different modes in which the CPU sampler can operate. * * @since 0.30 */ public enum Mode { /** * Sample {@link RootTag Roots} excluding the ones that get inlined during * compilation. This mode is the default and has the least amount of impact on peak * performance. * * @since 0.30 */ EXCLUDE_INLINED_ROOTS, /** * Sample {@link RootTag Roots} including the ones that get inlined during * compilation. * * @since 0.30 */ ROOTS, /** * Sample all {@link com.oracle.truffle.api.instrumentation.StandardTags.StatementTag * Statements}. This mode has serious impact on peek performance. * * @since 0.30 */ STATEMENTS } private Mode mode = Mode.EXCLUDE_INLINED_ROOTS; static final SourceSectionFilter DEFAULT_FILTER = SourceSectionFilter.newBuilder().tagIs(RootTag.class).build(); private volatile boolean closed; private volatile boolean collecting; private long period = 1; private long delay = 0; private int stackLimit = 10000; private SourceSectionFilter filter; private boolean stackOverflowed = false; private AtomicLong samplesTaken = new AtomicLong(0); private Timer samplerThread; private TimerTask samplerTask; private ShadowStack shadowStack; private EventBinding stacksBinding; private final ProfilerNode rootNode = new ProfilerNode<>(this, new Payload()); private final Env env; private boolean gatherSelfHitTimes = false; CPUSampler(Env env) { this.env = env; } /** * Finds {@link CPUSampler} associated with given engine. * * @param engine the engine to find debugger for * @return an instance of associated {@link CPUSampler} * @since 0.30 */ public static CPUSampler find(PolyglotEngine engine) { return CPUSamplerInstrument.getSampler(engine); } /** * Controls whether the sampler is collecting data or not. * * @param collecting the new state of the sampler. * @since 0.30 */ public synchronized void setCollecting(boolean collecting) { if (this.collecting != collecting) { this.collecting = collecting; resetSampling(); } } /** * @return whether or not the sampler is currently collecting data. * @since 0.30 */ public synchronized boolean isCollecting() { return collecting; } /** * Sets the {@link Mode mode} for the sampler. * * @param mode the new mode for the sampler. * @since 0.30 */ public synchronized void setMode(Mode mode) { verifyConfigAllowed(); this.mode = mode; } /** * Sets the sampling period i.e. the time between two samples of the shadow stack are taken. * * @param samplePeriod the new sampling period. * @since 0.30 */ public synchronized void setPeriod(long samplePeriod) { verifyConfigAllowed(); if (samplePeriod < 1) { throw new IllegalArgumentException(String.format("Invalid sample period %s.", samplePeriod)); } this.period = samplePeriod; } /** * @return the sampling period i.e. the time between two samples of the shadow stack are taken. * @since 0.30 */ public synchronized long getPeriod() { return period; } /** * Sets the delay period i.e. the time that is allowed to pass between when the first sample * would have been taken and when the sampler actually starts taking samples. * * @param delay the delay period. * @since 0.30 */ public synchronized void setDelay(long delay) { verifyConfigAllowed(); this.delay = delay; } /** * Sets the maximum amount of stack frames that are sampled. Whether or not the stack grew more * than the provided size during execution can be checked with {@linkplain #hasStackOverflowed} * * @param stackLimit the new size of the shadow stack * @since 0.30 */ public synchronized void setStackLimit(int stackLimit) { verifyConfigAllowed(); if (stackLimit < 1) { throw new IllegalArgumentException(String.format("Invalid stack limit %s.", stackLimit)); } this.stackLimit = stackLimit; } /** * @return size of the shadow stack * @since 0.30 */ public synchronized int getStackLimit() { return stackLimit; } /** * Sets the {@link SourceSectionFilter filter} for the sampler. The sampler will only observe * parts of the executed source code that is specified by the filter. * * @param filter The new filter describing which part of the source code to sample * @since 0.30 */ public synchronized void setFilter(SourceSectionFilter filter) { verifyConfigAllowed(); this.filter = filter; } /** * @return The filter describing which part of the source code to sample * @since 0.30 */ public synchronized SourceSectionFilter getFilter() { return filter; } /** * @return Total number of samples taken during execution * @since 0.30 */ public long getSampleCount() { return samplesTaken.get(); } /** * @return was the shadow stack size insufficient for the execution. * @since 0.30 */ public boolean hasStackOverflowed() { return stackOverflowed; } /** * @return The roots of the trees representing the profile of the execution. * @since 0.30 */ public Collection> getRootNodes() { return rootNode.getChildren(); } /** * Erases all the data gathered by the sampler and resets the sample count to 0. * * @since 0.30 */ public synchronized void clearData() { samplesTaken.set(0); Map> rootChildren = rootNode.children; if (rootChildren != null) { rootChildren.clear(); } } /** * @return whether or not the sampler has collected any data so far. * @since 0.30 */ public synchronized boolean hasData() { Map> rootChildren = rootNode.children; return rootChildren != null && !rootChildren.isEmpty(); } /** * Closes the sampler for fuhrer use, deleting all the gathered data. * * @since 0.30 */ @Override public synchronized void close() { closed = true; resetSampling(); clearData(); } /** * @return Whether or not timestamp information for the element at the top of the stack for each * sample is gathered * * @since 0.30 */ public boolean isGatherSelfHitTimes() { return gatherSelfHitTimes; } /** * Sets whether or not to gather timestamp information for the element at the top of the stack * for each sample. * * @param gatherSelfHitTimes new value for whether or not to gather timestamps * * @since 0.30 */ public void setGatherSelfHitTimes(boolean gatherSelfHitTimes) { verifyConfigAllowed(); this.gatherSelfHitTimes = gatherSelfHitTimes; } private void resetSampling() { assert Thread.holdsLock(this); cleanup(); if (!collecting || closed) { return; } if (samplerThread == null) { samplerThread = new Timer("Sampling thread", true); } SourceSectionFilter f = this.filter; if (f == null) { f = DEFAULT_FILTER; } this.stackOverflowed = false; this.shadowStack = new ShadowStack(stackLimit); this.stacksBinding = this.shadowStack.install(env.getInstrumenter(), combine(f, mode), mode == Mode.EXCLUDE_INLINED_ROOTS); this.samplerTask = new SamplingTimerTask(); this.samplerThread.schedule(samplerTask, 0, period); } private static SourceSectionFilter combine(SourceSectionFilter filter, Mode mode) { List> tags = new ArrayList<>(); if (mode == Mode.EXCLUDE_INLINED_ROOTS || mode == Mode.ROOTS) { tags.add(StandardTags.RootTag.class); } if (mode == Mode.STATEMENTS) { tags.add(StandardTags.StatementTag.class); } return SourceSectionFilter.newBuilder().tagIs(tags.toArray(new Class[0])).and(filter).build(); } private void cleanup() { assert Thread.holdsLock(this); if (stacksBinding != null) { stacksBinding.dispose(); stacksBinding = null; } if (shadowStack != null) { shadowStack = null; } if (samplerTask != null) { samplerTask.cancel(); samplerTask = null; } if (samplerThread != null) { samplerThread.cancel(); samplerThread = null; } } private void verifyConfigAllowed() { assert Thread.holdsLock(this); if (closed) { throw new IllegalStateException("CPUSampler is already closed."); } else if (collecting) { throw new IllegalStateException("Cannot change sampler configuration while collecting. Call setCollecting(false) to disable collection first."); } } private class SamplingTimerTask extends TimerTask { int runcount = 0; @Override public void run() { runcount++; if (runcount < delay / period) { return; } long timestamp = System.currentTimeMillis(); boolean sampleTaken = false; ShadowStack localShadowStack = shadowStack; if (localShadowStack != null) { for (ShadowStack.ThreadLocalStack stack : localShadowStack.getStacks()) { sampleTaken |= sample(stack, timestamp); } } if (sampleTaken) { samplesTaken.incrementAndGet(); } } boolean sample(ShadowStack.ThreadLocalStack stack, long timestamp) { if (stack.hasStackOverflowed()) { stackOverflowed = true; return false; } if (stack.getStackIndex() == -1) { // nothing on the stack return false; } final ShadowStack.ThreadLocalStack.CorrectedStackInfo correctedStackInfo = ShadowStack.ThreadLocalStack.CorrectedStackInfo.build(stack); if (correctedStackInfo == null || correctedStackInfo.getLength() == 0) { return false; } // now traverse the stack and insert the path into the tree ProfilerNode treeNode = rootNode; for (int i = 0; i < correctedStackInfo.getLength(); i++) { SourceLocation location = correctedStackInfo.getStack()[i]; boolean isCompiled = correctedStackInfo.getCompiledStack()[i]; treeNode = addOrUpdateChild(treeNode, location); Payload payload = treeNode.getPayload(); if (i == correctedStackInfo.getLength() - 1) { // last element is counted as self time if (isCompiled) { payload.selfCompiledHitCount++; } else { payload.selfInterpretedHitCount++; } if (gatherSelfHitTimes) { payload.selfHitTimes.add(timestamp); assert payload.selfHitTimes.size() == payload.getSelfHitCount(); } } if (isCompiled) { payload.compiledHitCount++; } else { payload.interpretedHitCount++; } } return true; } private ProfilerNode addOrUpdateChild(ProfilerNode treeNode, SourceLocation location) { ProfilerNode child = treeNode.findChild(location); if (child == null) { Payload payload = new Payload(); child = new ProfilerNode<>(treeNode, location, payload); treeNode.addChild(location, child); } return child; } } static { CPUSamplerInstrument.setFactory(new ProfilerToolFactory() { @Override public CPUSampler create(Env env) { return new CPUSampler(env); } }); } } class CPUSamplerSnippets { @SuppressWarnings("unused") public void example() { // @formatter:off // BEGIN: CPUSamplerSnippets#example PolyglotEngine engine = PolyglotEngine.newBuilder().build(); CPUSampler sampler = CPUSampler.find(engine); sampler.setCollecting(true); Source someCode = Source.newBuilder("..."). mimeType("..."). name("example").build(); engine.eval(someCode); sampler.setCollecting(false); sampler.close(); // Read information about the roots of the tree. for (ProfilerNode node : sampler.getRootNodes()) { final String rootName = node.getRootName(); final int selfHitCount = node.getPayload().getSelfHitCount(); // ... } // END: CPUSamplerSnippets#example // @formatter:on } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy