blob: 9babdd42fd7463f55e205991347afb3225f67ee9 [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.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.management.ThreadInfo;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.zip.GZIPOutputStream;
import javax.management.StandardMBean;
/**
*
* @author Tomas Hurka
*/
class SamplesOutputStream {
static final String ID = "NPSS"; // NetBeans Profiler samples stream
public static final String FILE_EXT = ".npss"; // NOI18N
static final int RESET_THRESHOLD = 5000;
static final int STEPS = 1000;
static byte version = 2;
OutputStream outStream;
Map<Long, ThreadInfo> lastThreadInfos;
Map<StackTraceElement, WeakReference<StackTraceElement>> steCache;
List<Sample> samples;
Sampler progress;
int maxSamples;
int offset;
public static boolean isSupported() {
return true;
}
SamplesOutputStream(OutputStream os, Sampler progress, int max) throws IOException {
maxSamples = max;
this.progress = progress;
outStream = os;
writeHeader(os);
// out = new ObjectOutputStream(os);
lastThreadInfos = new HashMap<>();
steCache = new WeakHashMap<>(8*1024);
samples = new ArrayList<Sample>(1024);
}
void writeSample(ThreadInfo[] infos, long time, long selfThreadId) throws IOException {
List<Long> sameT = new ArrayList<Long>();
List<ThreadInfo> newT = new ArrayList<ThreadInfo>();
List<Long> tids = new ArrayList<Long>();
for (ThreadInfo tinfo : infos) {
long id;
if (tinfo == null) continue; // ignore null ThreadInfo
id = tinfo.getThreadId();
if (id != selfThreadId) { // ignore sampling thread
Long tid = Long.valueOf(tinfo.getThreadId());
ThreadInfo lastThread = lastThreadInfos.get(tid);
tids.add(tid);
if (lastThread != null) {
if (lastThread.getThreadState().equals(tinfo.getThreadState())) {
StackTraceElement[] lastStack = lastThread.getStackTrace();
StackTraceElement[] stack = tinfo.getStackTrace();
if (Arrays.deepEquals(lastStack, stack)) {
sameT.add(tid);
continue;
}
}
}
internStackTrace(tinfo);
newT.add(tinfo);
lastThreadInfos.put(tid, tinfo);
}
}
addSample(new Sample(time, sameT, newT));
// remove dead threads
Set<Long> ids = new HashSet<>(lastThreadInfos.keySet());
ids.removeAll(tids);
lastThreadInfos.keySet().removeAll(ids);
}
private void addSample(Sample sample) {
if (samples.size() == maxSamples) {
Sample lastSample;
Sample removedSample = samples.set(offset, sample);
offset = (offset + 1) % maxSamples;
lastSample = samples.get(offset);
updateLastSample(removedSample,lastSample);
} else {
samples.add(sample);
}
}
Sample getSample(int index) {
int arrayIndex = index;
if (samples.size() == maxSamples) {
arrayIndex = (offset + index) % maxSamples;
}
return samples.get(arrayIndex);
}
void removeSample(int index) {
int arrayIndex = index;
if (samples.size() == maxSamples) {
arrayIndex = (offset + index) % maxSamples;
}
samples.set(arrayIndex,null);
}
private void updateLastSample(Sample removedSample, Sample lastSample) {
List<ThreadInfo> removedNewThreads = removedSample.getNewThreads();
List<Long> sameThreads = lastSample.getSameThread();
List<ThreadInfo> newThreads = lastSample.getNewThreads();
for (ThreadInfo ti : removedNewThreads) {
Long tid = Long.valueOf(ti.getThreadId());
if (sameThreads.contains(tid)) {
newThreads.add(ti);
sameThreads.remove(tid);
}
}
}
void close() throws IOException {
steCache = null;
GZIPOutputStream stream = new GZIPOutputStream(outStream, 64 * 1024);
ObjectOutputStream out = new ObjectOutputStream(stream);
int size = samples.size();
out.writeInt(size);
out.writeLong(getSample(size-1).getTime());
openProgress();
for (int i=0; i<size;i++) {
Sample s = getSample(i);
removeSample(i);
if (i > 0 && i % RESET_THRESHOLD == 0) {
out.reset();
}
s.writeToStream(out);
if ((i+40) % 50 == 0) step((STEPS*i)/size);
}
step(STEPS); // set progress at 100%
out.close();
closeProgress();
}
private void writeHeader(OutputStream os) throws IOException {
os.write(ID.getBytes());
os.write(version);
}
private void internStackTrace(ThreadInfo tinfo) {
if (steCache == null) {
return;
}
StackTraceElement[] stack = tinfo.getStackTrace();
for (int i = 0; i < stack.length; i++) {
StackTraceElement ste = stack[i];
WeakReference<StackTraceElement> oldStackRef = steCache.get(ste);
if (oldStackRef != null) {
stack[i] = oldStackRef.get();
assert stack[i] != null;
} else {
steCache.put(ste, new WeakReference<>(ste));
}
}
}
private void openProgress() {
if (progress != null) {
progress.openProgress(STEPS);
}
}
private void closeProgress() {
if (progress != null) {
progress.closeProgress();
}
}
private void step(int i) {
if (progress != null) {
progress.progress(i);
}
}
private static class Sample {
private final long time;
private final List<Long> sameThread;
private final List<ThreadInfo> newThreads;
Sample(long t, List<Long> sameT, List<ThreadInfo> newT) {
time = t;
sameThread = sameT;
newThreads = newT;
}
private long getTime() {
return time;
}
private List<Long> getSameThread() {
return sameThread;
}
private List<ThreadInfo> getNewThreads() {
return newThreads;
}
private void writeToStream(ObjectOutputStream out) throws IOException {
out.writeLong(time);
out.writeInt(sameThread.size());
for (Long tid : sameThread) {
out.writeLong(tid.longValue());
}
out.writeInt(newThreads.size());
for (Object t : toCompositeData(newThreads)) {
out.writeObject(t);
}
}
private Object[] toCompositeData(final List<ThreadInfo> threadInfos) {
CompositeDataGetter getter = new CompositeDataGetter() {
@Override
public ThreadInfo[] getThreads() {
return threadInfos.toArray(new ThreadInfo[0]);
}
};
try {
StandardMBean getterBean = new StandardMBean(getter,
CompositeDataGetter.class,
true);
return (Object[]) getterBean.getAttribute("Threads");
} catch (Exception ex) {
return new Object[0];
}
}
public interface CompositeDataGetter {
public ThreadInfo[] getThreads();
}
}
}