/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 ~ 2010 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version 3
as published by the Free Software Foundation. You may not use, modify
or distribute this program under any other version of the
GNU Affero General Public License.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package tools;
import java.io.IOException;
import java.io.Writer;
import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
public class CPUSampler {
private List<String> included = new LinkedList<String>();
private static CPUSampler instance = new CPUSampler();
private long interval = 5;
private SamplerThread sampler = null;
private Map<StackTrace, Integer> recorded = new HashMap<StackTrace, Integer>();
private int totalSamples = 0;
public static CPUSampler getInstance() {
return instance;
}
public void setInterval(long millis) {
interval = millis;
}
public void addIncluded(String include) {
for (String alreadyIncluded : included) {
if (include.startsWith(alreadyIncluded)) {
return;
}
}
included.add(include);
}
public void reset() {
recorded.clear();
totalSamples = 0;
}
public void start() {
if (sampler == null) {
sampler = new SamplerThread();
sampler.start();
}
}
public void stop() {
if (sampler != null) {
sampler.stop();
sampler = null;
}
}
public SampledStacktraces getTopConsumers() {
List<StacktraceWithCount> ret = new ArrayList<StacktraceWithCount>();
Set<Entry<StackTrace, Integer>> entrySet = recorded.entrySet();
for (Entry<StackTrace, Integer> entry : entrySet) {
ret.add(new StacktraceWithCount(entry.getValue(), entry.getKey()));
}
Collections.sort(ret);
return new SampledStacktraces(ret, totalSamples);
}
public void save(Writer writer, int minInvocations, int topMethods) throws IOException {
SampledStacktraces topConsumers = getTopConsumers();
StringBuilder builder = new StringBuilder(); // build our summary :o
builder.append("Top Methods:\n");
for (int i = 0; i < topMethods && i < topConsumers.getTopConsumers().size(); i++) {
builder.append(topConsumers.getTopConsumers().get(i).toString(topConsumers.getTotalInvocations(), 1));
}
builder.append("\nStack Traces:\n");
writer.write(builder.toString());
writer.write(topConsumers.toString(minInvocations));
writer.flush();
}
private void consumeStackTraces(Map<Thread, StackTraceElement[]> traces) {
for (Entry<Thread, StackTraceElement[]> trace : traces.entrySet()) {
int relevant = findRelevantElement(trace.getValue());
if (relevant != -1) {
StackTrace st = new StackTrace(trace.getValue(), relevant, trace.getKey().getState());
Integer i = recorded.get(st);
totalSamples++;
if (i == null) {
recorded.put(st, Integer.valueOf(1));
} else {
recorded.put(st, Integer.valueOf(i.intValue() + 1));
}
}
}
}
private int findRelevantElement(StackTraceElement[] trace) {
if (trace.length == 0) {
return -1;
} else if (included.size() == 0) {
return 0;
}
int firstIncluded = -1;
for (String myIncluded : included) {
for (int i = 0; i < trace.length; i++) {
StackTraceElement ste = trace[i];
if (ste.getClassName().startsWith(myIncluded)) {
if (i < firstIncluded || firstIncluded == -1) {
firstIncluded = i;
break;
}
}
}
}
if (firstIncluded >= 0 && trace[firstIncluded].getClassName().equals("net.sf.odinms.tools.performance.CPUSampler$SamplerThread")) { // don't sample us
return -1;
}
return firstIncluded;
}
private static class StackTrace {
private StackTraceElement[] trace;
private State state;
public StackTrace(StackTraceElement[] trace, int startAt, State state) {
this.state = state;
if (startAt == 0) {
this.trace = trace;
} else {
this.trace = new StackTraceElement[trace.length - startAt];
System.arraycopy(trace, startAt, this.trace, 0, this.trace.length);
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StackTrace)) {
return false;
}
StackTrace other = (StackTrace) obj;
if (other.trace.length != trace.length) {
return false;
}
if (!(other.state == this.state)) {
return false;
}
for (int i = 0; i < trace.length; i++) {
if (!trace[i].equals(other.trace[i])) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int ret = 13 * trace.length + state.hashCode();
for (StackTraceElement ste : trace) {
ret ^= ste.hashCode();
}
return ret;
}
public StackTraceElement[] getTrace() {
return trace;
}
@Override
public String toString() {
return toString(-1);
}
public String toString(int traceLength) {
StringBuilder ret = new StringBuilder("State: ");
ret.append(state.name());
if (traceLength > 1) {
ret.append("\n");
} else {
ret.append(" ");
}
int i = 0;
for (StackTraceElement ste : trace) {
i++;
if (i > traceLength) {
break;
}
ret.append(ste.getClassName());
ret.append("#");
ret.append(ste.getMethodName());
ret.append(" (Line: ");
ret.append(ste.getLineNumber());
ret.append(")\n");
}
return ret.toString();
}
}
private class SamplerThread implements Runnable {
private boolean running = false;
private boolean shouldRun = false;
private Thread rthread;
public void start() {
if (!running) {
shouldRun = true;
rthread = new Thread(this, "CPU Sampling Thread");
rthread.start();
running = true;
}
}
public void stop() {
this.shouldRun = false;
rthread.interrupt();
try {
rthread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (shouldRun) {
consumeStackTraces(Thread.getAllStackTraces());
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
return;
}
}
}
}
public static class StacktraceWithCount implements Comparable<StacktraceWithCount> {
private int count;
private StackTrace trace;
public StacktraceWithCount(int count, StackTrace trace) {
super();
this.count = count;
this.trace = trace;
}
public int getCount() {
return count;
}
public StackTraceElement[] getTrace() {
return trace.getTrace();
}
@Override
public int compareTo(StacktraceWithCount o) {
return -Integer.valueOf(count).compareTo(Integer.valueOf(o.count));
}
@Override
public boolean equals(Object oth) {
if (!(oth instanceof StacktraceWithCount)) {
return false;
}
final StacktraceWithCount o = (StacktraceWithCount) oth;
return count == o.count;
}
@Override
public String toString() {
return count + " Sampled Invocations\n" + trace.toString();
}
private double getPercentage(int total) {
return Math.round((((double) count) / total) * 10000.0) / 100.0;
}
public String toString(int totalInvoations, int traceLength) {
return count + "/" + totalInvoations + " Sampled Invocations (" + getPercentage(totalInvoations) + "%) "
+ trace.toString(traceLength);
}
}
public static class SampledStacktraces {
List<StacktraceWithCount> topConsumers;
int totalInvocations;
public SampledStacktraces(List<StacktraceWithCount> topConsumers, int totalInvocations) {
super();
this.topConsumers = topConsumers;
this.totalInvocations = totalInvocations;
}
public List<StacktraceWithCount> getTopConsumers() {
return topConsumers;
}
public int getTotalInvocations() {
return totalInvocations;
}
@Override
public String toString() {
return toString(0);
}
public String toString(int minInvocation) {
StringBuilder ret = new StringBuilder();
for (StacktraceWithCount swc : topConsumers) {
if (swc.getCount() >= minInvocation) {
ret.append(swc.toString(totalInvocations, Integer.MAX_VALUE));
ret.append("\n");
}
}
return ret.toString();
}
}
}