blob: 94bd7a70ee126215678e6c4823516aa0d23de78c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.sampler;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
/**
* Sampler class provides API for self-sampling of NetBeans
* platform application. The self-sampling should be used for
* diagnostic purposes and should help NetBeans platform developers
* with solving CPU related performance problems. Sampled data are
* stored in NPSS file, which can be opened by NetBeans Profiler or
* Java VisualVM for later analysis of sampled data.
*
* @author Jaroslav Bachorik, Tomas Hurka, Jaroslav Tulach
*/
public abstract class Sampler {
private static final int SAMPLER_RATE = 10;
private static final double MAX_AVERAGE = SAMPLER_RATE * 3;
private static final double MAX_STDDEVIATION = SAMPLER_RATE * 4;
private static final int MAX_SAMPLING_TIME = 5*60; // 5 minutes
private static final int MIN_SAMPLES = 50;
private static final int MAX_SAMPLES = MAX_SAMPLING_TIME * (1000/SAMPLER_RATE);
private final String name;
private Timer timer;
private ByteArrayOutputStream out;
private SamplesOutputStream samplesStream;
private long startTime;
private long nanoTimeCorrection;
private long samples;
private long laststamp;
private double max;
private double min = Long.MAX_VALUE;
private double sum;
private double devSquaresSum;
private volatile boolean stopped;
private volatile boolean running;
/**
* Factory method for creating Sampler suitable for automatic reporting
* of the slow operation (i.e. AWT blocked for some time). This method can
* return <code>null</code> if the sampled application is in nonstandard mode
* which will produce unrealistic data - for example application is
* running under debugger or profiler. It can return <code>null</code> if it
* is running on some exotic variant of JDK, where sampling is not supported.
* @param name which identifies the sampler thread
* @return instance of the {@link Sampler} or <code>null</code> if application
* is in nonstandard mode or sampling is not supported.
*/
public static @CheckForNull Sampler createSampler(@NonNull String name) {
if (SamplesOutputStream.isSupported()) {
try {
return InternalSampler.createInternalSampler(name);
} catch (LinkageError ex) {
return new StandaloneSampler(name);
}
}
return null;
}
/**
* Factory method for creating Sampler suitable for manual or user invoked
* scanning. This method can return <code>null</code> if it is running on
* some exotic variant of JDK, where sampling is not supported.
* @param name which identifies the sampler thread
* @return instance of the {@link Sampler} or <code>null</code> if sampling
* is not supported.
*/
public static @CheckForNull Sampler createManualSampler(@NonNull String name) {
if (SamplesOutputStream.isSupported()) {
try {
return new InternalSampler(name);
} catch (LinkageError ex) {
return new StandaloneSampler(name);
}
}
return null;
}
Sampler(String n) {
name = n;
}
/** Returns the bean to use for sampling.
* @return instance of the bean to take thread dumps from
*/
abstract ThreadMXBean getThreadMXBean();
/** Allows subclasses to handle created snapshot
* @param arr the content of the snapshot
* @throws IOException thrown in case of I/O error
*/
abstract void saveSnapshot(byte[] arr) throws IOException;
/** How to report an exception.
*
* @param ex exception
*/
abstract void printStackTrace(Throwable ex);
/** Methods for displaying progress.
*/
abstract void openProgress(int steps);
abstract void closeProgress();
abstract void progress(int i);
private void updateStats(long timestamp) {
if (laststamp != 0) {
double diff = (timestamp - laststamp) / 1000000.0;
samples++;
sum += diff;
devSquaresSum += (diff - SAMPLER_RATE) * (diff - SAMPLER_RATE);
if (diff > max) {
max = diff;
} else if (diff < min) {
min = diff;
}
}
laststamp = timestamp;
}
/**
* Start self-sampling. This method starts timer identified by <code>name</code>
* for actual sampling and returns immediately.
*/
public final synchronized void start() {
if (running) throw new IllegalStateException("sampling is already running"); // NOI18N
if (stopped) throw new IllegalStateException("it is not possible to restart sampling"); // NOI18N
running = true;
final ThreadMXBean threadBean = getThreadMXBean();
out = new ByteArrayOutputStream(64 * 1024);
try {
samplesStream = new SamplesOutputStream(out, this, MAX_SAMPLES);
} catch (IOException ex) {
printStackTrace(ex);
return;
}
startTime = System.currentTimeMillis();
nanoTimeCorrection = startTime * 1000000 - System.nanoTime();
// make sure that the sampler thread can be detected in a thread dump - add prefix
timer = new Timer("sampler-"+name); // NOI18N
timer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (Sampler.this) {
if (stopped) {
return;
}
try {
ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
long timestamp = System.nanoTime() + nanoTimeCorrection;
samplesStream.writeSample(infos, timestamp, Thread.currentThread().getId());
updateStats(timestamp);
} catch (Throwable ex) {
printStackTrace(ex);
}
}
}
}, SAMPLER_RATE, SAMPLER_RATE);
}
/**
* Cancels the self-sampling. All sampled data are discarded.
*/
public final void cancel() {
stopSampling(true, null);
}
/**
* Stop the self-sampling started by {@link #start()} method and writes the data to
* {@link DataOutputStream}. If the internal sampling logic detects that
* the data are distorted for example due to heavy system I/O, collected
* samples are discarded and nothing is written to {@link DataOutputStream}.
* <br>
* This method can take a long time and should not be invoked from EDT.
* @param dos {@link DataOutputStream} where sampled data is written to.
*/
public final void stopAndWriteTo(@NonNull DataOutputStream dos) {
stopSampling(false, dos);
}
/**
* Stop the self-sampling, save and open gathered data. If there is no
* sampled data, this method does nothing and returns immediately.
* <br>
* This method can take a long time and should not be invoked from EDT.
*/
public final void stop() {
stopSampling(false, null);
}
private synchronized void stopSampling(boolean cancel, DataOutputStream dos) {
try {
if (!running) throw new IllegalStateException("sampling was not started"); // NOI18N
if (stopped) throw new IllegalStateException("sampling is not running"); // NOI18N
stopped = true;
timer.cancel();
if (cancel || samples < 1) {
return;
}
if (isDispatchThread()) throw new IllegalStateException("sampling cannot be stopped from EDT"); //NOI18N
double average = sum / samples;
double std_deviation = Math.sqrt(devSquaresSum / samples);
boolean writeCommand = dos != null;
if (writeCommand) {
Object[] params = new Object[]{startTime, "Samples", samples, "Average", average, "Minimum", min, "Maximum", max, "Std. deviation", std_deviation}; // NOI18N
Logger.getLogger("org.netbeans.ui.performance").log(Level.CONFIG, "Snapshot statistics", params); // NOI18N
if (average > MAX_AVERAGE || std_deviation > MAX_STDDEVIATION || samples < MIN_SAMPLES) {
// do not take snapshot if the sampling was not regular enough
return;
}
}
samplesStream.close();
samplesStream = null;
if (writeCommand) {
dos.write(out.toByteArray());
dos.close();
return;
}
saveSnapshot(out.toByteArray());
} catch (IOException ex) {
printStackTrace(ex);
} finally {
// just to be sure
out = null;
samplesStream = null;
}
}
boolean isDispatchThread() {
return false;
}
}