55import io .gatling .javaapi .core .ScenarioBuilder ;
66import io .gatling .javaapi .core .Session ;
77import io .gatling .javaapi .http .HttpRequestActionBuilder ;
8- import org .heigit .ors .benchmark .BenchmarkEnums .DirectionsModes ;
8+ import org .heigit .ors .benchmark .BenchmarkEnums .MatrixModes ;
99import org .heigit .ors .benchmark .exceptions .RequestBodyCreationException ;
1010import org .heigit .ors .util .SourceUtils ;
1111import org .slf4j .LoggerFactory ;
2020import static io .gatling .javaapi .http .HttpDsl .http ;
2121import static io .gatling .javaapi .http .HttpDsl .status ;
2222
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+ */
2337public class MatrixAlgorithmLoadTest extends AbstractLoadTest {
2438
2539 static {
2640 logger = LoggerFactory .getLogger (MatrixAlgorithmLoadTest .class );
2741 }
2842
43+ /**
44+ * Constructs a new MatrixAlgorithmLoadTest instance.
45+ * Initializes the load test with configuration from the parent class.
46+ */
2947 public MatrixAlgorithmLoadTest () {
3048 super ();
3149 }
3250
51+ /**
52+ * Logs configuration information specific to matrix load testing.
53+ * Displays source files, concurrent users, and execution mode.
54+ */
3355 @ Override
3456 protected void logConfigInfo () {
3557 logger .info ("Initializing MatrixAlgorithmLoadTest:" );
@@ -38,33 +60,69 @@ protected void logConfigInfo() {
3860 logger .info ("- Execution mode: {}" , config .isParallelExecution () ? "parallel" : "sequential" );
3961 }
4062
63+ /**
64+ * Logs the type of test being performed.
65+ */
4166 @ Override
4267 protected void logTestTypeInfo () {
4368 logger .info ("Testing matrix" );
4469 }
4570
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+ */
4678 @ Override
4779 protected Stream <PopulationBuilder > createScenarios (boolean isParallel ) {
48- return config .getDirectionsModes ().stream ()
80+ return config .getMatrixModes ().stream ()
4981 .flatMap (mode -> config .getSourceFiles ().stream ()
5082 .flatMap (sourceFile -> mode .getProfiles ().stream ()
5183 .map (profile -> createScenarioWithInjection (sourceFile , isParallel , mode , profile ))));
5284 }
5385
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 ,
5596 String profile ) {
5697 String scenarioName = formatScenarioName (mode , profile , isParallel );
5798 return createMatrixScenario (scenarioName , sourceFile , config , mode , profile )
5899 .injectOpen (atOnceUsers (config .getNumConcurrentUsers ()));
59100 }
60101
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 ) {
62111 return String .format ("%s - %s - %s" , isParallel ? "Parallel" : "Sequential" , mode , profile );
63112 }
64113
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+ */
65124 private static ScenarioBuilder createMatrixScenario (String name , String sourceFile , Config config ,
66- DirectionsModes mode , String profile ) {
67-
125+ MatrixModes mode , String profile ) {
68126 try {
69127 List <Map <String , Object >> records = csv (sourceFile ).readRecords ();
70128 List <Map <String , Object >> targetRecords = SourceUtils .getRecordsByProfile (records , profile );
@@ -86,7 +144,16 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi
86144 }
87145 }
88146
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 ,
90157 String profile ) {
91158 return http (name )
92159 .post ("/v2/matrix/" + profile )
@@ -95,34 +162,113 @@ private static HttpRequestActionBuilder createRequest(String name, Config config
95162 .check (status ().is (200 ));
96163 }
97164
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 ) {
99175 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+
100181 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+
104186 requestBody .putAll (mode .getRequestParams ());
105187 return objectMapper .writeValueAsString (requestBody );
106188 } catch (JsonProcessingException e ) {
107189 throw new RequestBodyCreationException ("Failed to create request body" , e );
108190 }
109191 }
110192
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 ) {
113246 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 );
125272 }
126- return locations ;
127273 }
128274}
0 commit comments