source: josm/trunk/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java @ 12649

Last change on this file since 12649 was 12649, checked in by Don-vip, 8 weeks ago

see #15182 - code refactoring to avoid dependence on GUI packages from Preferences

  • Property svn:eol-style set to native
File size: 12.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint;
3
4import java.awt.Color;
5import java.awt.Graphics2D;
6import java.awt.image.BufferedImage;
7import java.io.File;
8import java.io.IOException;
9import java.io.InputStream;
10import java.util.ArrayList;
11import java.util.Collections;
12import java.util.EnumMap;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Locale;
16import java.util.Map;
17import java.util.stream.Collectors;
18
19import javax.imageio.ImageIO;
20
21import org.junit.AfterClass;
22import org.junit.Assert;
23import org.junit.BeforeClass;
24import org.junit.Rule;
25import org.junit.Test;
26import org.junit.rules.Timeout;
27import org.openstreetmap.josm.JOSMFixture;
28import org.openstreetmap.josm.PerformanceTestUtils;
29import org.openstreetmap.josm.TestUtils;
30import org.openstreetmap.josm.data.Bounds;
31import org.openstreetmap.josm.data.coor.LatLon;
32import org.openstreetmap.josm.data.osm.DataSet;
33import org.openstreetmap.josm.data.osm.visitor.paint.RenderBenchmarkCollector.CapturingBenchmark;
34import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
35import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer.StyleRecord;
36import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
37import org.openstreetmap.josm.data.projection.Projections;
38import org.openstreetmap.josm.gui.MainApplication;
39import org.openstreetmap.josm.gui.NavigatableComponent;
40import org.openstreetmap.josm.gui.mappaint.StyleSetting.BooleanStyleSetting;
41import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
42import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
43import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
44import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
45import org.openstreetmap.josm.io.Compression;
46import org.openstreetmap.josm.io.OsmReader;
47import org.openstreetmap.josm.tools.Logging;
48
49import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
50
51public class MapRendererPerformanceTest {
52
53    private static final boolean DUMP_IMAGE = false; // dump images to file for debugging purpose
54
55    private static final int IMG_WIDTH = 2048;
56    private static final int IMG_HEIGHT = 1536;
57
58    private static Graphics2D g;
59    private static BufferedImage img;
60    private static NavigatableComponent nc;
61    private static DataSet dsCity;
62    private static final Bounds BOUNDS_CITY_ALL = new Bounds(53.4382, 13.1094, 53.6153, 13.4074, false);
63    private static final LatLon LL_CITY = new LatLon(53.5574458, 13.2602781);
64    private static final double SCALE_Z17 = 1.5;
65
66    private static int defaultStyleIdx;
67    private static BooleanStyleSetting hideIconsSetting;
68
69    private static int filterStyleIdx;
70    private static StyleSource filterStyle;
71
72    private enum Feature {
73        ICON, SYMBOL, NODE_TEXT, LINE, LINE_TEXT, AREA;
74        public String label() {
75            return name().toLowerCase(Locale.ENGLISH);
76        }
77    }
78
79    private static final EnumMap<Feature, BooleanStyleSetting> filters = new EnumMap<>(Feature.class);
80
81    /**
82     * Global timeout applied to all test methods.
83     */
84    @Rule
85    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
86    public Timeout globalTimeout = Timeout.seconds(15*60);
87
88    @BeforeClass
89    public static void load() throws Exception {
90        JOSMFixture.createPerformanceTestFixture().init(true);
91
92        img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_ARGB);
93        g = (Graphics2D) img.getGraphics();
94        g.setClip(0, 0, IMG_WIDTH, IMG_WIDTH);
95        g.setColor(Color.BLACK);
96        g.fillRect(0, 0, IMG_WIDTH, IMG_WIDTH);
97        nc = MainApplication.getMap().mapView;
98        nc.setBounds(0, 0, IMG_WIDTH, IMG_HEIGHT);
99
100        MapPaintStyles.readFromPreferences();
101
102        SourceEntry se = new MapCSSStyleSource(TestUtils.getTestDataRoot() + "styles/filter.mapcss", "filter", "");
103        filterStyle = MapPaintStyles.addStyle(se);
104        List<StyleSource> sources = MapPaintStyles.getStyles().getStyleSources();
105        filterStyleIdx = sources.indexOf(filterStyle);
106        Assert.assertEquals(2, filterStyleIdx);
107
108        Assert.assertEquals(Feature.values().length, filterStyle.settings.size());
109        for (StyleSetting set : filterStyle.settings) {
110            BooleanStyleSetting bset = (BooleanStyleSetting) set;
111            String prefKey = bset.prefKey;
112            boolean found = false;
113            for (Feature f : Feature.values()) {
114                if (prefKey.endsWith(":" + f.label() + "_off")) {
115                    filters.put(f, bset);
116                    found = true;
117                    break;
118                }
119            }
120            Assert.assertTrue(prefKey, found);
121        }
122
123        MapCSSStyleSource defaultStyle = null;
124        for (int i = 0; i < sources.size(); i++) {
125            StyleSource s = sources.get(i);
126            if ("resource://styles/standard/elemstyles.mapcss".equals(s.url)) {
127                defaultStyle = (MapCSSStyleSource) s;
128                defaultStyleIdx = i;
129                break;
130            }
131        }
132        Assert.assertNotNull(defaultStyle);
133
134        for (StyleSetting set : defaultStyle.settings) {
135            if (set instanceof BooleanStyleSetting) {
136                BooleanStyleSetting bset = (BooleanStyleSetting) set;
137                if (bset.prefKey.endsWith(":hide_icons")) {
138                    hideIconsSetting = bset;
139                }
140            }
141        }
142        Assert.assertNotNull(hideIconsSetting);
143        hideIconsSetting.setValue(false);
144        MapPaintStyles.reloadStyles(defaultStyleIdx);
145
146        try (
147            InputStream fisC = Compression.getUncompressedFileInputStream(new File("data_nodist/neubrandenburg.osm.bz2"));
148        ) {
149            dsCity = OsmReader.parseDataSet(fisC, NullProgressMonitor.INSTANCE);
150        }
151    }
152
153    @AfterClass
154    public static void cleanUp() {
155        setFilterStyleActive(false);
156        if (hideIconsSetting != null) {
157            hideIconsSetting.setValue(true);
158        }
159        MapPaintStyles.reloadStyles(defaultStyleIdx);
160    }
161
162    private static class PerformanceTester {
163        public double scale = 0;
164        public LatLon center = LL_CITY;
165        public Bounds bounds;
166        public int noWarmup = 3;
167        public int noIterations = 7;
168        public boolean dumpImage = DUMP_IMAGE;
169        public boolean clearStyleCache = true;
170        public String label = "";
171        public boolean mpGenerate = false;
172        public boolean mpSort = false;
173        public boolean mpDraw = false;
174        public boolean mpTotal = false;
175
176        private final List<Long> generateTimes = new ArrayList<>();
177        private final List<Long> sortTimes = new ArrayList<>();
178        private final List<Long> drawTimes = new ArrayList<>();
179        private final List<Long> totalTimes = new ArrayList<>();
180
181        @SuppressFBWarnings(value = "DM_GC")
182        public void run() throws IOException {
183            boolean checkScale = false;
184            if (scale == 0) {
185                checkScale = true;
186                scale = SCALE_Z17;
187            }
188            nc.zoomTo(Projections.project(center), scale);
189            if (checkScale) {
190                int lvl = Selector.OptimizedGeneralSelector.scale2level(nc.getDist100Pixel());
191                Assert.assertEquals(17, lvl);
192            }
193
194            if (bounds == null) {
195                bounds = nc.getLatLonBounds(g.getClipBounds());
196            }
197
198            StyledMapRenderer renderer = new StyledMapRenderer(g, nc, false);
199
200            int noTotal = noWarmup + noIterations;
201            for (int i = 1; i <= noTotal; i++) {
202                g.setColor(Color.BLACK);
203                g.fillRect(0, 0, IMG_WIDTH, IMG_WIDTH);
204                if (clearStyleCache) {
205                    MapPaintStyles.getStyles().clearCached();
206                }
207                System.gc();
208                System.runFinalization();
209                try {
210                    Thread.sleep(300);
211                } catch (InterruptedException ex) {
212                    Logging.warn(ex);
213                }
214                BenchmarkData data = new BenchmarkData();
215                renderer.setBenchmarkFactory(() -> data);
216                renderer.render(dsCity, false, bounds);
217
218                if (i > noWarmup) {
219                    generateTimes.add(data.getGenerateTime());
220                    sortTimes.add(data.getSortTime());
221                    drawTimes.add(data.getDrawTime());
222                    totalTimes.add(data.getGenerateTime() + data.getSortTime() + data.getDrawTime());
223                }
224                if (i == 1) {
225                    dumpElementCount(data);
226                }
227                dumpTimes(data);
228                if (dumpImage && i == noTotal) {
229                    dumpRenderedImage(label);
230                }
231            }
232
233            if (mpGenerate) {
234                processTimes(generateTimes, "generate");
235            }
236            if (mpSort) {
237                processTimes(sortTimes, "sort");
238            }
239            if (mpDraw) {
240                processTimes(drawTimes, "draw");
241            }
242            if (mpTotal) {
243                processTimes(totalTimes, "total");
244            }
245        }
246
247        private void processTimes(List<Long> times, String sublabel) {
248            Collections.sort(times);
249            // Take median instead of average. This should give a more stable
250            // result and avoids distortions by outliers.
251            long medianTime = times.get(times.size() / 2);
252            PerformanceTestUtils.measurementPlotsPluginOutput(label + " " + sublabel + " (ms)", medianTime);
253        }
254    }
255
256    /**
257     * Test phase 1, the calculation of {@link StyleElement}s.
258     * @throws IOException in case of an I/O error
259     */
260    @Test
261    public void testPerformanceGenerate() throws IOException {
262        setFilterStyleActive(false);
263        PerformanceTester test = new PerformanceTester();
264        test.bounds = BOUNDS_CITY_ALL;
265        test.label = "big";
266        test.dumpImage = false;
267        test.noWarmup = 3;
268        test.noIterations = 10;
269        test.mpGenerate = true;
270        test.clearStyleCache = true;
271        test.run();
272    }
273
274    private static void testDrawFeature(Feature feature) throws IOException {
275        PerformanceTester test = new PerformanceTester();
276        test.noWarmup = 3;
277        test.noIterations = 10;
278        test.mpDraw = true;
279        test.clearStyleCache = false;
280        if (feature != null) {
281            BooleanStyleSetting filterSetting = filters.get(feature);
282            test.label = filterSetting.label;
283            setFilterStyleActive(true);
284            for (Feature f : Feature.values()) {
285                filters.get(f).setValue(true);
286            }
287            filterSetting.setValue(false);
288        } else {
289            test.label = "all";
290            setFilterStyleActive(false);
291        }
292        MapPaintStyles.reloadStyles(filterStyleIdx);
293        test.run();
294    }
295
296    /**
297     * Test phase 2, the actual drawing.
298     * Several runs: Icons, lines, etc. are tested separately (+ one run with
299     * all features activated)
300     * @throws IOException in case of an I/O error
301     */
302    @Test
303    public void testPerformanceDrawFeatures() throws IOException {
304        testDrawFeature(null);
305        for (Feature f : Feature.values()) {
306            testDrawFeature(f);
307        }
308    }
309
310    private static void setFilterStyleActive(boolean active) {
311        if (filterStyle.active != active) {
312            MapPaintStyles.toggleStyleActive(filterStyleIdx);
313        }
314        Assert.assertEquals(active, filterStyle.active);
315    }
316
317    private static void dumpRenderedImage(String id) throws IOException {
318        File outputfile = new File("test-neubrandenburg-"+id+".png");
319        ImageIO.write(img, "png", outputfile);
320    }
321
322    public static void dumpTimes(BenchmarkData bd) {
323        System.out.print(String.format("gen. %3d, sort %3d, draw %3d%n", bd.getGenerateTime(), bd.getSortTime(), bd.getDrawTime()));
324    }
325
326    public static void dumpElementCount(BenchmarkData bd) {
327        System.out.println(bd.recordElementStats().entrySet().stream()
328                .map(e -> e.getKey().getSimpleName().replace("Element", "") + ":" + e.getValue()).collect(Collectors.joining(" ")));
329    }
330
331    public static class BenchmarkData extends CapturingBenchmark {
332
333        private List<StyleRecord> allStyleElems;
334
335        @Override
336        public boolean renderDraw(List<StyleRecord> allStyleElems) {
337            this.allStyleElems = allStyleElems;
338            return super.renderDraw(allStyleElems);
339        }
340
341        private Map<Class<? extends StyleElement>, Integer> recordElementStats() {
342            Map<Class<? extends StyleElement>, Integer> styleElementCount = new HashMap<>();
343            for (StyleRecord r : allStyleElems) {
344                Class<? extends StyleElement> klass = r.getStyle().getClass();
345                Integer count = styleElementCount.get(klass);
346                if (count == null) {
347                    count = 0;
348                }
349                styleElementCount.put(klass, count + 1);
350            }
351            return styleElementCount;
352        }
353    }
354}
Note: See TracBrowser for help on using the repository browser.