/* * Licensed to GraphHopper GmbH under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. * * GraphHopper GmbH licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.graphhopper.tools; import com.graphhopper.GHRequest; import com.graphhopper.GHResponse; import com.graphhopper.GraphHopper; import com.graphhopper.PathWrapper; import com.graphhopper.coll.GHBitSet; import com.graphhopper.coll.GHBitSetImpl; import com.graphhopper.reader.DataReader; import com.graphhopper.reader.osm.GraphHopperOSM; import com.graphhopper.routing.util.*; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.CHGraph; import com.graphhopper.storage.Graph; import com.graphhopper.storage.GraphHopperStorage; import com.graphhopper.storage.NodeAccess; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.util.*; import com.graphhopper.util.Parameters.Algorithms; import com.graphhopper.util.Parameters.CH; import com.graphhopper.util.Parameters.Landmark; import com.graphhopper.util.shapes.BBox; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.FileWriter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** * @author Peter Karich */ public class Measurement { private static final Logger logger = LoggerFactory.getLogger(Measurement.class); private final Map<String, String> properties = new TreeMap<String, String>(); private long seed; private int maxNode; public static void main(String[] strs) { new Measurement().start(CmdArgs.read(strs)); } // creates properties file in the format key=value // Every value is one y-value in a separate diagram with an identical x-value for every Measurement.start call void start(CmdArgs args) { String graphLocation = args.get("graph.location", ""); String propLocation = args.get("measurement.location", ""); if (Helper.isEmpty(propLocation)) propLocation = "measurement" + new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss").format(new Date()) + ".properties"; seed = args.getLong("measurement.seed", 123); String gitCommit = args.get("measurement.gitinfo", ""); int count = args.getInt("measurement.count", 5000); GraphHopper hopper = new GraphHopperOSM() { @Override protected void prepareCH() { StopWatch sw = new StopWatch().start(); super.prepareCH(); put(Parameters.CH.PREPARE + "time", sw.stop().getTime()); int edges = getGraphHopperStorage().getAllEdges().getMaxId(); if (getCHFactoryDecorator().hasWeightings()) { Weighting weighting = getCHFactoryDecorator().getWeightings().get(0); int edgesAndShortcuts = getGraphHopperStorage().getGraph(CHGraph.class, weighting).getAllEdges().getMaxId(); put(Parameters.CH.PREPARE + "shortcuts", edgesAndShortcuts - edges); } } @Override protected DataReader importData() throws IOException { StopWatch sw = new StopWatch().start(); DataReader dr = super.importData(); put("graph.import_time", sw.stop().getSeconds()); return dr; } }; hopper.init(args). forDesktop(); hopper.getCHFactoryDecorator().setDisablingAllowed(true); hopper.getLMFactoryDecorator().setDisablingAllowed(true); hopper.importOrLoad(); GraphHopperStorage g = hopper.getGraphHopperStorage(); String vehicleStr = args.get("graph.flag_encoders", "car"); FlagEncoder encoder = hopper.getEncodingManager().getEncoder(vehicleStr); StopWatch sw = new StopWatch().start(); try { maxNode = g.getNodes(); boolean isCH = false; boolean isLM = false; GHBitSet allowedEdges = printGraphDetails(g, vehicleStr); printMiscUnitPerfTests(g, isCH, encoder, count * 100, allowedEdges); printLocationIndexQuery(g, hopper.getLocationIndex(), count); printTimeOfRouteQuery(hopper, isCH, isLM, count / 20, "routing", vehicleStr, true, -1); if (hopper.getLMFactoryDecorator().isEnabled()) { System.gc(); isLM = true; int activeLMCount = 12; for (; activeLMCount > 3; activeLMCount -= 4) { printTimeOfRouteQuery(hopper, isCH, isLM, count / 4, "routingLM" + activeLMCount, vehicleStr, true, activeLMCount); } // compareRouting(hopper, vehicleStr, count / 5); } if (hopper.getCHFactoryDecorator().isEnabled()) { isCH = true; if (hopper.getLMFactoryDecorator().isEnabled()) { isLM = true; System.gc(); // try just one constellation, often ~4-6 is best int lmCount = 5; printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCHLM" + lmCount, vehicleStr, true, lmCount); } isLM = false; System.gc(); Weighting weighting = hopper.getCHFactoryDecorator().getWeightings().get(0); CHGraph lg = g.getGraph(CHGraph.class, weighting); fillAllowedEdges(lg.getAllEdges(), allowedEdges); printMiscUnitPerfTests(lg, isCH, encoder, count * 100, allowedEdges); printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH", vehicleStr, true, -1); printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_no_instr", vehicleStr, false, -1); } logger.info("store into " + propLocation); } catch (Exception ex) { logger.error("Problem while measuring " + graphLocation, ex); put("error", ex.toString()); } finally { put("measurement.gitinfo", gitCommit); put("measurement.count", count); put("measurement.seed", seed); put("measurement.time", sw.stop().getTime()); System.gc(); put("measurement.totalMB", Helper.getTotalMB()); put("measurement.usedMB", Helper.getUsedMB()); try { store(new FileWriter(propLocation), "measurement finish, " + new Date().toString() + ", " + Constants.BUILD_DATE); } catch (IOException ex) { logger.error("Problem while storing properties " + graphLocation + ", " + propLocation, ex); } } } void fillAllowedEdges(AllEdgesIterator iter, GHBitSet bs) { bs.clear(); while (iter.next()) { bs.add(iter.getEdge()); } } private GHBitSet printGraphDetails(GraphHopperStorage g, String vehicleStr) { // graph size (edge, node and storage size) put("graph.nodes", g.getNodes()); put("graph.edges", g.getAllEdges().getMaxId()); put("graph.size_in_MB", g.getCapacity() / Helper.MB); put("graph.encoder", vehicleStr); AllEdgesIterator iter = g.getAllEdges(); final int maxEdgesId = g.getAllEdges().getMaxId(); final GHBitSet allowedEdges = new GHBitSetImpl(maxEdgesId); fillAllowedEdges(iter, allowedEdges); put("graph.valid_edges", allowedEdges.getCardinality()); return allowedEdges; } private void printLocationIndexQuery(Graph g, final LocationIndex idx, int count) { count *= 2; final BBox bbox = g.getBounds(); final double latDelta = bbox.maxLat - bbox.minLat; final double lonDelta = bbox.maxLon - bbox.minLon; final Random rand = new Random(seed); MiniPerfTest miniPerf = new MiniPerfTest() { @Override public int doCalc(boolean warmup, int run) { double lat = rand.nextDouble() * latDelta + bbox.minLat; double lon = rand.nextDouble() * lonDelta + bbox.minLon; int val = idx.findClosest(lat, lon, EdgeFilter.ALL_EDGES).getClosestNode(); // if (!warmup && val >= 0) // list.add(val); return val; } }.setIterations(count).start(); print("location_index", miniPerf); } private void printMiscUnitPerfTests(final Graph graph, boolean isCH, final FlagEncoder encoder, int count, final GHBitSet allowedEdges) { final Random rand = new Random(seed); String description = ""; if (isCH) { description = "CH"; CHGraph lg = (CHGraph) graph; final CHEdgeExplorer chExplorer = lg.createEdgeExplorer(new LevelEdgeFilter(lg)); MiniPerfTest miniPerf = new MiniPerfTest() { @Override public int doCalc(boolean warmup, int run) { int nodeId = rand.nextInt(maxNode); return GHUtility.count(chExplorer.setBaseNode(nodeId)); } }.setIterations(count).start(); print("unit_testsCH.level_edge_state_next", miniPerf); final CHEdgeExplorer chExplorer2 = lg.createEdgeExplorer(); miniPerf = new MiniPerfTest() { @Override public int doCalc(boolean warmup, int run) { int nodeId = rand.nextInt(maxNode); CHEdgeIterator iter = chExplorer2.setBaseNode(nodeId); while (iter.next()) { if (iter.isShortcut()) nodeId += (int) iter.getWeight(); } return nodeId; } }.setIterations(count).start(); print("unit_testsCH.get_weight", miniPerf); } EdgeFilter outFilter = new DefaultEdgeFilter(encoder, false, true); final EdgeExplorer outExplorer = graph.createEdgeExplorer(outFilter); MiniPerfTest miniPerf = new MiniPerfTest() { @Override public int doCalc(boolean warmup, int run) { int nodeId = rand.nextInt(maxNode); return GHUtility.count(outExplorer.setBaseNode(nodeId)); } }.setIterations(count).start(); print("unit_tests" + description + ".out_edge_state_next", miniPerf); final EdgeExplorer allExplorer = graph.createEdgeExplorer(); miniPerf = new MiniPerfTest() { @Override public int doCalc(boolean warmup, int run) { int nodeId = rand.nextInt(maxNode); return GHUtility.count(allExplorer.setBaseNode(nodeId)); } }.setIterations(count).start(); print("unit_tests" + description + ".all_edge_state_next", miniPerf); final int maxEdgesId = graph.getAllEdges().getMaxId(); miniPerf = new MiniPerfTest() { @Override public int doCalc(boolean warmup, int run) { while (true) { int edgeId = rand.nextInt(maxEdgesId); if (allowedEdges.contains(edgeId)) return graph.getEdgeIteratorState(edgeId, Integer.MIN_VALUE).getEdge(); } } }.setIterations(count).start(); print("unit_tests" + description + ".get_edge_state", miniPerf); } private void compareRouting(final GraphHopper hopper, String vehicle, int count) { logger.info("Comparing " + count + " routes. Differences will be printed to stderr."); String algo = Algorithms.ASTAR_BI; final Random rand = new Random(seed); final Graph g = hopper.getGraphHopperStorage(); final NodeAccess na = g.getNodeAccess(); for (int i = 0; i < count; i++) { int from = rand.nextInt(maxNode); int to = rand.nextInt(maxNode); double fromLat = na.getLatitude(from); double fromLon = na.getLongitude(from); double toLat = na.getLatitude(to); double toLon = na.getLongitude(to); GHRequest req = new GHRequest(fromLat, fromLon, toLat, toLon). setWeighting("fastest"). setVehicle(vehicle). setAlgorithm(algo); GHResponse lmRsp = hopper.route(req); req.getHints().put(Landmark.DISABLE, true); GHResponse originalRsp = hopper.route(req); String locStr = " iteration " + i + ". " + fromLat + "," + fromLon + " -> " + toLat + "," + toLon; if (lmRsp.hasErrors()) { if (originalRsp.hasErrors()) continue; logger.error("Error for LM but not for original response " + locStr); } String infoStr = " weight:" + lmRsp.getBest().getRouteWeight() + ", original: " + originalRsp.getBest().getRouteWeight() + " distance:" + lmRsp.getBest().getDistance() + ", original: " + originalRsp.getBest().getDistance() + " time:" + Helper.round2(lmRsp.getBest().getTime() / 1000) + ", original: " + Helper.round2(originalRsp.getBest().getTime() / 1000) + " points:" + lmRsp.getBest().getPoints().size() + ", original: " + originalRsp.getBest().getPoints().size(); if (Math.abs(1 - lmRsp.getBest().getRouteWeight() / originalRsp.getBest().getRouteWeight()) > 0.000001) logger.error("Too big weight difference for LM. " + locStr + infoStr); } } private void printTimeOfRouteQuery(final GraphHopper hopper, final boolean ch, final boolean lm, int count, String prefix, final String vehicle, final boolean withInstructions, final int activeLandmarks) { final Graph g = hopper.getGraphHopperStorage(); final AtomicLong maxDistance = new AtomicLong(0); final AtomicLong minDistance = new AtomicLong(Long.MAX_VALUE); final AtomicLong distSum = new AtomicLong(0); final AtomicLong airDistSum = new AtomicLong(0); final AtomicInteger failedCount = new AtomicInteger(0); final DistanceCalc distCalc = new DistanceCalcEarth(); final AtomicLong visitedNodesSum = new AtomicLong(0); // final AtomicLong extractTimeSum = new AtomicLong(0); // final AtomicLong calcPointsTimeSum = new AtomicLong(0); // final AtomicLong calcDistTimeSum = new AtomicLong(0); // final AtomicLong tmpDist = new AtomicLong(0); final Random rand = new Random(seed); final NodeAccess na = g.getNodeAccess(); MiniPerfTest miniPerf = new MiniPerfTest() { @Override public int doCalc(boolean warmup, int run) { int from = rand.nextInt(maxNode); int to = rand.nextInt(maxNode); double fromLat = na.getLatitude(from); double fromLon = na.getLongitude(from); double toLat = na.getLatitude(to); double toLon = na.getLongitude(to); GHRequest req = new GHRequest(fromLat, fromLon, toLat, toLon). setWeighting("fastest"). setVehicle(vehicle); req.getHints().put(CH.DISABLE, !ch). put(Landmark.DISABLE, !lm). put(Landmark.ACTIVE_COUNT, activeLandmarks). put("instructions", withInstructions); // put(algo + ".approximation", "BeelineSimplification"). // put(algo + ".epsilon", 2); GHResponse rsp; try { rsp = hopper.route(req); } catch (Exception ex) { // 'not found' can happen if import creates more than one subnetwork throw new RuntimeException("Error while calculating route! " + "nodes:" + from + " -> " + to + ", request:" + req, ex); } if (rsp.hasErrors()) { if (!warmup) failedCount.incrementAndGet(); if (rsp.getErrors().get(0).getMessage() == null) rsp.getErrors().get(0).printStackTrace(); else if (!rsp.getErrors().get(0).getMessage().toLowerCase().contains("not found")) logger.error("errors should NOT happen in Measurement! " + req + " => " + rsp.getErrors()); return 0; } PathWrapper arsp = rsp.getBest(); if (!warmup) { visitedNodesSum.addAndGet(rsp.getHints().getLong("visited_nodes.sum", 0)); long dist = (long) arsp.getDistance(); distSum.addAndGet(dist); airDistSum.addAndGet((long) distCalc.calcDist(fromLat, fromLon, toLat, toLon)); if (dist > maxDistance.get()) maxDistance.set(dist); if (dist < minDistance.get()) minDistance.set(dist); // extractTimeSum.addAndGet(p.getExtractTime()); // long start = System.nanoTime(); // size = p.calcPoints().getSize(); // calcPointsTimeSum.addAndGet(System.nanoTime() - start); } return arsp.getPoints().getSize(); } }.setIterations(count).start(); count -= failedCount.get(); // if using none-bidirectional algorithm make sure you exclude CH routing final String algoStr = ch ? Algorithms.DIJKSTRA_BI : Algorithms.ASTAR_BI; put(prefix + ".guessed_algorithm", algoStr); put(prefix + ".failed_count", failedCount.get()); put(prefix + ".distance_min", minDistance.get()); put(prefix + ".distance_mean", (float) distSum.get() / count); put(prefix + ".air_distance_mean", (float) airDistSum.get() / count); put(prefix + ".distance_max", maxDistance.get()); put(prefix + ".visited_nodes_mean", (float) visitedNodesSum.get() / count); // put(prefix + ".extractTime", (float) extractTimeSum.get() / count / 1000000f); // put(prefix + ".calcPointsTime", (float) calcPointsTimeSum.get() / count / 1000000f); // put(prefix + ".calcDistTime", (float) calcDistTimeSum.get() / count / 1000000f); print(prefix, miniPerf); } void print(String prefix, MiniPerfTest perf) { logger.info(prefix + ": " + perf.getReport()); put(prefix + ".sum", perf.getSum()); // put(prefix+".rms", perf.getRMS()); put(prefix + ".min", perf.getMin()); put(prefix + ".mean", perf.getMean()); put(prefix + ".max", perf.getMax()); } void put(String key, Object val) { // convert object to string to make serialization possible properties.put(key, "" + val); } private void store(FileWriter fileWriter, String comment) throws IOException { fileWriter.append("#" + comment + "\n"); for (Entry<String, String> e : properties.entrySet()) { fileWriter.append(e.getKey()); fileWriter.append("="); fileWriter.append(e.getValue()); fileWriter.append("\n"); } fileWriter.flush(); } }