From c8f1c10e6c661ecf949ab29e0e8efeaa13324670 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Fri, 22 Oct 2021 01:40:06 +0800 Subject: Add Performance Analysis --- .../me/shedaniel/rei/RoughlyEnoughItemsCore.java | 8 +- .../rei/RoughlyEnoughItemsCoreClient.java | 10 +- .../rei/impl/client/config/ConfigManagerImpl.java | 2 + .../client/config/entries/ReloadPluginsEntry.java | 5 +- .../client/gui/performance/PerformanceScreen.java | 193 +++++++++++++++++++++ .../gui/performance/entry/EntryListEntry.java | 71 ++++++++ .../gui/performance/entry/PerformanceEntry.java | 93 ++++++++++ .../performance/entry/SubCategoryListEntry.java | 177 +++++++++++++++++++ .../gui/screen/UncertainDisplayViewingScreen.java | 2 + .../logging/performance/PerformanceLogger.java | 56 ++++++ .../logging/performance/PerformanceLoggerImpl.java | 96 ++++++++++ .../rei/impl/common/plugins/PluginManagerImpl.java | 95 +++++++--- 12 files changed, 777 insertions(+), 31 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/EntryListEntry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/PerformanceEntry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/logging/performance/PerformanceLogger.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/logging/performance/PerformanceLoggerImpl.java (limited to 'runtime/src/main/java/me') diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java index 119841487..654552028 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCore.java @@ -55,6 +55,8 @@ import me.shedaniel.rei.impl.common.logging.FileLogger; import me.shedaniel.rei.impl.common.logging.Log4JLogger; import me.shedaniel.rei.impl.common.logging.Logger; import me.shedaniel.rei.impl.common.logging.MultiLogger; +import me.shedaniel.rei.impl.common.logging.performance.PerformanceLogger; +import me.shedaniel.rei.impl.common.logging.performance.PerformanceLoggerImpl; import me.shedaniel.rei.impl.common.plugins.PluginManagerImpl; import me.shedaniel.rei.impl.common.registry.RecipeManagerContextImpl; import me.shedaniel.rei.impl.common.transfer.MenuInfoRegistryImpl; @@ -75,6 +77,7 @@ public class RoughlyEnoughItemsCore { new FileLogger(Platform.getGameFolder().resolve("logs/rei.log")), new Log4JLogger(LogManager.getFormatterLogger("REI")) )); + public static final PerformanceLogger PERFORMANCE_LOGGER = new PerformanceLoggerImpl(); static { attachCommonInternals(); @@ -151,7 +154,10 @@ public class RoughlyEnoughItemsCore { if (Platform.getEnvironment() == Env.SERVER) { MutableLong lastReload = new MutableLong(-1); ReloadListenerRegistry.register(PackType.SERVER_DATA, (preparationBarrier, resourceManager, profilerFiller, profilerFiller2, executor, executor2) -> { - return preparationBarrier.wait(Unit.INSTANCE).thenRunAsync(() -> RoughlyEnoughItemsCore._reloadPlugins(null), executor2); + return preparationBarrier.wait(Unit.INSTANCE).thenRunAsync(() -> { + PERFORMANCE_LOGGER.clear(); + RoughlyEnoughItemsCore._reloadPlugins(null); + }, executor2); }); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java index 9f0b4552f..131592218 100644 --- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java +++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java @@ -272,8 +272,14 @@ public class RoughlyEnoughItemsCoreClient { final ResourceLocation recipeButtonTex = new ResourceLocation("textures/gui/recipe_button.png"); MutableLong startReload = new MutableLong(-1); MutableLong endReload = new MutableLong(-1); - PRE_UPDATE_RECIPES.register(recipeManager -> reloadPlugins(startReload, ReloadStage.START)); - ClientRecipeUpdateEvent.EVENT.register(recipeManager -> reloadPlugins(endReload, Platform.isFabric() ? ReloadStage.END : null)); + PRE_UPDATE_RECIPES.register(recipeManager -> { + RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.clear(); + reloadPlugins(startReload, ReloadStage.START); + }); + ClientRecipeUpdateEvent.EVENT.register(recipeManager -> { + if (!Platform.isFabric()) RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.clear(); + reloadPlugins(endReload, Platform.isFabric() ? ReloadStage.END : null); + }); ClientGuiEvent.INIT_POST.register((screen, access) -> { REIRuntimeImpl.getInstance().setPreviousScreen(screen); if (ConfigObject.getInstance().doesDisableRecipeBook() && screen instanceof AbstractContainerScreen) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java index f582734e2..e6eefa6b6 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigManagerImpl.java @@ -63,6 +63,7 @@ import me.shedaniel.rei.impl.client.entry.filtering.FilteringRule; import me.shedaniel.rei.impl.client.entry.filtering.rules.ManualFilteringRule; import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl; import me.shedaniel.rei.impl.client.gui.credits.CreditsScreen; +import me.shedaniel.rei.impl.client.gui.performance.entry.PerformanceEntry; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.ChatFormatting; @@ -340,6 +341,7 @@ public class ConfigManagerImpl implements ConfigManager { builder.setGlobalizedExpanded(false); if (Minecraft.getInstance().getConnection() != null && Minecraft.getInstance().getConnection().getRecipeManager() != null) { builder.getOrCreateCategory(new TranslatableComponent("config.roughlyenoughitems.advanced")).getEntries().add(0, new ReloadPluginsEntry(220)); + builder.getOrCreateCategory(new TranslatableComponent("config.roughlyenoughitems.advanced")).getEntries().add(0, new PerformanceEntry(220)); } return builder.setAfterInitConsumer(screen -> { TextListEntry feedbackEntry = ConfigEntryBuilder.create().startTextDescription( diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ReloadPluginsEntry.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ReloadPluginsEntry.java index a96680983..6452e883c 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ReloadPluginsEntry.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/ReloadPluginsEntry.java @@ -48,7 +48,10 @@ import java.util.Optional; @ApiStatus.Internal public class ReloadPluginsEntry extends AbstractConfigListEntry { private int width; - private AbstractWidget buttonWidget = new Button(0, 0, 0, 20, NarratorChatListener.NO_TITLE, button -> RoughlyEnoughItemsCoreClient.reloadPlugins(null, null)) { + private AbstractWidget buttonWidget = new Button(0, 0, 0, 20, NarratorChatListener.NO_TITLE, button -> { + RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.clear(); + RoughlyEnoughItemsCoreClient.reloadPlugins(null, null); + }) { @Override public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { if (PluginManager.areAnyReloading()) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java new file mode 100644 index 000000000..92bfb78be --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/PerformanceScreen.java @@ -0,0 +1,193 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.gui.performance; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.datafixers.util.Pair; +import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget; +import me.shedaniel.rei.RoughlyEnoughItemsCore; +import me.shedaniel.rei.api.common.plugins.REIPlugin; +import me.shedaniel.rei.api.common.plugins.REIPluginProvider; +import me.shedaniel.rei.impl.client.gui.performance.entry.EntryListEntry; +import me.shedaniel.rei.impl.client.gui.performance.entry.SubCategoryListEntry; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextColor; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.util.FormattedCharSequence; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static java.util.concurrent.TimeUnit.*; + +@Environment(EnvType.CLIENT) +public class PerformanceScreen extends Screen { + private Screen parent; + + public PerformanceScreen(Screen parent) { + super(new TranslatableComponent("text.rei.performance")); + this.parent = parent; + } + + private PerformanceEntryListWidget list; + + /* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + public static FormattedCharSequence formatTime(long time, boolean total) { + TimeUnit unit = chooseUnit(time); + double value = (double) time / NANOSECONDS.convert(1, unit); + return new TextComponent(String.format(Locale.ROOT, "%.4g", value) + " " + abbreviate(unit)) + .withStyle(style -> style.withColor(TextColor.fromRgb(chooseColor(MILLISECONDS.convert(time, NANOSECONDS), total)))) + .getVisualOrderText(); + } + + private static int chooseColor(long time, boolean total) { + if (time > (total ? 2500 : 1000)) { + return 0xff5555; + } else if (time > (total ? 700 : 300)) { + return 0xffa600; + } else if (time > (total ? 200 : 100)) { + return 0xfff017; + } + return 0x12ff22; + } + + private static TimeUnit chooseUnit(long nanos) { + if (DAYS.convert(nanos, NANOSECONDS) > 0) { + return DAYS; + } + if (HOURS.convert(nanos, NANOSECONDS) > 0) { + return HOURS; + } + if (MINUTES.convert(nanos, NANOSECONDS) > 0) { + return MINUTES; + } + if (SECONDS.convert(nanos, NANOSECONDS) > 0) { + return SECONDS; + } + if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) { + return MILLISECONDS; + } + if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) { + return MICROSECONDS; + } + return NANOSECONDS; + } + + private static String abbreviate(TimeUnit unit) { + switch (unit) { + case NANOSECONDS: + return "ns"; + case MICROSECONDS: + return "\u03bcs"; // μs + case MILLISECONDS: + return "ms"; + case SECONDS: + return "s"; + case MINUTES: + return "min"; + case HOURS: + return "h"; + case DAYS: + return "d"; + default: + throw new AssertionError(); + } + } + + @Override + public void init() { + { + Component backText = new TextComponent("↩ ").append(new TranslatableComponent("gui.back")); + addRenderableWidget(new Button(4, 4, Minecraft.getInstance().font.width(backText) + 10, 20, backText, button -> { + minecraft.setScreen(parent); + this.parent = null; + })); + } + list = new PerformanceEntryListWidget(); + RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.getStages().forEach((stage, inner) -> { + List entries = new ArrayList<>(); + inner.times().forEach((obj, time) -> { + entries.add(new EntryListEntry(new TextComponent(obj instanceof Pair ? ((Pair, REIPlugin>) obj).getFirst().getPluginProviderName() : Objects.toString(obj)), time)); + }); + long separateTime = inner.times().values().stream().collect(Collectors.summarizingLong(value -> value)).getSum(); + if ((inner.totalNano() - separateTime) > 1000000) { + entries.add(new EntryListEntry(new TextComponent("Miscellaneous Operations"), inner.totalNano() - separateTime)); + } + entries.sort(Comparator.comparingLong(value -> value.time).reversed()); + list.addItem(new SubCategoryListEntry(new TextComponent(stage), (List) (List) entries, inner.totalNano(), false)); + }); + addWidget(list); + } + + @Override + public void render(PoseStack poses, int mouseX, int mouseY, float delta) { + renderDirtBackground(0); + list.render(poses, mouseX, mouseY, delta); + this.font.drawShadow(poses, this.title.getVisualOrderText(), this.width / 2.0F - this.font.width(this.title) / 2.0F, 12.0F, -1); + super.render(poses, mouseX, mouseY, delta); + } + + public static abstract class PerformanceEntry extends DynamicElementListWidget.ElementEntry { + + } + + private class PerformanceEntryListWidget extends DynamicElementListWidget { + public PerformanceEntryListWidget() {super(PerformanceScreen.this.minecraft, PerformanceScreen.this.width, PerformanceScreen.this.height, 30, PerformanceScreen.this.height, GuiComponent.BACKGROUND_LOCATION);} + + @Override + public int getItemWidth() { + return width; + } + + @Override + public int addItem(PerformanceEntry item) { + return super.addItem(item); + } + + @Override + protected int getScrollbarPosition() { + return width - 6; + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/EntryListEntry.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/EntryListEntry.java new file mode 100644 index 000000000..5c2db0471 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/EntryListEntry.java @@ -0,0 +1,71 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.gui.performance.entry; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.rei.impl.client.gui.performance.PerformanceScreen; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; + +import java.util.Collections; +import java.util.List; + +@Environment(EnvType.CLIENT) +public class EntryListEntry extends PerformanceScreen.PerformanceEntry { + private final Component name; + public final long time; + + public EntryListEntry(Component name, long time) { + this.name = name; + this.time = time; + } + + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + Minecraft.getInstance().font.drawShadow(matrices, this.name.getVisualOrderText(), (float) x, (float) (y + 6), -1); + FormattedCharSequence timeText = PerformanceScreen.formatTime(time, false); + Minecraft.getInstance().font.drawShadow(matrices, timeText, (float) x + entryWidth - 6 - 4 - Minecraft.getInstance().font.width(timeText), (float) (y + 6), -1); + } + + @Override + public int getItemHeight() { + return 24; + } + + @Override + public List children() { + return Collections.emptyList(); + } + + @Override + public List narratables() { + return Collections.emptyList(); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/PerformanceEntry.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/PerformanceEntry.java new file mode 100644 index 000000000..99b84ef5d --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/PerformanceEntry.java @@ -0,0 +1,93 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.gui.performance.entry; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.api.AbstractConfigListEntry; +import me.shedaniel.rei.impl.client.gui.performance.PerformanceScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.chat.NarratorChatListener; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.util.Unit; +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; +import java.util.Optional; + +@ApiStatus.Internal +public class PerformanceEntry extends AbstractConfigListEntry { + private int width; + private AbstractWidget buttonWidget = new Button(0, 0, 0, 20, NarratorChatListener.NO_TITLE, button -> { + Minecraft.getInstance().setScreen(new PerformanceScreen(Minecraft.getInstance().screen)); + }); + private List children = ImmutableList.of(buttonWidget); + + public PerformanceEntry(int width) { + super(NarratorChatListener.NO_TITLE, false); + this.width = width; + buttonWidget.setMessage(new TranslatableComponent("text.rei.performance")); + } + + @Override + public Unit getValue() { + return Unit.INSTANCE; + } + + @Override + public Optional getDefaultValue() { + return Optional.of(Unit.INSTANCE); + } + + @Override + public void save() { + + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) { + super.render(matrices, index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta); + Window window = Minecraft.getInstance().getWindow(); + this.buttonWidget.active = this.isEditable(); + this.buttonWidget.y = y; + this.buttonWidget.x = x + entryWidth / 2 - width / 2; + this.buttonWidget.setWidth(width); + this.buttonWidget.render(matrices, mouseX, mouseY, delta); + } + + @Override + public List children() { + return children; + } + + @Override + public List narratables() { + return children; + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java new file mode 100644 index 000000000..dfb63d959 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/performance/entry/SubCategoryListEntry.java @@ -0,0 +1,177 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.gui.performance.entry; + +import com.google.common.collect.Lists; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import me.shedaniel.clothconfig2.api.Expandable; +import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.impl.client.gui.performance.PerformanceScreen; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.FormattedCharSequence; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +@Environment(EnvType.CLIENT) +public class SubCategoryListEntry extends PerformanceScreen.PerformanceEntry implements Expandable { + private static final ResourceLocation CONFIG_TEX = new ResourceLocation("cloth-config2", "textures/gui/cloth_config.png"); + private final List entries; + private final CategoryLabelWidget widget; + private final List children; + private final Component name; + private final long totalTime; + private boolean expanded; + + public SubCategoryListEntry(Component name, List entries, long totalTime, boolean defaultExpanded) { + this.name = name; + this.entries = entries; + this.totalTime = totalTime; + this.expanded = defaultExpanded; + this.widget = new CategoryLabelWidget(); + this.children = Lists.newArrayList(new GuiEventListener[]{this.widget}); + this.children.addAll(entries); + } + + @Override + public boolean isExpanded() { + return this.expanded; + } + + @Override + public void setExpanded(boolean expanded) { + this.expanded = expanded; + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isHovered, float delta) { + RenderSystem.setShaderTexture(0, CONFIG_TEX); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + this.widget.rectangle.x = x + 3; + this.widget.rectangle.y = y; + this.widget.rectangle.width = entryWidth - 6; + this.widget.rectangle.height = 24; + this.blit(matrices, x + 3, y + 5, 24, (this.widget.rectangle.contains(mouseX, mouseY) ? 18 : 0) + (this.expanded ? 9 : 0), 9, 9); + Minecraft.getInstance().font.drawShadow(matrices, this.name.getVisualOrderText(), (float) x + 3 + 15, (float) (y + 6), this.widget.rectangle.contains(mouseX, mouseY) ? -1638890 : -1); + + for (PerformanceScreen.PerformanceEntry performanceEntry : this.entries) { + performanceEntry.setParent(this.getParent()); + } + + if (this.expanded) { + int yy = y + 24; + + PerformanceScreen.PerformanceEntry entry; + for (Iterator iterator = this.entries.iterator(); iterator.hasNext(); yy += entry.getItemHeight()) { + entry = iterator.next(); + entry.render(matrices, -1, yy, x + 3 + 15, entryWidth - 15 - 3, entry.getItemHeight(), mouseX, mouseY, isHovered && this.getFocused() == entry, delta); + } + } + FormattedCharSequence timeText = PerformanceScreen.formatTime(totalTime, true); + Minecraft.getInstance().font.drawShadow(matrices, timeText, (float) x + entryWidth - 6 - 4 - Minecraft.getInstance().font.width(timeText), (float) (y + 6), -1); + } + + @Override + public int getMorePossibleHeight() { + if (!this.expanded) { + return -1; + } else { + List list = new ArrayList(); + int i = 24; + + for (PerformanceScreen.PerformanceEntry entry : this.entries) { + i += entry.getItemHeight(); + if (entry.getMorePossibleHeight() >= 0) { + list.add(i + entry.getMorePossibleHeight()); + } + } + + list.add(i); + return list.stream().max(Integer::compare).orElse(0) - this.getItemHeight(); + } + } + + public Rectangle getEntryArea(int x, int y, int entryWidth, int entryHeight) { + this.widget.rectangle.x = x; + this.widget.rectangle.y = y; + this.widget.rectangle.width = entryWidth; + this.widget.rectangle.height = 24; + return new Rectangle(this.getParent().left, y, this.getParent().right - this.getParent().left, 20); + } + + @Override + public int getItemHeight() { + if (!this.expanded) { + return 24; + } else { + int i = 24; + + PerformanceScreen.PerformanceEntry entry; + for (Iterator iterator = this.entries.iterator(); iterator.hasNext(); i += entry.getItemHeight()) { + entry = iterator.next(); + } + + return i; + } + } + + @Override + public List children() { + return this.expanded ? this.children : Collections.singletonList(this.widget); + } + + @Override + public List narratables() { + return Collections.emptyList(); + } + + public class CategoryLabelWidget implements GuiEventListener { + private final Rectangle rectangle = new Rectangle(); + + public CategoryLabelWidget() { + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (this.rectangle.contains(mouseX, mouseY)) { + SubCategoryListEntry.this.expanded = !SubCategoryListEntry.this.expanded; + Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + return true; + } else { + return false; + } + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/UncertainDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/UncertainDisplayViewingScreen.java index 1ec5aa3f5..5f98de662 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/UncertainDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/UncertainDisplayViewingScreen.java @@ -34,6 +34,7 @@ import me.shedaniel.clothconfig2.gui.widget.DynamicNewSmoothScrollingEntryListWi import me.shedaniel.clothconfig2.impl.EasingMethod; import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.RoughlyEnoughItemsCore; import me.shedaniel.rei.RoughlyEnoughItemsCoreClient; import me.shedaniel.rei.api.client.ClientHelper; import me.shedaniel.rei.api.client.REIRuntime; @@ -146,6 +147,7 @@ public class UncertainDisplayViewingScreen extends Screen { } else if (allModsUsingJEI != null && jeiEnabled) { ConfigManagerImpl.getInstance().getConfig().setJEICompatibilityLayerEnabled(jeiEnabled); ConfigManager.getInstance().saveConfig(); + RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.clear(); RoughlyEnoughItemsCoreClient.reloadPlugins(null, null); Minecraft.getInstance().setScreen(new ConfigReloadingScreen(() -> callback.accept(original))); } else { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/logging/performance/PerformanceLogger.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/logging/performance/PerformanceLogger.java new file mode 100644 index 000000000..7dca73ca7 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/logging/performance/PerformanceLogger.java @@ -0,0 +1,56 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.common.logging.performance; + +import com.mojang.datafixers.util.Pair; +import me.shedaniel.rei.api.common.plugins.REIPlugin; +import me.shedaniel.rei.api.common.plugins.REIPluginProvider; + +import java.util.Map; + +public interface PerformanceLogger { + Plugin stage(String stage); + + void clear(); + + Map getStages(); + + interface Plugin extends AutoCloseable { + Inner stage(String stage); + + Inner plugin(Pair, REIPlugin> plugin); + + long totalNano(); + + @Override + void close(); + + Map times(); + + interface Inner extends AutoCloseable { + @Override + void close(); + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/logging/performance/PerformanceLoggerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/logging/performance/PerformanceLoggerImpl.java new file mode 100644 index 000000000..daae98b6b --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/logging/performance/PerformanceLoggerImpl.java @@ -0,0 +1,96 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.common.logging.performance; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.Maps; +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import me.shedaniel.rei.api.common.plugins.REIPlugin; +import me.shedaniel.rei.api.common.plugins.REIPluginProvider; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class PerformanceLoggerImpl implements PerformanceLogger { + private final Map stages = Maps.newLinkedHashMap(); + + @Override + public Plugin stage(String stage) { + PluginImpl plugin = stages.computeIfAbsent(stage, $ -> new PluginImpl()); + plugin.stopwatch.start(); + return plugin; + } + + @Override + public Map getStages() { + return (Map) (Map) stages; + } + + private static class PluginImpl implements Plugin { + private final Stopwatch stopwatch = Stopwatch.createUnstarted(); + private long totalTime = 0; + private Object2LongMap times = new Object2LongLinkedOpenHashMap<>(); + + @Override + public Inner stage(String stage) { + Stopwatch s = Stopwatch.createStarted(); + return () -> { + s.stop(); + times.put(stage, times.getOrDefault(stage, 0) + s.elapsed(TimeUnit.NANOSECONDS)); + }; + } + + @Override + public Inner plugin(Pair, REIPlugin> plugin) { + Stopwatch s = Stopwatch.createStarted(); + return () -> { + s.stop(); + times.put(plugin, times.getOrDefault(plugin, 0) + s.elapsed(TimeUnit.NANOSECONDS)); + }; + } + + @Override + public void close() { + stopwatch.stop(); + totalTime += stopwatch.elapsed(TimeUnit.NANOSECONDS); + } + + @Override + public long totalNano() { + return totalTime; + } + + @Override + public Map times() { + return times; + } + } + + @Override + public void clear() { + stages.clear(); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java index d49081816..71b1cb716 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java @@ -26,6 +26,7 @@ package me.shedaniel.rei.impl.common.plugins; import com.google.common.base.Stopwatch; import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; +import com.mojang.datafixers.util.Pair; import dev.architectury.platform.Platform; import dev.architectury.utils.Env; import dev.architectury.utils.EnvExecutor; @@ -38,6 +39,7 @@ import me.shedaniel.rei.api.common.plugins.REIPluginProvider; import me.shedaniel.rei.api.common.registry.ReloadStage; import me.shedaniel.rei.api.common.registry.Reloadable; import me.shedaniel.rei.api.common.util.CollectionUtils; +import me.shedaniel.rei.impl.common.logging.performance.PerformanceLogger; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.server.MinecraftServer; @@ -122,6 +124,30 @@ public class PluginManagerImpl

> implements PluginManager< return FluentIterable.concat(Iterables.transform(plugins, REIPluginProvider::provide)); } + private static class PluginWrapper

> { + private final P plugin; + private final REIPluginProvider

provider; + + public PluginWrapper(P plugin, REIPluginProvider

provider) { + this.plugin = plugin; + this.provider = provider; + } + + public double getPriority() { + return plugin.getPriority(); + } + + public String getPluginProviderName() { + return provider.getPluginProviderName(); + } + } + + @SuppressWarnings("RedundantTypeArguments") + public FluentIterable> getPluginWrapped() { + return FluentIterable.>concat(Iterables., Iterable>>transform(plugins, input -> Iterables.>transform(input.provide(), + plugin -> new PluginWrapper(plugin, input)))); + } + private static class SectionClosable implements Closeable { private ReloadStage stage; private MutablePair sectionData; @@ -147,18 +173,18 @@ public class PluginManagerImpl

> implements PluginManager< return new SectionClosable(stage, section); } - private void pluginSection(ReloadStage stage, String sectionName, List

list, @Nullable Reloadable reloadable, Consumer

consumer) { - for (P plugin : list) { - try (SectionClosable section = section(stage, sectionName + plugin.getPluginProviderName() + "/")) { - if (reloadable == null || !plugin.shouldBeForcefullyDoneOnMainThread(reloadable)) { - consumer.accept(plugin); + private void pluginSection(ReloadStage stage, String sectionName, List> list, @Nullable Reloadable reloadable, Consumer> consumer) { + for (PluginWrapper

wrapper : list) { + try (SectionClosable section = section(stage, sectionName + wrapper.getPluginProviderName() + "/")) { + if (reloadable == null || !wrapper.plugin.shouldBeForcefullyDoneOnMainThread(reloadable)) { + consumer.accept(wrapper); } else if (Platform.getEnvironment() == Env.CLIENT) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> queueExecutionClient(() -> consumer.accept(plugin))); + EnvExecutor.runInEnv(Env.CLIENT, () -> () -> queueExecutionClient(() -> consumer.accept(wrapper))); } else { - queueExecution(() -> consumer.accept(plugin)); + queueExecution(() -> consumer.accept(wrapper)); } } catch (Throwable throwable) { - RoughlyEnoughItemsCore.LOGGER.error(plugin.getPluginProviderName() + " plugin failed to " + sectionName + "!", throwable); + RoughlyEnoughItemsCore.LOGGER.error(wrapper.getPluginProviderName() + " plugin failed to " + sectionName + "!", throwable); } } } @@ -176,26 +202,32 @@ public class PluginManagerImpl

> implements PluginManager< @Override public void pre(ReloadStage stage) { - List

plugins = new ArrayList<>(getPlugins().toList()); - plugins.sort(Comparator.comparingDouble(P::getPriority).reversed()); + List> plugins = new ArrayList<>(getPluginWrapped().toList()); + plugins.sort(Comparator.comparingDouble(PluginWrapper

::getPriority).reversed()); Collections.reverse(plugins); - try (SectionClosable preRegister = section(stage, "pre-register/")) { + try (SectionClosable preRegister = section(stage, "pre-register/"); + PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Pre Registration")) { pluginSection(stage, "pre-register/", plugins, null, plugin -> { - plugin.preRegister(); - ((REIPlugin

) plugin).preStage(this, stage); + try (PerformanceLogger.Plugin.Inner inner = perfLogger.plugin(new Pair<>(plugin.provider, plugin.plugin))) { + plugin.plugin.preRegister(); + ((REIPlugin

) plugin.plugin).preStage(this, stage); + } }); } } @Override public void post(ReloadStage stage) { - List

plugins = new ArrayList<>(getPlugins().toList()); - plugins.sort(Comparator.comparingDouble(P::getPriority).reversed()); + List> plugins = new ArrayList<>(getPluginWrapped().toList()); + plugins.sort(Comparator.comparingDouble(PluginWrapper

::getPriority).reversed()); Collections.reverse(plugins); - try (SectionClosable postRegister = section(stage, "post-register/")) { + try (SectionClosable postRegister = section(stage, "post-register/"); + PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Post Registration")) { pluginSection(stage, "post-register/", plugins, null, plugin -> { - plugin.postRegister(); - ((REIPlugin

) plugin).postStage(this, stage); + try (PerformanceLogger.Plugin.Inner inner = perfLogger.plugin(new Pair<>(plugin.provider, plugin.plugin))) { + plugin.plugin.postRegister(); + ((REIPlugin

) plugin.plugin).postStage(this, stage); + } }); } } @@ -212,10 +244,12 @@ public class PluginManagerImpl

> implements PluginManager< reloading = true; long startTime = Util.getMillis(); - try (SectionClosable startReloadAll = section(stage, "start-reload/")) { + try (SectionClosable startReloadAll = section(stage, "start-reload/"); + PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Reload Initialization")) { for (Reloadable

reloadable : reloadables) { Class reloadableClass = reloadable.getClass(); - try (SectionClosable startReload = section(stage, "start-reload/" + name(reloadableClass) + "/")) { + try (SectionClosable startReload = section(stage, "start-reload/" + name(reloadableClass) + "/"); + PerformanceLogger.Plugin.Inner inner = perfLogger.stage(name(reloadableClass))) { reloadable.startReload(stage); } catch (Throwable throwable) { throwable.printStackTrace(); @@ -223,22 +257,29 @@ public class PluginManagerImpl

> implements PluginManager< } } - List

plugins = new ArrayList<>(getPlugins().toList()); - plugins.sort(Comparator.comparingDouble(P::getPriority).reversed()); - RoughlyEnoughItemsCore.LOGGER.info("Reloading Plugin Manager [%s] stage [%s], registered %d plugins: %s", name(pluginClass), stage.toString(), plugins.size(), CollectionUtils.mapAndJoinToString(plugins, REIPlugin::getPluginProviderName, ", ")); + List> plugins = new ArrayList<>(getPluginWrapped().toList()); + plugins.sort(Comparator.comparingDouble(PluginWrapper

::getPriority).reversed()); + RoughlyEnoughItemsCore.LOGGER.info("Reloading Plugin Manager [%s] stage [%s], registered %d plugins: %s", name(pluginClass), stage.toString(), plugins.size(), CollectionUtils.mapAndJoinToString(plugins, PluginWrapper::getPluginProviderName, ", ")); Collections.reverse(plugins); for (Reloadable

reloadable : getReloadables()) { Class reloadableClass = reloadable.getClass(); - try (SectionClosable reloadablePlugin = section(stage, "reloadable-plugin/" + name(reloadableClass) + "/")) { - pluginSection(stage, "reloadable-plugin/" + name(reloadableClass) + "/", plugins, reloadable, plugin -> reloadable.acceptPlugin(plugin, stage)); + try (SectionClosable reloadablePlugin = section(stage, "reloadable-plugin/" + name(reloadableClass) + "/"); + PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage(name(reloadableClass))) { + pluginSection(stage, "reloadable-plugin/" + name(reloadableClass) + "/", plugins, reloadable, plugin -> { + try (PerformanceLogger.Plugin.Inner inner = perfLogger.plugin(new Pair<>(plugin.provider, plugin.plugin))) { + reloadable.acceptPlugin(plugin.plugin, stage); + } + }); } } - try (SectionClosable endReloadAll = section(stage, "end-reload/")) { + try (SectionClosable endReloadAll = section(stage, "end-reload/"); + PerformanceLogger.Plugin perfLogger = RoughlyEnoughItemsCore.PERFORMANCE_LOGGER.stage("Reload Finalization")) { for (Reloadable

reloadable : reloadables) { Class reloadableClass = reloadable.getClass(); - try (SectionClosable endReload = section(stage, "end-reload/" + name(reloadableClass) + "/")) { + try (SectionClosable endReload = section(stage, "end-reload/" + name(reloadableClass) + "/"); + PerformanceLogger.Plugin.Inner inner = perfLogger.stage(name(reloadableClass))) { reloadable.endReload(stage); } catch (Throwable throwable) { throwable.printStackTrace(); -- cgit