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;
|
||||
|
||||
import eu.mhsl.minecraft.pixelpics.utils.DebugLog;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -22,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.IllegalPluginAccessException;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
@@ -66,12 +68,16 @@ public final class RenderManager {
|
||||
private final ChunkHolds chunkHolds;
|
||||
private volatile boolean shuttingDown = false;
|
||||
private final AtomicInteger inFlight = new AtomicInteger();
|
||||
private final int capacity;
|
||||
private final long timeoutMillis;
|
||||
private final AtomicInteger running = new AtomicInteger();
|
||||
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) {
|
||||
this.plugin = plugin;
|
||||
this.capacity = maxConcurrent + queueSize;
|
||||
this.maxConcurrent = maxConcurrent;
|
||||
this.timeoutMillis = Math.max(1L, timeoutSeconds) * 1000L;
|
||||
this.tracePool = new ForkJoinPool(threads, lowPriorityForkJoinFactory(), null, false);
|
||||
this.dispatcher = new ThreadPoolExecutor(
|
||||
@@ -91,6 +97,50 @@ public final class RenderManager {
|
||||
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). */
|
||||
public ChunkHolds chunkHolds() {
|
||||
return chunkHolds;
|
||||
@@ -132,25 +182,30 @@ public final class RenderManager {
|
||||
Job<T> job = new Job<>(onSuccess, onFailure);
|
||||
jobs.add(job);
|
||||
dispatcher.execute(() -> {
|
||||
running.incrementAndGet();
|
||||
long timeout = timeoutMillis;
|
||||
AtomicBoolean timedOut = new AtomicBoolean(false);
|
||||
ScheduledFuture<?> deadline = watchdog.schedule(
|
||||
() -> {
|
||||
timedOut.set(true);
|
||||
job.cancelled.set(true);
|
||||
},
|
||||
timeoutMillis,
|
||||
timeout,
|
||||
TimeUnit.MILLISECONDS);
|
||||
try {
|
||||
T result = job.cancelled.get() ? null : work.apply(job.cancelled);
|
||||
if (result != null && !job.cancelled.get()) job.result = result;
|
||||
} 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 {
|
||||
deadline.cancel(false);
|
||||
running.decrementAndGet();
|
||||
if (timedOut.get()) {
|
||||
plugin.getLogger()
|
||||
.warning(
|
||||
"Render for " + user + " aborted after " + (timeoutMillis / 1000) + "s (timeout).");
|
||||
.warning("Render for " + user + " aborted after " + (timeout / 1000) + "s (timeout).");
|
||||
}
|
||||
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