aboutsummaryrefslogtreecommitdiff
path: root/spark-common/src/main/java
diff options
context:
space:
mode:
authorLuck <git@lucko.me>2022-11-13 21:24:57 +0000
committerLuck <git@lucko.me>2022-11-13 21:24:57 +0000
commitf2d77d875f32f107987c93da1f90529fc6812444 (patch)
tree1ceef4324fc9ece8f9c4389e2f4da07cb844c3d2 /spark-common/src/main/java
parent76f43ab59d3839600bd9e040ff2d09199ebe778a (diff)
downloadspark-f2d77d875f32f107987c93da1f90529fc6812444.tar.gz
spark-f2d77d875f32f107987c93da1f90529fc6812444.tar.bz2
spark-f2d77d875f32f107987c93da1f90529fc6812444.zip
Background profiler
Diffstat (limited to 'spark-common/src/main/java')
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java82
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java11
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/Command.java58
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java22
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java115
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java19
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java7
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java28
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerContainer.java9
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerSettings.java61
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java14
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java20
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/node/ThreadNode.java1
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/sampler/window/WindowStatisticsCollector.java5
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/Configuration.java10
-rw-r--r--spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java20
16 files changed, 362 insertions, 120 deletions
diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java
index a015e42..5461ed4 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java
@@ -45,7 +45,10 @@ import me.lucko.spark.common.monitor.ping.PingStatistics;
import me.lucko.spark.common.monitor.ping.PlayerPingProvider;
import me.lucko.spark.common.monitor.tick.TickStatistics;
import me.lucko.spark.common.platform.PlatformStatisticsProvider;
+import me.lucko.spark.common.sampler.Sampler;
+import me.lucko.spark.common.sampler.SamplerBuilder;
import me.lucko.spark.common.sampler.SamplerContainer;
+import me.lucko.spark.common.sampler.ThreadGrouper;
import me.lucko.spark.common.sampler.source.ClassSourceLookup;
import me.lucko.spark.common.tick.TickHook;
import me.lucko.spark.common.tick.TickReporter;
@@ -64,6 +67,7 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -74,7 +78,6 @@ import java.util.stream.Collectors;
import static net.kyori.adventure.text.Component.space;
import static net.kyori.adventure.text.Component.text;
-import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.GOLD;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
@@ -139,7 +142,7 @@ public class SparkPlatform {
this.activityLog = new ActivityLog(plugin.getPluginDirectory().resolve("activity.json"));
this.activityLog.load();
- this.samplerContainer = new SamplerContainer();
+ this.samplerContainer = new SamplerContainer(this.configuration.getBoolean("backgroundProfiler", true));
this.tickHook = plugin.createTickHook();
this.tickReporter = plugin.createTickReporter();
@@ -179,6 +182,16 @@ public class SparkPlatform {
SparkApi api = new SparkApi(this);
this.plugin.registerApi(api);
SparkApi.register(api);
+
+ if (this.samplerContainer.isBackgroundProfilerEnabled()) {
+ this.plugin.log(Level.INFO, "Starting background profiler...");
+ try {
+ startBackgroundProfiler();
+ this.plugin.log(Level.INFO, "... done!");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
}
public void disable() {
@@ -196,6 +209,8 @@ public class SparkPlatform {
module.close();
}
+ this.samplerContainer.close();
+
SparkApi.unregister();
this.temporaryFiles.deleteTemporaryFiles();
@@ -269,6 +284,17 @@ public class SparkPlatform {
return this.serverNormalOperationStartTime;
}
+ public void startBackgroundProfiler() {
+ Sampler sampler = new SamplerBuilder()
+ .background(true)
+ .threadDumper(this.plugin.getDefaultThreadDumper())
+ .threadGrouper(ThreadGrouper.BY_POOL)
+ .samplingInterval(this.configuration.getInteger("backgroundProfilerInterval", 10))
+ .start(this);
+
+ this.samplerContainer.setActiveSampler(sampler);
+ }
+
public Path resolveSaveFile(String prefix, String extension) {
Path pluginFolder = this.plugin.getPluginDirectory();
try {
@@ -394,7 +420,7 @@ public class SparkPlatform {
if (command.aliases().contains(alias)) {
resp.setCommandPrimaryAlias(command.primaryAlias());
try {
- command.executor().execute(this, sender, resp, new Arguments(rawArgs));
+ command.executor().execute(this, sender, resp, new Arguments(rawArgs, command.allowSubCommand()));
} catch (Arguments.ParseException e) {
resp.replyPrefixed(text(e.getMessage(), RED));
}
@@ -442,32 +468,38 @@ public class SparkPlatform {
);
for (Command command : commands) {
String usage = "/" + getPlugin().getCommandName() + " " + command.primaryAlias();
- ClickEvent clickEvent = ClickEvent.suggestCommand(usage);
- sender.reply(text()
- .append(text(">", GOLD, BOLD))
- .append(space())
- .append(text().content(usage).color(GRAY).clickEvent(clickEvent).build())
- .build()
- );
- for (Command.ArgumentInfo arg : command.arguments()) {
- if (arg.requiresParameter()) {
+
+ if (command.allowSubCommand()) {
+ Map<String, List<Command.ArgumentInfo>> argumentsBySubCommand = command.arguments().stream()
+ .collect(Collectors.groupingBy(Command.ArgumentInfo::subCommandName, LinkedHashMap::new, Collectors.toList()));
+
+ argumentsBySubCommand.forEach((subCommand, arguments) -> {
+ String subCommandUsage = usage + " " + subCommand;
+
sender.reply(text()
- .content(" ")
- .append(text("[", DARK_GRAY))
- .append(text("--" + arg.argumentName(), GRAY))
+ .append(text(">", GOLD, BOLD))
.append(space())
- .append(text("<" + arg.parameterDescription() + ">", DARK_GRAY))
- .append(text("]", DARK_GRAY))
- .build()
- );
- } else {
- sender.reply(text()
- .content(" ")
- .append(text("[", DARK_GRAY))
- .append(text("--" + arg.argumentName(), GRAY))
- .append(text("]", DARK_GRAY))
+ .append(text().content(subCommandUsage).color(GRAY).clickEvent(ClickEvent.suggestCommand(subCommandUsage)).build())
.build()
);
+
+ for (Command.ArgumentInfo arg : arguments) {
+ if (arg.argumentName().isEmpty()) {
+ continue;
+ }
+ sender.reply(arg.toComponent(" "));
+ }
+ });
+ } else {
+ sender.reply(text()
+ .append(text(">", GOLD, BOLD))
+ .append(space())
+ .append(text().content(usage).color(GRAY).clickEvent(ClickEvent.suggestCommand(usage)).build())
+ .build()
+ );
+
+ for (Command.ArgumentInfo arg : command.arguments()) {
+ sender.reply(arg.toComponent(" "));
}
}
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java b/spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java
index 17c49e2..ad8c777 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/Arguments.java
@@ -38,8 +38,9 @@ public class Arguments {
private final List<String> rawArgs;
private final SetMultimap<String, String> parsedArgs;
+ private String parsedSubCommand = null;
- public Arguments(List<String> rawArgs) {
+ public Arguments(List<String> rawArgs, boolean allowSubCommand) {
this.rawArgs = rawArgs;
this.parsedArgs = HashMultimap.create();
@@ -52,7 +53,9 @@ public class Arguments {
Matcher matcher = FLAG_REGEX.matcher(arg);
boolean matches = matcher.matches();
- if (flag == null || matches) {
+ if (i == 0 && allowSubCommand && !matches) {
+ this.parsedSubCommand = arg;
+ } else if (flag == null || matches) {
if (!matches) {
throw new ParseException("Expected flag at position " + i + " but got '" + arg + "' instead!");
}
@@ -80,6 +83,10 @@ public class Arguments {
return this.rawArgs;
}
+ public String subCommand() {
+ return this.parsedSubCommand;
+ }
+
public int intFlag(String key) {
Iterator<String> it = this.parsedArgs.get(key).iterator();
if (it.hasNext()) {
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/Command.java b/spark-common/src/main/java/me/lucko/spark/common/command/Command.java
index dad15e6..c6871a9 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/Command.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/Command.java
@@ -25,10 +25,17 @@ import com.google.common.collect.ImmutableList;
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.command.sender.CommandSender;
+import net.kyori.adventure.text.Component;
+
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import static net.kyori.adventure.text.Component.space;
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.DARK_GRAY;
+import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
+
public class Command {
public static Builder builder() {
@@ -39,12 +46,14 @@ public class Command {
private final List<ArgumentInfo> arguments;
private final Executor executor;
private final TabCompleter tabCompleter;
+ private final boolean allowSubCommand;
- private Command(List<String> aliases, List<ArgumentInfo> arguments, Executor executor, TabCompleter tabCompleter) {
+ private Command(List<String> aliases, List<ArgumentInfo> arguments, Executor executor, TabCompleter tabCompleter, boolean allowSubCommand) {
this.aliases = aliases;
this.arguments = arguments;
this.executor = executor;
this.tabCompleter = tabCompleter;
+ this.allowSubCommand = allowSubCommand;
}
public List<String> aliases() {
@@ -67,11 +76,16 @@ public class Command {
return this.aliases.get(0);
}
+ public boolean allowSubCommand() {
+ return this.allowSubCommand;
+ }
+
public static final class Builder {
private final ImmutableList.Builder<String> aliases = ImmutableList.builder();
private final ImmutableList.Builder<ArgumentInfo> arguments = ImmutableList.builder();
private Executor executor = null;
private TabCompleter tabCompleter = null;
+ private boolean allowSubCommand = false;
Builder() {
@@ -82,8 +96,13 @@ public class Command {
return this;
}
+ public Builder argumentUsage(String subCommandName, String argumentName, String parameterDescription) {
+ this.arguments.add(new ArgumentInfo(subCommandName, argumentName, parameterDescription));
+ return this;
+ }
+
public Builder argumentUsage(String argumentName, String parameterDescription) {
- this.arguments.add(new ArgumentInfo(argumentName, parameterDescription));
+ this.arguments.add(new ArgumentInfo("", argumentName, parameterDescription));
return this;
}
@@ -97,6 +116,11 @@ public class Command {
return this;
}
+ public Builder allowSubCommand(boolean allowSubCommand) {
+ this.allowSubCommand = allowSubCommand;
+ return this;
+ }
+
public Command build() {
List<String> aliases = this.aliases.build();
if (aliases.isEmpty()) {
@@ -108,7 +132,7 @@ public class Command {
if (this.tabCompleter == null) {
this.tabCompleter = TabCompleter.empty();
}
- return new Command(aliases, this.arguments.build(), this.executor, this.tabCompleter);
+ return new Command(aliases, this.arguments.build(), this.executor, this.tabCompleter, this.allowSubCommand);
}
}
@@ -127,14 +151,20 @@ public class Command {
}
public static final class ArgumentInfo {
+ private final String subCommandName;
private final String argumentName;
private final String parameterDescription;
- public ArgumentInfo(String argumentName, String parameterDescription) {
+ public ArgumentInfo(String subCommandName, String argumentName, String parameterDescription) {
+ this.subCommandName = subCommandName;
this.argumentName = argumentName;
this.parameterDescription = parameterDescription;
}
+ public String subCommandName() {
+ return this.subCommandName;
+ }
+
public String argumentName() {
return this.argumentName;
}
@@ -146,6 +176,26 @@ public class Command {
public boolean requiresParameter() {
return this.parameterDescription != null;
}
+
+ public Component toComponent(String padding) {
+ if (requiresParameter()) {
+ return text()
+ .content(padding)
+ .append(text("[", DARK_GRAY))
+ .append(text("--" + argumentName(), GRAY))
+ .append(space())
+ .append(text("<" + parameterDescription() + ">", DARK_GRAY))
+ .append(text("]", DARK_GRAY))
+ .build();
+ } else {
+ return text()
+ .content(padding)
+ .append(text("[", DARK_GRAY))
+ .append(text("--" + argumentName(), GRAY))
+ .append(text("]", DARK_GRAY))
+ .build();
+ }
+ }
}
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java
index 2ce83fd..a2da0a0 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/GcMonitoringModule.java
@@ -123,7 +123,7 @@ public class GcMonitoringModule implements CommandModule {
);
report.add(text()
.content(" ")
- .append(text(formatTime((long) averageFrequency), WHITE))
+ .append(text(FormatUtil.formatSeconds((long) averageFrequency / 1000), WHITE))
.append(text(" avg frequency", GRAY))
.build()
);
@@ -153,26 +153,6 @@ public class GcMonitoringModule implements CommandModule {
);
}
- private static String formatTime(long millis) {
- if (millis <= 0) {
- return "0s";
- }
-
- long second = millis / 1000;
- long minute = second / 60;
- second = second % 60;
-
- StringBuilder sb = new StringBuilder();
- if (minute != 0) {
- sb.append(minute).append("m ");
- }
- if (second != 0) {
- sb.append(second).append("s ");
- }
-
- return sb.toString().trim();
- }
-
private static class ReportingGcMonitor extends GarbageCollectionMonitor implements GarbageCollectionMonitor.Listener {
private final SparkPlatform platform;
private final CommandResponseHandler resp;
diff --git a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java
index 00bf1a9..6a76748 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/command/modules/SamplerModule.java
@@ -39,6 +39,7 @@ import me.lucko.spark.common.sampler.async.AsyncSampler;
import me.lucko.spark.common.sampler.node.MergeMode;
import me.lucko.spark.common.sampler.source.ClassSourceLookup;
import me.lucko.spark.common.tick.TickHook;
+import me.lucko.spark.common.util.FormatUtil;
import me.lucko.spark.common.util.MethodDisambiguator;
import me.lucko.spark.proto.SparkSamplerProtos;
@@ -72,31 +73,36 @@ public class SamplerModule implements CommandModule {
public void registerCommands(Consumer<Command> consumer) {
consumer.accept(Command.builder()
.aliases("profiler", "sampler")
- .argumentUsage("info", null)
- .argumentUsage("stop", null)
- .argumentUsage("timeout", "timeout seconds")
- .argumentUsage("thread *", null)
- .argumentUsage("thread", "thread name")
- .argumentUsage("only-ticks-over", "tick length millis")
- .argumentUsage("interval", "interval millis")
+ .allowSubCommand(true)
+ .argumentUsage("info", "", null)
+ .argumentUsage("start", "timeout", "timeout seconds")
+ .argumentUsage("start", "thread *", null)
+ .argumentUsage("start", "thread", "thread name")
+ .argumentUsage("start", "only-ticks-over", "tick length millis")
+ .argumentUsage("start", "interval", "interval millis")
+ .argumentUsage("stop", "", null)
+ .argumentUsage("cancel", "", null)
.executor(this::profiler)
.tabCompleter((platform, sender, arguments) -> {
- if (arguments.contains("--info") || arguments.contains("--cancel")) {
- return Collections.emptyList();
+ List<String> opts = Collections.emptyList();
+
+ if (arguments.size() > 0) {
+ String subCommand = arguments.get(0);
+ if (subCommand.equals("stop") || subCommand.equals("upload")) {
+ opts = new ArrayList<>(Arrays.asList("--comment", "--save-to-file"));
+ opts.removeAll(arguments);
+ }
+ if (subCommand.equals("start")) {
+ opts = new ArrayList<>(Arrays.asList("--timeout", "--regex", "--combine-all",
+ "--not-combined", "--interval", "--only-ticks-over", "--force-java-sampler"));
+ opts.removeAll(arguments);
+ opts.add("--thread"); // allowed multiple times
+ }
}
- if (arguments.contains("--stop") || arguments.contains("--upload")) {
- return TabCompleter.completeForOpts(arguments, "--comment", "--save-to-file");
- }
-
- List<String> opts = new ArrayList<>(Arrays.asList("--info", "--stop", "--cancel",
- "--timeout", "--regex", "--combine-all", "--not-combined", "--interval",
- "--only-ticks-over", "--force-java-sampler"));
- opts.removeAll(arguments);
- opts.add("--thread"); // allowed multiple times
-
return TabCompleter.create()
- .from(0, CompletionSupplier.startsWith(opts))
+ .at(0, CompletionSupplier.startsWith(Arrays.asList("info", "start", "stop", "cancel")))
+ .from(1, CompletionSupplier.startsWith(opts))
.complete(arguments);
})
.build()
@@ -104,28 +110,48 @@ public class SamplerModule implements CommandModule {
}
private void profiler(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
- if (arguments.boolFlag("info")) {
+ String subCommand = arguments.subCommand() == null ? "" : arguments.subCommand();
+
+ if (subCommand.equals("info") || arguments.boolFlag("info")) {
profilerInfo(platform, resp);
return;
}
- if (arguments.boolFlag("cancel")) {
+ if (subCommand.equals("cancel") || arguments.boolFlag("cancel")) {
profilerCancel(platform, resp);
return;
}
- if (arguments.boolFlag("stop") || arguments.boolFlag("upload")) {
+ if (subCommand.equals("stop") || arguments.boolFlag("stop") || arguments.boolFlag("upload")) {
profilerStop(platform, sender, resp, arguments);
return;
}
- profilerStart(platform, sender, resp, arguments);
+ if (subCommand.equals("start") || arguments.boolFlag("start")) {
+ profilerStart(platform, sender, resp, arguments);
+ return;
+ }
+
+ if (arguments.raw().isEmpty()) {
+ profilerInfo(platform, resp);
+ } else {
+ profilerStart(platform, sender, resp, arguments);
+ }
}
private void profilerStart(SparkPlatform platform, CommandSender sender, CommandResponseHandler resp, Arguments arguments) {
- if (platform.getSamplerContainer().getActiveSampler() != null) {
- profilerInfo(platform, resp);
- return;
+ Sampler previousSampler = platform.getSamplerContainer().getActiveSampler();
+ if (previousSampler != null) {
+ if (previousSampler.isRunningInBackground()) {
+ // there is a background profiler running - stop that first
+ resp.replyPrefixed(text("Stopping the background profiler before starting... please wait"));
+ previousSampler.stop();
+ platform.getSamplerContainer().unsetActiveSampler(previousSampler);
+ } else {
+ // there is a non-background profiler running - tell the user
+ profilerInfo(platform, resp);
+ return;
+ }
}
int timeoutSeconds = arguments.intFlag("timeout");
@@ -212,9 +238,9 @@ public class SamplerModule implements CommandModule {
if (timeoutSeconds == -1) {
resp.broadcastPrefixed(text("It will run in the background until it is stopped by an admin."));
resp.broadcastPrefixed(text("To stop the profiler and upload the results, run:"));
- resp.broadcastPrefixed(cmdPrompt("/" + platform.getPlugin().getCommandName() + " profiler --stop"));
+ resp.broadcastPrefixed(cmdPrompt("/" + platform.getPlugin().getCommandName() + " profiler stop"));
} else {
- resp.broadcastPrefixed(text("The results will be automatically returned after the profiler has been running for " + timeoutSeconds + " seconds."));
+ resp.broadcastPrefixed(text("The results will be automatically returned after the profiler has been running for " + FormatUtil.formatSeconds(timeoutSeconds) + "."));
}
CompletableFuture<Sampler> future = sampler.getFuture();
@@ -248,24 +274,34 @@ public class SamplerModule implements CommandModule {
if (sampler == null) {
resp.replyPrefixed(text("The profiler isn't running!"));
resp.replyPrefixed(text("To start a new one, run:"));
- resp.replyPrefixed(cmdPrompt("/" + platform.getPlugin().getCommandName() + " profiler"));
+ resp.replyPrefixed(cmdPrompt("/" + platform.getPlugin().getCommandName() + " profiler start"));
} else {
resp.replyPrefixed(text("Profiler is already running!", GOLD));
long runningTime = (System.currentTimeMillis() - sampler.getStartTime()) / 1000L;
- resp.replyPrefixed(text("So far, it has profiled for " + runningTime + " seconds."));
+
+ if (sampler.isRunningInBackground()) {
+ resp.replyPrefixed(text()
+ .append(text("It was started "))
+ .append(text("automatically", WHITE))
+ .append(text(" when spark enabled and has been running in the background for " + FormatUtil.formatSeconds(runningTime) + "."))
+ .build()
+ );
+ } else {
+ resp.replyPrefixed(text("So far, it has profiled for " + FormatUtil.formatSeconds(runningTime) + "."));
+ }
long timeout = sampler.getAutoEndTime();
if (timeout == -1) {
resp.replyPrefixed(text("To stop the profiler and upload the results, run:"));
- resp.replyPrefixed(cmdPrompt("/" + platform.getPlugin().getCommandName() + " profiler --stop"));
+ resp.replyPrefixed(cmdPrompt("/" + platform.getPlugin().getCommandName() + " profiler stop"));
} else {
long timeoutDiff = (timeout - System.currentTimeMillis()) / 1000L;
- resp.replyPrefixed(text("It is due to complete automatically and upload results in " + timeoutDiff + " seconds."));
+ resp.replyPrefixed(text("It is due to complete automatically and upload results in " + FormatUtil.formatSeconds(timeoutDiff) + "."));
}
resp.replyPrefixed(text("To cancel the profiler without uploading the results, run:"));
- resp.replyPrefixed(cmdPrompt("/" + platform.getPlugin().getCommandName() + " profiler --cancel"));
+ resp.replyPrefixed(cmdPrompt("/" + platform.getPlugin().getCommandName() + " profiler cancel"));
}
}
@@ -299,6 +335,17 @@ public class SamplerModule implements CommandModule {
MethodDisambiguator methodDisambiguator = new MethodDisambiguator();
MergeMode mergeMode = arguments.boolFlag("separate-parent-calls") ? MergeMode.separateParentCalls(methodDisambiguator) : MergeMode.sameMethod(methodDisambiguator);
handleUpload(platform, resp, sampler, comment, mergeMode, saveToFile);
+
+ // if the previous sampler was running in the background, create a new one
+ if (platform.getSamplerContainer().isBackgroundProfilerEnabled()) {
+ platform.startBackgroundProfiler();
+
+ resp.broadcastPrefixed(text()
+ .append(text("Restarted the background profiler. "))
+ .append(text("(If you don't want this to happen, run: /" + platform.getPlugin().getCommandName() + " profiler cancel)", DARK_GRAY))
+ .build()
+ );
+ }
}
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java
index c650738..feefd66 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/AbstractSampler.java
@@ -32,8 +32,6 @@ import me.lucko.spark.common.sampler.source.ClassSourceLookup;
import me.lucko.spark.common.sampler.source.SourceMetadata;
import me.lucko.spark.common.sampler.window.ProtoTimeEncoder;
import me.lucko.spark.common.sampler.window.WindowStatisticsCollector;
-import me.lucko.spark.common.tick.TickHook;
-import me.lucko.spark.proto.SparkProtos;
import me.lucko.spark.proto.SparkSamplerProtos.SamplerData;
import me.lucko.spark.proto.SparkSamplerProtos.SamplerMetadata;
@@ -64,6 +62,9 @@ public abstract class AbstractSampler implements Sampler {
/** The unix timestamp (in millis) when this sampler should automatically complete. */
protected final long autoEndTime; // -1 for nothing
+ /** If the sampler is running in the background */
+ protected boolean background;
+
/** Collects statistics for each window in the sample */
protected final WindowStatisticsCollector windowStatisticsCollector;
@@ -73,11 +74,12 @@ public abstract class AbstractSampler implements Sampler {
/** The garbage collector statistics when profiling started */
protected Map<String, GarbageCollectorStatistics> initialGcStats;
- protected AbstractSampler(SparkPlatform platform, int interval, ThreadDumper threadDumper, long autoEndTime) {
+ protected AbstractSampler(SparkPlatform platform, SamplerSettings settings) {
this.platform = platform;
- this.interval = interval;
- this.threadDumper = threadDumper;
- this.autoEndTime = autoEndTime;
+ this.interval = settings.interval();
+ this.threadDumper = settings.threadDumper();
+ this.autoEndTime = settings.autoEndTime();
+ this.background = settings.runningInBackground();
this.windowStatisticsCollector = new WindowStatisticsCollector(platform);
}
@@ -95,6 +97,11 @@ public abstract class AbstractSampler implements Sampler {
}
@Override
+ public boolean isRunningInBackground() {
+ return this.background;
+ }
+
+ @Override
public CompletableFuture<Sampler> getFuture() {
return this.future;
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java
index e06cba6..5d2026d 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/Sampler.java
@@ -58,6 +58,13 @@ public interface Sampler {
long getAutoEndTime();
/**
+ * If this sampler is running in the background. (wasn't started by a specific user)
+ *
+ * @return true if the sampler is running in the background
+ */
+ boolean isRunningInBackground();
+
+ /**
* Gets a future to encapsulate the completion of the sampler
*
* @return a future
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java
index 382950a..ec635ef 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerBuilder.java
@@ -38,7 +38,8 @@ public class SamplerBuilder {
private boolean ignoreSleeping = false;
private boolean ignoreNative = false;
private boolean useAsyncProfiler = true;
- private long timeout = -1;
+ private long autoEndTime = -1;
+ private boolean background = false;
private ThreadDumper threadDumper = ThreadDumper.ALL;
private ThreadGrouper threadGrouper = ThreadGrouper.BY_NAME;
@@ -57,7 +58,12 @@ public class SamplerBuilder {
if (timeout <= 0) {
throw new IllegalArgumentException("timeout > 0");
}
- this.timeout = System.currentTimeMillis() + unit.toMillis(timeout);
+ this.autoEndTime = System.currentTimeMillis() + unit.toMillis(timeout);
+ return this;
+ }
+
+ public SamplerBuilder background(boolean background) {
+ this.background = background;
return this;
}
@@ -95,26 +101,22 @@ public class SamplerBuilder {
public Sampler start(SparkPlatform platform) {
boolean onlyTicksOverMode = this.ticksOver != -1 && this.tickHook != null;
boolean canUseAsyncProfiler = this.useAsyncProfiler &&
+ !onlyTicksOverMode &&
!(this.ignoreSleeping || this.ignoreNative) &&
!(this.threadDumper instanceof ThreadDumper.Regex) &&
AsyncProfilerAccess.getInstance(platform).checkSupported(platform);
int intervalMicros = (int) (this.samplingInterval * 1000d);
+ SamplerSettings settings = new SamplerSettings(intervalMicros, this.threadDumper, this.threadGrouper, this.autoEndTime, this.background);
Sampler sampler;
- if (onlyTicksOverMode) {
- sampler = new JavaSampler(platform, intervalMicros, this.threadDumper,
- this.threadGrouper, this.timeout, this.ignoreSleeping, this.ignoreNative,
- this.tickHook, this.ticksOver);
-
- } else if (canUseAsyncProfiler) {
- sampler = new AsyncSampler(platform, intervalMicros, this.threadDumper,
- this.threadGrouper, this.timeout);
-
+ if (canUseAsyncProfiler) {
+ sampler = new AsyncSampler(platform, settings);
+ } else if (onlyTicksOverMode) {
+ sampler = new JavaSampler(platform, settings, this.ignoreSleeping, this.ignoreNative, this.tickHook, this.ticksOver);
} else {
- sampler = new JavaSampler(platform, intervalMicros, this.threadDumper,
- this.threadGrouper, this.timeout, this.ignoreSleeping, this.ignoreNative);
+ sampler = new JavaSampler(platform, settings, this.ignoreSleeping, this.ignoreNative);
}
sampler.start();
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerContainer.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerContainer.java
index 55913d8..f56dee5 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerContainer.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerContainer.java
@@ -28,6 +28,11 @@ import java.util.concurrent.atomic.AtomicReference;
public class SamplerContainer implements AutoCloseable {
private final AtomicReference<Sampler> activeSampler = new AtomicReference<>();
+ private final boolean backgroundProfilerEnabled;
+
+ public SamplerContainer(boolean backgroundProfilerEnabled) {
+ this.backgroundProfilerEnabled = backgroundProfilerEnabled;
+ }
/**
* Gets the active sampler, or null if a sampler is not active.
@@ -68,6 +73,10 @@ public class SamplerContainer implements AutoCloseable {
}
}
+ public boolean isBackgroundProfilerEnabled() {
+ return this.backgroundProfilerEnabled;
+ }
+
@Override
public void close() {
stopActiveSampler();
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerSettings.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerSettings.java
new file mode 100644
index 0000000..6e55a43
--- /dev/null
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/SamplerSettings.java
@@ -0,0 +1,61 @@
+/*
+ * This file is part of spark.
+ *
+ * Copyright (c) lucko (Luck) <luck@lucko.me>
+ * Copyright (c) contributors
+ *
+ * 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 me.lucko.spark.common.sampler;
+
+/**
+ * Base settings for all samplers
+ */
+public class SamplerSettings {
+
+ private final int interval;
+ private final ThreadDumper threadDumper;
+ private final ThreadGrouper threadGrouper;
+ private final long autoEndTime;
+ private final boolean runningInBackground;
+
+ public SamplerSettings(int interval, ThreadDumper threadDumper, ThreadGrouper threadGrouper, long autoEndTime, boolean runningInBackground) {
+ this.interval = interval;
+ this.threadDumper = threadDumper;
+ this.threadGrouper = threadGrouper;
+ this.autoEndTime = autoEndTime;
+ this.runningInBackground = runningInBackground;
+ }
+
+ public int interval() {
+ return this.interval;
+ }
+
+ public ThreadDumper threadDumper() {
+ return this.threadDumper;
+ }
+
+ public ThreadGrouper threadGrouper() {
+ return this.threadGrouper;
+ }
+
+ public long autoEndTime() {
+ return this.autoEndTime;
+ }
+
+ public boolean runningInBackground() {
+ return this.runningInBackground;
+ }
+}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java
index cbc81c7..d6cfd4f 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncSampler.java
@@ -25,8 +25,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.common.sampler.AbstractSampler;
-import me.lucko.spark.common.sampler.ThreadDumper;
-import me.lucko.spark.common.sampler.ThreadGrouper;
+import me.lucko.spark.common.sampler.SamplerSettings;
import me.lucko.spark.common.sampler.node.MergeMode;
import me.lucko.spark.common.sampler.source.ClassSourceLookup;
import me.lucko.spark.common.sampler.window.ProfilingWindowUtils;
@@ -36,6 +35,7 @@ import me.lucko.spark.proto.SparkSamplerProtos.SamplerData;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.function.IntPredicate;
/**
* A sampler implementation using async-profiler.
@@ -55,10 +55,10 @@ public class AsyncSampler extends AbstractSampler {
/** The executor used for scheduling and management */
private ScheduledExecutorService scheduler;
- public AsyncSampler(SparkPlatform platform, int interval, ThreadDumper threadDumper, ThreadGrouper threadGrouper, long endTime) {
- super(platform, interval, threadDumper, endTime);
+ public AsyncSampler(SparkPlatform platform, SamplerSettings settings) {
+ super(platform, settings);
this.profilerAccess = AsyncProfilerAccess.getInstance(platform);
- this.dataAggregator = new AsyncDataAggregator(threadGrouper);
+ this.dataAggregator = new AsyncDataAggregator(settings.threadGrouper());
this.scheduler = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder().setNameFormat("spark-asyncsampler-worker-thread").build()
);
@@ -124,7 +124,9 @@ public class AsyncSampler extends AbstractSampler {
previousJob.aggregate(this.dataAggregator);
// prune data older than the history size
- this.dataAggregator.pruneData(ProfilingWindowUtils.keepHistoryBefore(window));
+ IntPredicate predicate = ProfilingWindowUtils.keepHistoryBefore(window);
+ this.dataAggregator.pruneData(predicate);
+ this.windowStatisticsCollector.pruneStatistics(predicate);
}
} catch (Throwable e) {
e.printStackTrace();
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java
index 6aad5e3..95c3508 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/java/JavaSampler.java
@@ -25,8 +25,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.common.sampler.AbstractSampler;
-import me.lucko.spark.common.sampler.ThreadDumper;
-import me.lucko.spark.common.sampler.ThreadGrouper;
+import me.lucko.spark.common.sampler.SamplerSettings;
import me.lucko.spark.common.sampler.node.MergeMode;
import me.lucko.spark.common.sampler.source.ClassSourceLookup;
import me.lucko.spark.common.sampler.window.ProfilingWindowUtils;
@@ -42,6 +41,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntPredicate;
/**
* A sampler implementation using Java (WarmRoast).
@@ -66,14 +66,14 @@ public class JavaSampler extends AbstractSampler implements Runnable {
/** The last window that was profiled */
private final AtomicInteger lastWindow = new AtomicInteger();
- public JavaSampler(SparkPlatform platform, int interval, ThreadDumper threadDumper, ThreadGrouper threadGrouper, long endTime, boolean ignoreSleeping, boolean ignoreNative) {
- super(platform, interval, threadDumper, endTime);
- this.dataAggregator = new SimpleDataAggregator(this.workerPool, threadGrouper, interval, ignoreSleeping, ignoreNative);
+ public JavaSampler(SparkPlatform platform, SamplerSettings settings, boolean ignoreSleeping, boolean ignoreNative) {
+ super(platform, settings);
+ this.dataAggregator = new SimpleDataAggregator(this.workerPool, settings.threadGrouper(), settings.interval(), ignoreSleeping, ignoreNative);
}
- public JavaSampler(SparkPlatform platform, int interval, ThreadDumper threadDumper, ThreadGrouper threadGrouper, long endTime, boolean ignoreSleeping, boolean ignoreNative, TickHook tickHook, int tickLengthThreshold) {
- super(platform, interval, threadDumper, endTime);
- this.dataAggregator = new TickedDataAggregator(this.workerPool, threadGrouper, interval, ignoreSleeping, ignoreNative, tickHook, tickLengthThreshold);
+ public JavaSampler(SparkPlatform platform, SamplerSettings settings, boolean ignoreSleeping, boolean ignoreNative, TickHook tickHook, int tickLengthThreshold) {
+ super(platform, settings);
+ this.dataAggregator = new TickedDataAggregator(this.workerPool, settings.threadGrouper(), settings.interval(), ignoreSleeping, ignoreNative, tickHook, tickLengthThreshold);
}
@Override
@@ -151,7 +151,9 @@ public class JavaSampler extends AbstractSampler implements Runnable {
JavaSampler.this.windowStatisticsCollector.measureNow(previousWindow);
// prune data older than the history size
- JavaSampler.this.dataAggregator.pruneData(ProfilingWindowUtils.keepHistoryBefore(this.window));
+ IntPredicate predicate = ProfilingWindowUtils.keepHistoryBefore(this.window);
+ JavaSampler.this.dataAggregator.pruneData(predicate);
+ JavaSampler.this.windowStatisticsCollector.pruneStatistics(predicate);
}
}
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/node/ThreadNode.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/node/ThreadNode.java
index 5035046..37ff359 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/sampler/node/ThreadNode.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/node/ThreadNode.java
@@ -130,6 +130,7 @@ public final class ThreadNode extends AbstractNode {
}
}
+ removeTimeWindows(predicate);
return getTimeWindows().isEmpty();
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/window/WindowStatisticsCollector.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/window/WindowStatisticsCollector.java
index 47f739d..7da62fa 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/sampler/window/WindowStatisticsCollector.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/window/WindowStatisticsCollector.java
@@ -30,6 +30,7 @@ import me.lucko.spark.proto.SparkProtos;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntPredicate;
/**
* Collects statistics for each profiling window.
@@ -116,6 +117,10 @@ public class WindowStatisticsCollector {
}
}
+ public void pruneStatistics(IntPredicate predicate) {
+ this.stats.keySet().removeIf(predicate::test);
+ }
+
public Map<Integer, SparkProtos.WindowStatistics> export() {
return this.stats;
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/Configuration.java b/spark-common/src/main/java/me/lucko/spark/common/util/Configuration.java
index 7588645..ce63878 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/util/Configuration.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/Configuration.java
@@ -67,4 +67,14 @@ public final class Configuration {
return val.isBoolean() ? val.getAsBoolean() : def;
}
+ public int getInteger(String path, int def) {
+ JsonElement el = this.root.get(path);
+ if (el == null || !el.isJsonPrimitive()) {
+ return def;
+ }
+
+ JsonPrimitive val = el.getAsJsonPrimitive();
+ return val.isBoolean() ? val.getAsInt() : def;
+ }
+
}
diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java
index c4a3d66..1ee3b0f 100644
--- a/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java
+++ b/spark-common/src/main/java/me/lucko/spark/common/util/FormatUtil.java
@@ -62,4 +62,24 @@ public enum FormatUtil {
.append(Component.text(unit))
.build();
}
+
+ public static String formatSeconds(long seconds) {
+ if (seconds <= 0) {
+ return "0s";
+ }
+
+ long second = seconds;
+ long minute = second / 60;
+ second = second % 60;
+
+ StringBuilder sb = new StringBuilder();
+ if (minute != 0) {
+ sb.append(minute).append("m ");
+ }
+ if (second != 0) {
+ sb.append(second).append("s ");
+ }
+
+ return sb.toString().trim();
+ }
}