diff --git a/htroot/PerformanceGraph.java b/htroot/PerformanceGraph.java index 7e1f34d7b..bb1c45b9a 100644 --- a/htroot/PerformanceGraph.java +++ b/htroot/PerformanceGraph.java @@ -24,6 +24,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import java.util.concurrent.TimeUnit; + import net.yacy.cora.protocol.RequestHeader; import net.yacy.peers.graphics.ProfilingGraph; import net.yacy.search.Switchboard; @@ -54,6 +56,7 @@ public class PerformanceGraph { RasterPlotter graph = ProfilingGraph.performanceGraph( width, height, indeSizeCache + " URLS / " + sb.index.RWICount() + " WORDS IN INDEX / " + sb.index.RWIBufferCount() + " WORDS IN CACHE", + 600, TimeUnit.SECONDS, showMemory, showPeers); return graph; } diff --git a/source/net/yacy/peers/graphics/ProfilingGraph.java b/source/net/yacy/peers/graphics/ProfilingGraph.java index 026787655..6871b50dd 100644 --- a/source/net/yacy/peers/graphics/ProfilingGraph.java +++ b/source/net/yacy/peers/graphics/ProfilingGraph.java @@ -1,4 +1,4 @@ -// plasmaProfiling.java +// ProfilingGraph.java // (C) 2007 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany // first published 04.12.2007 on http://yacy.net // @@ -28,6 +28,7 @@ package net.yacy.peers.graphics; import java.util.ConcurrentModificationException; import java.util.Iterator; +import java.util.concurrent.TimeUnit; import net.yacy.search.EventTracker; import net.yacy.search.EventTracker.Event; @@ -55,7 +56,18 @@ public class ProfilingGraph { return max; } - public static RasterPlotter performanceGraph(final int width, final int height, final String subline, final boolean showMemory, final boolean showPeers) { + /** + * + * @param width width of the graph in pixels + * @param height height of the graph in pixels + * @param subline the eventual graph subtitle. May be null. + * @param maxTime maximum time range + * @param timeUnit the time unit for the time range. When null, default to seconds. + * @param showMemory draw memory usage when true + * @param showPeers draw peer ping when true + * @return a RasterPlotter instance drawn from the event tracker + */ + public static RasterPlotter performanceGraph(final int width, final int height, final String subline, final int maxTime, final TimeUnit timeUnit, final boolean showMemory, final boolean showPeers) { // find maximum values for automatic graph dimension adoption final int maxppm = (int) maxPayload(EventTracker.EClass.PPM, 25); final int maxwords = (int) maxPayload(EventTracker.EClass.WORDCACHE, 12000); @@ -70,19 +82,19 @@ public class ProfilingGraph { final int leftscale = (maxwords > 150000) ? maxwords / 150000 * 20000 : 10000; final int rightscale = showMemory ? ((maxmbytes > 1500) ? maxmbytes / 1500 * 200 : 100) : Math.max(100, maxppm / 100 * 100); final int anotscale = 1000; - final int bottomscale = 60; + final int bottomscale = Math.max(1, maxTime / 10); final int vspace = height - topborder - bottomborder; final int hspace = width - leftborder - rightborder; - final int maxtime = 600; ChartPlotter chart = new ChartPlotter(width, height, 0xFFFFFFl, 0x000000l, 0xAAAAAAl, leftborder, rightborder, topborder, bottomborder, "YACY PEER PERFORMANCE: MAIN MEMORY, WORD CACHE AND PAGES/MINUTE (PPM)", subline); - chart.declareDimension(ChartPlotter.DIMENSION_BOTTOM, bottomscale, hspace / (maxtime / bottomscale), -maxtime, 0x000000l, 0xCCCCCCl, "TIME/SECONDS"); - chart.declareDimension(ChartPlotter.DIMENSION_LEFT, leftscale, vspace * leftscale / maxwords, 0, 0x008800l, null , "WORDS IN INDEXING CACHE"); + chart.declareDimension(ChartPlotter.DIMENSION_BOTTOM, bottomscale, hspace / (maxTime / bottomscale), -maxTime, + 0x000000l, 0xCCCCCCl, "TIME/" + timeUnit != null ? timeUnit.toString() : TimeUnit.SECONDS.toString()); + chart.declareDimension(ChartPlotter.DIMENSION_LEFT, leftscale, (int)((long)vspace * (long)leftscale / maxwords), 0, 0x008800l, null , "WORDS IN INDEXING CACHE"); if (showMemory) { - chart.declareDimension(ChartPlotter.DIMENSION_RIGHT, rightscale, vspace * rightscale / maxmbytes, 0, 0x0000FFl, 0xCCCCCCl, "MEMORY/MEGABYTE"); + chart.declareDimension(ChartPlotter.DIMENSION_RIGHT, rightscale, (int)((long)vspace * (long)rightscale / maxmbytes), 0, 0x0000FFl, 0xCCCCCCl, "MEMORY/MEGABYTE"); } else { - chart.declareDimension(ChartPlotter.DIMENSION_RIGHT, rightscale, vspace * rightscale / Math.max(1, maxppm), 0, 0xFF0000l, 0xCCCCCCl, "INDEXING SPEED/PAGES PER MINUTE"); + chart.declareDimension(ChartPlotter.DIMENSION_RIGHT, rightscale, (int)((long)vspace * (long)rightscale / Math.max(1, maxppm)), 0, 0xFF0000l, 0xCCCCCCl, "INDEXING SPEED/PAGES PER MINUTE"); } - chart.declareDimension(ChartPlotter.DIMENSION_ANOT0, anotscale, vspace * anotscale / maxppm, 0, 0x008800l, null , "PPM [PAGES/MINUTE]"); + chart.declareDimension(ChartPlotter.DIMENSION_ANOT0, anotscale, (int)((long)vspace * (long)anotscale / maxppm), 0, 0x008800l, null , "PPM [PAGES/MINUTE]"); chart.declareDimension(ChartPlotter.DIMENSION_ANOT1, vspace / 6, vspace / 6, 0, 0x888800l, null , "URL"); chart.declareDimension(ChartPlotter.DIMENSION_ANOT2, 1, 1, 0, 0x888800l, null , "PING"); @@ -120,7 +132,7 @@ public class ProfilingGraph { event = events.next(); time = event.getTime() - now; bytes = ((Long) event.payload).longValue(); - x1 = time / 1000.0f; + x1 = timeUnit.convert(time, TimeUnit.MILLISECONDS); y1 = (int) (bytes / 1024 / 1024); // the dots don't chart.setColor(0xAAAAFF); // very nice chart.chartDot(ChartPlotter.DIMENSION_BOTTOM, ChartPlotter.DIMENSION_RIGHT, x1, y1, 2, null, 0); @@ -141,7 +153,7 @@ public class ProfilingGraph { event = events.next(); time = event.getTime() - now; words = (int) ((Long) event.payload).longValue(); - x1 = time / 1000.0f; + x1 = timeUnit.convert(time, TimeUnit.MILLISECONDS); y1 = words; chart.setColor(0x228822); chart.chartDot(ChartPlotter.DIMENSION_BOTTOM, ChartPlotter.DIMENSION_LEFT, x1, y1, 2, null, 315); @@ -161,7 +173,7 @@ public class ProfilingGraph { event = events.next(); time = event.getTime() - now; ppm = (int) ((Long) event.payload).longValue(); - x1 = time / 1000.0f; + x1 = timeUnit.convert(time, TimeUnit.MILLISECONDS); y1 = ppm; chart.setColor(0xAA8888); if (x0 < 0) chart.chartLine(ChartPlotter.DIMENSION_BOTTOM, ChartPlotter.DIMENSION_ANOT0, x0, y0, x1, y1); @@ -183,7 +195,7 @@ public class ProfilingGraph { event = events.next(); time = event.getTime() - now; ping = (EventPing) event.payload; - x1 = time / 1000.0f; + x1 = timeUnit.convert(time, TimeUnit.MILLISECONDS); y1 = Math.abs((ping.outgoing ? ping.toPeer : ping.fromPeer).hashCode()) % vspace; pingPeer = ping.outgoing ? "-> " + ping.toPeer.toUpperCase() : "<- " + ping.fromPeer.toUpperCase(); chart.setColor(0x9999AA); diff --git a/source/net/yacy/visualization/ChartPlotter.java b/source/net/yacy/visualization/ChartPlotter.java index fe8aff26d..2eae51860 100644 --- a/source/net/yacy/visualization/ChartPlotter.java +++ b/source/net/yacy/visualization/ChartPlotter.java @@ -98,7 +98,7 @@ public class ChartPlotter extends RasterPlotter { public void chartDot(final int dimension_x, final int dimension_y, final float coord_x, final int coord_y, final int dotsize, final String anot, final int anotAngle) { final int x = (int) ((coord_x - this.offsets[dimension_x]) * this.pixels[dimension_x] / this.scales[dimension_x]); assert this.scales[dimension_y] != 0; - final int y = (int)((long)(coord_y - this.offsets[dimension_y]) * (long)(this.pixels[dimension_y]) / (long)(this.scales[dimension_y])); + final int y = (int)((long)(coord_y - this.offsets[dimension_y]) * (long)(this.pixels[dimension_y]) / (this.scales[dimension_y])); if (dotsize == 1) plot(this.leftborder + x, this.height - this.bottomborder - y, 100); else dot(this.leftborder + x, this.height - this.bottomborder - y, dotsize, true, 100); if (anot != null) PrintTool.print(this, this.leftborder + x + dotsize + 2 + ((anotAngle == 315) ? -9 : 0), this.height - this.bottomborder - y + ((anotAngle == 315) ? -3 : 0), anotAngle, anot, (anotAngle == 0) ? (anot.length() * 6 + x > this.width ? 1 : -1) : ((anotAngle == 315) ? 1 : 0), 100); @@ -106,9 +106,9 @@ public class ChartPlotter extends RasterPlotter { public void chartLine(final int dimension_x, final int dimension_y, final float coord_x1, final int coord_y1, final float coord_x2, final int coord_y2) { final int x1 = (int) ((coord_x1 - this.offsets[dimension_x]) * this.pixels[dimension_x] / this.scales[dimension_x]); - final int y1 = (int)((long)(coord_y1 - this.offsets[dimension_y]) * (long)(this.pixels[dimension_y]) / (long)(this.scales[dimension_y])); + final int y1 = (int)((long)(coord_y1 - this.offsets[dimension_y]) * (long)(this.pixels[dimension_y]) / this.scales[dimension_y]); final int x2 = (int) ((coord_x2 - this.offsets[dimension_x]) * this.pixels[dimension_x] / this.scales[dimension_x]); - final int y2 = (int)((long)(coord_y2 - this.offsets[dimension_y]) * (long)(this.pixels[dimension_y]) / (long)(this.scales[dimension_y])); + final int y2 = (int)((long)(coord_y2 - this.offsets[dimension_y]) * (long)(this.pixels[dimension_y]) / this.scales[dimension_y]); line(this.leftborder + x1, this.height - this.bottomborder - y1, this.leftborder + x2, this.height - this.bottomborder - y2, 100); } @@ -179,8 +179,12 @@ public class ChartPlotter extends RasterPlotter { line(x, this.topborder - 4, x, this.height - this.bottomborder + 4, 100); } + /** + * Write a test chart to a temporary file testimage.png + */ public static void main(final String[] args) { System.setProperty("java.awt.headless", "true"); + final long bg = 0xFFFFFF; final long fg = 0x000000; final long scale = 0xCCCCCC; @@ -188,27 +192,42 @@ public class ChartPlotter extends RasterPlotter { final long blue = 0x0000FF; final ChartPlotter ip = new ChartPlotter(660, 240, bg, fg, fg, 30, 30, 20, 20, "PEER PERFORMANCE GRAPH: PAGES/MINUTE and USED MEMORY", ""); ip.declareDimension(DIMENSION_BOTTOM, 60, 60, -600, fg, scale, "TIME/SECONDS"); - //ip.declareDimension(DIMENSION_TOP, 10, 40, "000000", null, "count"); ip.declareDimension(DIMENSION_LEFT, 50, 40, 0, green, scale , "PPM [PAGES/MINUTE]"); ip.declareDimension(DIMENSION_RIGHT, 100, 20, 0, blue, scale, "MEMORY/MEGABYTE"); + + /* Draw an ascending line of 10 plots */ ip.setColor(green); - ip.chartDot(DIMENSION_BOTTOM, DIMENSION_LEFT, -160, 100, 5, null, 0); - ip.chartLine(DIMENSION_BOTTOM, DIMENSION_LEFT, -160, 100, -130, 200); + final int width = 600, maxPPM = 240; + int steps = 10, x = - width; + int ppm = (int)(maxPPM * 0.1); + int ppmStep = (int)((maxPPM * 0.9) / steps); + for(int step = 0; step < steps; step++) { + ip.chartDot(DIMENSION_BOTTOM, DIMENSION_LEFT, x, ppm, 5, null, 0); + ip.chartLine(DIMENSION_BOTTOM, DIMENSION_LEFT, x, ppm, x + (width / steps), ppm + ppmStep); + ppm += ppmStep; + x += (width / steps); + } + + /* Draw a descending line of 20 plots */ ip.setColor(blue); - ip.chartDot(DIMENSION_BOTTOM, DIMENSION_RIGHT, -50, 300, 2, null, 0); - ip.chartLine(DIMENSION_BOTTOM, DIMENSION_RIGHT, -80, 100, -50, 300); - //ip.print(100, 100, 0, "TEXT", true); - //ip.print(100, 100, 0, "1234", false); - //ip.print(100, 100, 90, "TEXT", true); - //ip.print(100, 100, 90, "1234", false); - final File file = new File(System.getProperty("java.io.tmpdir") + File.separator + "testimage.png"); + steps = 20; + final int maxMBytes = 800; + int mBytes = (int)(maxMBytes * 0.8); + int mBytesStep = (int)((maxMBytes * 0.6) / steps); + x = - width; + for(int step = 0; step < steps; step++) { + ip.chartDot(DIMENSION_BOTTOM, DIMENSION_RIGHT, x, mBytes, 5, null, 0); + ip.chartLine(DIMENSION_BOTTOM, DIMENSION_RIGHT, x, mBytes, x + (width / steps), mBytes - mBytesStep); + mBytes -= mBytesStep; + x += (width / steps); + } + final File file = new File(System.getProperty("java.io.tmpdir"),"testimage.png"); try ( /* Automatically closed by this try-with-resources statement */ final FileOutputStream fos = new FileOutputStream(file); ) { - System.out.println("Writing file " + file); fos.write(RasterPlotter.exportImage(ip.getImage(), "png").getBytes()); - //ImageIO.write(ip.getImage(), "png", fos); + System.out.println("CharPlotter test file written at " + file); } catch (final IOException e) { e.printStackTrace(); } diff --git a/test/java/net/yacy/peers/graphics/ProfilingGraphTest.java b/test/java/net/yacy/peers/graphics/ProfilingGraphTest.java new file mode 100644 index 000000000..318b469c0 --- /dev/null +++ b/test/java/net/yacy/peers/graphics/ProfilingGraphTest.java @@ -0,0 +1,108 @@ +// ProfilingGraphTest.java +// Copyright 2017 by luccioman; https://github.com/luccioman +// +// This is a part of YaCy, a peer-to-peer based web search engine +// +// LICENSE +// +// 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 2 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, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package net.yacy.peers.graphics; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import net.yacy.cora.util.ConcurrentLog; +import net.yacy.search.EventTracker; +import net.yacy.visualization.RasterPlotter; + +/** + * Unit tests for the {@link ProfilingGraph} class + */ +public class ProfilingGraphTest { + + /** + * Generate a performance graph and write it to a temporary file for manual + * viasual checking.
+ * Note : it is not an automated JUnit test function as it is time dependant. + * + * @throws IOException + * when a read/write exception occurred + * @throws InterruptedException + * when interrupted before termination + */ + public static void main(final String args[]) throws IOException, InterruptedException { + long time = System.currentTimeMillis(); + final long beginTime = time; + long prevTime = time; + + /* Feed the event tracker with test values */ + final int steps = 100; + + /* Ascending memory usage from 500MB to 16GB */ + long bytes = 500L * 1024L * 1024L; + final long bytesStep = (16L * 1204L * 1204L * 1204L) / steps; + + /* Descending words, from max integer value to zero. + * (events values are stored as long, but currently the actual maximum possible value ford WORDCACHE is an Integer.MAX_VALUE) */ + long words = Integer.MAX_VALUE; + final long wordsStep = words / steps; + + for (int step = 0; step < steps; step++) { + if ((step % 30) == 0) { + /* Stable PPRM and peer ping values */ + EventTracker.update(EventTracker.EClass.PPM, Long.valueOf(500), false); + EventTracker.update(EventTracker.EClass.PEERPING, + new ProfilingGraph.EventPing("localPeerName", "aaaa", true, 1536), false); + } + EventTracker.update(EventTracker.EClass.WORDCACHE, Long.valueOf(words), false); + EventTracker.update(EventTracker.EClass.MEMORY, Long.valueOf(bytes), false); + time = System.currentTimeMillis(); + /* Ensure each test event is separated at least from 1ms */ + while (time == prevTime) { + Thread.sleep(1); + time = System.currentTimeMillis(); + } + prevTime = time; + + bytes += bytesStep; + words -= wordsStep; + } + + long timeRange = (time - beginTime) * 2; + + /* Parameters likely to be encountered on the PerformanceGraph calling class */ + final int indexSizeCache = 865749; + final int rwiCount = 512378; + final int rwiBufferCount = 6754; + final RasterPlotter graph = ProfilingGraph.performanceGraph(660, 240, + indexSizeCache + " URLS / " + rwiCount + " WORDS IN INDEX / " + rwiBufferCount + " WORDS IN CACHE", + (int) timeRange, TimeUnit.MILLISECONDS, true, true); + + /* Now write the result to a temporary file for visual checking */ + final File outputFile = new File(System.getProperty("java.io.tmpdir"), "testPerformanceGraph.png"); + try ( + /* Automatically closed by this try-with-resources statement */ + final FileOutputStream fos = new FileOutputStream(outputFile);) { + fos.write(RasterPlotter.exportImage(graph.getImage(), "png").getBytes()); + System.out.println("Performance graph writtent to file " + outputFile); + } finally { + ConcurrentLog.shutdown(); + } + } + +}