From 30f66631bcb91989f4291070dc68db8226365f17 Mon Sep 17 00:00:00 2001 From: Michael Peter Christen Date: Thu, 9 Jul 2026 22:17:32 +0200 Subject: enhanced Network Graph Caching --- source/net/yacy/htroot/NetworkPicture.java | 94 +++++++++------------- source/net/yacy/peers/graphics/NetworkGraph.java | 86 ++++++++++++++------ source/net/yacy/search/Switchboard.java | 3 +- .../net/yacy/peers/graphics/NetworkGraphTest.java | 56 +++++++++++++ 4 files changed, 159 insertions(+), 80 deletions(-) create mode 100644 test/java/net/yacy/peers/graphics/NetworkGraphTest.java diff --git a/source/net/yacy/htroot/NetworkPicture.java b/source/net/yacy/htroot/NetworkPicture.java index 0ebfb7f91..ea89744f7 100644 --- a/source/net/yacy/htroot/NetworkPicture.java +++ b/source/net/yacy/htroot/NetworkPicture.java @@ -37,13 +37,17 @@ import net.yacy.search.SwitchboardConstants; import net.yacy.server.serverObjects; import net.yacy.server.serverSwitch; + /** draw a picture of the yacy network */ public class NetworkPicture { - private static final ConcurrentLog log = new ConcurrentLog("NetworkPicture"); - private static final Semaphore sync = new Semaphore(1, true); - private static long lastAccessSeconds = 0; + private static final ConcurrentLog log = new ConcurrentLog("NetworkPicture"); + private static final Semaphore sync = new Semaphore(1, true); + private static final int NETWORK_PICTURE_CACHE_MAX_SIZE = 20; + private static final long NETWORK_PICTURE_CACHE_MAX_AGE_MILLIS = 10000; + public static final NetworkGraph.Cache cache = new NetworkGraph.Cache(NETWORK_PICTURE_CACHE_MAX_AGE_MILLIS, NETWORK_PICTURE_CACHE_MAX_SIZE); + public static EncodedImage respond( final RequestHeader header, final serverObjects post, @@ -51,35 +55,8 @@ public class NetworkPicture { final Switchboard sb = (Switchboard) env; final boolean authorized = sb.adminAuthenticated(header) >= 2; - final long timeSeconds = System.currentTimeMillis() / 1000; - if (NetworkGraph.buffer != null && timeSeconds - lastAccessSeconds < 2) { - if (log.isFine()) log.fine("cache hit (1); authorized = " - + authorized - + ", timeSeconds - lastAccessSeconds = " - + (timeSeconds - lastAccessSeconds)); - return NetworkGraph.buffer; - } - - boolean lockAcquired = sync.tryAcquire(); - if (!lockAcquired) { - if (NetworkGraph.buffer != null) { - return NetworkGraph.buffer; - } - sync.acquireUninterruptibly(); - lockAcquired = true; - } - try { - final long refreshCheckSeconds = System.currentTimeMillis() / 1000; - if (NetworkGraph.buffer != null && refreshCheckSeconds - lastAccessSeconds < 2) { - if (log.isFine()) log.fine("cache hit (2); authorized = " - + authorized - + ", timeSeconds - lastAccessSeconds = " - + (refreshCheckSeconds - lastAccessSeconds)); - return NetworkGraph.buffer; - } - - int width = 1280; // 640x480 = VGA, 768x576 = SD/4:3, 1024x576 =SD/16:9 1280x720 = HD/16:9, 1920x1080 = FULL HD/16:9 - int height = 720; + int width = 1280; // 640x480 = VGA, 768x576 = SD/4:3, 1024x576 =SD/16:9 1280x720 = HD/16:9, 1920x1080 = FULL HD/16:9 + int height = 720; int passiveLimit = 1440; // minutes; 1440 = 1 day; 720 = 12 hours; 1440 = 24 hours, 10080 = 1 week; int potentialLimit = 1440; int maxCount = 9000; @@ -111,11 +88,34 @@ public class NetworkPicture { if ( potentialLimit > 1000000 ) { potentialLimit = 1000000; } - if ( maxCount > 10000 ) { - maxCount = 10000; - } - - NetworkGraph.buffer = + if ( maxCount > 10000 ) { + maxCount = 10000; + } + + EncodedImage cachedPicture = cache.getFresh(width, height, coronaangle); + if (cachedPicture != null) { + if (log.isFine()) log.fine("cache hit (1); authorized = " + authorized + ", width = " + width + ", height = " + height); + return cachedPicture; + } + + boolean lockAcquired = sync.tryAcquire(); + if (!lockAcquired) { + /* A stale picture is preferable to waiting, but only for a matching variant. */ + cachedPicture = cache.getCached(width, height, coronaangle); + if (cachedPicture != null) { + return cachedPicture; + } + sync.acquireUninterruptibly(); + lockAcquired = true; + } + try { + cachedPicture = cache.getFresh(width, height, coronaangle); + if (cachedPicture != null) { + if (log.isFine()) log.fine("cache hit (2); authorized = " + authorized + ", width = " + width + ", height = " + height); + return cachedPicture; + } + + final EncodedImage networkPicture = new EncodedImage(NetworkGraph.getNetworkPicture( sb.peers, width, @@ -130,24 +130,8 @@ public class NetworkPicture { Long.parseLong(bgcolor, 16), cyc), "png", false); - /* - NetworkGraph.buffer = - new EncodedImage(NetworkSkylineGraph.getNetworkSkylinePicture( - sb.peers, - width, - height, - passiveLimit, - potentialLimit, - maxCount, - coronaangle, - communicationTimeout, - env.getConfig(SwitchboardConstants.NETWORK_NAME, "unspecified"), - env.getConfig("network.unit.description", "unspecified"), - Long.parseLong(bgcolor, 16), - cyc), "png", false); - */ - lastAccessSeconds = System.currentTimeMillis() / 1000; - return NetworkGraph.buffer; + cache.put(width, height, coronaangle, networkPicture); + return networkPicture; } finally { if (lockAcquired) { sync.release(); diff --git a/source/net/yacy/peers/graphics/NetworkGraph.java b/source/net/yacy/peers/graphics/NetworkGraph.java index cd501dccd..1b14b0379 100644 --- a/source/net/yacy/peers/graphics/NetworkGraph.java +++ b/source/net/yacy/peers/graphics/NetworkGraph.java @@ -30,9 +30,12 @@ import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; -import java.util.Date; -import java.util.Iterator; +import java.util.AbstractMap; +import java.util.Date; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import net.yacy.cora.document.encoding.ASCII; import net.yacy.cora.document.encoding.UTF8; @@ -50,12 +53,11 @@ import net.yacy.search.query.SearchEventCache; import net.yacy.visualization.PrintTool; import net.yacy.visualization.RasterPlotter; -public class NetworkGraph { +public class NetworkGraph { - private final static double DOUBLE_LONG_MAX_VALUE = Long.MAX_VALUE; - public static EncodedImage buffer = null; - + private static final double DOUBLE_LONG_MAX_VALUE = Long.MAX_VALUE; + private static int shortestName = 10; private static int longestName = 30; @@ -81,13 +83,62 @@ public class NetworkGraph { private static final long COL_NORMAL_TEXT = 0x000000; private static final long COL_LOAD_BG = 0xF7F7F7; - /** Private constructor to avoid instantiation of utility class. */ - private NetworkGraph() { } - public static void clearcache() { - buffer = null; + public static class Cache { + + private final Map> cache; + private final long maxAge; + private final int maxSize; + + public Cache(long maxAge, int maxSize) { + this.maxAge = maxAge; + this.maxSize = maxSize; + this.cache = new ConcurrentHashMap<>(); + } + + private String cacheKey(final int width, final int height, final int coronaangle) { + return "key-" + width + "-" + height + "-" + coronaangle; + } + + public EncodedImage getCached(final int width, final int height, final int coronaangle) { + final Map.Entry cached = cache.get(cacheKey(width, height, coronaangle)); + return cached == null ? null : cached.getValue(); + } + + public EncodedImage getFresh(final int width, final int height, final int coronaangle) { + final String key = cacheKey(width, height, coronaangle); + final Map.Entry cached = this.cache.get(key); + if (cached == null) return null; + if (System.currentTimeMillis() - cached.getKey() >= this.maxAge) return null; + return cached.getValue(); + } + + public synchronized void put(final int width, final int height, final int coronaangle, final EncodedImage image) { + final String key = cacheKey(width, height, coronaangle); + if (!this.cache.containsKey(key) && this.cache.size() >= this.maxSize) { + String oldestKey = null; + long oldestCreationTime = Long.MAX_VALUE; + for (final Map.Entry> entry: this.cache.entrySet()) { + if (entry.getValue().getKey() < oldestCreationTime) { + oldestKey = entry.getKey(); + oldestCreationTime = entry.getValue().getKey(); + } + } + if (oldestKey != null) { + this.cache.remove(oldestKey); + } + } + this.cache.put(key, new AbstractMap.SimpleEntry(System.currentTimeMillis(), image)); + } + + public void clear() { + this.cache.clear(); + } } + /** Private constructor to avoid instantiation of utility class. */ + private NetworkGraph() { } + public static class CircleThreadPiece { private final String pieceName; private final Color color; @@ -148,19 +199,6 @@ public class NetworkGraph { eventPicture.arcLine(cx, cy, cr - 20, cr, angle, true, null, null, -1, -1, -1, false); } - // draw in the secondary search peers - /* - if (secondarySearches != null) { - for (final Thread secondarySearche : secondarySearches) { - if (secondarySearche == null) continue; - eventPicture.setColor((secondarySearche.isAlive()) ? RasterPlotter.RED : RasterPlotter.GREEN); - angle = cyc + (360.0d * ((FlatWordPartitionScheme.std.dhtPosition(UTF8.getBytes(secondarySearche.target().hash), null)) / DOUBLE_LONG_MAX_VALUE)); - eventPicture.arcLine(cx, cy, cr - 10, cr, angle - 1.0, true, null, null, -1, -1, -1, false); - eventPicture.arcLine(cx, cy, cr - 10, cr, angle + 1.0, true, null, null, -1, -1, -1, false); - } - } - */ - // draw in the search target final Iterator i = event.query.getQueryGoal().getIncludeHashes().iterator(); eventPicture.setColor(RasterPlotter.GREY); @@ -447,4 +485,4 @@ public class NetworkGraph { g.drawChars(caption.toCharArray(), 0, caption.length(), x+LEGEND_BOX_SIZE+5,y); } -} \ No newline at end of file +} diff --git a/source/net/yacy/search/Switchboard.java b/source/net/yacy/search/Switchboard.java index 1d0b8d42a..fb19d2a99 100644 --- a/source/net/yacy/search/Switchboard.java +++ b/source/net/yacy/search/Switchboard.java @@ -182,6 +182,7 @@ import net.yacy.document.parser.pdfParser; import net.yacy.document.parser.html.Evaluation; import net.yacy.gui.Audio; import net.yacy.gui.Tray; +import net.yacy.htroot.NetworkPicture; import net.yacy.http.YaCyHttpServer; import net.yacy.kelondro.blob.ArrayStack; import net.yacy.kelondro.blob.BEncodedHeap; @@ -2445,7 +2446,7 @@ public final class Switchboard extends serverSwitch { // clear graphics caches CircleTool.clearcache(); - NetworkGraph.clearcache(); + NetworkPicture.cache.clear();; } public int schedulerJobSize() { diff --git a/test/java/net/yacy/peers/graphics/NetworkGraphTest.java b/test/java/net/yacy/peers/graphics/NetworkGraphTest.java new file mode 100644 index 000000000..ca3d2f24d --- /dev/null +++ b/test/java/net/yacy/peers/graphics/NetworkGraphTest.java @@ -0,0 +1,56 @@ +// NetworkGraphTest.java +// This is a part of YaCy, a peer-to-peer based web search engine + +package net.yacy.peers.graphics; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** Unit tests for the network picture cache in {@link NetworkGraph}. */ +public class NetworkGraphTest { + + @Before + @After + public void clearCache() { + NetworkGraph.clearcache(); + } + + @Test + public void testNetworkPictureCacheIsPartitionedByImageSize() { + final EncodedImage small = new EncodedImage(new byte[] {1}, "png", false); + final EncodedImage large = new EncodedImage(new byte[] {2}, "png", false); + + NetworkGraph.cacheNetworkPicture(825, 450, 0, small); + NetworkGraph.cacheNetworkPicture(1280, 900, 0, large); + + assertSame(small, NetworkGraph.getCachedNetworkPicture(825, 450, 0)); + assertSame(large, NetworkGraph.getCachedNetworkPicture(1280, 900, 0)); + assertNull(NetworkGraph.getCachedNetworkPicture(825, 900, 0)); + } + + @Test + public void testNetworkPictureCacheIsPartitionedByCoronaAngle() { + final EncodedImage firstPhase = new EncodedImage(new byte[] {1}, "png", false); + final EncodedImage secondPhase = new EncodedImage(new byte[] {2}, "png", false); + + NetworkGraph.cacheNetworkPicture(1280, 900, 0, firstPhase); + NetworkGraph.cacheNetworkPicture(1280, 900, 60, secondPhase); + + assertSame(firstPhase, NetworkGraph.getCachedNetworkPicture(1280, 900, 0)); + assertSame(secondPhase, NetworkGraph.getCachedNetworkPicture(1280, 900, 60)); + assertNull(NetworkGraph.getCachedNetworkPicture(1280, 900, 120)); + } + + @Test + public void testExpiredNetworkPictureRemainsAvailableForBusyFallback() { + final EncodedImage image = new EncodedImage(new byte[] {1}, "png", false); + NetworkGraph.cacheNetworkPicture(825, 450, 0, image); + + assertNull(NetworkGraph.getCachedNetworkPicture(825, 450, 0, 0)); + assertSame(image, NetworkGraph.getCachedNetworkPicture(825, 450, 0)); + } +} -- cgit v1.2.3