From 1ebcc65460e85afd10b7e276892f2eb2f236701f Mon Sep 17 00:00:00 2001 From: Luck Date: Sun, 7 Aug 2022 21:25:39 +0100 Subject: Update forge/fabric to 1.19.2 --- spark-fabric/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'spark-fabric') diff --git a/spark-fabric/build.gradle b/spark-fabric/build.gradle index 35b7a86..30b1ff6 100644 --- a/spark-fabric/build.gradle +++ b/spark-fabric/build.gradle @@ -28,9 +28,9 @@ configurations { dependencies { // https://modmuss50.me/fabric.html - minecraft 'com.mojang:minecraft:1.19' - mappings 'net.fabricmc:yarn:1.19+build.1:v2' - modImplementation 'net.fabricmc:fabric-loader:0.14.7' + minecraft 'com.mojang:minecraft:1.19.2' + mappings 'net.fabricmc:yarn:1.19.2+build.1:v2' + modImplementation 'net.fabricmc:fabric-loader:0.14.9' Set apiModules = [ "fabric-api-base", @@ -40,7 +40,7 @@ dependencies { // Add each module as a dependency apiModules.forEach { - modImplementation(fabricApi.module(it, '0.55.3+1.19')) + modImplementation(fabricApi.module(it, '0.59.0+1.19.2')) } include(modImplementation('me.lucko:fabric-permissions-api:0.1-SNAPSHOT')) -- cgit From 7ef9b6281135ce0a24f3c14c2255d9a2c2eca969 Mon Sep 17 00:00:00 2001 From: ishland Date: Mon, 19 Sep 2022 21:48:28 +0800 Subject: Display source info for mixin injected methods (#249) Co-authored-by: Luck --- .../spark/common/sampler/AbstractSampler.java | 12 +- .../lucko/spark/common/util/ClassSourceLookup.java | 257 +++++++++++++++++++-- .../src/main/proto/spark/spark_sampler.proto | 2 + spark-fabric/build.gradle | 10 +- .../spark/fabric/FabricClassSourceLookup.java | 159 ++++++++++++- .../fabric/plugin/FabricSparkMixinPlugin.java | 71 ++++++ .../me/lucko/spark/fabric/smap/MixinUtils.java | 52 +++++ .../lucko/spark/fabric/smap/SourceDebugCache.java | 87 +++++++ .../java/me/lucko/spark/fabric/smap/SourceMap.java | 133 +++++++++++ .../lucko/spark/fabric/smap/SourceMapProvider.java | 53 +++++ spark-fabric/src/main/resources/spark.mixins.json | 3 +- 11 files changed, 806 insertions(+), 33 deletions(-) create mode 100644 spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java create mode 100644 spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java create mode 100644 spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java create mode 100644 spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java create mode 100644 spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java (limited to 'spark-fabric') 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 1c217db..3cfef0b 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 @@ -164,8 +164,16 @@ public abstract class AbstractSampler implements Sampler { classSourceVisitor.visit(entry); } - if (classSourceVisitor.hasMappings()) { - proto.putAllClassSources(classSourceVisitor.getMapping()); + if (classSourceVisitor.hasClassSourceMappings()) { + proto.putAllClassSources(classSourceVisitor.getClassSourceMapping()); + } + + if (classSourceVisitor.hasMethodSourceMappings()) { + proto.putAllMethodSources(classSourceVisitor.getMethodSourceMapping()); + } + + if (classSourceVisitor.hasLineSourceMappings()) { + proto.putAllLineSources(classSourceVisitor.getLineSourceMapping()); } } } diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java b/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java index bd9ec37..668f31a 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java +++ b/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java @@ -38,9 +38,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; /** - * A function which defines the source of given {@link Class}es. + * A function which defines the source of given {@link Class}es or (Mixin) method calls. */ public interface ClassSourceLookup { @@ -52,6 +54,26 @@ public interface ClassSourceLookup { */ @Nullable String identify(Class clazz) throws Exception; + /** + * Identify the given method call. + * + * @param methodCall the method call info + * @return the source of the method call + */ + default @Nullable String identify(MethodCall methodCall) throws Exception { + return null; + } + + /** + * Identify the given method call. + * + * @param methodCall the method call info + * @return the source of the method call + */ + default @Nullable String identify(MethodCallByLine methodCall) throws Exception { + return null; + } + /** * A no-operation {@link ClassSourceLookup}. */ @@ -156,9 +178,17 @@ public interface ClassSourceLookup { interface Visitor { void visit(ThreadNode node); - boolean hasMappings(); + boolean hasClassSourceMappings(); + + Map getClassSourceMapping(); + + boolean hasMethodSourceMappings(); + + Map getMethodSourceMapping(); + + boolean hasLineSourceMappings(); - Map getMapping(); + Map getLineSourceMapping(); } static Visitor createVisitor(ClassSourceLookup lookup) { @@ -177,25 +207,46 @@ public interface ClassSourceLookup { } @Override - public boolean hasMappings() { + public boolean hasClassSourceMappings() { + return false; + } + + @Override + public Map getClassSourceMapping() { + return Collections.emptyMap(); + } + + @Override + public boolean hasMethodSourceMappings() { + return false; + } + + @Override + public Map getMethodSourceMapping() { + return Collections.emptyMap(); + } + + @Override + public boolean hasLineSourceMappings() { return false; } @Override - public Map getMapping() { + public Map getLineSourceMapping() { return Collections.emptyMap(); } } /** - * Visitor which scans {@link StackTraceNode}s and accumulates class identities. + * Visitor which scans {@link StackTraceNode}s and accumulates class/method call identities. */ class VisitorImpl implements Visitor { private final ClassSourceLookup lookup; private final ClassFinder classFinder = new ClassFinder(); - // class name --> identifier (plugin name) - private final Map map = new HashMap<>(); + private final SourcesMap classSources = new SourcesMap<>(Function.identity()); + private final SourcesMap methodSources = new SourcesMap<>(MethodCall::toString); + private final SourcesMap lineSources = new SourcesMap<>(MethodCallByLine::toString); VisitorImpl(ClassSourceLookup lookup) { this.lookup = lookup; @@ -208,34 +259,194 @@ public interface ClassSourceLookup { } } + private void visitStackNode(StackTraceNode node) { + this.classSources.computeIfAbsent( + node.getClassName(), + className -> { + Class clazz = this.classFinder.findClass(className); + if (clazz == null) { + return null; + } + return this.lookup.identify(clazz); + }); + + if (node.getMethodDescription() != null) { + MethodCall methodCall = new MethodCall(node.getClassName(), node.getMethodName(), node.getMethodDescription()); + this.methodSources.computeIfAbsent(methodCall, this.lookup::identify); + } else { + MethodCallByLine methodCall = new MethodCallByLine(node.getClassName(), node.getMethodName(), node.getLineNumber()); + this.lineSources.computeIfAbsent(methodCall, this.lookup::identify); + } + + // recursively + for (StackTraceNode child : node.getChildren()) { + visitStackNode(child); + } + } + @Override - public boolean hasMappings() { - return !this.map.isEmpty(); + public boolean hasClassSourceMappings() { + return this.classSources.hasMappings(); } @Override - public Map getMapping() { - this.map.values().removeIf(Objects::isNull); - return this.map; + public Map getClassSourceMapping() { + return this.classSources.export(); } - private void visitStackNode(StackTraceNode node) { - String className = node.getClassName(); - if (!this.map.containsKey(className)) { + @Override + public boolean hasMethodSourceMappings() { + return this.methodSources.hasMappings(); + } + + @Override + public Map getMethodSourceMapping() { + return this.methodSources.export(); + } + + @Override + public boolean hasLineSourceMappings() { + return this.lineSources.hasMappings(); + } + + @Override + public Map getLineSourceMapping() { + return this.lineSources.export(); + } + } + + final class SourcesMap { + // --> identifier (plugin name) + private final Map map = new HashMap<>(); + private final Function keyToStringFunction; + + private SourcesMap(Function keyToStringFunction) { + this.keyToStringFunction = keyToStringFunction; + } + + public void computeIfAbsent(T key, ComputeSourceFunction function) { + if (!this.map.containsKey(key)) { try { - Class clazz = this.classFinder.findClass(className); - Objects.requireNonNull(clazz); - this.map.put(className, this.lookup.identify(clazz)); + this.map.put(key, function.compute(key)); } catch (Throwable e) { - this.map.put(className, null); + this.map.put(key, null); } } + } - // recursively - for (StackTraceNode child : node.getChildren()) { - visitStackNode(child); + public boolean hasMappings() { + this.map.values().removeIf(Objects::isNull); + return !this.map.isEmpty(); + } + + public Map export() { + this.map.values().removeIf(Objects::isNull); + if (this.keyToStringFunction.equals(Function.identity())) { + //noinspection unchecked + return (Map) this.map; + } else { + return this.map.entrySet().stream().collect(Collectors.toMap( + e -> this.keyToStringFunction.apply(e.getKey()), + Map.Entry::getValue + )); } } + + private interface ComputeSourceFunction { + String compute(T key) throws Exception; + } + } + + /** + * Encapsulates information about a given method call using the name + method description. + */ + final class MethodCall { + private final String className; + private final String methodName; + private final String methodDescriptor; + + public MethodCall(String className, String methodName, String methodDescriptor) { + this.className = className; + this.methodName = methodName; + this.methodDescriptor = methodDescriptor; + } + + public String getClassName() { + return this.className; + } + + public String getMethodName() { + return this.methodName; + } + + public String getMethodDescriptor() { + return this.methodDescriptor; + } + + @Override + public String toString() { + return this.className + ";" + this.methodName + ";" + this.methodDescriptor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MethodCall)) return false; + MethodCall that = (MethodCall) o; + return this.className.equals(that.className) && + this.methodName.equals(that.methodName) && + this.methodDescriptor.equals(that.methodDescriptor); + } + + @Override + public int hashCode() { + return Objects.hash(this.className, this.methodName, this.methodDescriptor); + } + } + + /** + * Encapsulates information about a given method call using the name + line number. + */ + final class MethodCallByLine { + private final String className; + private final String methodName; + private final int lineNumber; + + public MethodCallByLine(String className, String methodName, int lineNumber) { + this.className = className; + this.methodName = methodName; + this.lineNumber = lineNumber; + } + + public String getClassName() { + return this.className; + } + + public String getMethodName() { + return this.methodName; + } + + public int getLineNumber() { + return this.lineNumber; + } + + @Override + public String toString() { + return this.className + ";" + this.lineNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MethodCallByLine)) return false; + MethodCallByLine that = (MethodCallByLine) o; + return this.lineNumber == that.lineNumber && this.className.equals(that.className); + } + + @Override + public int hashCode() { + return Objects.hash(this.className, this.lineNumber); + } } } diff --git a/spark-common/src/main/proto/spark/spark_sampler.proto b/spark-common/src/main/proto/spark/spark_sampler.proto index 8d9512a..f670ddf 100644 --- a/spark-common/src/main/proto/spark/spark_sampler.proto +++ b/spark-common/src/main/proto/spark/spark_sampler.proto @@ -11,6 +11,8 @@ message SamplerData { SamplerMetadata metadata = 1; repeated ThreadNode threads = 2; map class_sources = 3; // optional + map method_sources = 4; // optional + map line_sources = 5; // optional } message SamplerMetadata { diff --git a/spark-fabric/build.gradle b/spark-fabric/build.gradle index 30b1ff6..fce859a 100644 --- a/spark-fabric/build.gradle +++ b/spark-fabric/build.gradle @@ -66,6 +66,10 @@ processResources { } } +license { + exclude '**/smap/SourceMap.java' +} + shadowJar { archiveFileName = "spark-fabric-${project.pluginVersion}-dev.jar" configurations = [project.configurations.shade] @@ -74,12 +78,16 @@ shadowJar { relocate 'net.kyori.examination', 'me.lucko.spark.lib.adventure.examination' relocate 'net.bytebuddy', 'me.lucko.spark.lib.bytebuddy' relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' - relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' +// relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' exclude 'module-info.class' exclude 'META-INF/maven/**' exclude 'META-INF/proguard/**' + + dependencies { + exclude(dependency('org.ow2.asm::')) + } } task remappedShadowJar(type: RemapJarTask) { diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java index 7030680..9ffac18 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java @@ -22,18 +22,35 @@ package me.lucko.spark.fabric; import com.google.common.collect.ImmutableMap; +import me.lucko.spark.common.util.ClassFinder; import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.fabric.smap.MixinUtils; +import me.lucko.spark.fabric.smap.SourceMap; +import me.lucko.spark.fabric.smap.SourceMapProvider; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.objectweb.asm.Type; +import org.spongepowered.asm.mixin.FabricUtil; +import org.spongepowered.asm.mixin.extensibility.IMixinConfig; +import org.spongepowered.asm.mixin.transformer.Config; +import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; + +import java.lang.reflect.Method; +import java.net.URI; import java.nio.file.Path; import java.util.Collection; import java.util.Map; public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource { + + private final ClassFinder classFinder = new ClassFinder(); + private final SourceMapProvider smapProvider = new SourceMapProvider(); + private final Path modsDirectory; - private final Map pathToModMap; + private final Map pathToModMap; public FabricClassSourceLookup() { FabricLoader loader = FabricLoader.getInstance(); @@ -43,7 +60,7 @@ public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource { @Override public String identifyFile(Path path) { - String id = this.pathToModMap.get(path); + String id = this.pathToModMap.get(path.toAbsolutePath().normalize().toString()); if (id != null) { return id; } @@ -55,11 +72,141 @@ public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource { return super.identifyFileName(this.modsDirectory.relativize(path).toString()); } - private static Map constructPathToModIdMap(Collection mods) { - ImmutableMap.Builder builder = ImmutableMap.builder(); + @Override + public @Nullable String identify(MethodCall methodCall) throws Exception { + String className = methodCall.getClassName(); + String methodName = methodCall.getMethodName(); + String methodDesc = methodCall.getMethodDescriptor(); + + if (className.equals("native") || methodName.equals("") || methodName.equals("")) { + return null; + } + + Class clazz = this.classFinder.findClass(className); + if (clazz == null) { + return null; + } + + Class[] params = getParameterTypesForMethodDesc(methodDesc); + Method reflectMethod = clazz.getDeclaredMethod(methodName, params); + + MixinMerged mixinMarker = reflectMethod.getDeclaredAnnotation(MixinMerged.class); + if (mixinMarker == null) { + return null; + } + + return modIdFromMixinClass(mixinMarker.mixin()); + } + + @Override + public @Nullable String identify(MethodCallByLine methodCall) throws Exception { + String className = methodCall.getClassName(); + String methodName = methodCall.getMethodName(); + int lineNumber = methodCall.getLineNumber(); + + if (className.equals("native") || methodName.equals("") || methodName.equals("")) { + return null; + } + + SourceMap smap = this.smapProvider.getSourceMap(className); + if (smap == null) { + return null; + } + + int[] inputLineInfo = smap.getReverseLineMapping().get(lineNumber); + if (inputLineInfo == null || inputLineInfo.length == 0) { + return null; + } + + for (int fileInfoIds : inputLineInfo) { + SourceMap.FileInfo inputFileInfo = smap.getFileInfo().get(fileInfoIds); + if (inputFileInfo == null) { + continue; + } + + String path = inputFileInfo.path(); + if (path.endsWith(".java")) { + path = path.substring(0, path.length() - 5); + } + + String possibleMixinClassName = path.replace('/', '.'); + if (possibleMixinClassName.equals(className)) { + continue; + } + + return modIdFromMixinClass(possibleMixinClassName); + } + + return null; + } + + private static String modIdFromMixinClass(String mixinClassName) { + for (Config config : MixinUtils.getMixinConfigs().values()) { + IMixinConfig mixinConfig = config.getConfig(); + if (mixinClassName.startsWith(mixinConfig.getMixinPackage())) { + return mixinConfig.getDecoration(FabricUtil.KEY_MOD_ID); + } + } + return null; + } + + private Class[] getParameterTypesForMethodDesc(String methodDesc) { + Type methodType = Type.getMethodType(methodDesc); + Class[] params = new Class[methodType.getArgumentTypes().length]; + Type[] argumentTypes = methodType.getArgumentTypes(); + + for (int i = 0, argumentTypesLength = argumentTypes.length; i < argumentTypesLength; i++) { + Type argumentType = argumentTypes[i]; + params[i] = getClassFromType(argumentType); + } + + return params; + } + + private Class getClassFromType(Type type) { + return switch (type.getSort()) { + case Type.VOID -> void.class; + case Type.BOOLEAN -> boolean.class; + case Type.CHAR -> char.class; + case Type.BYTE -> byte.class; + case Type.SHORT -> short.class; + case Type.INT -> int.class; + case Type.FLOAT -> float.class; + case Type.LONG -> long.class; + case Type.DOUBLE -> double.class; + case Type.ARRAY -> { + final Class classFromType = getClassFromType(type.getElementType()); + Class result = classFromType; + if (classFromType != null) { + for (int i = 0; i < type.getDimensions(); i++) { + result = result.arrayType(); + } + } + yield result; + } + case Type.OBJECT -> this.classFinder.findClass(type.getClassName()); + default -> null; + }; + } + + private static Map constructPathToModIdMap(Collection mods) { + ImmutableMap.Builder builder = ImmutableMap.builder(); for (ModContainer mod : mods) { - Path path = mod.getRootPath().toAbsolutePath().normalize(); - builder.put(path, mod.getMetadata().getId()); + String modId = mod.getMetadata().getId(); + if (modId.equals("java")) { + continue; + } + + for (Path path : mod.getRootPaths()) { + URI uri = path.toUri(); + if (uri.getScheme().equals("jar") && path.toString().equals("/")) { // ZipFileSystem + String zipFilePath = path.getFileSystem().toString(); + builder.put(zipFilePath, modId); + } else { + builder.put(path.toAbsolutePath().normalize().toString(), modId); + } + + } } return builder.build(); } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java new file mode 100644 index 0000000..cfc8c95 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java @@ -0,0 +1,71 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.fabric.plugin; + +import me.lucko.spark.fabric.smap.SourceDebugCache; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.transformer.IMixinTransformer; +import org.spongepowered.asm.mixin.transformer.ext.Extensions; +import org.spongepowered.asm.mixin.transformer.ext.IExtension; +import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext; + +import java.util.List; +import java.util.Set; + +public class FabricSparkMixinPlugin implements IMixinConfigPlugin, IExtension { + + private static final Logger LOGGER = LogManager.getLogger("spark"); + + @Override + public void onLoad(String mixinPackage) { + Object activeTransformer = MixinEnvironment.getCurrentEnvironment().getActiveTransformer(); + if (activeTransformer instanceof IMixinTransformer transformer && transformer.getExtensions() instanceof Extensions extensions) { + extensions.add(this); + } else { + LOGGER.error( + "Failed to initialize SMAP parser for spark profiler. " + + "Mod information for mixin injected methods is now only available with the async-profiler engine." + ); + } + } + + @Override + public void export(MixinEnvironment env, String name, boolean force, ClassNode classNode) { + SourceDebugCache.put(name, classNode); + } + + // noop + @Override public String getRefMapperConfig() { return null; } + @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { return true; } + @Override public void acceptTargets(Set myTargets, Set otherTargets) { } + @Override public List getMixins() { return null; } + @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } + @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } + @Override public boolean checkActive(MixinEnvironment environment) { return true; } + @Override public void preApply(ITargetClassContext context) { } + @Override public void postApply(ITargetClassContext context) { } + +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java new file mode 100644 index 0000000..ebf2766 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java @@ -0,0 +1,52 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.fabric.smap; + +import org.spongepowered.asm.mixin.transformer.Config; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public enum MixinUtils { + ; + + private static final Map MIXIN_CONFIGS; + + static { + Map configs; + try { + Field allConfigsField = Config.class.getDeclaredField("allConfigs"); + allConfigsField.setAccessible(true); + + //noinspection unchecked + configs = (Map) allConfigsField.get(null); + } catch (Exception e) { + e.printStackTrace(); + configs = new HashMap<>(); + } + MIXIN_CONFIGS = configs; + } + + public static Map getMixinConfigs() { + return MIXIN_CONFIGS; + } +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java new file mode 100644 index 0000000..88adae6 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java @@ -0,0 +1,87 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.fabric.smap; + +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.service.IClassBytecodeProvider; +import org.spongepowered.asm.service.MixinService; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Caches the lookup of class -> source debug info for classes loaded on the JVM. + * + * The {@link me.lucko.spark.fabric.plugin.FabricSparkMixinPlugin} also supplements this cache with + * extra information as classes are exported. + */ +public enum SourceDebugCache { + ; + + // class name -> smap + private static final Map CACHE = new ConcurrentHashMap<>(); + + public static void put(String className, ClassNode node) { + if (className == null || node == null) { + return; + } + className = className.replace('/', '.'); + CACHE.put(className, SmapValue.of(node.sourceDebug)); + } + + public static String getSourceDebugInfo(String className) { + SmapValue cached = CACHE.get(className); + if (cached != null) { + return cached.value(); + } + + try { + IClassBytecodeProvider provider = MixinService.getService().getBytecodeProvider(); + ClassNode classNode = provider.getClassNode(className.replace('.', '/')); + + if (classNode != null) { + put(className, classNode); + return classNode.sourceDebug; + } + + } catch (Exception e) { + // ignore + } + + CACHE.put(className, SmapValue.NULL); + return null; + } + + private record SmapValue(String value) { + static final SmapValue NULL = new SmapValue(null); + + static SmapValue of(String value) { + if (value == null) { + return NULL; + } else { + return new SmapValue(value); + } + } + + } + +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java new file mode 100644 index 0000000..5105a26 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java @@ -0,0 +1,133 @@ +/* + * SMAPSourceDebugExtension.java - Parse source debug extensions and + * enhance stack traces. + * + * Copyright (c) 2012 Michael Schierl + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package me.lucko.spark.fabric.smap; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility class to parse "SMAP" (source map) information from loaded Java classes. + * + * @author Michael Schierl + */ +public class SourceMap { + + private final String generatedFileName; + private final String firstStratum; + private final Map fileinfo = new HashMap<>(); + private final Map reverseLineMapping = new HashMap<>(); + + private static final Pattern LINE_INFO_PATTERN = Pattern.compile("([0-9]+)(?:#([0-9]+))?(?:,([0-9]+))?:([0-9]+)(?:,([0-9]+))?"); + + public SourceMap(String value) { + String[] lines = value.split("\n"); + if (!lines[0].equals("SMAP") || !lines[3].startsWith("*S ") || !lines[4].equals("*F")) { + throw new IllegalArgumentException(value); + } + + this.generatedFileName = lines[1]; + this.firstStratum = lines[3].substring(3); + + int idx = 5; + while (!lines[idx].startsWith("*")) { + String infoline = lines[idx++]; + String path = null; + + if (infoline.startsWith("+ ")) { + path = lines[idx++]; + infoline = infoline.substring(2); + } + + int pos = infoline.indexOf(" "); + int filenum = Integer.parseInt(infoline.substring(0, pos)); + String name = infoline.substring(pos + 1); + + this.fileinfo.put(filenum, new FileInfo(name, path == null ? name : path)); + } + + if (lines[idx].equals("*L")) { + idx++; + int lastLFI = 0; + + while (!lines[idx].startsWith("*")) { + Matcher m = LINE_INFO_PATTERN.matcher(lines[idx++]); + if (!m.matches()) { + throw new IllegalArgumentException(lines[idx - 1]); + } + + int inputStartLine = Integer.parseInt(m.group(1)); + int lineFileID = m.group(2) == null ? lastLFI : Integer.parseInt(m.group(2)); + int repeatCount = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); + int outputStartLine = Integer.parseInt(m.group(4)); + int outputLineIncrement = m.group(5) == null ? 1 : Integer.parseInt(m.group(5)); + + for (int i = 0; i < repeatCount; i++) { + int[] inputMapping = new int[] { lineFileID, inputStartLine + i }; + int baseOL = outputStartLine + i * outputLineIncrement; + + for (int ol = baseOL; ol < baseOL + outputLineIncrement; ol++) { + if (!this.reverseLineMapping.containsKey(ol)) { + this.reverseLineMapping.put(ol, inputMapping); + } + } + } + + lastLFI = lineFileID; + } + } + } + + public String getGeneratedFileName() { + return this.generatedFileName; + } + + public String getFirstStratum() { + return this.firstStratum; + } + + public Map getFileInfo() { + return this.fileinfo; + } + + public Map getReverseLineMapping() { + return this.reverseLineMapping; + } + + public record FileInfo(String name, String path) { } +} \ No newline at end of file diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java new file mode 100644 index 0000000..1a4f246 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java @@ -0,0 +1,53 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.fabric.smap; + +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class SourceMapProvider { + private final Map cache = new HashMap<>(); + + public @Nullable SourceMap getSourceMap(String className) { + if (this.cache.containsKey(className)) { + return this.cache.get(className); + } + + SourceMap smap = null; + try { + String value = SourceDebugCache.getSourceDebugInfo(className); + if (value != null) { + value = value.replaceAll("\r\n?", "\n"); + if (value.startsWith("SMAP\n")) { + smap = new SourceMap(value); + } + } + } catch (Exception e) { + // ignore + } + + this.cache.put(className, smap); + return smap; + } + +} diff --git a/spark-fabric/src/main/resources/spark.mixins.json b/spark-fabric/src/main/resources/spark.mixins.json index e75b34f..63c1078 100644 --- a/spark-fabric/src/main/resources/spark.mixins.json +++ b/spark-fabric/src/main/resources/spark.mixins.json @@ -11,5 +11,6 @@ "server": [ "ServerEntityManagerAccessor", "ServerWorldAccessor" - ] + ], + "plugin": "me.lucko.spark.fabric.plugin.FabricSparkMixinPlugin" } \ No newline at end of file -- cgit From 7079484d428321c9b3db09394577efda4d591a4e Mon Sep 17 00:00:00 2001 From: Luck Date: Mon, 19 Sep 2022 18:57:02 +0100 Subject: Provide extra metadata about sources in sampler data --- .../spark/bukkit/BukkitClassSourceLookup.java | 2 +- .../me/lucko/spark/bukkit/BukkitSparkPlugin.java | 16 +- .../bungeecord/BungeeCordClassSourceLookup.java | 2 +- .../spark/bungeecord/BungeeCordSparkPlugin.java | 14 +- .../java/me/lucko/spark/common/SparkPlatform.java | 2 +- .../java/me/lucko/spark/common/SparkPlugin.java | 14 +- .../common/command/modules/SamplerModule.java | 3 +- .../spark/common/sampler/AbstractSampler.java | 10 +- .../me/lucko/spark/common/sampler/Sampler.java | 2 +- .../spark/common/sampler/async/AsyncSampler.java | 2 +- .../spark/common/sampler/java/JavaSampler.java | 2 +- .../common/sampler/source/ClassSourceLookup.java | 463 +++++++++++++++++++++ .../common/sampler/source/SourceMetadata.java | 81 ++++ .../lucko/spark/common/util/ClassSourceLookup.java | 452 -------------------- .../src/main/proto/spark/spark_sampler.proto | 6 + .../spark/fabric/FabricClassSourceLookup.java | 2 +- .../spark/fabric/plugin/FabricSparkPlugin.java | 19 +- .../lucko/spark/forge/ForgeClassSourceLookup.java | 2 +- .../lucko/spark/forge/plugin/ForgeSparkPlugin.java | 16 +- .../spark/minestom/MinestomClassSourceLookup.java | 2 +- .../lucko/spark/minestom/MinestomSparkPlugin.java | 14 +- .../spark/nukkit/NukkitClassSourceLookup.java | 2 +- .../me/lucko/spark/nukkit/NukkitSparkPlugin.java | 2 +- .../spark/sponge/Sponge7ClassSourceLookup.java | 2 +- .../me/lucko/spark/sponge/Sponge7SparkPlugin.java | 2 +- .../spark/sponge/Sponge8ClassSourceLookup.java | 2 +- .../me/lucko/spark/sponge/Sponge8SparkPlugin.java | 17 +- .../spark/velocity/VelocityClassSourceLookup.java | 2 +- .../lucko/spark/velocity/VelocitySparkPlugin.java | 14 +- .../spark/velocity/Velocity4ClassSourceLookup.java | 4 +- .../lucko/spark/velocity/Velocity4SparkPlugin.java | 14 +- .../spark/waterdog/WaterdogClassSourceLookup.java | 2 +- .../lucko/spark/waterdog/WaterdogSparkPlugin.java | 14 +- 33 files changed, 721 insertions(+), 482 deletions(-) create mode 100644 spark-common/src/main/java/me/lucko/spark/common/sampler/source/ClassSourceLookup.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/sampler/source/SourceMetadata.java delete mode 100644 spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java (limited to 'spark-fabric') diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitClassSourceLookup.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitClassSourceLookup.java index 6d8afda..f9c0c0b 100644 --- a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitClassSourceLookup.java +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitClassSourceLookup.java @@ -20,7 +20,7 @@ package me.lucko.spark.bukkit; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import org.bukkit.plugin.java.JavaPlugin; diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitSparkPlugin.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitSparkPlugin.java index 5737d3d..87490ea 100644 --- a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitSparkPlugin.java +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitSparkPlugin.java @@ -30,9 +30,10 @@ import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import me.lucko.spark.common.platform.world.WorldInfoProvider; import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; -import me.lucko.spark.common.util.ClassSourceLookup; import net.kyori.adventure.platform.bukkit.BukkitAudiences; @@ -40,10 +41,13 @@ import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.stream.Stream; @@ -180,6 +184,16 @@ public class BukkitSparkPlugin extends JavaPlugin implements SparkPlugin { return new BukkitClassSourceLookup(); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + Arrays.asList(getServer().getPluginManager().getPlugins()), + Plugin::getName, + plugin -> plugin.getDescription().getVersion(), + plugin -> String.join(", ", plugin.getDescription().getAuthors()) + ); + } + @Override public PlayerPingProvider createPlayerPingProvider() { if (BukkitPlayerPingProvider.isSupported()) { diff --git a/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordClassSourceLookup.java b/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordClassSourceLookup.java index e601f87..2024d54 100644 --- a/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordClassSourceLookup.java +++ b/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordClassSourceLookup.java @@ -20,7 +20,7 @@ package me.lucko.spark.bungeecord; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import net.md_5.bungee.api.plugin.PluginDescription; diff --git a/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordSparkPlugin.java b/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordSparkPlugin.java index e259adc..71beddb 100644 --- a/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordSparkPlugin.java +++ b/spark-bungeecord/src/main/java/me/lucko/spark/bungeecord/BungeeCordSparkPlugin.java @@ -24,7 +24,8 @@ import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import net.kyori.adventure.platform.bungeecord.BungeeAudiences; import net.md_5.bungee.api.CommandSender; @@ -33,6 +34,7 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.TabExecutor; import java.nio.file.Path; +import java.util.Collection; import java.util.logging.Level; import java.util.stream.Stream; @@ -91,6 +93,16 @@ public class BungeeCordSparkPlugin extends Plugin implements SparkPlugin { return new BungeeCordClassSourceLookup(); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + getProxy().getPluginManager().getPlugins(), + plugin -> plugin.getDescription().getName(), + plugin -> plugin.getDescription().getVersion(), + plugin -> plugin.getDescription().getAuthor() + ); + } + @Override public PlayerPingProvider createPlayerPingProvider() { return new BungeeCordPlayerPingProvider(getProxy()); 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 f92abf3..1969206 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,10 +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.source.ClassSourceLookup; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.common.util.BytebinClient; -import me.lucko.spark.common.util.ClassSourceLookup; import me.lucko.spark.common.util.Configuration; import me.lucko.spark.common.util.TemporaryFiles; diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java index 1116b04..e2a2dbd 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java @@ -27,11 +27,14 @@ import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import me.lucko.spark.common.platform.world.WorldInfoProvider; import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; -import me.lucko.spark.common.util.ClassSourceLookup; import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; import java.util.logging.Level; import java.util.stream.Stream; @@ -132,6 +135,15 @@ public interface SparkPlugin { return ClassSourceLookup.NO_OP; } + /** + * Gets a list of known sources (plugins/mods) on the platform. + * + * @return a list of sources + */ + default Collection getKnownSources() { + return Collections.emptyList(); + } + /** * Creates a player ping provider function. * 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 0a80c31..2afed64 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 @@ -38,6 +38,7 @@ import me.lucko.spark.common.sampler.ThreadGrouper; import me.lucko.spark.common.sampler.ThreadNodeOrder; 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.MethodDisambiguator; import me.lucko.spark.proto.SparkSamplerProtos; @@ -303,7 +304,7 @@ public class SamplerModule implements CommandModule { } private void handleUpload(SparkPlatform platform, CommandResponseHandler resp, Sampler sampler, ThreadNodeOrder threadOrder, String comment, MergeMode mergeMode, boolean saveToFileFlag) { - SparkSamplerProtos.SamplerData output = sampler.toProto(platform, resp.sender(), threadOrder, comment, mergeMode, platform.createClassSourceLookup()); + SparkSamplerProtos.SamplerData output = sampler.toProto(platform, resp.sender(), threadOrder, comment, mergeMode, ClassSourceLookup.create(platform)); boolean saveToFile = false; if (saveToFileFlag) { 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 3cfef0b..7b57504 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 @@ -27,13 +27,16 @@ import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import me.lucko.spark.common.sampler.aggregator.DataAggregator; import me.lucko.spark.common.sampler.node.MergeMode; import me.lucko.spark.common.sampler.node.ThreadNode; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.tick.TickHook; -import me.lucko.spark.common.util.ClassSourceLookup; import me.lucko.spark.proto.SparkSamplerProtos.SamplerData; import me.lucko.spark.proto.SparkSamplerProtos.SamplerMetadata; +import java.util.Collection; import java.util.Comparator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -150,6 +153,11 @@ public abstract class AbstractSampler implements Sampler { e.printStackTrace(); } + Collection knownSources = platform.getPlugin().getKnownSources(); + for (SourceMetadata source : knownSources) { + metadata.putSources(source.getName().toLowerCase(Locale.ROOT), source.toProto()); + } + proto.setMetadata(metadata); } 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 84f2da1..98281de 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 @@ -24,7 +24,7 @@ import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.sampler.node.MergeMode; import me.lucko.spark.common.sampler.node.ThreadNode; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import me.lucko.spark.proto.SparkSamplerProtos.SamplerData; import java.util.Comparator; 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 dae3852..37ccd96 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 @@ -30,7 +30,7 @@ import me.lucko.spark.common.sampler.ThreadGrouper; import me.lucko.spark.common.sampler.async.jfr.JfrReader; import me.lucko.spark.common.sampler.node.MergeMode; import me.lucko.spark.common.sampler.node.ThreadNode; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import me.lucko.spark.common.util.TemporaryFiles; import me.lucko.spark.proto.SparkSamplerProtos.SamplerData; 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 913faee..0f73a9f 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 @@ -29,8 +29,8 @@ import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.sampler.ThreadGrouper; import me.lucko.spark.common.sampler.node.MergeMode; import me.lucko.spark.common.sampler.node.ThreadNode; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import me.lucko.spark.common.tick.TickHook; -import me.lucko.spark.common.util.ClassSourceLookup; import me.lucko.spark.proto.SparkSamplerProtos.SamplerData; import java.lang.management.ManagementFactory; diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/source/ClassSourceLookup.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/source/ClassSourceLookup.java new file mode 100644 index 0000000..66b41d2 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/source/ClassSourceLookup.java @@ -0,0 +1,463 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.common.sampler.source; + +import me.lucko.spark.common.SparkPlatform; +import me.lucko.spark.common.sampler.node.StackTraceNode; +import me.lucko.spark.common.sampler.node.ThreadNode; +import me.lucko.spark.common.util.ClassFinder; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * A function which defines the source of given {@link Class}es or (Mixin) method calls. + */ +public interface ClassSourceLookup { + + /** + * Identify the given class. + * + * @param clazz the class + * @return the source of the class + */ + @Nullable String identify(Class clazz) throws Exception; + + /** + * Identify the given method call. + * + * @param methodCall the method call info + * @return the source of the method call + */ + default @Nullable String identify(MethodCall methodCall) throws Exception { + return null; + } + + /** + * Identify the given method call. + * + * @param methodCall the method call info + * @return the source of the method call + */ + default @Nullable String identify(MethodCallByLine methodCall) throws Exception { + return null; + } + + /** + * A no-operation {@link ClassSourceLookup}. + */ + ClassSourceLookup NO_OP = new ClassSourceLookup() { + @Override + public @Nullable String identify(Class clazz) { + return null; + } + }; + + static ClassSourceLookup create(SparkPlatform platform) { + try { + return platform.createClassSourceLookup(); + } catch (Exception e) { + e.printStackTrace(); + return NO_OP; + } + } + + /** + * A {@link ClassSourceLookup} which identifies classes based on their {@link ClassLoader}. + */ + abstract class ByClassLoader implements ClassSourceLookup { + + public abstract @Nullable String identify(ClassLoader loader) throws Exception; + + @Override + public final @Nullable String identify(Class clazz) throws Exception { + ClassLoader loader = clazz.getClassLoader(); + while (loader != null) { + String source = identify(loader); + if (source != null) { + return source; + } + loader = loader.getParent(); + } + return null; + } + } + + /** + * A {@link ClassSourceLookup} which identifies classes based on URL. + */ + interface ByUrl extends ClassSourceLookup { + + default String identifyUrl(URL url) throws URISyntaxException, MalformedURLException { + Path path = null; + + String protocol = url.getProtocol(); + if (protocol.equals("file")) { + path = Paths.get(url.toURI()); + } else if (protocol.equals("jar")) { + URL innerUrl = new URL(url.getPath()); + path = Paths.get(innerUrl.getPath().split("!")[0]); + } + + if (path != null) { + return identifyFile(path.toAbsolutePath().normalize()); + } + + return null; + } + + default String identifyFile(Path path) { + return identifyFileName(path.getFileName().toString()); + } + + default String identifyFileName(String fileName) { + return fileName.endsWith(".jar") ? fileName.substring(0, fileName.length() - 4) : null; + } + } + + /** + * A {@link ClassSourceLookup} which identifies classes based on the first URL in a {@link URLClassLoader}. + */ + class ByFirstUrlSource extends ClassSourceLookup.ByClassLoader implements ClassSourceLookup.ByUrl { + @Override + public @Nullable String identify(ClassLoader loader) throws IOException, URISyntaxException { + if (loader instanceof URLClassLoader) { + URLClassLoader urlClassLoader = (URLClassLoader) loader; + URL[] urls = urlClassLoader.getURLs(); + if (urls.length == 0) { + return null; + } + return identifyUrl(urls[0]); + } + return null; + } + } + + /** + * A {@link ClassSourceLookup} which identifies classes based on their {@link ProtectionDomain#getCodeSource()}. + */ + class ByCodeSource implements ClassSourceLookup, ClassSourceLookup.ByUrl { + @Override + public @Nullable String identify(Class clazz) throws URISyntaxException, MalformedURLException { + ProtectionDomain protectionDomain = clazz.getProtectionDomain(); + if (protectionDomain == null) { + return null; + } + CodeSource codeSource = protectionDomain.getCodeSource(); + if (codeSource == null) { + return null; + } + + URL url = codeSource.getLocation(); + return url == null ? null : identifyUrl(url); + } + } + + interface Visitor { + void visit(ThreadNode node); + + boolean hasClassSourceMappings(); + + Map getClassSourceMapping(); + + boolean hasMethodSourceMappings(); + + Map getMethodSourceMapping(); + + boolean hasLineSourceMappings(); + + Map getLineSourceMapping(); + } + + static Visitor createVisitor(ClassSourceLookup lookup) { + if (lookup == ClassSourceLookup.NO_OP) { + return NoOpVisitor.INSTANCE; // don't bother! + } + return new VisitorImpl(lookup); + } + + enum NoOpVisitor implements Visitor { + INSTANCE; + + @Override + public void visit(ThreadNode node) { + + } + + @Override + public boolean hasClassSourceMappings() { + return false; + } + + @Override + public Map getClassSourceMapping() { + return Collections.emptyMap(); + } + + @Override + public boolean hasMethodSourceMappings() { + return false; + } + + @Override + public Map getMethodSourceMapping() { + return Collections.emptyMap(); + } + + @Override + public boolean hasLineSourceMappings() { + return false; + } + + @Override + public Map getLineSourceMapping() { + return Collections.emptyMap(); + } + } + + /** + * Visitor which scans {@link StackTraceNode}s and accumulates class/method call identities. + */ + class VisitorImpl implements Visitor { + private final ClassSourceLookup lookup; + private final ClassFinder classFinder = new ClassFinder(); + + private final SourcesMap classSources = new SourcesMap<>(Function.identity()); + private final SourcesMap methodSources = new SourcesMap<>(MethodCall::toString); + private final SourcesMap lineSources = new SourcesMap<>(MethodCallByLine::toString); + + VisitorImpl(ClassSourceLookup lookup) { + this.lookup = lookup; + } + + @Override + public void visit(ThreadNode node) { + for (StackTraceNode child : node.getChildren()) { + visitStackNode(child); + } + } + + private void visitStackNode(StackTraceNode node) { + this.classSources.computeIfAbsent( + node.getClassName(), + className -> { + Class clazz = this.classFinder.findClass(className); + if (clazz == null) { + return null; + } + return this.lookup.identify(clazz); + }); + + if (node.getMethodDescription() != null) { + MethodCall methodCall = new MethodCall(node.getClassName(), node.getMethodName(), node.getMethodDescription()); + this.methodSources.computeIfAbsent(methodCall, this.lookup::identify); + } else { + MethodCallByLine methodCall = new MethodCallByLine(node.getClassName(), node.getMethodName(), node.getLineNumber()); + this.lineSources.computeIfAbsent(methodCall, this.lookup::identify); + } + + // recursively + for (StackTraceNode child : node.getChildren()) { + visitStackNode(child); + } + } + + @Override + public boolean hasClassSourceMappings() { + return this.classSources.hasMappings(); + } + + @Override + public Map getClassSourceMapping() { + return this.classSources.export(); + } + + @Override + public boolean hasMethodSourceMappings() { + return this.methodSources.hasMappings(); + } + + @Override + public Map getMethodSourceMapping() { + return this.methodSources.export(); + } + + @Override + public boolean hasLineSourceMappings() { + return this.lineSources.hasMappings(); + } + + @Override + public Map getLineSourceMapping() { + return this.lineSources.export(); + } + } + + final class SourcesMap { + // --> identifier (plugin name) + private final Map map = new HashMap<>(); + private final Function keyToStringFunction; + + private SourcesMap(Function keyToStringFunction) { + this.keyToStringFunction = keyToStringFunction; + } + + public void computeIfAbsent(T key, ComputeSourceFunction function) { + if (!this.map.containsKey(key)) { + try { + this.map.put(key, function.compute(key)); + } catch (Throwable e) { + this.map.put(key, null); + } + } + } + + public boolean hasMappings() { + this.map.values().removeIf(Objects::isNull); + return !this.map.isEmpty(); + } + + public Map export() { + this.map.values().removeIf(Objects::isNull); + if (this.keyToStringFunction.equals(Function.identity())) { + //noinspection unchecked + return (Map) this.map; + } else { + return this.map.entrySet().stream().collect(Collectors.toMap( + e -> this.keyToStringFunction.apply(e.getKey()), + Map.Entry::getValue + )); + } + } + + private interface ComputeSourceFunction { + String compute(T key) throws Exception; + } + } + + /** + * Encapsulates information about a given method call using the name + method description. + */ + final class MethodCall { + private final String className; + private final String methodName; + private final String methodDescriptor; + + public MethodCall(String className, String methodName, String methodDescriptor) { + this.className = className; + this.methodName = methodName; + this.methodDescriptor = methodDescriptor; + } + + public String getClassName() { + return this.className; + } + + public String getMethodName() { + return this.methodName; + } + + public String getMethodDescriptor() { + return this.methodDescriptor; + } + + @Override + public String toString() { + return this.className + ";" + this.methodName + ";" + this.methodDescriptor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MethodCall)) return false; + MethodCall that = (MethodCall) o; + return this.className.equals(that.className) && + this.methodName.equals(that.methodName) && + this.methodDescriptor.equals(that.methodDescriptor); + } + + @Override + public int hashCode() { + return Objects.hash(this.className, this.methodName, this.methodDescriptor); + } + } + + /** + * Encapsulates information about a given method call using the name + line number. + */ + final class MethodCallByLine { + private final String className; + private final String methodName; + private final int lineNumber; + + public MethodCallByLine(String className, String methodName, int lineNumber) { + this.className = className; + this.methodName = methodName; + this.lineNumber = lineNumber; + } + + public String getClassName() { + return this.className; + } + + public String getMethodName() { + return this.methodName; + } + + public int getLineNumber() { + return this.lineNumber; + } + + @Override + public String toString() { + return this.className + ";" + this.lineNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MethodCallByLine)) return false; + MethodCallByLine that = (MethodCallByLine) o; + return this.lineNumber == that.lineNumber && this.className.equals(that.className); + } + + @Override + public int hashCode() { + return Objects.hash(this.className, this.lineNumber); + } + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/source/SourceMetadata.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/source/SourceMetadata.java new file mode 100644 index 0000000..0808d66 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/source/SourceMetadata.java @@ -0,0 +1,81 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.common.sampler.source; + +import com.google.common.collect.ImmutableList; + +import me.lucko.spark.proto.SparkSamplerProtos.SamplerMetadata; + +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +/** + * A "source" is a plugin or mod on the platform that may be identified + * as a source of a method call in a profile. + */ +public class SourceMetadata { + + public static List gather(Collection sources, Function nameFunction, Function versionFunction, Function authorFunction) { + ImmutableList.Builder builder = ImmutableList.builder(); + + for (T source : sources) { + String name = nameFunction.apply(source); + String version = versionFunction.apply(source); + String author = authorFunction.apply(source); + + SourceMetadata metadata = new SourceMetadata(name, version, author); + builder.add(metadata); + } + + return builder.build(); + } + + private final String name; + private final String version; + private final String author; + + public SourceMetadata(String name, String version, String author) { + this.name = name; + this.version = version; + this.author = author; + } + + public String getName() { + return this.name; + } + + public String getVersion() { + return this.version; + } + + public String getAuthor() { + return this.author; + } + + public SamplerMetadata.SourceMetadata toProto() { + return SamplerMetadata.SourceMetadata.newBuilder() + .setName(this.name) + .setVersion(this.version) + .build(); + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java b/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java deleted file mode 100644 index 668f31a..0000000 --- a/spark-common/src/main/java/me/lucko/spark/common/util/ClassSourceLookup.java +++ /dev/null @@ -1,452 +0,0 @@ -/* - * This file is part of spark. - * - * Copyright (c) lucko (Luck) - * 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 . - */ - -package me.lucko.spark.common.util; - -import me.lucko.spark.common.sampler.node.StackTraceNode; -import me.lucko.spark.common.sampler.node.ThreadNode; - -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.CodeSource; -import java.security.ProtectionDomain; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * A function which defines the source of given {@link Class}es or (Mixin) method calls. - */ -public interface ClassSourceLookup { - - /** - * Identify the given class. - * - * @param clazz the class - * @return the source of the class - */ - @Nullable String identify(Class clazz) throws Exception; - - /** - * Identify the given method call. - * - * @param methodCall the method call info - * @return the source of the method call - */ - default @Nullable String identify(MethodCall methodCall) throws Exception { - return null; - } - - /** - * Identify the given method call. - * - * @param methodCall the method call info - * @return the source of the method call - */ - default @Nullable String identify(MethodCallByLine methodCall) throws Exception { - return null; - } - - /** - * A no-operation {@link ClassSourceLookup}. - */ - ClassSourceLookup NO_OP = new ClassSourceLookup() { - @Override - public @Nullable String identify(Class clazz) { - return null; - } - }; - - /** - * A {@link ClassSourceLookup} which identifies classes based on their {@link ClassLoader}. - */ - abstract class ByClassLoader implements ClassSourceLookup { - - public abstract @Nullable String identify(ClassLoader loader) throws Exception; - - @Override - public final @Nullable String identify(Class clazz) throws Exception { - ClassLoader loader = clazz.getClassLoader(); - while (loader != null) { - String source = identify(loader); - if (source != null) { - return source; - } - loader = loader.getParent(); - } - return null; - } - } - - /** - * A {@link ClassSourceLookup} which identifies classes based on URL. - */ - interface ByUrl extends ClassSourceLookup { - - default String identifyUrl(URL url) throws URISyntaxException, MalformedURLException { - Path path = null; - - String protocol = url.getProtocol(); - if (protocol.equals("file")) { - path = Paths.get(url.toURI()); - } else if (protocol.equals("jar")) { - URL innerUrl = new URL(url.getPath()); - path = Paths.get(innerUrl.getPath().split("!")[0]); - } - - if (path != null) { - return identifyFile(path.toAbsolutePath().normalize()); - } - - return null; - } - - default String identifyFile(Path path) { - return identifyFileName(path.getFileName().toString()); - } - - default String identifyFileName(String fileName) { - return fileName.endsWith(".jar") ? fileName.substring(0, fileName.length() - 4) : null; - } - } - - /** - * A {@link ClassSourceLookup} which identifies classes based on the first URL in a {@link URLClassLoader}. - */ - class ByFirstUrlSource extends ByClassLoader implements ByUrl { - @Override - public @Nullable String identify(ClassLoader loader) throws IOException, URISyntaxException { - if (loader instanceof URLClassLoader) { - URLClassLoader urlClassLoader = (URLClassLoader) loader; - URL[] urls = urlClassLoader.getURLs(); - if (urls.length == 0) { - return null; - } - return identifyUrl(urls[0]); - } - return null; - } - } - - /** - * A {@link ClassSourceLookup} which identifies classes based on their {@link ProtectionDomain#getCodeSource()}. - */ - class ByCodeSource implements ClassSourceLookup, ByUrl { - @Override - public @Nullable String identify(Class clazz) throws URISyntaxException, MalformedURLException { - ProtectionDomain protectionDomain = clazz.getProtectionDomain(); - if (protectionDomain == null) { - return null; - } - CodeSource codeSource = protectionDomain.getCodeSource(); - if (codeSource == null) { - return null; - } - - URL url = codeSource.getLocation(); - return url == null ? null : identifyUrl(url); - } - } - - interface Visitor { - void visit(ThreadNode node); - - boolean hasClassSourceMappings(); - - Map getClassSourceMapping(); - - boolean hasMethodSourceMappings(); - - Map getMethodSourceMapping(); - - boolean hasLineSourceMappings(); - - Map getLineSourceMapping(); - } - - static Visitor createVisitor(ClassSourceLookup lookup) { - if (lookup == ClassSourceLookup.NO_OP) { - return NoOpVisitor.INSTANCE; // don't bother! - } - return new VisitorImpl(lookup); - } - - enum NoOpVisitor implements Visitor { - INSTANCE; - - @Override - public void visit(ThreadNode node) { - - } - - @Override - public boolean hasClassSourceMappings() { - return false; - } - - @Override - public Map getClassSourceMapping() { - return Collections.emptyMap(); - } - - @Override - public boolean hasMethodSourceMappings() { - return false; - } - - @Override - public Map getMethodSourceMapping() { - return Collections.emptyMap(); - } - - @Override - public boolean hasLineSourceMappings() { - return false; - } - - @Override - public Map getLineSourceMapping() { - return Collections.emptyMap(); - } - } - - /** - * Visitor which scans {@link StackTraceNode}s and accumulates class/method call identities. - */ - class VisitorImpl implements Visitor { - private final ClassSourceLookup lookup; - private final ClassFinder classFinder = new ClassFinder(); - - private final SourcesMap classSources = new SourcesMap<>(Function.identity()); - private final SourcesMap methodSources = new SourcesMap<>(MethodCall::toString); - private final SourcesMap lineSources = new SourcesMap<>(MethodCallByLine::toString); - - VisitorImpl(ClassSourceLookup lookup) { - this.lookup = lookup; - } - - @Override - public void visit(ThreadNode node) { - for (StackTraceNode child : node.getChildren()) { - visitStackNode(child); - } - } - - private void visitStackNode(StackTraceNode node) { - this.classSources.computeIfAbsent( - node.getClassName(), - className -> { - Class clazz = this.classFinder.findClass(className); - if (clazz == null) { - return null; - } - return this.lookup.identify(clazz); - }); - - if (node.getMethodDescription() != null) { - MethodCall methodCall = new MethodCall(node.getClassName(), node.getMethodName(), node.getMethodDescription()); - this.methodSources.computeIfAbsent(methodCall, this.lookup::identify); - } else { - MethodCallByLine methodCall = new MethodCallByLine(node.getClassName(), node.getMethodName(), node.getLineNumber()); - this.lineSources.computeIfAbsent(methodCall, this.lookup::identify); - } - - // recursively - for (StackTraceNode child : node.getChildren()) { - visitStackNode(child); - } - } - - @Override - public boolean hasClassSourceMappings() { - return this.classSources.hasMappings(); - } - - @Override - public Map getClassSourceMapping() { - return this.classSources.export(); - } - - @Override - public boolean hasMethodSourceMappings() { - return this.methodSources.hasMappings(); - } - - @Override - public Map getMethodSourceMapping() { - return this.methodSources.export(); - } - - @Override - public boolean hasLineSourceMappings() { - return this.lineSources.hasMappings(); - } - - @Override - public Map getLineSourceMapping() { - return this.lineSources.export(); - } - } - - final class SourcesMap { - // --> identifier (plugin name) - private final Map map = new HashMap<>(); - private final Function keyToStringFunction; - - private SourcesMap(Function keyToStringFunction) { - this.keyToStringFunction = keyToStringFunction; - } - - public void computeIfAbsent(T key, ComputeSourceFunction function) { - if (!this.map.containsKey(key)) { - try { - this.map.put(key, function.compute(key)); - } catch (Throwable e) { - this.map.put(key, null); - } - } - } - - public boolean hasMappings() { - this.map.values().removeIf(Objects::isNull); - return !this.map.isEmpty(); - } - - public Map export() { - this.map.values().removeIf(Objects::isNull); - if (this.keyToStringFunction.equals(Function.identity())) { - //noinspection unchecked - return (Map) this.map; - } else { - return this.map.entrySet().stream().collect(Collectors.toMap( - e -> this.keyToStringFunction.apply(e.getKey()), - Map.Entry::getValue - )); - } - } - - private interface ComputeSourceFunction { - String compute(T key) throws Exception; - } - } - - /** - * Encapsulates information about a given method call using the name + method description. - */ - final class MethodCall { - private final String className; - private final String methodName; - private final String methodDescriptor; - - public MethodCall(String className, String methodName, String methodDescriptor) { - this.className = className; - this.methodName = methodName; - this.methodDescriptor = methodDescriptor; - } - - public String getClassName() { - return this.className; - } - - public String getMethodName() { - return this.methodName; - } - - public String getMethodDescriptor() { - return this.methodDescriptor; - } - - @Override - public String toString() { - return this.className + ";" + this.methodName + ";" + this.methodDescriptor; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof MethodCall)) return false; - MethodCall that = (MethodCall) o; - return this.className.equals(that.className) && - this.methodName.equals(that.methodName) && - this.methodDescriptor.equals(that.methodDescriptor); - } - - @Override - public int hashCode() { - return Objects.hash(this.className, this.methodName, this.methodDescriptor); - } - } - - /** - * Encapsulates information about a given method call using the name + line number. - */ - final class MethodCallByLine { - private final String className; - private final String methodName; - private final int lineNumber; - - public MethodCallByLine(String className, String methodName, int lineNumber) { - this.className = className; - this.methodName = methodName; - this.lineNumber = lineNumber; - } - - public String getClassName() { - return this.className; - } - - public String getMethodName() { - return this.methodName; - } - - public int getLineNumber() { - return this.lineNumber; - } - - @Override - public String toString() { - return this.className + ";" + this.lineNumber; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof MethodCallByLine)) return false; - MethodCallByLine that = (MethodCallByLine) o; - return this.lineNumber == that.lineNumber && this.className.equals(that.className); - } - - @Override - public int hashCode() { - return Objects.hash(this.className, this.lineNumber); - } - } - -} diff --git a/spark-common/src/main/proto/spark/spark_sampler.proto b/spark-common/src/main/proto/spark/spark_sampler.proto index f670ddf..e4c2481 100644 --- a/spark-common/src/main/proto/spark/spark_sampler.proto +++ b/spark-common/src/main/proto/spark/spark_sampler.proto @@ -28,6 +28,7 @@ message SamplerMetadata { map server_configurations = 10; int64 end_time = 11; int32 number_of_ticks = 12; + map sources = 13; message ThreadDumper { Type type = 1; @@ -58,6 +59,11 @@ message SamplerMetadata { AS_ONE = 2; } } + + message SourceMetadata { + string name = 1; + string version = 2; + } } message ThreadNode { diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java index 9ffac18..51834fc 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java @@ -22,8 +22,8 @@ package me.lucko.spark.fabric; import com.google.common.collect.ImmutableMap; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import me.lucko.spark.common.util.ClassFinder; -import me.lucko.spark.common.util.ClassSourceLookup; import me.lucko.spark.fabric.smap.MixinUtils; import me.lucko.spark.fabric.smap.SourceMap; import me.lucko.spark.fabric.smap.SourceMapProvider; diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java index 3126f28..9a03b4e 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java @@ -34,11 +34,14 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.command.sender.CommandSender; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.util.SparkThreadFactory; import me.lucko.spark.fabric.FabricClassSourceLookup; import me.lucko.spark.fabric.FabricSparkMod; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.metadata.Person; import net.minecraft.server.command.CommandOutput; import org.apache.logging.log4j.LogManager; @@ -46,10 +49,12 @@ import org.apache.logging.log4j.Logger; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Level; +import java.util.stream.Collectors; public abstract class FabricSparkPlugin implements SparkPlugin { @@ -110,6 +115,18 @@ public abstract class FabricSparkPlugin implements SparkPlugin { return new FabricClassSourceLookup(); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + FabricLoader.getInstance().getAllMods(), + mod -> mod.getMetadata().getId(), + mod -> mod.getMetadata().getVersion().getFriendlyString(), + mod -> mod.getMetadata().getAuthors().stream() + .map(Person::getName) + .collect(Collectors.joining(", ")) + ); + } + protected CompletableFuture generateSuggestions(CommandSender sender, String[] args, SuggestionsBuilder builder) { SuggestionsBuilder suggestions; diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/ForgeClassSourceLookup.java b/spark-forge/src/main/java/me/lucko/spark/forge/ForgeClassSourceLookup.java index 7900bc3..82d66ca 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/ForgeClassSourceLookup.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/ForgeClassSourceLookup.java @@ -20,7 +20,7 @@ package me.lucko.spark.forge; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import cpw.mods.modlauncher.TransformingClassLoader; diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeSparkPlugin.java b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeSparkPlugin.java index 36a7ce8..56061b9 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeSparkPlugin.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeSparkPlugin.java @@ -34,18 +34,22 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.command.sender.CommandSender; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.util.SparkThreadFactory; import me.lucko.spark.forge.ForgeClassSourceLookup; import me.lucko.spark.forge.ForgeSparkMod; import net.minecraft.commands.CommandSource; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.forgespi.language.IModInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -110,6 +114,16 @@ public abstract class ForgeSparkPlugin implements SparkPlugin { return new ForgeClassSourceLookup(); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + ModList.get().getMods(), + IModInfo::getModId, + mod -> mod.getVersion().toString(), + mod -> null // ? + ); + } + protected CompletableFuture generateSuggestions(CommandSender sender, String[] args, SuggestionsBuilder builder) { SuggestionsBuilder suggestions; diff --git a/spark-minestom/src/main/java/me/lucko/spark/minestom/MinestomClassSourceLookup.java b/spark-minestom/src/main/java/me/lucko/spark/minestom/MinestomClassSourceLookup.java index 252060e..ca44eea 100644 --- a/spark-minestom/src/main/java/me/lucko/spark/minestom/MinestomClassSourceLookup.java +++ b/spark-minestom/src/main/java/me/lucko/spark/minestom/MinestomClassSourceLookup.java @@ -20,7 +20,7 @@ package me.lucko.spark.minestom; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import net.minestom.server.MinecraftServer; import net.minestom.server.extensions.Extension; diff --git a/spark-minestom/src/main/java/me/lucko/spark/minestom/MinestomSparkPlugin.java b/spark-minestom/src/main/java/me/lucko/spark/minestom/MinestomSparkPlugin.java index 2b43cae..9014476 100644 --- a/spark-minestom/src/main/java/me/lucko/spark/minestom/MinestomSparkPlugin.java +++ b/spark-minestom/src/main/java/me/lucko/spark/minestom/MinestomSparkPlugin.java @@ -24,9 +24,10 @@ import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; -import me.lucko.spark.common.util.ClassSourceLookup; import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandSender; @@ -45,6 +46,7 @@ import org.jetbrains.annotations.NotNull; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.logging.Level; import java.util.stream.Stream; @@ -117,6 +119,16 @@ public class MinestomSparkPlugin extends Extension implements SparkPlugin { return new MinestomClassSourceLookup(); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + MinecraftServer.getExtensionManager().getExtensions(), + extension -> extension.getOrigin().getName(), + extension -> extension.getOrigin().getVersion(), + extension -> String.join(", ", extension.getOrigin().getAuthors()) + ); + } + @Override public PlayerPingProvider createPlayerPingProvider() { return new MinestomPlayerPingProvider(); diff --git a/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitClassSourceLookup.java b/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitClassSourceLookup.java index 4fed396..180e0af 100644 --- a/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitClassSourceLookup.java +++ b/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitClassSourceLookup.java @@ -20,7 +20,7 @@ package me.lucko.spark.nukkit; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import cn.nukkit.plugin.PluginClassLoader; diff --git a/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitSparkPlugin.java b/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitSparkPlugin.java index 87d9f09..ae21241 100644 --- a/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitSparkPlugin.java +++ b/spark-nukkit/src/main/java/me/lucko/spark/nukkit/NukkitSparkPlugin.java @@ -25,7 +25,7 @@ import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; diff --git a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7ClassSourceLookup.java b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7ClassSourceLookup.java index 90f3b8f..899ce58 100644 --- a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7ClassSourceLookup.java +++ b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7ClassSourceLookup.java @@ -20,7 +20,7 @@ package me.lucko.spark.sponge; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import org.spongepowered.api.Game; diff --git a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java index e6c9a04..0e3f4eb 100644 --- a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java +++ b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7SparkPlugin.java @@ -29,8 +29,8 @@ import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.world.WorldInfoProvider; import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import me.lucko.spark.common.tick.TickHook; -import me.lucko.spark.common.util.ClassSourceLookup; import org.slf4j.Logger; import org.spongepowered.api.Game; diff --git a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8ClassSourceLookup.java b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8ClassSourceLookup.java index fa4ac45..7f02e75 100644 --- a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8ClassSourceLookup.java +++ b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8ClassSourceLookup.java @@ -22,7 +22,7 @@ package me.lucko.spark.sponge; import com.google.common.collect.ImmutableMap; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import org.spongepowered.api.Game; import org.spongepowered.plugin.PluginCandidate; diff --git a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8SparkPlugin.java b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8SparkPlugin.java index 83b2ec2..b1d31e9 100644 --- a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8SparkPlugin.java +++ b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8SparkPlugin.java @@ -30,8 +30,9 @@ import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.world.WorldInfoProvider; import me.lucko.spark.common.sampler.ThreadDumper; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.tick.TickHook; -import me.lucko.spark.common.util.ClassSourceLookup; import net.kyori.adventure.text.Component; @@ -52,8 +53,10 @@ import org.spongepowered.api.event.lifecycle.StartedEngineEvent; import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; +import org.spongepowered.plugin.metadata.model.PluginContributor; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutorService; @@ -177,6 +180,18 @@ public class Sponge8SparkPlugin implements SparkPlugin { return new Sponge8ClassSourceLookup(this.game); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + this.game.pluginManager().plugins(), + plugin -> plugin.metadata().id(), + plugin -> plugin.metadata().version().toString(), + plugin -> plugin.metadata().contributors().stream() + .map(PluginContributor::name) + .collect(Collectors.joining(", ")) + ); + } + @Override public PlayerPingProvider createPlayerPingProvider() { if (this.game.isServerAvailable()) { diff --git a/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocityClassSourceLookup.java b/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocityClassSourceLookup.java index bcb8176..9b697c3 100644 --- a/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocityClassSourceLookup.java +++ b/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocityClassSourceLookup.java @@ -23,7 +23,7 @@ package me.lucko.spark.velocity; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginManager; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocitySparkPlugin.java b/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocitySparkPlugin.java index 7d9ced8..4a89a4e 100644 --- a/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocitySparkPlugin.java +++ b/spark-velocity/src/main/java/me/lucko/spark/velocity/VelocitySparkPlugin.java @@ -34,11 +34,13 @@ import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import org.slf4j.Logger; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.stream.Stream; @@ -133,6 +135,16 @@ public class VelocitySparkPlugin implements SparkPlugin, SimpleCommand { return new VelocityClassSourceLookup(this.proxy.getPluginManager()); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + this.proxy.getPluginManager().getPlugins(), + plugin -> plugin.getDescription().getId(), + plugin -> plugin.getDescription().getVersion().orElse("unspecified"), + plugin -> String.join(", ", plugin.getDescription().getAuthors()) + ); + } + @Override public PlayerPingProvider createPlayerPingProvider() { return new VelocityPlayerPingProvider(this.proxy); diff --git a/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4ClassSourceLookup.java b/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4ClassSourceLookup.java index c5c22c3..84840d2 100644 --- a/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4ClassSourceLookup.java +++ b/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4ClassSourceLookup.java @@ -23,7 +23,7 @@ package me.lucko.spark.velocity; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginManager; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import org.checkerframework.checker.nullness.qual.Nullable; @@ -48,7 +48,7 @@ public class Velocity4ClassSourceLookup extends ClassSourceLookup.ByClassLoader for (PluginContainer plugin : pluginManager.plugins()) { Object instance = plugin.instance(); if (instance != null) { - this.classLoadersToPlugin.put(instance.getClass().getClassLoader(), plugin.description().name()); + this.classLoadersToPlugin.put(instance.getClass().getClassLoader(), plugin.description().id()); } } } diff --git a/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4SparkPlugin.java b/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4SparkPlugin.java index 0c57689..b638246 100644 --- a/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4SparkPlugin.java +++ b/spark-velocity4/src/main/java/me/lucko/spark/velocity/Velocity4SparkPlugin.java @@ -34,11 +34,13 @@ import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import org.slf4j.Logger; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.stream.Stream; @@ -133,6 +135,16 @@ public class Velocity4SparkPlugin implements SparkPlugin, SimpleCommand { return new Velocity4ClassSourceLookup(this.proxy.pluginManager()); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + this.proxy.pluginManager().plugins(), + plugin -> plugin.description().id(), + plugin -> plugin.description().version(), + plugin -> String.join(", ", plugin.description().authors()) + ); + } + @Override public PlayerPingProvider createPlayerPingProvider() { return new Velocity4PlayerPingProvider(this.proxy); diff --git a/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogClassSourceLookup.java b/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogClassSourceLookup.java index 36e6a57..2207c9e 100644 --- a/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogClassSourceLookup.java +++ b/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogClassSourceLookup.java @@ -20,7 +20,7 @@ package me.lucko.spark.waterdog; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; import dev.waterdog.waterdogpe.ProxyServer; import dev.waterdog.waterdogpe.plugin.Plugin; diff --git a/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogSparkPlugin.java b/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogSparkPlugin.java index 07b153a..1a64a98 100644 --- a/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogSparkPlugin.java +++ b/spark-waterdog/src/main/java/me/lucko/spark/waterdog/WaterdogSparkPlugin.java @@ -24,7 +24,8 @@ import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; import me.lucko.spark.common.platform.PlatformInfo; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import dev.waterdog.waterdogpe.ProxyServer; import dev.waterdog.waterdogpe.command.Command; @@ -32,6 +33,7 @@ import dev.waterdog.waterdogpe.command.CommandSender; import dev.waterdog.waterdogpe.plugin.Plugin; import java.nio.file.Path; +import java.util.Collection; import java.util.logging.Level; import java.util.stream.Stream; @@ -100,6 +102,16 @@ public class WaterdogSparkPlugin extends Plugin implements SparkPlugin { return new WaterdogClassSourceLookup(getProxy()); } + @Override + public Collection getKnownSources() { + return SourceMetadata.gather( + getProxy().getPluginManager().getPlugins(), + Plugin::getName, + plugin -> plugin.getDescription().getVersion(), + plugin -> plugin.getDescription().getAuthor() + ); + } + @Override public PlayerPingProvider createPlayerPingProvider() { return new WaterdogPlayerPingProvider(getProxy()); -- cgit From dbdd3eb1344b837abb13538b9c55d1d99e697e54 Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 22 Sep 2022 22:06:10 +0100 Subject: Allow platforms to pass extra misc metadata to the viewer --- .../spark/bukkit/BukkitServerConfigProvider.java | 4 +- .../java/me/lucko/spark/common/SparkPlugin.java | 12 +++- .../spark/common/platform/MetadataProvider.java | 47 ++++++++++++++ .../serverconfig/AbstractServerConfigProvider.java | 73 --------------------- .../serverconfig/ServerConfigProvider.java | 66 ++++++++++++------- .../spark/common/sampler/AbstractSampler.java | 10 ++- .../src/main/proto/spark/spark_sampler.proto | 1 + .../spark/fabric/FabricExtraMetadataProvider.java | 75 ++++++++++++++++++++++ .../spark/fabric/FabricServerConfigProvider.java | 4 +- .../fabric/plugin/FabricClientSparkPlugin.java | 7 ++ .../fabric/plugin/FabricServerSparkPlugin.java | 7 ++ .../spark/forge/ForgeExtraMetadataProvider.java | 75 ++++++++++++++++++++++ .../spark/forge/ForgeServerConfigProvider.java | 4 +- .../spark/forge/plugin/ForgeClientSparkPlugin.java | 7 ++ .../spark/forge/plugin/ForgeServerSparkPlugin.java | 7 ++ 15 files changed, 295 insertions(+), 104 deletions(-) create mode 100644 spark-common/src/main/java/me/lucko/spark/common/platform/MetadataProvider.java delete mode 100644 spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java create mode 100644 spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java create mode 100644 spark-forge/src/main/java/me/lucko/spark/forge/ForgeExtraMetadataProvider.java (limited to 'spark-fabric') diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitServerConfigProvider.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitServerConfigProvider.java index d095bed..5db1b38 100644 --- a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitServerConfigProvider.java +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitServerConfigProvider.java @@ -28,10 +28,10 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializer; -import me.lucko.spark.common.platform.serverconfig.AbstractServerConfigProvider; import me.lucko.spark.common.platform.serverconfig.ConfigParser; import me.lucko.spark.common.platform.serverconfig.ExcludedConfigFilter; import me.lucko.spark.common.platform.serverconfig.PropertiesConfigParser; +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import org.bukkit.Bukkit; import org.bukkit.World; @@ -51,7 +51,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -public class BukkitServerConfigProvider extends AbstractServerConfigProvider { +public class BukkitServerConfigProvider extends ServerConfigProvider { /** A map of provided files and their type */ private static final Map FILES; diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java index e2a2dbd..b7aef2a 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlugin.java @@ -23,6 +23,7 @@ package me.lucko.spark.common; import me.lucko.spark.api.Spark; import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; +import me.lucko.spark.common.platform.MetadataProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import me.lucko.spark.common.platform.world.WorldInfoProvider; @@ -161,7 +162,16 @@ public interface SparkPlugin { * @return the server config provider function */ default ServerConfigProvider createServerConfigProvider() { - return ServerConfigProvider.NO_OP; + return null; + } + + /** + * Creates a metadata provider for the platform. + * + * @return the platform extra metadata provider + */ + default MetadataProvider createExtraMetadataProvider() { + return null; } /** diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/MetadataProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/MetadataProvider.java new file mode 100644 index 0000000..39022b4 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/MetadataProvider.java @@ -0,0 +1,47 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.common.platform; + +import com.google.gson.JsonElement; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Function to export dynamic metadata to be displayed within the spark viewer. + */ +@FunctionalInterface +public interface MetadataProvider { + + /** + * Produces a map of the metadata. + * + * @return the metadata + */ + Map get(); + + default Map export() { + Map map = new LinkedHashMap<>(); + get().forEach((key, value) -> map.put(key, value.toString())); + return map; + } + +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java deleted file mode 100644 index 559ae95..0000000 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/AbstractServerConfigProvider.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * This file is part of spark. - * - * Copyright (c) lucko (Luck) - * 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 . - */ - -package me.lucko.spark.common.platform.serverconfig; - -import com.google.common.collect.ImmutableMap; -import com.google.gson.JsonElement; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Abstract implementation of {@link ServerConfigProvider}. - * - *

This implementation is able to delete hidden paths from - * the configurations before they are sent to the viewer.

- */ -public abstract class AbstractServerConfigProvider implements ServerConfigProvider { - private final Map files; - private final ExcludedConfigFilter hiddenPathFilters; - - protected AbstractServerConfigProvider(Map files, Collection hiddenPaths) { - this.files = files; - this.hiddenPathFilters = new ExcludedConfigFilter(hiddenPaths); - } - - @Override - public final Map loadServerConfigurations() { - ImmutableMap.Builder builder = ImmutableMap.builder(); - - this.files.forEach((path, parser) -> { - try { - JsonElement json = parser.load(path, this.hiddenPathFilters); - if (json == null) { - return; - } - builder.put(path, json); - } catch (Exception e) { - e.printStackTrace(); - } - }); - - return builder.build(); - } - - protected static List getSystemPropertyList(String property) { - String value = System.getProperty(property); - return value == null - ? Collections.emptyList() - : Arrays.asList(value.split(",")); - } - -} diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/ServerConfigProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/ServerConfigProvider.java index c66305f..485f215 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/ServerConfigProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/serverconfig/ServerConfigProvider.java @@ -20,37 +20,57 @@ package me.lucko.spark.common.platform.serverconfig; +import com.google.common.collect.ImmutableMap; import com.google.gson.JsonElement; +import me.lucko.spark.common.platform.MetadataProvider; + +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** - * Function to export server configuration files for access within the spark viewer. + * Abstract implementation of {@link MetadataProvider} which + * provides server configuration data. + * + *

This implementation is able to delete hidden paths from + * the configurations before they are sent to the viewer.

*/ -@FunctionalInterface -public interface ServerConfigProvider { - - /** - * Loads a map of the server configuration files. - * - *

The key is the name of the file and the value is a - * {@link JsonElement} of the contents.

- * - * @return the exported server configurations - */ - Map loadServerConfigurations(); - - default Map exportServerConfigurations() { - Map map = new LinkedHashMap<>(); - loadServerConfigurations().forEach((key, value) -> map.put(key, value.toString())); - return map; +public abstract class ServerConfigProvider implements MetadataProvider { + private final Map files; + private final ExcludedConfigFilter hiddenPathFilters; + + protected ServerConfigProvider(Map files, Collection hiddenPaths) { + this.files = files; + this.hiddenPathFilters = new ExcludedConfigFilter(hiddenPaths); + } + + @Override + public final Map get() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + this.files.forEach((path, parser) -> { + try { + JsonElement json = parser.load(path, this.hiddenPathFilters); + if (json == null) { + return; + } + builder.put(path, json); + } catch (Exception e) { + e.printStackTrace(); + } + }); + + return builder.build(); } - /** - * A no-op implementation - */ - ServerConfigProvider NO_OP = Collections::emptyMap; + protected static List getSystemPropertyList(String property) { + String value = System.getProperty(property); + return value == null + ? Collections.emptyList() + : Arrays.asList(value.split(",")); + } } 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 7b57504..e20a2a8 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 @@ -23,6 +23,7 @@ package me.lucko.spark.common.sampler; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics; +import me.lucko.spark.common.platform.MetadataProvider; import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import me.lucko.spark.common.sampler.aggregator.DataAggregator; import me.lucko.spark.common.sampler.node.MergeMode; @@ -148,7 +149,14 @@ public abstract class AbstractSampler implements Sampler { try { ServerConfigProvider serverConfigProvider = platform.getPlugin().createServerConfigProvider(); - metadata.putAllServerConfigurations(serverConfigProvider.exportServerConfigurations()); + metadata.putAllServerConfigurations(serverConfigProvider.export()); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + MetadataProvider extraMetadataProvider = platform.getPlugin().createExtraMetadataProvider(); + metadata.putAllExtraPlatformMetadata(extraMetadataProvider.export()); } catch (Exception e) { e.printStackTrace(); } diff --git a/spark-common/src/main/proto/spark/spark_sampler.proto b/spark-common/src/main/proto/spark/spark_sampler.proto index e4c2481..3f30fb2 100644 --- a/spark-common/src/main/proto/spark/spark_sampler.proto +++ b/spark-common/src/main/proto/spark/spark_sampler.proto @@ -29,6 +29,7 @@ message SamplerMetadata { int64 end_time = 11; int32 number_of_ticks = 12; map sources = 13; + map extra_platform_metadata = 14; message ThreadDumper { Type type = 1; diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java new file mode 100644 index 0000000..9eb2694 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java @@ -0,0 +1,75 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.fabric; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import me.lucko.spark.common.platform.MetadataProvider; + +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ResourcePackProfile; +import net.minecraft.resource.ResourcePackSource; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class FabricExtraMetadataProvider implements MetadataProvider { + + private final ResourcePackManager resourcePackManager; + + public FabricExtraMetadataProvider(ResourcePackManager resourcePackManager) { + this.resourcePackManager = resourcePackManager; + } + + @Override + public Map get() { + Map metadata = new LinkedHashMap<>(); + metadata.put("datapacks", datapackMetadata()); + return metadata; + } + + private JsonElement datapackMetadata() { + JsonObject datapacks = new JsonObject(); + for (ResourcePackProfile profile : this.resourcePackManager.getEnabledProfiles()) { + JsonObject obj = new JsonObject(); + obj.addProperty("name", profile.getDisplayName().getString()); + obj.addProperty("description", profile.getDescription().getString()); + obj.addProperty("source", resourcePackSource(profile.getSource())); + datapacks.add(profile.getName(), obj); + } + return datapacks; + } + + private static String resourcePackSource(ResourcePackSource source) { + if (source == ResourcePackSource.PACK_SOURCE_NONE) { + return "none"; + } else if (source == ResourcePackSource.PACK_SOURCE_BUILTIN) { + return "builtin"; + } else if (source == ResourcePackSource.PACK_SOURCE_WORLD) { + return "world"; + } else if (source == ResourcePackSource.PACK_SOURCE_SERVER) { + return "server"; + } else { + return "unknown"; + } + } +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java index 18079d3..325a324 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java @@ -23,14 +23,14 @@ package me.lucko.spark.fabric; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import me.lucko.spark.common.platform.serverconfig.AbstractServerConfigProvider; import me.lucko.spark.common.platform.serverconfig.ConfigParser; import me.lucko.spark.common.platform.serverconfig.PropertiesConfigParser; +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import java.util.Collection; import java.util.Map; -public class FabricServerConfigProvider extends AbstractServerConfigProvider { +public class FabricServerConfigProvider extends ServerConfigProvider { /** A map of provided files and their type */ private static final Map FILES; diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java index 0ef6620..faf4eef 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java @@ -28,12 +28,14 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import me.lucko.spark.common.platform.MetadataProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.world.WorldInfoProvider; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.fabric.FabricCommandSender; +import me.lucko.spark.fabric.FabricExtraMetadataProvider; import me.lucko.spark.fabric.FabricPlatformInfo; import me.lucko.spark.fabric.FabricSparkMod; import me.lucko.spark.fabric.FabricTickHook; @@ -137,6 +139,11 @@ public class FabricClientSparkPlugin extends FabricSparkPlugin implements Comman return new FabricTickReporter.Client(); } + @Override + public MetadataProvider createExtraMetadataProvider() { + return new FabricExtraMetadataProvider(this.minecraft.getResourcePackManager()); + } + @Override public WorldInfoProvider createWorldInfoProvider() { return new FabricWorldInfoProvider.Client(this.minecraft); diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java index f840f5e..c528e5b 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java @@ -30,6 +30,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import me.lucko.fabric.api.permissions.v0.Permissions; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; +import me.lucko.spark.common.platform.MetadataProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import me.lucko.spark.common.platform.world.WorldInfoProvider; @@ -37,6 +38,7 @@ import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.fabric.FabricCommandSender; +import me.lucko.spark.fabric.FabricExtraMetadataProvider; import me.lucko.spark.fabric.FabricPlatformInfo; import me.lucko.spark.fabric.FabricPlayerPingProvider; import me.lucko.spark.fabric.FabricServerConfigProvider; @@ -162,6 +164,11 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman return new FabricServerConfigProvider(); } + @Override + public MetadataProvider createExtraMetadataProvider() { + return new FabricExtraMetadataProvider(this.server.getDataPackManager()); + } + @Override public WorldInfoProvider createWorldInfoProvider() { return new FabricWorldInfoProvider.Server(this.server); diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/ForgeExtraMetadataProvider.java b/spark-forge/src/main/java/me/lucko/spark/forge/ForgeExtraMetadataProvider.java new file mode 100644 index 0000000..cac2771 --- /dev/null +++ b/spark-forge/src/main/java/me/lucko/spark/forge/ForgeExtraMetadataProvider.java @@ -0,0 +1,75 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.forge; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import me.lucko.spark.common.platform.MetadataProvider; + +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.packs.repository.PackSource; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class ForgeExtraMetadataProvider implements MetadataProvider { + + private final PackRepository resourcePackManager; + + public ForgeExtraMetadataProvider(PackRepository resourcePackManager) { + this.resourcePackManager = resourcePackManager; + } + + @Override + public Map get() { + Map metadata = new LinkedHashMap<>(); + metadata.put("datapacks", datapackMetadata()); + return metadata; + } + + private JsonElement datapackMetadata() { + JsonObject datapacks = new JsonObject(); + for (Pack profile : this.resourcePackManager.getSelectedPacks()) { + JsonObject obj = new JsonObject(); + obj.addProperty("name", profile.getTitle().getString()); + obj.addProperty("description", profile.getDescription().getString()); + obj.addProperty("source", resourcePackSource(profile.getPackSource())); + datapacks.add(profile.getId(), obj); + } + return datapacks; + } + + private static String resourcePackSource(PackSource source) { + if (source == PackSource.DEFAULT) { + return "none"; + } else if (source == PackSource.BUILT_IN) { + return "builtin"; + } else if (source == PackSource.WORLD) { + return "world"; + } else if (source == PackSource.SERVER) { + return "server"; + } else { + return "unknown"; + } + } +} diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/ForgeServerConfigProvider.java b/spark-forge/src/main/java/me/lucko/spark/forge/ForgeServerConfigProvider.java index baa1358..6feba52 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/ForgeServerConfigProvider.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/ForgeServerConfigProvider.java @@ -23,14 +23,14 @@ package me.lucko.spark.forge; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import me.lucko.spark.common.platform.serverconfig.AbstractServerConfigProvider; import me.lucko.spark.common.platform.serverconfig.ConfigParser; import me.lucko.spark.common.platform.serverconfig.PropertiesConfigParser; +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import java.util.Collection; import java.util.Map; -public class ForgeServerConfigProvider extends AbstractServerConfigProvider { +public class ForgeServerConfigProvider extends ServerConfigProvider { /** A map of provided files and their type */ private static final Map FILES; diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeClientSparkPlugin.java b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeClientSparkPlugin.java index a4c6bd1..a8c7c92 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeClientSparkPlugin.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeClientSparkPlugin.java @@ -27,12 +27,14 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import me.lucko.spark.common.platform.MetadataProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.world.WorldInfoProvider; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.forge.ForgeCommandSender; +import me.lucko.spark.forge.ForgeExtraMetadataProvider; import me.lucko.spark.forge.ForgePlatformInfo; import me.lucko.spark.forge.ForgeSparkMod; import me.lucko.spark.forge.ForgeTickHook; @@ -136,6 +138,11 @@ public class ForgeClientSparkPlugin extends ForgeSparkPlugin implements Command< return new ForgeWorldInfoProvider.Client(this.minecraft); } + @Override + public MetadataProvider createExtraMetadataProvider() { + return new ForgeExtraMetadataProvider(this.minecraft.getResourcePackRepository()); + } + @Override public PlatformInfo getPlatformInfo() { return new ForgePlatformInfo(PlatformInfo.Type.CLIENT); diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java index 1aeb2b1..56d30b7 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java @@ -30,6 +30,7 @@ import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; +import me.lucko.spark.common.platform.MetadataProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import me.lucko.spark.common.platform.world.WorldInfoProvider; @@ -37,6 +38,7 @@ import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.forge.ForgeCommandSender; +import me.lucko.spark.forge.ForgeExtraMetadataProvider; import me.lucko.spark.forge.ForgePlatformInfo; import me.lucko.spark.forge.ForgePlayerPingProvider; import me.lucko.spark.forge.ForgeServerConfigProvider; @@ -219,6 +221,11 @@ public class ForgeServerSparkPlugin extends ForgeSparkPlugin implements Command< return new ForgeServerConfigProvider(); } + @Override + public MetadataProvider createExtraMetadataProvider() { + return new ForgeExtraMetadataProvider(this.server.getPackRepository()); + } + @Override public WorldInfoProvider createWorldInfoProvider() { return new ForgeWorldInfoProvider.Server(this.server); -- cgit From 65f9460a1a27e930b3749525766fd44d57b65300 Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 26 Nov 2022 23:00:53 +0000 Subject: Include player/entity/chunk counts in window statistics --- .../spark/bukkit/BukkitWorldInfoProvider.java | 44 ++++++++++- .../common/command/modules/SamplerModule.java | 6 +- .../platform/PlatformStatisticsProvider.java | 7 +- .../platform/world/AsyncWorldInfoProvider.java | 90 ++++++++++++++++++++++ .../common/platform/world/WorldInfoProvider.java | 57 ++++++++++++-- .../platform/world/WorldStatisticsProvider.java | 37 +-------- .../spark/common/sampler/AbstractSampler.java | 2 +- .../me/lucko/spark/common/sampler/Sampler.java | 2 +- .../spark/common/sampler/SamplerContainer.java | 6 +- .../common/sampler/async/AsyncProfilerJob.java | 6 +- .../spark/common/sampler/async/AsyncSampler.java | 14 ++-- .../spark/common/sampler/java/JavaSampler.java | 14 ++-- .../sampler/window/WindowStatisticsCollector.java | 15 ++++ spark-common/src/main/proto/spark/spark.proto | 6 ++ .../spark/fabric/FabricWorldInfoProvider.java | 42 +++++++++- .../fabric/mixin/ClientEntityManagerAccessor.java | 4 + .../fabric/mixin/ServerEntityManagerAccessor.java | 4 + .../lucko/spark/forge/ForgeWorldInfoProvider.java | 42 +++++++++- .../main/resources/META-INF/accesstransformer.cfg | 4 +- .../spark/sponge/Sponge7WorldInfoProvider.java | 21 ++++- .../spark/sponge/Sponge8WorldInfoProvider.java | 21 ++++- 21 files changed, 366 insertions(+), 78 deletions(-) create mode 100644 spark-common/src/main/java/me/lucko/spark/common/platform/world/AsyncWorldInfoProvider.java (limited to 'spark-fabric') diff --git a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitWorldInfoProvider.java b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitWorldInfoProvider.java index 79c2715..8f876cf 100644 --- a/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitWorldInfoProvider.java +++ b/spark-bukkit/src/main/java/me/lucko/spark/bukkit/BukkitWorldInfoProvider.java @@ -34,6 +34,21 @@ import java.util.ArrayList; import java.util.List; public class BukkitWorldInfoProvider implements WorldInfoProvider { + private static final boolean SUPPORTS_PAPER_COUNT_METHODS; + + static { + boolean supportsPaperCountMethods = false; + try { + World.class.getMethod("getEntityCount"); + World.class.getMethod("getTileEntityCount"); + World.class.getMethod("getChunkCount"); + supportsPaperCountMethods = true; + } catch (Exception e) { + // ignored + } + SUPPORTS_PAPER_COUNT_METHODS = supportsPaperCountMethods; + } + private final Server server; public BukkitWorldInfoProvider(Server server) { @@ -41,8 +56,33 @@ public class BukkitWorldInfoProvider implements WorldInfoProvider { } @Override - public Result poll() { - Result data = new Result<>(); + public CountsResult pollCounts() { + int players = this.server.getOnlinePlayers().size(); + int entities = 0; + int tileEntities = 0; + int chunks = 0; + + for (World world : this.server.getWorlds()) { + if (SUPPORTS_PAPER_COUNT_METHODS) { + entities += world.getEntityCount(); + tileEntities += world.getTileEntityCount(); + chunks += world.getChunkCount(); + } else { + entities += world.getEntities().size(); + Chunk[] chunksArray = world.getLoadedChunks(); + for (Chunk chunk : chunksArray) { + tileEntities += chunk.getTileEntities().length; + } + chunks += chunksArray.length; + } + } + + return new CountsResult(players, entities, tileEntities, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); for (World world : this.server.getWorlds()) { Chunk[] chunks = world.getLoadedChunks(); 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 00cd4fa..f576eac 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 @@ -145,7 +145,7 @@ public class SamplerModule implements CommandModule { 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(); + previousSampler.stop(true); platform.getSamplerContainer().unsetActiveSampler(previousSampler); } else { // there is a non-background profiler running - tell the user @@ -310,7 +310,7 @@ public class SamplerModule implements CommandModule { if (sampler == null) { resp.replyPrefixed(text("There isn't an active profiler running.")); } else { - platform.getSamplerContainer().stopActiveSampler(); + platform.getSamplerContainer().stopActiveSampler(true); resp.broadcastPrefixed(text("Profiler has been cancelled.", GOLD)); } } @@ -322,7 +322,7 @@ public class SamplerModule implements CommandModule { resp.replyPrefixed(text("There isn't an active profiler running.")); } else { platform.getSamplerContainer().unsetActiveSampler(sampler); - sampler.stop(); + sampler.stop(false); boolean saveToFile = arguments.boolFlag("save-to-file"); if (saveToFile) { diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java index 1eb9753..fc7e78a 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/PlatformStatisticsProvider.java @@ -31,7 +31,7 @@ import me.lucko.spark.common.monitor.net.NetworkMonitor; import me.lucko.spark.common.monitor.os.OperatingSystemInfo; import me.lucko.spark.common.monitor.ping.PingStatistics; import me.lucko.spark.common.monitor.tick.TickStatistics; -import me.lucko.spark.common.platform.world.WorldInfoProvider; +import me.lucko.spark.common.platform.world.AsyncWorldInfoProvider; import me.lucko.spark.common.platform.world.WorldStatisticsProvider; import me.lucko.spark.proto.SparkProtos.PlatformStatistics; import me.lucko.spark.proto.SparkProtos.SystemStatistics; @@ -188,8 +188,9 @@ public class PlatformStatisticsProvider { } try { - WorldInfoProvider worldInfo = this.platform.getPlugin().createWorldInfoProvider(); - WorldStatisticsProvider worldStatisticsProvider = new WorldStatisticsProvider(this.platform, worldInfo); + WorldStatisticsProvider worldStatisticsProvider = new WorldStatisticsProvider( + new AsyncWorldInfoProvider(this.platform, this.platform.getPlugin().createWorldInfoProvider()) + ); WorldStatistics worldStatistics = worldStatisticsProvider.getWorldStatistics(); if (worldStatistics != null) { builder.setWorld(worldStatistics); diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/world/AsyncWorldInfoProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/world/AsyncWorldInfoProvider.java new file mode 100644 index 0000000..82cddef --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/world/AsyncWorldInfoProvider.java @@ -0,0 +1,90 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) + * 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 . + */ + +package me.lucko.spark.common.platform.world; + +import me.lucko.spark.common.SparkPlatform; +import me.lucko.spark.common.SparkPlugin; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; +import java.util.logging.Level; + +/** + * Async-friendly wrapper around {@link WorldInfoProvider}. + */ +public class AsyncWorldInfoProvider { + private static final int TIMEOUT_SECONDS = 5; + + private final SparkPlatform platform; + private final WorldInfoProvider provider; + + public AsyncWorldInfoProvider(SparkPlatform platform, WorldInfoProvider provider) { + this.platform = platform; + this.provider = provider == WorldInfoProvider.NO_OP ? null : provider; + } + + private CompletableFuture async(Function function) { + if (this.provider == null) { + return null; + } + + if (this.provider.mustCallSync()) { + SparkPlugin plugin = this.platform.getPlugin(); + return CompletableFuture.supplyAsync(() -> function.apply(this.provider), plugin::executeSync); + } else { + return CompletableFuture.completedFuture(function.apply(this.provider)); + } + } + + private T get(CompletableFuture future) { + if (future == null) { + return null; + } + + try { + return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } catch (TimeoutException e) { + this.platform.getPlugin().log(Level.WARNING, "Timed out waiting for world statistics"); + return null; + } + } + + public CompletableFuture pollCounts() { + return async(WorldInfoProvider::pollCounts); + } + + public CompletableFuture>> pollChunks() { + return async(WorldInfoProvider::pollChunks); + } + + public WorldInfoProvider.CountsResult getCounts() { + return get(pollCounts()); + } + + public WorldInfoProvider.ChunksResult> getChunks() { + return get(pollChunks()); + } +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/world/WorldInfoProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/world/WorldInfoProvider.java index 9494816..7fb581d 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/world/WorldInfoProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/world/WorldInfoProvider.java @@ -29,20 +29,37 @@ import java.util.Map; */ public interface WorldInfoProvider { - WorldInfoProvider NO_OP = () -> null; + WorldInfoProvider NO_OP = new WorldInfoProvider() { + @Override + public CountsResult pollCounts() { + return null; + } + + @Override + public ChunksResult> pollChunks() { + return null; + } + }; + + /** + * Polls for counts. + * + * @return the counts + */ + CountsResult pollCounts(); /** - * Polls for information. + * Polls for chunk information. * - * @return the information + * @return the chunk information */ - Result> poll(); + ChunksResult> pollChunks(); default boolean mustCallSync() { return true; } - final class Result { + final class ChunksResult> { private final Map> worlds = new HashMap<>(); public void put(String worldName, List chunks) { @@ -54,4 +71,34 @@ public interface WorldInfoProvider { } } + final class CountsResult { + private final int players; + private final int entities; + private final int tileEntities; + private final int chunks; + + public CountsResult(int players, int entities, int tileEntities, int chunks) { + this.players = players; + this.entities = entities; + this.tileEntities = tileEntities; + this.chunks = chunks; + } + + public int players() { + return this.players; + } + + public int entities() { + return this.entities; + } + + public int tileEntities() { + return this.tileEntities; + } + + public int chunks() { + return this.chunks; + } + } + } diff --git a/spark-common/src/main/java/me/lucko/spark/common/platform/world/WorldStatisticsProvider.java b/spark-common/src/main/java/me/lucko/spark/common/platform/world/WorldStatisticsProvider.java index 80c35a6..7e63222 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/platform/world/WorldStatisticsProvider.java +++ b/spark-common/src/main/java/me/lucko/spark/common/platform/world/WorldStatisticsProvider.java @@ -20,8 +20,6 @@ package me.lucko.spark.common.platform.world; -import me.lucko.spark.common.SparkPlatform; -import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.proto.SparkProtos.WorldStatistics; import java.util.ArrayList; @@ -30,46 +28,17 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; public class WorldStatisticsProvider { - private final SparkPlatform platform; - private final WorldInfoProvider provider; + private final AsyncWorldInfoProvider provider; - public WorldStatisticsProvider(SparkPlatform platform, WorldInfoProvider provider) { - this.platform = platform; + public WorldStatisticsProvider(AsyncWorldInfoProvider provider) { this.provider = provider; } public WorldStatistics getWorldStatistics() { - if (this.provider == WorldInfoProvider.NO_OP) { - return null; - } - - CompletableFuture>> future; - - if (this.provider.mustCallSync()) { - SparkPlugin plugin = this.platform.getPlugin(); - future = CompletableFuture.supplyAsync(this.provider::poll, plugin::executeSync); - } else { - future = CompletableFuture.completedFuture(this.provider.poll()); - } - - WorldInfoProvider.Result> result; - try { - result = future.get(5, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } catch (TimeoutException e) { - this.platform.getPlugin().log(Level.WARNING, "Timed out waiting for world statistics"); - return null; - } - + WorldInfoProvider.ChunksResult> result = provider.getChunks(); if (result == null) { return null; } 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 59e873c..e324fd3 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 @@ -120,7 +120,7 @@ public abstract class AbstractSampler implements Sampler { } @Override - public void stop() { + public void stop(boolean cancelled) { this.windowStatisticsCollector.stop(); } 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 5d2026d..36a63f1 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 @@ -41,7 +41,7 @@ public interface Sampler { /** * Stops the sampler. */ - void stop(); + void stop(boolean cancelled); /** * Gets the time when the sampler started (unix timestamp in millis) 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 f56dee5..d55909c 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 @@ -66,10 +66,10 @@ public class SamplerContainer implements AutoCloseable { /** * Stops the active sampler, if there is one. */ - public void stopActiveSampler() { + public void stopActiveSampler(boolean cancelled) { Sampler sampler = this.activeSampler.getAndSet(null); if (sampler != null) { - sampler.stop(); + sampler.stop(cancelled); } } @@ -79,7 +79,7 @@ public class SamplerContainer implements AutoCloseable { @Override public void close() { - stopActiveSampler(); + stopActiveSampler(true); } } diff --git a/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncProfilerJob.java b/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncProfilerJob.java index db1808c..d74b75f 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncProfilerJob.java +++ b/spark-common/src/main/java/me/lucko/spark/common/sampler/async/AsyncProfilerJob.java @@ -224,13 +224,15 @@ public class AsyncProfilerJob { } } - // delete the output file after reading + deleteOutputFile(); + } + + public void deleteOutputFile() { try { Files.deleteIfExists(this.outputFile); } catch (IOException e) { // ignore } - } private void readSegments(JfrReader reader, Predicate threadFilter, AsyncDataAggregator dataAggregator, int window) throws IOException { 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 f2e7191..178f055 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 @@ -144,7 +144,7 @@ public class AsyncSampler extends AbstractSampler { } this.scheduler.schedule(() -> { - stop(); + stop(false); this.future.complete(this); }, delay, TimeUnit.MILLISECONDS); } @@ -153,13 +153,17 @@ public class AsyncSampler extends AbstractSampler { * Stops the profiler. */ @Override - public void stop() { - super.stop(); + public void stop(boolean cancelled) { + super.stop(cancelled); synchronized (this.currentJobMutex) { this.currentJob.stop(); - this.windowStatisticsCollector.measureNow(this.currentJob.getWindow()); - this.currentJob.aggregate(this.dataAggregator); + if (!cancelled) { + this.windowStatisticsCollector.measureNow(this.currentJob.getWindow()); + this.currentJob.aggregate(this.dataAggregator); + } else { + this.currentJob.deleteOutputFile(); + } this.currentJob = null; } 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 42a457d..72a37e8 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 @@ -94,13 +94,15 @@ public class JavaSampler extends AbstractSampler implements Runnable { } @Override - public void stop() { - super.stop(); + public void stop(boolean cancelled) { + super.stop(cancelled); this.task.cancel(false); - // collect statistics for the final window - this.windowStatisticsCollector.measureNow(this.lastWindow.get()); + if (!cancelled) { + // collect statistics for the final window + this.windowStatisticsCollector.measureNow(this.lastWindow.get()); + } } @Override @@ -111,7 +113,7 @@ public class JavaSampler extends AbstractSampler implements Runnable { long time = System.currentTimeMillis(); if (this.autoEndTime != -1 && this.autoEndTime <= time) { - stop(); + stop(false); this.future.complete(this); return; } @@ -120,7 +122,7 @@ public class JavaSampler extends AbstractSampler implements Runnable { ThreadInfo[] threadDumps = this.threadDumper.dumpThreads(this.threadBean); this.workerPool.execute(new InsertDataTask(threadDumps, window)); } catch (Throwable t) { - stop(); + stop(false); this.future.completeExceptionally(t); } } 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 7da62fa..ce65013 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 @@ -23,6 +23,8 @@ package me.lucko.spark.common.sampler.window; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.tick.TickStatistics; +import me.lucko.spark.common.platform.world.AsyncWorldInfoProvider; +import me.lucko.spark.common.platform.world.WorldInfoProvider; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.util.RollingAverage; import me.lucko.spark.proto.SparkProtos; @@ -152,6 +154,19 @@ public class WindowStatisticsCollector { builder.setCpuProcess(CpuMonitor.processLoad1MinAvg()); builder.setCpuSystem(CpuMonitor.systemLoad1MinAvg()); + try { + AsyncWorldInfoProvider worldInfoProvider = new AsyncWorldInfoProvider(this.platform, this.platform.getPlugin().createWorldInfoProvider()); + WorldInfoProvider.CountsResult counts = worldInfoProvider.getCounts(); + if (counts != null) { + builder.setPlayers(counts.players()); + builder.setEntities(counts.entities()); + builder.setTileEntities(counts.tileEntities()); + builder.setChunks(counts.chunks()); + } + } catch (Exception e) { + e.printStackTrace(); + } + return builder.build(); } diff --git a/spark-common/src/main/proto/spark/spark.proto b/spark-common/src/main/proto/spark/spark.proto index be76bd7..f61e585 100644 --- a/spark-common/src/main/proto/spark/spark.proto +++ b/spark-common/src/main/proto/spark/spark.proto @@ -159,6 +159,12 @@ message WindowStatistics { double tps = 4; double mspt_median = 5; double mspt_max = 6; + + // world + int32 players = 7; + int32 entities = 8; + int32 tile_entities = 9; + int32 chunks = 10; } message RollingAverageValues { diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java index f2f7b96..156db89 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java @@ -40,6 +40,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.world.ServerEntityManager; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.entity.EntityIndex; import net.minecraft.world.entity.EntityTrackingSection; import net.minecraft.world.entity.SectionedEntityCache; @@ -72,8 +73,25 @@ public abstract class FabricWorldInfoProvider implements WorldInfoProvider { } @Override - public Result poll() { - Result data = new Result<>(); + public CountsResult pollCounts() { + int players = this.server.getCurrentPlayerCount(); + int entities = 0; + int chunks = 0; + + for (ServerWorld world : this.server.getWorlds()) { + ServerEntityManager entityManager = ((ServerWorldAccessor) world).getEntityManager(); + EntityIndex entityIndex = ((ServerEntityManagerAccessor) entityManager).getIndex(); + + entities += entityIndex.size(); + chunks += world.getChunkManager().getLoadedChunkCount(); + } + + return new CountsResult(players, entities, -1, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); for (ServerWorld world : this.server.getWorlds()) { ServerEntityManager entityManager = ((ServerWorldAccessor) world).getEntityManager(); @@ -95,8 +113,24 @@ public abstract class FabricWorldInfoProvider implements WorldInfoProvider { } @Override - public Result poll() { - Result data = new Result<>(); + public CountsResult pollCounts() { + ClientWorld world = this.client.world; + if (world == null) { + return null; + } + + ClientEntityManager entityManager = ((ClientWorldAccessor) world).getEntityManager(); + EntityIndex entityIndex = ((ClientEntityManagerAccessor) entityManager).getIndex(); + + int entities = entityIndex.size(); + int chunks = world.getChunkManager().getLoadedChunkCount(); + + return new CountsResult(-1, entities, -1, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); ClientWorld world = this.client.world; if (world == null) { diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java index 88c9521..994c9a3 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java @@ -22,6 +22,7 @@ package me.lucko.spark.fabric.mixin; import net.minecraft.client.world.ClientEntityManager; import net.minecraft.entity.Entity; +import net.minecraft.world.entity.EntityIndex; import net.minecraft.world.entity.SectionedEntityCache; import org.spongepowered.asm.mixin.Mixin; @@ -33,4 +34,7 @@ public interface ClientEntityManagerAccessor { @Accessor SectionedEntityCache getCache(); + @Accessor + EntityIndex getIndex(); + } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java index 160a12b..2c67502 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java @@ -22,6 +22,7 @@ package me.lucko.spark.fabric.mixin; import net.minecraft.entity.Entity; import net.minecraft.server.world.ServerEntityManager; +import net.minecraft.world.entity.EntityIndex; import net.minecraft.world.entity.SectionedEntityCache; import org.spongepowered.asm.mixin.Mixin; @@ -33,4 +34,7 @@ public interface ServerEntityManagerAccessor { @Accessor SectionedEntityCache getCache(); + @Accessor + EntityIndex getIndex(); + } diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/ForgeWorldInfoProvider.java b/spark-forge/src/main/java/me/lucko/spark/forge/ForgeWorldInfoProvider.java index 1d65d6a..4750c08 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/ForgeWorldInfoProvider.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/ForgeWorldInfoProvider.java @@ -34,6 +34,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.entity.EntityLookup; import net.minecraft.world.level.entity.EntitySection; import net.minecraft.world.level.entity.EntitySectionStorage; import net.minecraft.world.level.entity.PersistentEntitySectionManager; @@ -68,8 +69,25 @@ public abstract class ForgeWorldInfoProvider implements WorldInfoProvider { } @Override - public Result poll() { - Result data = new Result<>(); + public CountsResult pollCounts() { + int players = this.server.getPlayerCount(); + int entities = 0; + int chunks = 0; + + for (ServerLevel level : this.server.getAllLevels()) { + PersistentEntitySectionManager entityManager = level.entityManager; + EntityLookup entityIndex = entityManager.visibleEntityStorage; + + entities += entityIndex.count(); + chunks += level.getChunkSource().getLoadedChunksCount(); + } + + return new CountsResult(players, entities, -1, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); for (ServerLevel level : this.server.getAllLevels()) { PersistentEntitySectionManager entityManager = level.entityManager; @@ -91,8 +109,24 @@ public abstract class ForgeWorldInfoProvider implements WorldInfoProvider { } @Override - public Result poll() { - Result data = new Result<>(); + public CountsResult pollCounts() { + ClientLevel level = this.client.level; + if (level == null) { + return null; + } + + TransientEntitySectionManager entityManager = level.entityStorage; + EntityLookup entityIndex = entityManager.entityStorage; + + int entities = entityIndex.count(); + int chunks = level.getChunkSource().getLoadedChunksCount(); + + return new CountsResult(-1, entities, -1, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); ClientLevel level = this.client.level; if (level == null) { diff --git a/spark-forge/src/main/resources/META-INF/accesstransformer.cfg b/spark-forge/src/main/resources/META-INF/accesstransformer.cfg index 39e9c1a..2699a0e 100644 --- a/spark-forge/src/main/resources/META-INF/accesstransformer.cfg +++ b/spark-forge/src/main/resources/META-INF/accesstransformer.cfg @@ -1,5 +1,7 @@ public net.minecraft.server.level.ServerLevel f_143244_ # entityManager public net.minecraft.world.level.entity.PersistentEntitySectionManager f_157495_ # sectionStorage +public net.minecraft.world.level.entity.PersistentEntitySectionManager f_157494_ # visibleEntityStorage public net.minecraft.client.multiplayer.ClientLevel f_171631_ # entityStorage public net.minecraft.world.level.entity.TransientEntitySectionManager f_157638_ # sectionStorage -public net.minecraft.client.Minecraft f_91018_ # gameThread \ No newline at end of file +public net.minecraft.world.level.entity.TransientEntitySectionManager f_157637_ # entityStorage +public net.minecraft.client.Minecraft f_91018_ # gameThread diff --git a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7WorldInfoProvider.java b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7WorldInfoProvider.java index fa6fa6b..df58028 100644 --- a/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7WorldInfoProvider.java +++ b/spark-sponge7/src/main/java/me/lucko/spark/sponge/Sponge7WorldInfoProvider.java @@ -20,6 +20,7 @@ package me.lucko.spark.sponge; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import me.lucko.spark.common.platform.world.AbstractChunkInfo; @@ -44,8 +45,24 @@ public class Sponge7WorldInfoProvider implements WorldInfoProvider { } @Override - public Result poll() { - Result data = new Result<>(); + public CountsResult pollCounts() { + int players = this.server.getOnlinePlayers().size(); + int entities = 0; + int tileEntities = 0; + int chunks = 0; + + for (World world : this.server.getWorlds()) { + entities += world.getEntities().size(); + tileEntities += world.getTileEntities().size(); + chunks += Iterables.size(world.getLoadedChunks()); + } + + return new CountsResult(players, entities, tileEntities, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); for (World world : this.server.getWorlds()) { List chunks = Lists.newArrayList(world.getLoadedChunks()); diff --git a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8WorldInfoProvider.java b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8WorldInfoProvider.java index bff4d6e..69b4515 100644 --- a/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8WorldInfoProvider.java +++ b/spark-sponge8/src/main/java/me/lucko/spark/sponge/Sponge8WorldInfoProvider.java @@ -20,6 +20,7 @@ package me.lucko.spark.sponge; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import me.lucko.spark.common.platform.world.AbstractChunkInfo; @@ -45,8 +46,24 @@ public class Sponge8WorldInfoProvider implements WorldInfoProvider { } @Override - public Result poll() { - Result data = new Result<>(); + public CountsResult pollCounts() { + int players = this.server.onlinePlayers().size(); + int entities = 0; + int tileEntities = 0; + int chunks = 0; + + for (ServerWorld world : this.server.worldManager().worlds()) { + entities += world.entities().size(); + tileEntities += world.blockEntities().size(); + chunks += Iterables.size(world.loadedChunks()); + } + + return new CountsResult(players, entities, tileEntities, chunks); + } + + @Override + public ChunksResult pollChunks() { + ChunksResult data = new ChunksResult<>(); for (ServerWorld world : this.server.worldManager().worlds()) { List chunks = Lists.newArrayList(world.loadedChunks()); -- cgit From 205073cfa9fea36a85da918a0f8cc26042a33aed Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 7 Dec 2022 22:16:26 +0000 Subject: Fabric 1.19.3 --- spark-fabric/build.gradle | 8 ++++---- .../java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'spark-fabric') diff --git a/spark-fabric/build.gradle b/spark-fabric/build.gradle index fce859a..8b6d030 100644 --- a/spark-fabric/build.gradle +++ b/spark-fabric/build.gradle @@ -28,9 +28,9 @@ configurations { dependencies { // https://modmuss50.me/fabric.html - minecraft 'com.mojang:minecraft:1.19.2' - mappings 'net.fabricmc:yarn:1.19.2+build.1:v2' - modImplementation 'net.fabricmc:fabric-loader:0.14.9' + minecraft 'com.mojang:minecraft:1.19.3' + mappings 'net.fabricmc:yarn:1.19.3+build.2:v2' + modImplementation 'net.fabricmc:fabric-loader:0.14.11' Set apiModules = [ "fabric-api-base", @@ -40,7 +40,7 @@ dependencies { // Add each module as a dependency apiModules.forEach { - modImplementation(fabricApi.module(it, '0.59.0+1.19.2')) + modImplementation(fabricApi.module(it, '0.68.1+1.19.3')) } include(modImplementation('me.lucko:fabric-permissions-api:0.1-SNAPSHOT')) diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java index 9eb2694..22794c2 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java @@ -60,13 +60,13 @@ public class FabricExtraMetadataProvider implements MetadataProvider { } private static String resourcePackSource(ResourcePackSource source) { - if (source == ResourcePackSource.PACK_SOURCE_NONE) { + if (source == ResourcePackSource.NONE) { return "none"; - } else if (source == ResourcePackSource.PACK_SOURCE_BUILTIN) { + } else if (source == ResourcePackSource.BUILTIN) { return "builtin"; - } else if (source == ResourcePackSource.PACK_SOURCE_WORLD) { + } else if (source == ResourcePackSource.WORLD) { return "world"; - } else if (source == ResourcePackSource.PACK_SOURCE_SERVER) { + } else if (source == ResourcePackSource.SERVER) { return "server"; } else { return "unknown"; -- cgit From 81326e7bd3b038c2e47cc418262fa4caaad50b48 Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 10 Dec 2022 09:53:59 +0000 Subject: Fix fabric mixin config --- spark-fabric/src/main/resources/spark.mixins.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'spark-fabric') diff --git a/spark-fabric/src/main/resources/spark.mixins.json b/spark-fabric/src/main/resources/spark.mixins.json index 63c1078..beaca2f 100644 --- a/spark-fabric/src/main/resources/spark.mixins.json +++ b/spark-fabric/src/main/resources/spark.mixins.json @@ -2,13 +2,12 @@ "required": true, "package": "me.lucko.spark.fabric.mixin", "compatibilityLevel": "JAVA_17", - "mixins": [], "client": [ "ClientEntityManagerAccessor", "ClientWorldAccessor", "MinecraftClientAccessor" ], - "server": [ + "mixins": [ "ServerEntityManagerAccessor", "ServerWorldAccessor" ], -- cgit From 521cf8889ea4938b93691233178640fea06db69e Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 10 Dec 2022 10:01:58 +0000 Subject: Give integrated server host all permissions --- .../spark/fabric/plugin/FabricServerSparkPlugin.java | 11 +++++++++-- .../spark/forge/plugin/ForgeServerSparkPlugin.java | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) (limited to 'spark-fabric') diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java index c528e5b..1606d57 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java @@ -119,8 +119,15 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman @Override public boolean hasPermission(CommandOutput sender, String permission) { - if (sender instanceof PlayerEntity) { - return Permissions.check(((PlayerEntity) sender), permission, 4); + if (sender instanceof PlayerEntity player) { + return Permissions.getPermissionValue(player, permission).orElseGet(() -> { + MinecraftServer server = player.getServer(); + if (server != null && server.isHost(player.getGameProfile())) { + return true; + } + + return player.hasPermissionLevel(4); + }); } else { return true; } diff --git a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java index 8ac0c7c..8737057 100644 --- a/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java +++ b/spark-forge/src/main/java/me/lucko/spark/forge/plugin/ForgeServerSparkPlugin.java @@ -78,6 +78,19 @@ public class ForgeServerSparkPlugin extends ForgeSparkPlugin implements Command< plugin.enable(); } + private static final PermissionResolver DEFAULT_PERMISSION_VALUE = (player, playerUUID, context) -> { + if (player == null) { + return false; + } + + MinecraftServer server = player.getServer(); + if (server != null && server.isSingleplayerOwner(player.getGameProfile())) { + return true; + } + + return player.hasPermissions(4); + }; + private final MinecraftServer server; private final ThreadDumper gameThreadDumper; private Map> registeredPermissions = Collections.emptyMap(); @@ -116,8 +129,6 @@ public class ForgeServerSparkPlugin extends ForgeSparkPlugin implements Command< @SubscribeEvent public void onPermissionGather(PermissionGatherEvent.Nodes e) { - PermissionResolver defaultValue = (player, playerUUID, context) -> player != null && player.hasPermissions(4); - // collect all possible permissions List permissions = this.platform.getCommands().stream() .map(me.lucko.spark.common.command.Command::primaryAlias) @@ -143,7 +154,7 @@ public class ForgeServerSparkPlugin extends ForgeSparkPlugin implements Command< continue; } - PermissionNode node = new PermissionNode<>("spark", permission, PermissionTypes.BOOLEAN, defaultValue); + PermissionNode node = new PermissionNode<>("spark", permission, PermissionTypes.BOOLEAN, DEFAULT_PERMISSION_VALUE); e.addNodes(node); builder.put(permissionString, node); } -- cgit