/* * 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 javastory.tools; import java.io.IOException; import java.io.Writer; import java.lang.Thread.State; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class CPUSampler { private final List<String> included = Lists.newLinkedList(); private static CPUSampler instance = new CPUSampler(); private long interval = 5; private SamplerThread sampler = null; private final Map<StackTrace, Integer> recorded = Maps.newHashMap(); private int totalSamples = 0; public static CPUSampler getInstance() { return instance; } public void setInterval(final long millis) { this.interval = millis; } public void addIncluded(final String include) { for (final String alreadyIncluded : this.included) { if (include.startsWith(alreadyIncluded)) { return; } } this.included.add(include); } public void reset() { this.recorded.clear(); this.totalSamples = 0; } public void start() { if (this.sampler == null) { this.sampler = new SamplerThread(); this.sampler.start(); } } public void stop() { if (this.sampler != null) { this.sampler.stop(); this.sampler = null; } } public SampledStacktraces getTopConsumers() { final List<StacktraceWithCount> ret = Lists.newArrayList(); final Set<Entry<StackTrace, Integer>> entrySet = this.recorded.entrySet(); for (final Entry<StackTrace, Integer> entry : entrySet) { ret.add(new StacktraceWithCount(entry.getValue(), entry.getKey())); } Collections.sort(ret); return new SampledStacktraces(ret, this.totalSamples); } public void save(final Writer writer, final int minInvocations, final int topMethods) throws IOException { final SampledStacktraces topConsumers = this.getTopConsumers(); final 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(final Map<Thread, StackTraceElement[]> traces) { for (final Entry<Thread, StackTraceElement[]> trace : traces.entrySet()) { final int relevant = this.findRelevantElement(trace.getValue()); if (relevant != -1) { final StackTrace st = new StackTrace(trace.getValue(), relevant, trace.getKey().getState()); final Integer i = this.recorded.get(st); this.totalSamples++; if (i == null) { this.recorded.put(st, Integer.valueOf(1)); } else { this.recorded.put(st, Integer.valueOf(i.intValue() + 1)); } } } } private int findRelevantElement(final StackTraceElement[] trace) { if (trace.length == 0) { return -1; } else if (this.included.isEmpty()) { return 0; } int firstIncluded = -1; for (final String myIncluded : this.included) { for (int i = 0; i < trace.length; i++) { final 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 final State state; public StackTrace(final StackTraceElement[] trace, final int startAt, final 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(final Object obj) { if (!(obj instanceof StackTrace)) { return false; } final StackTrace other = (StackTrace) obj; if (other.trace.length != this.trace.length) { return false; } if (!(other.state == this.state)) { return false; } for (int i = 0; i < this.trace.length; i++) { if (!this.trace[i].equals(other.trace[i])) { return false; } } return true; } @Override public int hashCode() { int ret = 13 * this.trace.length + this.state.hashCode(); for (final StackTraceElement ste : this.trace) { ret ^= ste.hashCode(); } return ret; } public StackTraceElement[] getTrace() { return this.trace; } @Override public String toString() { return this.toString(-1); } public String toString(final int traceLength) { final StringBuilder ret = new StringBuilder("State: "); ret.append(this.state.name()); if (traceLength > 1) { ret.append("\n"); } else { ret.append(" "); } int i = 0; for (final StackTraceElement ste : this.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 (!this.running) { this.shouldRun = true; this.rthread = new Thread(this, "CPU Sampling Thread"); this.rthread.start(); this.running = true; } } public void stop() { this.shouldRun = false; this.rthread.interrupt(); try { this.rthread.join(); } catch (final InterruptedException e) { e.printStackTrace(); } } @Override public void run() { while (this.shouldRun) { CPUSampler.this.consumeStackTraces(Thread.getAllStackTraces()); try { Thread.sleep(CPUSampler.this.interval); } catch (final InterruptedException e) { return; } } } } public static class StacktraceWithCount implements Comparable<StacktraceWithCount> { private final int count; private final StackTrace trace; public StacktraceWithCount(final int count, final StackTrace trace) { super(); this.count = count; this.trace = trace; } public int getCount() { return this.count; } public StackTraceElement[] getTrace() { return this.trace.getTrace(); } @Override public int compareTo(final StacktraceWithCount o) { return -Integer.valueOf(this.count).compareTo(Integer.valueOf(o.count)); } @Override public String toString() { return this.count + " Sampled Invocations\n" + this.trace.toString(); } private double getPercentage(final int total) { return Math.round((double) this.count / total * 10000.0) / 100.0; } public String toString(final int totalInvoations, final int traceLength) { return this.count + "/" + totalInvoations + " Sampled Invocations (" + this.getPercentage(totalInvoations) + "%) " + this.trace.toString(traceLength); } } public static class SampledStacktraces { List<StacktraceWithCount> topConsumers; int totalInvocations; public SampledStacktraces(final List<StacktraceWithCount> topConsumers, final int totalInvocations) { super(); this.topConsumers = topConsumers; this.totalInvocations = totalInvocations; } public List<StacktraceWithCount> getTopConsumers() { return this.topConsumers; } public int getTotalInvocations() { return this.totalInvocations; } @Override public String toString() { return this.toString(0); } public String toString(final int minInvocation) { final StringBuilder ret = new StringBuilder(); for (final StacktraceWithCount swc : this.topConsumers) { if (swc.getCount() >= minInvocation) { ret.append(swc.toString(this.totalInvocations, Integer.MAX_VALUE)); ret.append("\n"); } } return ret.toString(); } } }