mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 22:10:18 +00:00
Report silent pregen failures
- Add PregenDiagnostics.probeFailed so capability probes log their name, class and message at debug instead of vanishing - Report the moonrise worker pool, mantle radius, engine access and Folia submit probe failures through that helper - Report async pregen mantle cleanup failures through IrisLogging.reportError with the trace instead of dropping them - Report a stranded plugin chunk ticket through IrisLogging.reportError so the leaked pin is visible - Emit the chunk load failure trace before the metrics snapshot so a failing snapshot can no longer silence it - Report the pregen cancellation and mantle handle probe failures in PregenMantleBackpressure - Report the Medieval engine resolution probe failure - Record the real Vector API initialization failure in SimdSupport and print the accurate kernel selection reason - Add PregenDiagnosticsTest, PregenSilentFailureContractTest and SimdSupportStatusTest
This commit is contained in:
1 parent
9d22a18a3a
commit
e030ac3be5
8 files changed
+513
-40
No files matched your search
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||||
|
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||||
|
*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package art.arcane.iris.core.pregenerator;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
|
|
||||||
|
public final class PregenDiagnostics {
|
||||||
|
private PregenDiagnostics() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void probeFailed(String probe, Throwable failure) {
|
||||||
|
IrisLogging.debug("Pregen probe " + describeProbe(probe) + " failed: " + describeFailure(failure));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String describeProbe(String probe) {
|
||||||
|
return probe == null || probe.isBlank() ? "unnamed" : probe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String describeFailure(Throwable failure) {
|
||||||
|
if (failure == null) {
|
||||||
|
return "no throwable reported";
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = failure.getMessage();
|
||||||
|
return message == null ? failure.getClass().getName() : failure.getClass().getName() + ": " + message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -170,7 +170,8 @@ public final class PregenMantleBackpressure {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
return cancelled.getAsBoolean();
|
return cancelled.getAsBoolean();
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
PregenDiagnostics.probeFailed("pregen cancellation state", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +179,8 @@ public final class PregenMantleBackpressure {
|
|||||||
private Mantle resolveMantle() {
|
private Mantle resolveMantle() {
|
||||||
try {
|
try {
|
||||||
return mantleSupplier.get();
|
return mantleSupplier.get();
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
PregenDiagnostics.probeFailed("mantle handle for backpressure", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-9
@@ -22,6 +22,7 @@ import art.arcane.iris.spi.IrisLogging;
|
|||||||
import art.arcane.iris.core.IrisPaperLikeBackendMode;
|
import art.arcane.iris.core.IrisPaperLikeBackendMode;
|
||||||
import art.arcane.iris.core.IrisRuntimeSchedulerMode;
|
import art.arcane.iris.core.IrisRuntimeSchedulerMode;
|
||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
|
import art.arcane.iris.core.pregenerator.PregenDiagnostics;
|
||||||
import art.arcane.iris.core.pregenerator.PregenListener;
|
import art.arcane.iris.core.pregenerator.PregenListener;
|
||||||
import art.arcane.iris.core.pregenerator.PregenMantleBackpressure;
|
import art.arcane.iris.core.pregenerator.PregenMantleBackpressure;
|
||||||
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
||||||
@@ -212,7 +213,8 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
if (coreThreads instanceof Thread[] threadsArray) {
|
if (coreThreads instanceof Thread[] threadsArray) {
|
||||||
return threadsArray.length;
|
return threadsArray.length;
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
PregenDiagnostics.probeFailed("moonrise worker pool size", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
@@ -238,7 +240,8 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
int resolved = radius > 0 ? Math.max(1, (int) Math.ceil(radius / 32.0)) : 2;
|
int resolved = radius > 0 ? Math.max(1, (int) Math.ceil(radius / 32.0)) : 2;
|
||||||
evictionWindowRegions = resolved;
|
evictionWindowRegions = resolved;
|
||||||
return resolved;
|
return resolved;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
PregenDiagnostics.probeFailed("mantle radius for eviction window", e);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,7 +434,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
private void unloadChunkSafely(int cx, int cz) {
|
private void unloadChunkSafely(int cx, int cz) {
|
||||||
try {
|
try {
|
||||||
world.removePluginChunkTicket(cx, cz, BukkitPlatform.plugin());
|
world.removePluginChunkTicket(cx, cz, BukkitPlatform.plugin());
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
IrisLogging.reportError("Async pregen could not release the plugin chunk ticket at " + cx + "," + cz
|
||||||
|
+ " in world " + world.getName() + "; the chunk stays pinned in memory.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -492,11 +497,12 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Chunk onChunkFutureFailure(int x, int z, Throwable throwable) {
|
private Chunk onChunkFutureFailure(int x, int z, Throwable throwable) {
|
||||||
try {
|
IrisLogging.reportError("Failed async pregen chunk load at " + x + "," + z + ".", throwable);
|
||||||
IrisLogging.warn("Failed async pregen chunk load at " + x + "," + z + ". " + metricsSnapshot());
|
|
||||||
|
|
||||||
IrisLogging.reportError(throwable);
|
try {
|
||||||
|
IrisLogging.warn("Async pregen state at the failed chunk " + x + "," + z + ". " + metricsSnapshot());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
PregenDiagnostics.probeFailed("pregen metrics snapshot", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -738,7 +744,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
if (engine != null) {
|
if (engine != null) {
|
||||||
try {
|
try {
|
||||||
engine.getMantle().cleanupChunksCoveredBy(x, z, true, listener::onChunkCleaned);
|
engine.getMantle().cleanupChunksCoveredBy(x, z, true, listener::onChunkCleaned);
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
IrisLogging.reportError("Async pregen mantle cleanup failed at chunk " + x + "," + z
|
||||||
|
+ " in world " + world.getName() + "; tectonic plates for that chunk stay resident.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -759,7 +767,8 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
metricsEngine = resolvedEngine;
|
metricsEngine = resolvedEngine;
|
||||||
}
|
}
|
||||||
return resolvedEngine;
|
return resolvedEngine;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
PregenDiagnostics.probeFailed("engine access for world " + world.getName(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1094,7 +1103,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
requestMonitoredChunkAsync(x, z)
|
requestMonitoredChunkAsync(x, z)
|
||||||
.whenComplete((chunk, throwable) -> completeChunk(x, z, listener, chunk, throwable));
|
.whenComplete((chunk, throwable) -> completeChunk(x, z, listener, chunk, throwable));
|
||||||
return;
|
return;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
PregenDiagnostics.probeFailed("folia direct chunk request at " + x + "," + z
|
||||||
|
+ ", falling back to a region task", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable regionTask = () -> {
|
Runnable regionTask = () -> {
|
||||||
|
|||||||
+3
-1
@@ -20,6 +20,7 @@ package art.arcane.iris.core.pregenerator.methods;
|
|||||||
|
|
||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
import art.arcane.iris.core.nms.INMS;
|
import art.arcane.iris.core.nms.INMS;
|
||||||
|
import art.arcane.iris.core.pregenerator.PregenDiagnostics;
|
||||||
import art.arcane.iris.core.pregenerator.PregenListener;
|
import art.arcane.iris.core.pregenerator.PregenListener;
|
||||||
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
|
||||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||||
@@ -102,7 +103,8 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
|||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
cachedEngine = cached;
|
cachedEngine = cached;
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable e) {
|
||||||
|
PregenDiagnostics.probeFailed("engine access for world " + world.getName(), e);
|
||||||
}
|
}
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ package art.arcane.iris.util.simd;
|
|||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public final class SimdSupport {
|
public final class SimdSupport {
|
||||||
private static final String VECTOR_MODULE = "jdk.incubator.vector";
|
private static final String VECTOR_MODULE = "jdk.incubator.vector";
|
||||||
private static final boolean MODULE_PRESENT = ModuleLayer.boot().findModule(VECTOR_MODULE).isPresent();
|
private static final boolean MODULE_PRESENT = ModuleLayer.boot().findModule(VECTOR_MODULE).isPresent();
|
||||||
|
private static final AtomicReference<Throwable> KERNEL_FAILURE = new AtomicReference<>();
|
||||||
|
private static final AtomicReference<Throwable> NOISE_KERNEL_FAILURE = new AtomicReference<>();
|
||||||
private static final SimdKernels KERNELS = selectKernels();
|
private static final SimdKernels KERNELS = selectKernels();
|
||||||
|
private static final NoiseKernels2D NOISE_KERNELS_2D = selectNoiseKernels2D();
|
||||||
|
|
||||||
private SimdSupport() {
|
private SimdSupport() {
|
||||||
}
|
}
|
||||||
@@ -15,6 +20,10 @@ public final class SimdSupport {
|
|||||||
return KERNELS;
|
return KERNELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static NoiseKernels2D noiseKernels2D() {
|
||||||
|
return NOISE_KERNELS_2D;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isVectorModulePresent() {
|
public static boolean isVectorModulePresent() {
|
||||||
return MODULE_PRESENT;
|
return MODULE_PRESENT;
|
||||||
}
|
}
|
||||||
@@ -23,13 +32,75 @@ public final class SimdSupport {
|
|||||||
return !(KERNELS instanceof ScalarSimdKernels);
|
return !(KERNELS instanceof ScalarSimdKernels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Throwable kernelInitializationFailure() {
|
||||||
|
return KERNEL_FAILURE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Throwable noiseKernelInitializationFailure() {
|
||||||
|
return NOISE_KERNEL_FAILURE.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String kernelStatus() {
|
||||||
|
return kernelStatus(isVectorized(), KERNELS.describe(), MODULE_PRESENT, KERNEL_FAILURE.get(), simdEnabledInSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String noiseKernelStatus() {
|
||||||
|
return noiseKernelStatus(!(NOISE_KERNELS_2D instanceof ScalarNoiseKernels2D), NOISE_KERNELS_2D.describe(),
|
||||||
|
MODULE_PRESENT, NOISE_KERNEL_FAILURE.get(), simdEnabledInSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
static String kernelStatus(boolean vectorized, String description, boolean modulePresent, Throwable failure, boolean settingEnabled) {
|
||||||
|
if (vectorized) {
|
||||||
|
return "vector kernels enabled (" + description + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modulePresent) {
|
||||||
|
return "scalar kernels active; add --add-modules " + VECTOR_MODULE + " to JVM flags to enable vectorized generation kernels";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failure != null) {
|
||||||
|
return "scalar kernels active; vector kernel initialization failed: " + describe(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settingEnabled) {
|
||||||
|
return "vector kernels disabled (performance.simdKernels=false)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "scalar kernels active; the Vector API reported no usable vector shape on this CPU";
|
||||||
|
}
|
||||||
|
|
||||||
|
static String noiseKernelStatus(boolean vectorized, String description, boolean modulePresent, Throwable failure, boolean settingEnabled) {
|
||||||
|
if (vectorized) {
|
||||||
|
return "2D noise vector kernels enabled (" + description + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modulePresent) {
|
||||||
|
return "2D noise scalar kernels active; the " + VECTOR_MODULE + " module is not on this JVM";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failure != null) {
|
||||||
|
return "2D noise scalar kernels active; vector kernel initialization failed: " + describe(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settingEnabled) {
|
||||||
|
return "2D noise vector kernels disabled (performance.simdKernels=false)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "2D noise scalar kernels active; this CPU has fewer than the 4 double lanes the vector kernel needs to pay for itself";
|
||||||
|
}
|
||||||
|
|
||||||
public static void install() {
|
public static void install() {
|
||||||
if (isVectorized()) {
|
IrisLogging.info("SIMD: " + kernelStatus());
|
||||||
IrisLogging.info("SIMD: vector kernels enabled (" + KERNELS.describe() + ")");
|
IrisLogging.info("SIMD: " + noiseKernelStatus());
|
||||||
} else if (!MODULE_PRESENT) {
|
|
||||||
IrisLogging.info("SIMD: scalar kernels active; add --add-modules " + VECTOR_MODULE + " to JVM flags to enable vectorized generation kernels");
|
Throwable failure = KERNEL_FAILURE.get();
|
||||||
} else {
|
if (failure != null) {
|
||||||
IrisLogging.info("SIMD: vector kernels disabled (performance.simdKernels=false)");
|
IrisLogging.reportError("Iris could not initialize its Vector API kernels; generation runs on the scalar kernels instead.", failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
Throwable noiseFailure = NOISE_KERNEL_FAILURE.get();
|
||||||
|
if (noiseFailure != null) {
|
||||||
|
IrisLogging.reportError("Iris could not initialize its Vector API 2D noise kernels; noise runs on the scalar kernels instead.", noiseFailure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +112,25 @@ public final class SimdSupport {
|
|||||||
try {
|
try {
|
||||||
return (SimdKernels) Class.forName("art.arcane.iris.util.simd.VectorSimdKernels").getDeclaredConstructor().newInstance();
|
return (SimdKernels) Class.forName("art.arcane.iris.util.simd.VectorSimdKernels").getDeclaredConstructor().newInstance();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
KERNEL_FAILURE.set(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NoiseKernels2D createVectorNoiseKernels2D() {
|
||||||
|
if (!MODULE_PRESENT) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> cls = Class.forName("art.arcane.iris.util.simd.VectorNoiseKernels2D");
|
||||||
|
boolean profitable = (boolean) cls.getMethod("profitable").invoke(null);
|
||||||
|
if (!profitable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (NoiseKernels2D) cls.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
NOISE_KERNEL_FAILURE.set(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,36 +144,25 @@ public final class SimdSupport {
|
|||||||
return vector == null ? new ScalarSimdKernels() : vector;
|
return vector == null ? new ScalarSimdKernels() : vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final NoiseKernels2D NOISE_KERNELS_2D = selectNoiseKernels2D();
|
|
||||||
|
|
||||||
public static NoiseKernels2D noiseKernels2D() {
|
|
||||||
return NOISE_KERNELS_2D;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NoiseKernels2D createVectorNoiseKernels2D() {
|
|
||||||
if (!MODULE_PRESENT) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Class<?> cls = Class.forName("art.arcane.iris.util.simd.VectorNoiseKernels2D");
|
|
||||||
boolean profitable = (boolean) cls.getMethod("profitable").invoke(null);
|
|
||||||
if (!profitable) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (NoiseKernels2D) cls.getDeclaredConstructor().newInstance();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NoiseKernels2D selectNoiseKernels2D() {
|
private static NoiseKernels2D selectNoiseKernels2D() {
|
||||||
if (!simdEnabledInSettings()) {
|
if (!simdEnabledInSettings()) {
|
||||||
return new ScalarNoiseKernels2D();
|
return new ScalarNoiseKernels2D();
|
||||||
}
|
}
|
||||||
|
|
||||||
NoiseKernels2D vector = createVectorNoiseKernels2D();
|
NoiseKernels2D vector = createVectorNoiseKernels2D();
|
||||||
return vector == null ? new ScalarNoiseKernels2D() : vector;
|
return vector == null ? new ScalarNoiseKernels2D() : vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String describe(Throwable failure) {
|
||||||
|
Throwable root = failure;
|
||||||
|
while (root.getCause() != null && root.getCause() != root) {
|
||||||
|
root = root.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
String message = root.getMessage();
|
||||||
|
return message == null ? root.getClass().getName() : root.getClass().getName() + ": " + message;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean simdEnabledInSettings() {
|
private static boolean simdEnabledInSettings() {
|
||||||
try {
|
try {
|
||||||
return IrisSettings.get().getPerformance().isSimdKernels();
|
return IrisSettings.get().getPerformance().isSimdKernels();
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package art.arcane.iris.core.pregenerator;
|
||||||
|
|
||||||
|
import art.arcane.iris.spi.IrisPlatform;
|
||||||
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
|
import art.arcane.iris.spi.LogLevel;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentMatchers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
public class PregenDiagnosticsTest {
|
||||||
|
private final List<String> emitted = new ArrayList<>();
|
||||||
|
private final List<LogLevel> levels = new ArrayList<>();
|
||||||
|
private IrisPlatform previousPlatform;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void captureLog() {
|
||||||
|
previousPlatform = IrisPlatforms.isBound() ? IrisPlatforms.get() : null;
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
emitted.clear();
|
||||||
|
levels.clear();
|
||||||
|
IrisPlatform platform = mock(IrisPlatform.class);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
levels.add(invocation.getArgument(0, LogLevel.class));
|
||||||
|
emitted.add(invocation.getArgument(1, String.class));
|
||||||
|
return null;
|
||||||
|
}).when(platform).log(ArgumentMatchers.any(LogLevel.class), ArgumentMatchers.anyString());
|
||||||
|
IrisPlatforms.bind(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void restorePlatform() {
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
if (previousPlatform != null) {
|
||||||
|
IrisPlatforms.bind(previousPlatform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aFailedProbeReportsItsNameClassAndMessageAtDebug() {
|
||||||
|
PregenDiagnostics.probeFailed("moonrise worker pool size", new IllegalStateException("no such field"));
|
||||||
|
|
||||||
|
assertEquals(1, emitted.size());
|
||||||
|
assertEquals(LogLevel.DEBUG, levels.get(0));
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains("moonrise worker pool size"));
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains(IllegalStateException.class.getName()));
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains("no such field"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aThrowableWithNoMessageStillReportsItsClass() {
|
||||||
|
PregenDiagnostics.probeFailed("engine access", new NoClassDefFoundError());
|
||||||
|
|
||||||
|
assertEquals(1, emitted.size());
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains(NoClassDefFoundError.class.getName()));
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains("engine access"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aMissingProbeNameAndThrowableStillProduceAReadableLine() {
|
||||||
|
PregenDiagnostics.probeFailed(null, null);
|
||||||
|
|
||||||
|
assertEquals(1, emitted.size());
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains("unnamed"));
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains("no throwable reported"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void percentSignsInAProbeNameAreNotTreatedAsFormatSpecifiers() {
|
||||||
|
PregenDiagnostics.probeFailed("heap at 95% headroom", new IllegalArgumentException("bad %s value"));
|
||||||
|
|
||||||
|
assertEquals(1, emitted.size());
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains("heap at 95% headroom"));
|
||||||
|
assertTrue(emitted.get(0), emitted.get(0).contains("bad %s value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
package art.arcane.iris.core.pregenerator;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class PregenSilentFailureContractTest {
|
||||||
|
@Test
|
||||||
|
public void noPregenSourceSwallowsAThrowableWithoutSayingAnything() throws IOException {
|
||||||
|
List<String> offenders = new ArrayList<>();
|
||||||
|
Path pregenRoot = sourceRoot().resolve("art/arcane/iris/core/pregenerator");
|
||||||
|
try (Stream<Path> sources = Files.walk(pregenRoot)) {
|
||||||
|
for (Path source : sources.filter(path -> path.toString().endsWith(".java")).toList()) {
|
||||||
|
String text = Files.readString(source);
|
||||||
|
if (text.contains("catch (Throwable ignored)") || text.contains("catch (Exception ignored)")) {
|
||||||
|
offenders.add(source.getFileName().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("silent catches on the pregen path: " + offenders, List.of(), offenders);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mantleCleanupFailureIsReportedWithItsStackTrace() throws IOException {
|
||||||
|
String cleanup = method(read("art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java"),
|
||||||
|
"private void cleanupMantleChunksCoveredBy(");
|
||||||
|
|
||||||
|
assertTrue(cleanup, cleanup.contains("IrisLogging.reportError("));
|
||||||
|
assertTrue(cleanup, cleanup.contains("catch (Throwable e)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aStrandedPluginChunkTicketIsReportedWithItsStackTrace() throws IOException {
|
||||||
|
String unload = method(read("art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java"),
|
||||||
|
"private void unloadChunkSafely(");
|
||||||
|
|
||||||
|
assertTrue(unload, unload.contains("removePluginChunkTicket"));
|
||||||
|
assertEquals(unload, 2, countOccurrences(unload, "IrisLogging.reportError("));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void capabilityProbesLogThroughTheNamedDebugHelperRatherThanReportingErrors() throws IOException {
|
||||||
|
String async = read("art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java");
|
||||||
|
|
||||||
|
assertTrue(method(async, "private int resolveWorkerPoolThreads()").contains("PregenDiagnostics.probeFailed("));
|
||||||
|
assertTrue(method(async, "private int evictionWindow()").contains("PregenDiagnostics.probeFailed("));
|
||||||
|
assertTrue(method(async, "private Engine resolveMetricsEngine()").contains("PregenDiagnostics.probeFailed("));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void theFoliaSubmitFallbackNamesTheFailureItIsFallingBackFrom() throws IOException {
|
||||||
|
String executor = method(read("art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java"),
|
||||||
|
"private class FoliaRegionExecutor");
|
||||||
|
|
||||||
|
assertTrue(executor, executor.contains("PregenDiagnostics.probeFailed("));
|
||||||
|
assertTrue(executor, executor.contains("falling back to a region task"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void backpressureProbesAnnounceWhyTheyFellBack() throws IOException {
|
||||||
|
String backpressure = read("art/arcane/iris/core/pregenerator/PregenMantleBackpressure.java");
|
||||||
|
|
||||||
|
assertTrue(method(backpressure, "private boolean isCancelled()").contains("PregenDiagnostics.probeFailed("));
|
||||||
|
assertTrue(method(backpressure, "private Mantle resolveMantle()").contains("PregenDiagnostics.probeFailed("));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void medievalEngineResolutionAnnouncesWhyItFellBack() throws IOException {
|
||||||
|
String medieval = read("art/arcane/iris/core/pregenerator/methods/MedievalPregenMethod.java");
|
||||||
|
|
||||||
|
assertTrue(method(medieval, "private Engine resolveEngine()").contains("PregenDiagnostics.probeFailed("));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int countOccurrences(String text, String needle) {
|
||||||
|
int count = 0;
|
||||||
|
int index = text.indexOf(needle);
|
||||||
|
while (index >= 0) {
|
||||||
|
count++;
|
||||||
|
index = text.indexOf(needle, index + needle.length());
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path sourceRoot() throws IOException {
|
||||||
|
Path current;
|
||||||
|
try {
|
||||||
|
current = Path.of(PregenSilentFailureContractTest.class.getProtectionDomain()
|
||||||
|
.getCodeSource()
|
||||||
|
.getLocation()
|
||||||
|
.toURI());
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IOException("Unable to resolve the core source root", e);
|
||||||
|
}
|
||||||
|
while (current != null && !Files.isDirectory(current.resolve("src/main/java"))) {
|
||||||
|
current = current.getParent();
|
||||||
|
}
|
||||||
|
if (current == null) {
|
||||||
|
throw new IOException("Unable to locate the core source root");
|
||||||
|
}
|
||||||
|
return current.resolve("src/main/java");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String read(String relativePath) throws IOException {
|
||||||
|
return Files.readString(sourceRoot().resolve(relativePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String method(String source, String signature) {
|
||||||
|
int start = source.indexOf(signature);
|
||||||
|
assertTrue("Missing source contract signature: " + signature, start >= 0);
|
||||||
|
int openBrace = source.indexOf('{', start);
|
||||||
|
assertTrue("Missing source contract method body: " + signature, openBrace >= 0);
|
||||||
|
int depth = 0;
|
||||||
|
for (int index = openBrace; index < source.length(); index++) {
|
||||||
|
char current = source.charAt(index);
|
||||||
|
if (current == '{') {
|
||||||
|
depth++;
|
||||||
|
} else if (current == '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth == 0) {
|
||||||
|
return source.substring(start, index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unclosed source contract method: " + signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package art.arcane.iris.util.simd;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.IrisSettings;
|
||||||
|
import art.arcane.iris.spi.IrisPlatform;
|
||||||
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
|
import art.arcane.iris.spi.LogLevel;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentMatchers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
public class SimdSupportStatusTest {
|
||||||
|
private final List<String> emitted = new ArrayList<>();
|
||||||
|
private IrisPlatform previousPlatform;
|
||||||
|
private IrisSettings previousSettings;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void captureLog() {
|
||||||
|
previousSettings = IrisSettings.settings;
|
||||||
|
IrisSettings.settings = new IrisSettings();
|
||||||
|
previousPlatform = IrisPlatforms.isBound() ? IrisPlatforms.get() : null;
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
emitted.clear();
|
||||||
|
IrisPlatform platform = mock(IrisPlatform.class);
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
emitted.add(invocation.getArgument(1, String.class));
|
||||||
|
return null;
|
||||||
|
}).when(platform).log(ArgumentMatchers.any(LogLevel.class), ArgumentMatchers.anyString());
|
||||||
|
IrisPlatforms.bind(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void restorePlatform() {
|
||||||
|
IrisPlatforms.unbind();
|
||||||
|
if (previousPlatform != null) {
|
||||||
|
IrisPlatforms.bind(previousPlatform);
|
||||||
|
}
|
||||||
|
IrisSettings.settings = previousSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void anInitializationFailureIsNamedInsteadOfBlamingTheSetting() {
|
||||||
|
String status = SimdSupport.kernelStatus(false, "scalar", true,
|
||||||
|
new ExceptionInInitializerError(new UnsupportedOperationException("no vector shape")), true);
|
||||||
|
|
||||||
|
assertFalse(status, status.contains("performance.simdKernels=false"));
|
||||||
|
assertTrue(status, status.contains(UnsupportedOperationException.class.getName()));
|
||||||
|
assertTrue(status, status.contains("no vector shape"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void anInitializationFailureOutranksADisabledSettingBecauseADisabledSettingNeverAttempts() {
|
||||||
|
String status = SimdSupport.kernelStatus(false, "scalar", true, new LinkageError("bad class"), false);
|
||||||
|
|
||||||
|
assertTrue(status, status.contains("bad class"));
|
||||||
|
assertFalse(status, status.contains("performance.simdKernels=false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aDisabledSettingIsReportedOnlyWhenNothingFailed() {
|
||||||
|
String status = SimdSupport.kernelStatus(false, "scalar", true, null, false);
|
||||||
|
|
||||||
|
assertTrue(status, status.contains("performance.simdKernels=false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void anAbsentVectorModuleIsReportedWithTheJvmFlagThatFixesIt() {
|
||||||
|
String status = SimdSupport.kernelStatus(false, "scalar", false, null, true);
|
||||||
|
|
||||||
|
assertTrue(status, status.contains("--add-modules jdk.incubator.vector"));
|
||||||
|
assertFalse(status, status.contains("performance.simdKernels=false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void anEnabledVectorKernelReportsItsShapeAndNoRemedy() {
|
||||||
|
String status = SimdSupport.kernelStatus(true, "4x64-bit lanes, S_256_BIT", true, null, true);
|
||||||
|
|
||||||
|
assertTrue(status, status.contains("4x64-bit lanes, S_256_BIT"));
|
||||||
|
assertFalse(status, status.contains("scalar kernels active"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aNarrowCpuIsReportedAsUnprofitableRatherThanDisabled() {
|
||||||
|
String status = SimdSupport.noiseKernelStatus(false, "scalar", true, null, true);
|
||||||
|
|
||||||
|
assertTrue(status, status.contains("4 double lanes"));
|
||||||
|
assertFalse(status, status.contains("performance.simdKernels=false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void installStatesTheLiveKernelSelectionForBothKernelSets() {
|
||||||
|
SimdSupport.install();
|
||||||
|
|
||||||
|
assertTrue(emitted.toString(), emitted.size() >= 2);
|
||||||
|
assertTrue(emitted.toString(), emitted.contains("SIMD: " + SimdSupport.kernelStatus()));
|
||||||
|
assertTrue(emitted.toString(), emitted.contains("SIMD: " + SimdSupport.noiseKernelStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void theLiveStatusNeverBlamesTheSettingWhileSimdKernelsAreEnabled() {
|
||||||
|
if (!IrisSettings.get().getPerformance().isSimdKernels()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(SimdSupport.kernelStatus(), SimdSupport.kernelStatus().contains("performance.simdKernels=false"));
|
||||||
|
assertFalse(SimdSupport.noiseKernelStatus(), SimdSupport.noiseKernelStatus().contains("performance.simdKernels=false"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user