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

Last change on this file since 17275 was 17275, checked in by Don-vip, 3 years ago

see #16567 - upgrade almost all tests to JUnit 5, except those depending on WiremockRule

See https://github.com/tomakehurst/wiremock/issues/684

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