render stats and live limits: photo counters and phase timings (queue, snapshot, trace, dither), running/queued load, render limits applicable to the running pool (trace parallelism, concurrent renders, queue, timeout), debug log switch with stack traces of failed renders
This commit is contained in:
1 parent
a082271f28
commit
be94c33ea6
3 files changed
+157
-6
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
package eu.mhsl.minecraft.pixelpics.render;
|
package eu.mhsl.minecraft.pixelpics.render;
|
||||||
|
|
||||||
|
import eu.mhsl.minecraft.pixelpics.utils.DebugLog;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -22,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.logging.Level;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.plugin.IllegalPluginAccessException;
|
import org.bukkit.plugin.IllegalPluginAccessException;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
@@ -66,12 +68,16 @@ public final class RenderManager {
|
|||||||
private final ChunkHolds chunkHolds;
|
private final ChunkHolds chunkHolds;
|
||||||
private volatile boolean shuttingDown = false;
|
private volatile boolean shuttingDown = false;
|
||||||
private final AtomicInteger inFlight = new AtomicInteger();
|
private final AtomicInteger inFlight = new AtomicInteger();
|
||||||
private final int capacity;
|
private final AtomicInteger running = new AtomicInteger();
|
||||||
private final long timeoutMillis;
|
private final RenderStats stats = new RenderStats();
|
||||||
|
private volatile int capacity;
|
||||||
|
private volatile int maxConcurrent;
|
||||||
|
private volatile long timeoutMillis;
|
||||||
|
|
||||||
public RenderManager(Plugin plugin, int threads, int maxConcurrent, int queueSize, int timeoutSeconds) {
|
public RenderManager(Plugin plugin, int threads, int maxConcurrent, int queueSize, int timeoutSeconds) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.capacity = maxConcurrent + queueSize;
|
this.capacity = maxConcurrent + queueSize;
|
||||||
|
this.maxConcurrent = maxConcurrent;
|
||||||
this.timeoutMillis = Math.max(1L, timeoutSeconds) * 1000L;
|
this.timeoutMillis = Math.max(1L, timeoutSeconds) * 1000L;
|
||||||
this.tracePool = new ForkJoinPool(threads, lowPriorityForkJoinFactory(), null, false);
|
this.tracePool = new ForkJoinPool(threads, lowPriorityForkJoinFactory(), null, false);
|
||||||
this.dispatcher = new ThreadPoolExecutor(
|
this.dispatcher = new ThreadPoolExecutor(
|
||||||
@@ -91,6 +97,50 @@ public final class RenderManager {
|
|||||||
this.chunkHolds = new ChunkHolds(plugin);
|
this.chunkHolds = new ChunkHolds(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies new limits to the running manager ({@code /pixelpic reload}): trace threads, concurrent renders, queue
|
||||||
|
* size and timeout. Running and queued jobs are kept; a smaller capacity only rejects new photos until enough
|
||||||
|
* are done.
|
||||||
|
*/
|
||||||
|
public void configure(int threads, int maxConcurrent, int queueSize, int timeoutSeconds) {
|
||||||
|
tracePool.setParallelism(threads);
|
||||||
|
if (maxConcurrent >= dispatcher.getMaximumPoolSize()) {
|
||||||
|
dispatcher.setMaximumPoolSize(maxConcurrent);
|
||||||
|
dispatcher.setCorePoolSize(maxConcurrent);
|
||||||
|
} else {
|
||||||
|
dispatcher.setCorePoolSize(maxConcurrent);
|
||||||
|
dispatcher.setMaximumPoolSize(maxConcurrent);
|
||||||
|
}
|
||||||
|
this.maxConcurrent = maxConcurrent;
|
||||||
|
this.capacity = maxConcurrent + queueSize;
|
||||||
|
this.timeoutMillis = Math.max(1L, timeoutSeconds) * 1000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Photos rendering now (incl. waiting for their snapshot), photos waiting for a worker, and the limits. */
|
||||||
|
public record Load(int running, int queued, int maxConcurrent, int queueSize, int threads, long timeoutSeconds) {}
|
||||||
|
|
||||||
|
public Load load() {
|
||||||
|
int total = inFlight.get();
|
||||||
|
int run = Math.min(running.get(), total);
|
||||||
|
return new Load(
|
||||||
|
run,
|
||||||
|
Math.max(0, total - run),
|
||||||
|
maxConcurrent,
|
||||||
|
capacity - maxConcurrent,
|
||||||
|
tracePool.getParallelism(),
|
||||||
|
timeoutMillis / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Counters and timings of the photos since the start. */
|
||||||
|
public RenderStats stats() {
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True while no job is running, queued or waiting for its callback. */
|
||||||
|
public boolean isIdle() {
|
||||||
|
return jobs.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
/** Keeps the chunks of queued photos loaded until their snapshot (main thread). */
|
/** Keeps the chunks of queued photos loaded until their snapshot (main thread). */
|
||||||
public ChunkHolds chunkHolds() {
|
public ChunkHolds chunkHolds() {
|
||||||
return chunkHolds;
|
return chunkHolds;
|
||||||
@@ -132,25 +182,30 @@ public final class RenderManager {
|
|||||||
Job<T> job = new Job<>(onSuccess, onFailure);
|
Job<T> job = new Job<>(onSuccess, onFailure);
|
||||||
jobs.add(job);
|
jobs.add(job);
|
||||||
dispatcher.execute(() -> {
|
dispatcher.execute(() -> {
|
||||||
|
running.incrementAndGet();
|
||||||
|
long timeout = timeoutMillis;
|
||||||
AtomicBoolean timedOut = new AtomicBoolean(false);
|
AtomicBoolean timedOut = new AtomicBoolean(false);
|
||||||
ScheduledFuture<?> deadline = watchdog.schedule(
|
ScheduledFuture<?> deadline = watchdog.schedule(
|
||||||
() -> {
|
() -> {
|
||||||
timedOut.set(true);
|
timedOut.set(true);
|
||||||
job.cancelled.set(true);
|
job.cancelled.set(true);
|
||||||
},
|
},
|
||||||
timeoutMillis,
|
timeout,
|
||||||
TimeUnit.MILLISECONDS);
|
TimeUnit.MILLISECONDS);
|
||||||
try {
|
try {
|
||||||
T result = job.cancelled.get() ? null : work.apply(job.cancelled);
|
T result = job.cancelled.get() ? null : work.apply(job.cancelled);
|
||||||
if (result != null && !job.cancelled.get()) job.result = result;
|
if (result != null && !job.cancelled.get()) job.result = result;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
if (!shuttingDown) plugin.getLogger().warning("Render job failed: " + t);
|
if (!shuttingDown) {
|
||||||
|
plugin.getLogger().warning("Render job failed: " + t);
|
||||||
|
if (DebugLog.enabled()) plugin.getLogger().log(Level.WARNING, "Render job failure", t);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
deadline.cancel(false);
|
deadline.cancel(false);
|
||||||
|
running.decrementAndGet();
|
||||||
if (timedOut.get()) {
|
if (timedOut.get()) {
|
||||||
plugin.getLogger()
|
plugin.getLogger()
|
||||||
.warning(
|
.warning("Render for " + user + " aborted after " + (timeout / 1000) + "s (timeout).");
|
||||||
"Render for " + user + " aborted after " + (timeoutMillis / 1000) + "s (timeout).");
|
|
||||||
}
|
}
|
||||||
release(user);
|
release(user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package eu.mhsl.minecraft.pixelpics.render;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/** Photo counters and summed phase timings since the start (thread-safe), shown by {@code /pixelpic info}. */
|
||||||
|
public final class RenderStats {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase durations of one developed photo in ms: waiting for a worker and its snapshot turn, the world snapshot on
|
||||||
|
* the main thread, ray tracing, dithering to map colours, and the whole time from the shot to the result.
|
||||||
|
*/
|
||||||
|
public record Timings(long queued, long snapshot, long trace, long dither, long total) {}
|
||||||
|
|
||||||
|
private final AtomicLong developed = new AtomicLong();
|
||||||
|
private final AtomicLong failed = new AtomicLong();
|
||||||
|
private final AtomicLong rejected = new AtomicLong();
|
||||||
|
private final AtomicLong queued = new AtomicLong();
|
||||||
|
private final AtomicLong snapshot = new AtomicLong();
|
||||||
|
private final AtomicLong trace = new AtomicLong();
|
||||||
|
private final AtomicLong dither = new AtomicLong();
|
||||||
|
private final AtomicLong total = new AtomicLong();
|
||||||
|
private final AtomicLong maxTotal = new AtomicLong();
|
||||||
|
private final AtomicLong maxSnapshot = new AtomicLong();
|
||||||
|
|
||||||
|
public void developed(Timings t) {
|
||||||
|
developed.incrementAndGet();
|
||||||
|
queued.addAndGet(t.queued());
|
||||||
|
snapshot.addAndGet(t.snapshot());
|
||||||
|
trace.addAndGet(t.trace());
|
||||||
|
dither.addAndGet(t.dither());
|
||||||
|
total.addAndGet(t.total());
|
||||||
|
maxTotal.accumulateAndGet(t.total(), Math::max);
|
||||||
|
maxSnapshot.accumulateAndGet(t.snapshot(), Math::max);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void failed() {
|
||||||
|
failed.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rejected() {
|
||||||
|
rejected.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long developedCount() {
|
||||||
|
return developed.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long failedCount() {
|
||||||
|
return failed.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long rejectedCount() {
|
||||||
|
return rejected.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Average timings of the developed photos (all zero before the first one). */
|
||||||
|
public Timings average() {
|
||||||
|
long n = Math.max(1, developed.get());
|
||||||
|
return new Timings(queued.get() / n, snapshot.get() / n, trace.get() / n, dither.get() / n, total.get() / n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long maxTotal() {
|
||||||
|
return maxTotal.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long maxSnapshot() {
|
||||||
|
return maxSnapshot.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package eu.mhsl.minecraft.pixelpics.utils;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/** Optional detailed log output, switched by {@code debug} in config.yml (applied on start and reload). */
|
||||||
|
public final class DebugLog {
|
||||||
|
|
||||||
|
private static volatile boolean enabled;
|
||||||
|
private static volatile Logger logger;
|
||||||
|
|
||||||
|
private DebugLog() {}
|
||||||
|
|
||||||
|
public static void configure(Logger log, boolean on) {
|
||||||
|
logger = log;
|
||||||
|
enabled = on;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean enabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logs {@code message} at INFO with a {@code [debug]} prefix when debug logging is on. */
|
||||||
|
public static void log(String message) {
|
||||||
|
Logger log = logger;
|
||||||
|
if (enabled && log != null) log.info("[debug] " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user