5
5
import io .gatling .javaapi .core .ScenarioBuilder ;
6
6
import io .gatling .javaapi .core .Session ;
7
7
import io .gatling .javaapi .http .HttpRequestActionBuilder ;
8
- import org .heigit .ors .benchmark .BenchmarkEnums .DirectionsModes ;
8
+ import org .heigit .ors .benchmark .BenchmarkEnums .MatrixModes ;
9
9
import org .heigit .ors .benchmark .exceptions .RequestBodyCreationException ;
10
10
import org .heigit .ors .util .SourceUtils ;
11
11
import org .slf4j .LoggerFactory ;
20
20
import static io .gatling .javaapi .http .HttpDsl .http ;
21
21
import static io .gatling .javaapi .http .HttpDsl .status ;
22
22
23
+ /**
24
+ * Load test implementation for OpenRouteService Matrix API using Gatling
25
+ * framework.
26
+ *
27
+ * This class performs load testing on the matrix endpoint by:
28
+ * - Reading matrix test data from CSV files containing coordinates, sources,
29
+ * and destinations
30
+ * - Creating HTTP requests to the /v2/matrix/{profile} endpoint
31
+ * - Testing different matrix calculation modes and routing profiles
32
+ * - Measuring response times and throughput under concurrent load
33
+ *
34
+ * The test data is expected to be in CSV format with columns:
35
+ * coordinates, sources, destinations, distances, profile
36
+ */
23
37
public class MatrixAlgorithmLoadTest extends AbstractLoadTest {
24
38
25
39
static {
26
40
logger = LoggerFactory .getLogger (MatrixAlgorithmLoadTest .class );
27
41
}
28
42
43
+ /**
44
+ * Constructs a new MatrixAlgorithmLoadTest instance.
45
+ * Initializes the load test with configuration from the parent class.
46
+ */
29
47
public MatrixAlgorithmLoadTest () {
30
48
super ();
31
49
}
32
50
51
+ /**
52
+ * Logs configuration information specific to matrix load testing.
53
+ * Displays source files, concurrent users, and execution mode.
54
+ */
33
55
@ Override
34
56
protected void logConfigInfo () {
35
57
logger .info ("Initializing MatrixAlgorithmLoadTest:" );
@@ -38,33 +60,69 @@ protected void logConfigInfo() {
38
60
logger .info ("- Execution mode: {}" , config .isParallelExecution () ? "parallel" : "sequential" );
39
61
}
40
62
63
+ /**
64
+ * Logs the type of test being performed.
65
+ */
41
66
@ Override
42
67
protected void logTestTypeInfo () {
43
68
logger .info ("Testing matrix" );
44
69
}
45
70
71
+ /**
72
+ * Creates test scenarios for all combinations of matrix modes, source files,
73
+ * and profiles.
74
+ *
75
+ * @param isParallel whether scenarios should be executed in parallel
76
+ * @return stream of PopulationBuilder instances for each test scenario
77
+ */
46
78
@ Override
47
79
protected Stream <PopulationBuilder > createScenarios (boolean isParallel ) {
48
- return config .getDirectionsModes ().stream ()
80
+ return config .getMatrixModes ().stream ()
49
81
.flatMap (mode -> config .getSourceFiles ().stream ()
50
82
.flatMap (sourceFile -> mode .getProfiles ().stream ()
51
83
.map (profile -> createScenarioWithInjection (sourceFile , isParallel , mode , profile ))));
52
84
}
53
85
54
- private PopulationBuilder createScenarioWithInjection (String sourceFile , boolean isParallel , DirectionsModes mode ,
86
+ /**
87
+ * Creates a single test scenario with user injection configuration.
88
+ *
89
+ * @param sourceFile path to the CSV file containing test data
90
+ * @param isParallel whether the scenario runs in parallel mode
91
+ * @param mode the matrix calculation mode to test
92
+ * @param profile the routing profile to test
93
+ * @return PopulationBuilder configured with the specified parameters
94
+ */
95
+ private PopulationBuilder createScenarioWithInjection (String sourceFile , boolean isParallel , MatrixModes mode ,
55
96
String profile ) {
56
97
String scenarioName = formatScenarioName (mode , profile , isParallel );
57
98
return createMatrixScenario (scenarioName , sourceFile , config , mode , profile )
58
99
.injectOpen (atOnceUsers (config .getNumConcurrentUsers ()));
59
100
}
60
101
61
- private String formatScenarioName (DirectionsModes mode , String profile , boolean isParallel ) {
102
+ /**
103
+ * Formats a descriptive name for the test scenario.
104
+ *
105
+ * @param mode the matrix calculation mode
106
+ * @param profile the routing profile
107
+ * @param isParallel whether the scenario runs in parallel
108
+ * @return formatted scenario name string
109
+ */
110
+ private String formatScenarioName (MatrixModes mode , String profile , boolean isParallel ) {
62
111
return String .format ("%s - %s - %s" , isParallel ? "Parallel" : "Sequential" , mode , profile );
63
112
}
64
113
114
+ /**
115
+ * Creates a Gatling scenario for matrix load testing.
116
+ *
117
+ * @param name descriptive name for the scenario
118
+ * @param sourceFile path to CSV file containing test coordinates
119
+ * @param config test configuration parameters
120
+ * @param mode matrix calculation mode to test
121
+ * @param profile routing profile to test
122
+ * @return ScenarioBuilder configured for matrix testing
123
+ */
65
124
private static ScenarioBuilder createMatrixScenario (String name , String sourceFile , Config config ,
66
- DirectionsModes mode , String profile ) {
67
-
125
+ MatrixModes mode , String profile ) {
68
126
try {
69
127
List <Map <String , Object >> records = csv (sourceFile ).readRecords ();
70
128
List <Map <String , Object >> targetRecords = SourceUtils .getRecordsByProfile (records , profile );
@@ -86,7 +144,16 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi
86
144
}
87
145
}
88
146
89
- private static HttpRequestActionBuilder createRequest (String name , Config config , DirectionsModes mode ,
147
+ /**
148
+ * Creates an HTTP request action for the matrix API endpoint.
149
+ *
150
+ * @param name request name for identification in test results
151
+ * @param config test configuration
152
+ * @param mode matrix calculation mode
153
+ * @param profile routing profile
154
+ * @return HttpRequestActionBuilder configured for matrix API calls
155
+ */
156
+ private static HttpRequestActionBuilder createRequest (String name , Config config , MatrixModes mode ,
90
157
String profile ) {
91
158
return http (name )
92
159
.post ("/v2/matrix/" + profile )
@@ -95,34 +162,113 @@ private static HttpRequestActionBuilder createRequest(String name, Config config
95
162
.check (status ().is (200 ));
96
163
}
97
164
98
- static String createRequestBody (Session session , Config config , DirectionsModes mode ) {
165
+ /**
166
+ * Creates the JSON request body for matrix API calls from CSV session data.
167
+ *
168
+ * @param session Gatling session containing CSV row data
169
+ * @param config test configuration
170
+ * @param mode matrix calculation mode providing additional parameters
171
+ * @return JSON string representation of the request body
172
+ * @throws RequestBodyCreationException if JSON serialization fails
173
+ */
174
+ static String createRequestBody (Session session , Config config , MatrixModes mode ) {
99
175
try {
176
+ // Get the data from the CSV row
177
+ String coordinatesStr = (String ) session .get ("coordinates" );
178
+ String sourcesStr = (String ) session .get ("sources" );
179
+ String destinationsStr = (String ) session .get ("destinations" );
180
+
100
181
Map <String , Object > requestBody = new java .util .HashMap <>(Map .of (
101
- "locations" , createLocationsListFromArrays (session , config ),
102
- "sources" , List .of (0 ),
103
- "destinations" , List .of (1 )));
182
+ "locations" , parseCoordinatesFromString (coordinatesStr ),
183
+ "sources" , parseIntegerArrayFromString (sourcesStr ),
184
+ "destinations" , parseIntegerArrayFromString (destinationsStr )));
185
+
104
186
requestBody .putAll (mode .getRequestParams ());
105
187
return objectMapper .writeValueAsString (requestBody );
106
188
} catch (JsonProcessingException e ) {
107
189
throw new RequestBodyCreationException ("Failed to create request body" , e );
108
190
}
109
191
}
110
192
111
- static List <List <Double >> createLocationsListFromArrays (Session session , Config config ) {
112
- List <List <Double >> locations = new ArrayList <>();
193
+ /**
194
+ * Parses coordinate pairs from CSV string format to nested list structure.
195
+ *
196
+ * Converts strings like "[[8.695556, 49.392701], [8.684623, 49.398284]]"
197
+ * into List<List<Double>> format expected by the matrix API.
198
+ *
199
+ * @param coordinatesStr string representation of coordinate array from CSV
200
+ * @return list of coordinate pairs as [longitude, latitude] arrays
201
+ * @throws RequestBodyCreationException if parsing fails or format is invalid
202
+ */
203
+ static List <List <Double >> parseCoordinatesFromString (String coordinatesStr ) {
204
+ try {
205
+ if (coordinatesStr == null || coordinatesStr .trim ().isEmpty ()) {
206
+ throw new RequestBodyCreationException ("Coordinates string is null or empty" );
207
+ }
208
+
209
+ // Remove quotes if present
210
+ String cleaned = coordinatesStr .trim ();
211
+ if (cleaned .startsWith ("\" " ) && cleaned .endsWith ("\" " )) {
212
+ cleaned = cleaned .substring (1 , cleaned .length () - 1 );
213
+ }
214
+
215
+ // Remove outer brackets
216
+ cleaned = cleaned .substring (2 , cleaned .length () - 2 );
217
+ String [] coordinatePairs = cleaned .split ("\\ ], \\ [" );
218
+
219
+ List <List <Double >> locations = new ArrayList <>();
220
+ for (String pair : coordinatePairs ) {
221
+ String [] values = pair .split (", " );
222
+ if (values .length != 2 ) {
223
+ throw new RequestBodyCreationException ("Invalid coordinate pair: " + pair );
224
+ }
225
+ double lon = Double .parseDouble (values [0 ]);
226
+ double lat = Double .parseDouble (values [1 ]);
227
+ locations .add (List .of (lon , lat ));
228
+ }
229
+ return locations ;
230
+ } catch (Exception e ) {
231
+ throw new RequestBodyCreationException ("Failed to parse coordinates: " + coordinatesStr , e );
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Parses integer arrays from CSV string format.
237
+ *
238
+ * Converts strings like "[0, 1, 2]" into List<Integer> format
239
+ * for sources and destinations parameters.
240
+ *
241
+ * @param arrayStr string representation of integer array from CSV
242
+ * @return list of integers
243
+ * @throws RequestBodyCreationException if parsing fails or format is invalid
244
+ */
245
+ static List <Integer > parseIntegerArrayFromString (String arrayStr ) {
113
246
try {
114
- Double startLon = Double .valueOf ((String ) session .getList (config .getFieldStartLon ()).get (0 ));
115
- Double startLat = Double .valueOf ((String ) session .getList (config .getFieldStartLat ()).get (0 ));
116
- locations .add (List .of (startLon , startLat ));
117
- Double endLon = Double .valueOf ((String ) session .getList (config .getFieldEndLon ()).get (0 ));
118
- Double endLat = Double .valueOf ((String ) session .getList (config .getFieldEndLat ()).get (0 ));
119
- locations .add (List .of (endLon , endLat ));
120
- } catch (NumberFormatException e ) {
121
- String errorMessage = String .format (
122
- "Failed to parse coordinate values in locations list at index %d. Original value could not be converted to double" ,
123
- locations .size ());
124
- throw new RequestBodyCreationException ("Error processing coordinates: " + errorMessage , e );
247
+ if (arrayStr == null || arrayStr .trim ().isEmpty ()) {
248
+ throw new RequestBodyCreationException ("Array string is null or empty" );
249
+ }
250
+
251
+ // Remove quotes if present
252
+ String cleaned = arrayStr .trim ();
253
+ if (cleaned .startsWith ("\" " ) && cleaned .endsWith ("\" " )) {
254
+ cleaned = cleaned .substring (1 , cleaned .length () - 1 );
255
+ }
256
+
257
+ // Remove brackets
258
+ cleaned = cleaned .substring (1 , cleaned .length () - 1 );
259
+
260
+ if (cleaned .trim ().isEmpty ()) {
261
+ return new ArrayList <>();
262
+ }
263
+
264
+ String [] values = cleaned .split (", " );
265
+ List <Integer > result = new ArrayList <>();
266
+ for (String value : values ) {
267
+ result .add (Integer .parseInt (value .trim ()));
268
+ }
269
+ return result ;
270
+ } catch (Exception e ) {
271
+ throw new RequestBodyCreationException ("Failed to parse integer array: " + arrayStr , e );
125
272
}
126
- return locations ;
127
273
}
128
274
}
0 commit comments