diff options
Diffstat (limited to 'common/src/main/java/com/sk89q')
4 files changed, 432 insertions, 0 deletions
diff --git a/common/src/main/java/com/sk89q/warmroast/Sampler.java b/common/src/main/java/com/sk89q/warmroast/Sampler.java new file mode 100644 index 0000000..6c4f60c --- /dev/null +++ b/common/src/main/java/com/sk89q/warmroast/Sampler.java @@ -0,0 +1,156 @@ +/* + * WarmRoast + * Copyright (C) 2013 Albert Pham <http://www.sk89q.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package com.sk89q.warmroast; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CompletableFuture; + +/** + * Main sampler class. + */ +public class Sampler extends TimerTask { + + /** + * The thread management interface for the current JVM + */ + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + + /** + * A map of root stack nodes for each thread with sampling data + */ + private final Map<String, StackNode> threadData = new HashMap<>(); + + /** + * A future to encapsulation the completion of this sampler instance + */ + private final CompletableFuture<Sampler> future = new CompletableFuture<>(); + + /** The interval to wait between sampling, in milliseconds */ + private final int interval; + /** The instance used to generate thread information for use in sampling */ + private final ThreadDumper threadDumper; + /** The time when sampling first began */ + private long startTime = -1; + /** The unix timestamp (in millis) when this sampler should automatically complete.*/ + private final long endTime; // -1 for nothing + + public Sampler(int interval, ThreadDumper threadDumper, long endTime) { + this.interval = interval; + this.threadDumper = threadDumper; + this.endTime = endTime; + } + + /** + * Starts the sampler. + * + * @param timer the timer to schedule the sampling on + */ + public synchronized void start(Timer timer) { + timer.scheduleAtFixedRate(this, 0, this.interval); + this.startTime = System.currentTimeMillis(); + } + + /** + * Gets the sampling data recorded by this instance. + * + * @return the data + */ + public Map<String, StackNode> getData() { + return this.threadData; + } + + public long getStartTime() { + if (this.startTime == -1) { + throw new IllegalStateException("Not yet started"); + } + return this.startTime; + } + + public long getEndTime() { + return this.endTime; + } + + public CompletableFuture<Sampler> getFuture() { + return this.future; + } + + private StackNode getRootNode(String threadName) { + return this.threadData.computeIfAbsent(threadName, StackNode::new); + } + + @Override + public synchronized void run() { + try { + if (this.endTime != -1 && this.endTime <= System.currentTimeMillis()) { + this.future.complete(this); + cancel(); + return; + } + + ThreadInfo[] threadDumps = this.threadDumper.dumpThreads(this.threadBean); + for (ThreadInfo threadInfo : threadDumps) { + String threadName = threadInfo.getThreadName(); + StackTraceElement[] stack = threadInfo.getStackTrace(); + + if (threadName == null || stack == null) { + continue; + } + + StackNode node = getRootNode(threadName); + node.log(stack, this.interval); + } + } catch (Throwable t) { + this.future.completeExceptionally(t); + cancel(); + } + } + + public JsonObject formOutput() { + JsonObject out = new JsonObject(); + + JsonArray threads = new JsonArray(); + + List<Map.Entry<String, StackNode>> data = new ArrayList<>(getData().entrySet()); + data.sort(Map.Entry.comparingByKey()); + + for (Map.Entry<String, StackNode> entry : data) { + JsonObject o = new JsonObject(); + o.addProperty("threadName", entry.getKey()); + o.addProperty("totalTime", entry.getValue().getTotalTime()); + o.add("rootNode", entry.getValue().serialize()); + + threads.add(o); + } + out.add("threads", threads); + + return out; + } + +} diff --git a/common/src/main/java/com/sk89q/warmroast/StackNode.java b/common/src/main/java/com/sk89q/warmroast/StackNode.java new file mode 100644 index 0000000..85941a3 --- /dev/null +++ b/common/src/main/java/com/sk89q/warmroast/StackNode.java @@ -0,0 +1,135 @@ +/* + * WarmRoast + * Copyright (C) 2013 Albert Pham <http://www.sk89q.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package com.sk89q.warmroast; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents a node in the overall sampling stack. + * + * <p>The base implementation of this class is only used for the root of node structures. The + * {@link StackTraceNode} class is used for representing method calls in the structure.</p> + */ +public class StackNode implements Comparable<StackNode> { + + /** + * The name of this node + */ + private final String name; + + /** + * A map of this nodes children + */ + private final Map<String, StackNode> children = new HashMap<>(); + + /** + * The accumulated sample time for this node + */ + private long totalTime = 0; + + public StackNode(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public Collection<StackNode> getChildren() { + if (this.children.isEmpty()) { + return Collections.emptyList(); + } + + List<StackNode> list = new ArrayList<>(this.children.values()); + list.sort(null); + return list; + } + + private StackNode resolveChild(String name) { + return this.children.computeIfAbsent(name, StackNode::new); + } + + private StackNode resolveChild(String className, String methodName) { + return this.children.computeIfAbsent(StackTraceNode.formName(className, methodName), name -> new StackTraceNode(className, methodName)); + } + + public long getTotalTime() { + return this.totalTime; + } + + public void accumulateTime(long time) { + this.totalTime += time; + } + + private void log(StackTraceElement[] elements, int skip, long time) { + accumulateTime(time); + + if (elements.length - skip == 0) { + return; + } + + StackTraceElement bottom = elements[elements.length - (skip + 1)]; + resolveChild(bottom.getClassName(), bottom.getMethodName()).log(elements, skip + 1, time); + } + + public void log(StackTraceElement[] elements, long time) { + log(elements, 0, time); + } + + @Override + public int compareTo(StackNode o) { + return getName().compareTo(o.getName()); + } + + public JsonObject serialize() { + JsonObject ret = new JsonObject(); + + // append metadata about this node + ret.addProperty("name", getName()); + appendMetadata(ret); + + // include the total time recorded for this node + ret.addProperty("totalTime", getTotalTime()); + + // append child nodes, if any are present + Collection<StackNode> childNodes = getChildren(); + if (!childNodes.isEmpty()) { + JsonArray children = new JsonArray(); + for (StackNode child : childNodes) { + children.add(child.serialize()); + } + ret.add("children", children); + } + + return ret; + } + + protected void appendMetadata(JsonObject obj) { + + } + +} diff --git a/common/src/main/java/com/sk89q/warmroast/StackTraceNode.java b/common/src/main/java/com/sk89q/warmroast/StackTraceNode.java new file mode 100644 index 0000000..016d700 --- /dev/null +++ b/common/src/main/java/com/sk89q/warmroast/StackTraceNode.java @@ -0,0 +1,69 @@ +/* + * WarmRoast + * Copyright (C) 2013 Albert Pham <http://www.sk89q.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package com.sk89q.warmroast; + +import com.google.gson.JsonObject; + +/** + * Represents a {@link StackNode node} for a method call. + */ +public class StackTraceNode extends StackNode { + + /** + * Forms the {@link StackNode#getName()} for a {@link StackTraceNode}. + * + * @param className the name of the class + * @param methodName the name of the method + * @return the name + */ + static String formName(String className, String methodName) { + return className + "." + methodName + "()"; + } + + /** The name of the class */ + private final String className; + /** The name of the method */ + private final String methodName; + + public StackTraceNode(String className, String methodName) { + super(formName(className, methodName)); + this.className = className; + this.methodName = methodName; + } + + public String getClassName() { + return className; + } + + public String getMethodName() { + return methodName; + } + + @Override + protected void appendMetadata(JsonObject obj) { + obj.addProperty("className", className); + obj.addProperty("methodName", methodName); + } + + @Override + public int compareTo(StackNode that) { + return Long.compare(that.getTotalTime(), this.getTotalTime()); + } + +} diff --git a/common/src/main/java/com/sk89q/warmroast/ThreadDumper.java b/common/src/main/java/com/sk89q/warmroast/ThreadDumper.java new file mode 100644 index 0000000..6c25daf --- /dev/null +++ b/common/src/main/java/com/sk89q/warmroast/ThreadDumper.java @@ -0,0 +1,72 @@ +/* + * WarmRoast + * Copyright (C) 2013 Albert Pham <http://www.sk89q.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package com.sk89q.warmroast; + +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; + +/** + * Uses the {@link ThreadMXBean} to generate {@link ThreadInfo} instances for the threads being + * sampled. + */ +@FunctionalInterface +public interface ThreadDumper { + + /** + * Generates {@link ThreadInfo} data for the sampled threads. + * + * @param threadBean the thread bean instance to obtain the data from + * @return an array of generated thread info instances + */ + ThreadInfo[] dumpThreads(ThreadMXBean threadBean); + + /** + * Implementation of {@link ThreadDumper} that generates data for all threads. + */ + final class All implements ThreadDumper { + @Override + public ThreadInfo[] dumpThreads(ThreadMXBean threadBean) { + return threadBean.dumpAllThreads(false, false); + } + } + + /** + * Implementation of {@link ThreadDumper} that generates data for a specific set of threads. + */ + final class Specific implements ThreadDumper { + private final long[] ids; + + public Specific(long[] ids) { + this.ids = ids; + } + + public Specific(String name) { + this.ids = Thread.getAllStackTraces().keySet().stream() + .filter(t -> t.getName().equalsIgnoreCase(name)) + .mapToLong(Thread::getId) + .toArray(); + } + + @Override + public ThreadInfo[] dumpThreads(ThreadMXBean threadBean) { + return threadBean.getThreadInfo(this.ids, Integer.MAX_VALUE); + } + } + +} |