Skip to content

Commit f886d3f

Browse files
committed
docs: Add API Benchmarks documentation
1 parent a05ad54 commit f886d3f

File tree

5 files changed

+264
-16
lines changed

5 files changed

+264
-16
lines changed

Diff for: docs/.vitepress/config.js

+1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ export default withMermaid(defineVersionedConfig({
292292
{text: 'Graph Management', link: '/technical-details/graph-repo-client/'},
293293
{text: 'Integration Tests', link: '/technical-details/integration-tests'},
294294
{text: 'Coordinate Generators', link: '/technical-details/coordinate-generators'},
295+
{text: 'API Benchmarks', link: '/technical-details/api-benchmarks'},
295296
]
296297
},
297298
{text: 'FAQ', link: '/frequently-asked-questions'}

Diff for: docs/technical-details/api-benchmarks.md

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# API Benchmarking Tools
2+
3+
The openrouteservice provides tools for benchmarking API performance, allowing you to test the responsiveness, throughput, and scalability of your openrouteservice instance or an external API under various load conditions.
4+
5+
## Prerequisites
6+
7+
To use the API benchmarking tools, you need:
8+
9+
- Java 17+
10+
- Maven
11+
- An active openrouteservice instance (local or remote)
12+
- CSV files with coordinates for testing (can be generated using the [Coordinate Generation Tools](/technical-details/coordinate-generators))
13+
14+
## Overview
15+
16+
The benchmarking tools are part of the `ors-benchmark` module and include:
17+
18+
1. **Isochrones Range Load Test** - Tests the performance of the Isochrones API under various load conditions with range options.
19+
2. **Routing Algorithm Load Test** - Tests the performance of the Directions API with different routing algorithms and profiles.
20+
21+
These tools use the Gatling framework to simulate user load and provide detailed performance metrics that help you:
22+
23+
- Evaluate the performance of your openrouteservice instance
24+
- Identify bottlenecks in your configuration
25+
- Compare different routing algorithms and profiles
26+
- Optimize your setup for specific use cases
27+
28+
## Isochrones Range Load Test
29+
30+
The Isochrones Range Load Test tool allows you to test the performance of the Isochrones API by sending multiple requests with configurable parameters, including batch processing of locations and Range types (time or distance).
31+
32+
### Isochrones Working Principles
33+
34+
The tool reads coordinates from CSV files and creates isochrone requests with varying batch sizes (number of locations per request). It uses the Gatling framework to simulate concurrent users and measure response times, throughput, and other performance metrics.
35+
36+
### Isochrones Options
37+
38+
| Option | Description | Default |
39+
|--------|-------------|---------|
40+
| `source_files` | Comma-separated list of CSV files containing coordinate pairs (required) | (Required) |
41+
| `base_url` | ORS API base URL | <http://localhost:8082/ors> |
42+
| `api_key` | API key for authentication | (none) |
43+
| `profile` | Routing profile | driving-car |
44+
| `range` | Comma-separated list of isochrone ranges in meters or seconds | 300 |
45+
| `field_lon` | CSV field name for longitude column | longitude |
46+
| `field_lat` | CSV field name for latitude column | latitude |
47+
| `concurrent_users` | Number of concurrent users | 1 |
48+
| `query_sizes` | Comma-separated list of batch sizes for location processing | 1 |
49+
| `test_unit` | Type of range to test - 'distance' or 'time' | distance |
50+
| `parallel_execution` | Run scenarios in parallel or sequential | false |
51+
52+
### Isochrones CSV File Format
53+
54+
The Isochrones Range Load Test expects CSV files with the following format:
55+
56+
```csv
57+
longitude,latitude,profile
58+
8.681495,49.41461,driving-car
59+
8.686507,49.41943,driving-car
60+
8.681495,49.41461,cycling-regular
61+
8.686507,49.41943,cycling-regular
62+
...
63+
```
64+
65+
### Isochrones Examples
66+
67+
#### Generating Points for Isochrones Testing
68+
69+
Generate 10,000 points across Germany for various profiles:
70+
71+
```bash
72+
mvn clean compile exec:java -Dexec.cleanupDaemonThreads=false -pl 'ors-benchmark' \
73+
-Dexec.mainClass="org.heigit.ors.coordinates_generator.SnappingGeneratorApp" \
74+
-Dexec.args="\
75+
-n 2500 \
76+
-e 7.5,47.3,13.5,55.0 \
77+
-p driving-car,cycling-regular,foot-walking,driving-hgv \
78+
-r 350 \
79+
-u http://localhost:8080/ors \
80+
-o germany_car-foot-bike-hgv_10000_points.csv"
81+
```
82+
83+
#### Isochrones Basic Usage
84+
85+
Test the Isochrones API with the default parameters:
86+
87+
```bash
88+
mvn -pl 'ors-benchmark' gatling:test \
89+
-Dgatling.simulationClass=org.heigit.ors.benchmark.IsochronesRangeLoadTest \
90+
-Dsource_files='germany_car-foot-bike-hgv_10000_points.csv' \
91+
-Dbase_url='http://localhost:8080/ors'
92+
```
93+
94+
#### Testing with Batch Processing
95+
96+
Test the Isochrones API with varying batch sizes, processing 1, 2, 4, and 8 locations per request:
97+
98+
```bash
99+
mvn -pl 'ors-benchmark' gatling:test \
100+
-Dgatling.simulationClass=org.heigit.ors.benchmark.IsochronesRangeLoadTest \
101+
-Dsource_files='germany_car-foot-bike-hgv_10000_points.csv' \
102+
-Dbase_url='http://localhost:8080/ors' \
103+
-Dquery_sizes='1,2,4,8' \
104+
-Drange='300,600,900' \
105+
-Dtest_unit='time'
106+
```
107+
108+
#### Comprehensive Test with All Parameters
109+
110+
Generate points specific to cycling-regular profile:
111+
112+
```bash
113+
mvn -pl 'ors-benchmark' gatling:test \
114+
-Dgatling.simulationClass=org.heigit.ors.benchmark.IsochronesRangeLoadTest \
115+
-Dsource_files='germany_car-foot-bike-hgv_10000_points.csv' \
116+
-Dbase_url='http://localhost:8080/ors' \
117+
-Dprofile='cycling-regular' \
118+
-Drange=500 \
119+
-Dconcurrent_users=1 \
120+
-Dquery_sizes='1,2,4,6,8,10,12,15,20' \
121+
-Dtest_unit='distance'
122+
```
123+
124+
## Routing Algorithm Load Test
125+
126+
The Routing Algorithm Load Test tool allows you to test the performance of the Directions API with different routing algorithms and profiles.
127+
128+
### Routing Working Principles
129+
130+
The tool reads origin-destination coordinate pairs from CSV files and sends routing requests to the Directions API. It can test different routing algorithms and profiles, simulating concurrent users and measuring response times, throughput, and other performance metrics.
131+
132+
### Routing Options
133+
134+
| Option | Description | Default |
135+
|--------|-------------|---------|
136+
| `source_files` | Comma-separated list of CSV files containing coordinate pairs (required) | (none) |
137+
| `base_url` | ORS API base URL | <http://localhost:8082/ors> |
138+
| `api_key` | API key for authentication | (none) |
139+
| `modes` | Comma-separated routing modes: algoch, algocore, algolmastar | all |
140+
| `field_start_lon` | CSV field name for start longitude column | start_longitude |
141+
| `field_start_lat` | CSV field name for start latitude column | start_latitude |
142+
| `field_end_lon` | CSV field name for end longitude column | end_longitude |
143+
| `field_end_lat` | CSV field name for end latitude column | end_latitude |
144+
| `concurrent_users` | Number of concurrent users | 1 |
145+
| `parallel_execution` | Run scenarios in parallel or sequential | false |
146+
147+
### Routing CSV File Format
148+
149+
The Routing Algorithm Load Test expects CSV files with the following format:
150+
151+
```csv
152+
start_longitude,start_latitude,end_longitude,end_latitude,profile
153+
8.681495,49.41461,8.686507,49.41943,driving-car
154+
8.686507,49.41943,8.691528,49.41567,driving-car
155+
...
156+
```
157+
158+
### Routing Examples
159+
160+
#### Generate Routes for Routing Tests
161+
162+
Generate 10,000 routes with different distance constraints per profile:
163+
164+
```bash
165+
mvn clean compile exec:java -Dexec.cleanupDaemonThreads=false -pl 'ors-benchmark' \
166+
-Dexec.mainClass="org.heigit.ors.coordinates_generator.RouteGeneratorApp" \
167+
-Dexec.args="\
168+
-n 2500 \
169+
-e 7.5,47.3,13.5,55.0 \
170+
-p driving-car,cycling-regular,foot-walking,driving-hgv \
171+
-d 200 \
172+
-m 600,10,5,600 \
173+
-t 8 \
174+
-u http://localhost:8080/ors \
175+
-o germany_car600-bike10-foot5-hgv600_10000_routes.csv"
176+
```
177+
178+
#### Routing Basic Usage
179+
180+
Test the Directions API with the default parameters:
181+
182+
```bash
183+
mvn -pl 'ors-benchmark' gatling:test \
184+
-Dgatling.simulationClass=org.heigit.ors.benchmark.RoutingAlgorithmLoadTest \
185+
-Dsource_files='germany_car600-bike10-foot5-hgv600_10000_routes.csv' \
186+
-Dbase_url='http://localhost:9082/ors'
187+
```
188+
189+
#### Testing with Custom Field Names
190+
191+
Generate routes with custom column names:
192+
193+
```bash
194+
mvn clean compile exec:java -Dexec.cleanupDaemonThreads=false -pl 'ors-benchmark' \
195+
-Dexec.mainClass="org.heigit.ors.coordinates_generator.RouteGeneratorApp" \
196+
-Dexec.args="\
197+
-n 10000 \
198+
-e 7.5,47.3,13.5,55.0 \
199+
-p driving-car,cycling-regular,foot-walking,driving-hgv \
200+
-d 200 \
201+
-m 600,10,5,600 \
202+
-t 8 \
203+
-u http://localhost:8080/ors \
204+
-o germany_car600-bike10-foot5-hgv600_10000_routes.csv"
205+
```
206+
207+
Test the Directions API with custom field names:
208+
209+
```bash
210+
mvn -pl 'ors-benchmark' gatling:test \
211+
-Dgatling.simulationClass=org.heigit.ors.benchmark.RoutingAlgorithmLoadTest \
212+
-Dsource_files='germany_car600-bike10-foot5-hgv600_10000_routes.csv' \
213+
-Dfield_start_lon='from_lon' \
214+
-Dfield_start_lat='from_lat' \
215+
-Dfield_end_lon='to_lon' \
216+
-Dfield_end_lat='to_lat' \
217+
-Dmodes='algolmastar'
218+
```
219+
220+
#### Testing Multiple Routing Algorithms
221+
222+
Test the Directions API with multiple routing algorithms:
223+
224+
```bash
225+
mvn -pl 'ors-benchmark' gatling:test \
226+
-Dgatling.simulationClass=org.heigit.ors.benchmark.RoutingAlgorithmLoadTest \
227+
-Dsource_files='germany_car600-bike10-foot5-hgv600_10000_routes.csv' \
228+
-Dparallel_execution=false \
229+
-Dmodes='algoch,algocore'
230+
```
231+
232+
## Performance Testing Best Practices
233+
234+
1. **Start with a baseline**: Run tests on a known, stable configuration to establish a baseline for comparison.
235+
2. **Incremental load**: Start with low concurrency and gradually increase to find the breaking point.
236+
3. **Realistic scenarios**: Use realistic data and request patterns based on your expected usage.
237+
4. **Steady state**: Allow the system to reach a steady state before drawing conclusions.
238+
5. **Isolate variables**: Change only one parameter at a time to understand its impact.
239+
6. **Test repeatedly**: Multiple test runs will give more reliable results.
240+
7. **Monitor resources**: Watch CPU, memory, disk I/O, and network usage during tests.
241+
242+
## Troubleshooting
243+
244+
- **Out of Memory Errors**: Increase the JVM heap size with `-Xmx` parameter.
245+
- **Connection Timeouts**: Check network connectivity and firewall settings.
246+
- **Rate Limiting**: If using a remote API, be aware of rate limiting policies.
247+
- **CSV File Issues**: Ensure CSV files are properly formatted with no BOM markers.

Diff for: ors-benchmark/src/main/java/org/heigit/ors/benchmark/IsochronesLoadTest.java renamed to ors-benchmark/src/main/java/org/heigit/ors/benchmark/IsochronesRangeLoadTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,19 @@
2121
import static io.gatling.javaapi.http.HttpDsl.status;
2222
import static org.heigit.ors.util.NameUtils.getFileNameWithoutExtension;
2323

24-
public class IsochronesLoadTest extends AbstractLoadTest {
24+
public class IsochronesRangeLoadTest extends AbstractLoadTest {
2525

2626
static {
27-
logger = LoggerFactory.getLogger(IsochronesLoadTest.class);
27+
logger = LoggerFactory.getLogger(IsochronesRangeLoadTest.class);
2828
}
2929

30-
public IsochronesLoadTest() {
30+
public IsochronesRangeLoadTest() {
3131
super();
3232
}
3333

3434
@Override
3535
protected void logConfigInfo() {
36-
logger.info("Initializing IsochronesLoadTest with configuration:");
36+
logger.info("Initializing IsochronesRangeLoadTest with configuration:");
3737
logger.info("- Source files: {}", config.getSourceFiles());
3838
logger.info("- Target profile: {}", config.getTargetProfile());
3939
logger.info("- Concurrent users: {}", config.getNumConcurrentUsers());

Diff for: ors-benchmark/src/main/java/org/heigit/ors/benchmark/DirectionsLoadTest.java renamed to ors-benchmark/src/main/java/org/heigit/ors/benchmark/RoutingAlgorithmLoadTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@
2020
import static io.gatling.javaapi.http.HttpDsl.http;
2121
import static io.gatling.javaapi.http.HttpDsl.status;
2222

23-
public class DirectionsLoadTest extends AbstractLoadTest {
23+
public class RoutingAlgorithmLoadTest extends AbstractLoadTest {
2424

2525
static {
26-
logger = LoggerFactory.getLogger(DirectionsLoadTest.class);
26+
logger = LoggerFactory.getLogger(RoutingAlgorithmLoadTest.class);
2727
}
2828

29-
public DirectionsLoadTest() {
29+
public RoutingAlgorithmLoadTest() {
3030
super();
3131
}
3232

3333
@Override
3434
protected void logConfigInfo() {
35-
logger.info("Initializing DirectionsLoadTest:");
35+
logger.info("Initializing RoutingAlgorithmLoadTest:");
3636
logger.info("- Source files: {}", config.getSourceFiles());
3737
logger.info("- Concurrent users: {}", config.getNumConcurrentUsers());
3838
logger.info("- Execution mode: {}", config.isParallelExecution() ? "parallel" : "sequential");

Diff for: ors-benchmark/src/test/java/org/heigit/ors/benchmark/IsochronesLoadTestTest.java renamed to ors-benchmark/src/test/java/org/heigit/ors/benchmark/IsochronesRangeLoadTestTest.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void createRequestBody_ShouldCreateValidJson() {
4343
Config config = new Config();
4444

4545
// when
46-
String result = IsochronesLoadTest.createRequestBody(mockSession, 1, config, RangeType.TIME);
46+
String result = IsochronesRangeLoadTest.createRequestBody(mockSession, 1, config, RangeType.TIME);
4747

4848
// then
4949
assertThat(result)
@@ -58,7 +58,7 @@ void createRequestBody_ShouldCreateValidJsonForDistance() {
5858
Config config = new Config();
5959

6060
// when
61-
String result = IsochronesLoadTest.createRequestBody(mockSession, 1, config, RangeType.DISTANCE);
61+
String result = IsochronesRangeLoadTest.createRequestBody(mockSession, 1, config, RangeType.DISTANCE);
6262

6363
// then
6464
assertThat(result)
@@ -73,15 +73,15 @@ void createRequestBody_ShouldIncludeMultipleLocations() {
7373
Config config = new Config();
7474

7575
// when
76-
String result = IsochronesLoadTest.createRequestBody(mockSession, 2, config, RangeType.TIME);
76+
String result = IsochronesRangeLoadTest.createRequestBody(mockSession, 2, config, RangeType.TIME);
7777

7878
// then
7979
assertThat(result).contains("\"locations\":[[8.681495,49.41461],[8.681495,49.41461]]");
8080
}
8181

8282
@Test
8383
void testCreateRequestBodySingleLocation() throws JsonProcessingException {
84-
String requestBody = IsochronesLoadTest.createRequestBody(mockSession, 1, mockConfig, RangeType.TIME);
84+
String requestBody = IsochronesRangeLoadTest.createRequestBody(mockSession, 1, mockConfig, RangeType.TIME);
8585
JsonNode json = objectMapper.readTree(requestBody);
8686

8787
assertEquals(1, json.get("locations").size());
@@ -97,7 +97,7 @@ void testCreateRequestBodyMultipleLocations() throws JsonProcessingException {
9797
when(mockSession.get("latitude")).thenReturn(Arrays.asList(49.41461, 49.41461, 49.41461));
9898
when(mockConfig.getRange()).thenReturn("500");
9999

100-
String requestBody = IsochronesLoadTest.createRequestBody(mockSession, 3, mockConfig, RangeType.TIME);
100+
String requestBody = IsochronesRangeLoadTest.createRequestBody(mockSession, 3, mockConfig, RangeType.TIME);
101101
JsonNode json = objectMapper.readTree(requestBody);
102102

103103
assertEquals(3, json.get("locations").size());
@@ -111,7 +111,7 @@ void testCreateRequestBodyMultipleLocations() throws JsonProcessingException {
111111
// Replace old testCreateLocationsList with new test
112112
@Test
113113
void testCreateLocationsListFromArrays() {
114-
List<List<Double>> locations = IsochronesLoadTest.createLocationsListFromArrays(mockSession, 2, mockConfig);
114+
List<List<Double>> locations = IsochronesRangeLoadTest.createLocationsListFromArrays(mockSession, 2, mockConfig);
115115

116116
assertEquals(2, locations.size());
117117
locations.forEach(coord -> {
@@ -125,7 +125,7 @@ void testCreateLocationsListFromArrays_WithNullValues() {
125125
when(mockSession.get("longitude")).thenReturn(null);
126126
when(mockSession.get("latitude")).thenReturn(Arrays.asList(49.41461));
127127

128-
List<List<Double>> locations = IsochronesLoadTest.createLocationsListFromArrays(mockSession, 2, mockConfig);
128+
List<List<Double>> locations = IsochronesRangeLoadTest.createLocationsListFromArrays(mockSession, 2, mockConfig);
129129

130130
assertTrue(locations.isEmpty());
131131
}
@@ -136,7 +136,7 @@ void createRequestBody_ShouldCreateValidJsonWithMultipleRanges() throws JsonProc
136136
when(mockConfig.getRanges()).thenReturn(Arrays.asList(300, 600, 900));
137137

138138
// when
139-
String result = IsochronesLoadTest.createRequestBody(mockSession, 1, mockConfig, RangeType.TIME);
139+
String result = IsochronesRangeLoadTest.createRequestBody(mockSession, 1, mockConfig, RangeType.TIME);
140140
JsonNode json = objectMapper.readTree(result);
141141

142142
// then

0 commit comments

Comments
 (0)