Skip to content

Commit a8af2a8

Browse files
committed
loadtest-controller: add type of users added to the final report
1 parent b3f00f2 commit a8af2a8

9 files changed

Lines changed: 199 additions & 50 deletions

File tree

loadtest-controller/src/main/java/io/openvidu/loadtest/models/testcase/CreateParticipantResponse.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.openvidu.loadtest.models.testcase;
22

3+
import io.openvidu.loadtest.models.testcase.Role;
4+
35
public class CreateParticipantResponse {
46
private boolean responseOk;
57
private String stopReason;
@@ -10,6 +12,7 @@ public class CreateParticipantResponse {
1012
private String userId;
1113
private String sessionId;
1214
private String workerUrl;
15+
private Role role;
1316

1417
public CreateParticipantResponse() {
1518
}
@@ -108,12 +111,21 @@ public CreateParticipantResponse setWorkerUrl(String workerUrl) {
108111
return this;
109112
}
110113

114+
public Role getRole() {
115+
return role;
116+
}
117+
118+
public CreateParticipantResponse setRole(Role role) {
119+
this.role = role;
120+
return this;
121+
}
122+
111123
@Override
112124
public String toString() {
113125
return "CreateParticipantResponse [responseOk=" + responseOk + ", stopReason=" + stopReason + ", connectionId="
114-
+ connectionId + ", workerCpuPct=" + workerCpuPct + ", streamsInWorker=" + streamsInWorker
115-
+ ", participantsInWorker=" + participantsInWorker + ", userId=" + userId + ", sessionId=" + sessionId
116-
+ ", workerUrl=" + workerUrl + "]";
126+
+ connectionId + ", workerCpuPct=" + workerCpuPct + ", streamsInWorker=" + streamsInWorker
127+
+ ", participantsInWorker=" + participantsInWorker + ", userId=" + userId + ", sessionId=" + sessionId
128+
+ ", workerUrl=" + workerUrl + ", role=" + role + "]";
117129
}
118130

119131
}

loadtest-controller/src/main/java/io/openvidu/loadtest/models/testcase/ResultReport.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,22 @@ public class ResultReport {
5454
private Map<String, Integer> userRetryCounts = new TreeMap<>(); // key: "session-user"
5555
private Map<String, Calendar> userDisconnectTimestamps = new TreeMap<>(); // key: "session-user"
5656
private Map<String, List<RetryAttempt>> userRetryAttempts = new LinkedHashMap<>(); // key: "session-user"
57+
private Map<String, String> roleByUser = new TreeMap<>();
5758

5859
public ResultReport() {
5960
}
6061

6162
public ResultReport build() {
6263
return new ResultReport(this.totalParticipants, this.numSessionsCompleted, this.numSessionsCreated,
63-
this.workersUsed, this.streamsPerWorker, this.sessionTopology,
64-
this.openviduRecording, this.browserRecording, this.isManualParticipantAllocation,
65-
this.usersPerWorker, this.participantsPerSession, this.stopReason, this.startTime, this.endTime,
66-
this.kibanaUrl, this.s3BucketName, this.timePerWorker, this.timePerRecordingWorker,
67-
this.userStartTimes, this.participantResponses, this.totalRetries, this.successfulRetries,
68-
this.retrySuccessRate, this.avgRetriesPerParticipant, this.maxRetriesInSingleParticipant,
69-
this.workerCpuAvg, this.workerCpuMax, this.workerStreams, this.workerParticipants,
70-
this.userStartDelaysPercentiles, this.userDisconnectTimestamps,
71-
this.userSuccessTimestamps, this.userRetryCounts, this.userRetryAttempts);
64+
this.workersUsed, this.streamsPerWorker, this.sessionTopology,
65+
this.openviduRecording, this.browserRecording, this.isManualParticipantAllocation,
66+
this.usersPerWorker, this.participantsPerSession, this.stopReason, this.startTime, this.endTime,
67+
this.kibanaUrl, this.s3BucketName, this.timePerWorker, this.timePerRecordingWorker,
68+
this.userStartTimes, this.participantResponses, this.totalRetries, this.successfulRetries,
69+
this.retrySuccessRate, this.avgRetriesPerParticipant, this.maxRetriesInSingleParticipant,
70+
this.workerCpuAvg, this.workerCpuMax, this.workerStreams, this.workerParticipants,
71+
this.userStartDelaysPercentiles, this.userDisconnectTimestamps,
72+
this.userSuccessTimestamps, this.userRetryCounts, this.userRetryAttempts, this.roleByUser);
7273
}
7374

7475
public ResultReport setManualParticipantAllocation(boolean isManualParticipantAllocation) {
@@ -233,6 +234,15 @@ public ResultReport setUserRetryAttempts(Map<String, List<RetryAttempt>> userRet
233234
return this;
234235
}
235236

237+
public ResultReport setRoleByUser(Map<String, String> roleByUser) {
238+
this.roleByUser = roleByUser != null ? roleByUser : new TreeMap<>();
239+
return this;
240+
}
241+
242+
public Map<String, String> getRoleByUser() {
243+
return roleByUser;
244+
}
245+
236246
private void computeAggregates() {
237247
// Compute per-worker and global CPU stats
238248
Map<String, List<Double>> cpuPerWorker = new TreeMap<>();
@@ -347,7 +357,7 @@ public List<CreateParticipantResponse> getParticipantResponses() {
347357
return participantResponses;
348358
}
349359

350-
private ResultReport(int totalParticipants, int numSessionsCompleted, int numSessionsCreated, int workersUsed,
360+
private ResultReport(int totalParticipants, int numSessionsCompleted, int numSessionsCreated, int workersUsed,
351361
List<Integer> streamsPerWorker, String sessionTopology,
352362
String openviduRecording, boolean browserRecording, boolean manualParticipantsAllocation,
353363
int usersPerWorker, String participantsPerSession, String stopReason, Calendar startTime,
@@ -359,7 +369,7 @@ private ResultReport(int totalParticipants, int numSessionsCompleted, int numSes
359369
Map<String, Integer> workerStreams, Map<String, Integer> workerParticipants,
360370
double[] userStartDelaysPercentiles, Map<String, Calendar> userDisconnectTimestamps,
361371
Map<String, Calendar> userSuccessTimestamps, Map<String, Integer> userRetryCounts,
362-
Map<String, List<RetryAttempt>> userRetryAttempts) {
372+
Map<String, List<RetryAttempt>> userRetryAttempts, Map<String, String> roleByUser) {
363373
this.totalParticipants = totalParticipants;
364374
this.numSessionsCompleted = numSessionsCompleted;
365375
this.numSessionsCreated = numSessionsCreated;
@@ -394,6 +404,7 @@ private ResultReport(int totalParticipants, int numSessionsCompleted, int numSes
394404
this.userSuccessTimestamps = userSuccessTimestamps;
395405
this.userRetryCounts = userRetryCounts;
396406
this.userRetryAttempts = userRetryAttempts;
407+
this.roleByUser = roleByUser != null ? roleByUser : new TreeMap<>();
397408
}
398409

399410
private String getDuration() {

loadtest-controller/src/main/java/io/openvidu/loadtest/services/BrowserEmulatorClient.java

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,21 @@ public BrowserEmulatorClient(LoadTestConfig loadTestConfig, CustomHttpClient htt
120120
this.httpProtocolPrefix = loadTestConfig.isHttpsDisabled() ? "http://" : "https://";
121121
}
122122

123-
public void clean() {
124-
this.isClean.set(true);
125-
this.clientFailures.clear();
126-
this.clientRetryAttempts.clear();
127-
this.clientRoles.clear();
128-
this.participantTestCases.clear();
129-
this.participantConnecting.clear();
130-
this.participantReconnecting.clear();
131-
this.userDisconnectTimestamps.clear();
132-
// Clear static collections that hold per-worker state so subsequent test
133-
// cases don't observe accumulated data from previous runs.
134-
BrowserEmulatorClient.publishersAndSubscribersInWorker.clear();
135-
BrowserEmulatorClient.recordingParticipantCreated.clear();
136-
this.isClean.set(false);
137-
}
123+
public void clean() {
124+
this.isClean.set(true);
125+
this.clientFailures.clear();
126+
this.clientRetryAttempts.clear();
127+
this.clientRoles.clear();
128+
this.participantTestCases.clear();
129+
this.participantConnecting.clear();
130+
this.participantReconnecting.clear();
131+
this.userDisconnectTimestamps.clear();
132+
// Clear static collections that hold per-worker state so subsequent test
133+
// cases don't observe accumulated data from previous runs.
134+
BrowserEmulatorClient.publishersAndSubscribersInWorker.clear();
135+
BrowserEmulatorClient.recordingParticipantCreated.clear();
136+
this.isClean.set(false);
137+
}
138138

139139
public void addDisconnectTimestamp(String userId, String sessionId) {
140140
String key = userId + "-" + sessionId;
@@ -530,7 +530,11 @@ private CreateParticipantResponse createParticipant(String workerUrl, int userNu
530530
this.participantConnecting.get(user).set(false);
531531
this.saveParticipantData(workerUrl, testCase.isTeaching() ? Role.PUBLISHER : role);
532532
}
533-
return processResponse(response, workerUrl);
533+
CreateParticipantResponse processed = processResponse(response, workerUrl);
534+
if (processed != null) {
535+
processed.setRole(role);
536+
}
537+
return processed;
534538
} catch (Exception e) {
535539
CreateParticipantErrorContext ctx = new CreateParticipantErrorContext(
536540
new UserInfo(workerUrl, userNumber, sessionNumber, role, userId, sessionId), testCase, cpr);
@@ -793,6 +797,19 @@ public Map<String, List<RetryAttempt>> getPerUserRetryAttempts() {
793797
return result;
794798
}
795799

800+
public Map<String, Role> getPerUserRoles() {
801+
Map<String, Role> result = new HashMap<>();
802+
for (ConcurrentHashMap<String, Role> workerRoles : clientRoles.values()) {
803+
if (workerRoles == null) {
804+
continue;
805+
}
806+
for (Map.Entry<String, Role> entry : workerRoles.entrySet()) {
807+
result.put(entry.getKey(), entry.getValue());
808+
}
809+
}
810+
return result;
811+
}
812+
796813
public void shutdownWorkers(List<String> workerUrls, boolean waitForResponse) {
797814
if (workerUrls == null || workerUrls.isEmpty()) {
798815
return;

loadtest-controller/src/main/java/io/openvidu/loadtest/services/core/LoadTestService.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,30 @@ private void saveResultReport(TestCase testCase, String participantsBySession, C
356356
log.warn("After fallback, userSuccessTimestamps size: {}", userSuccessTimestamps.size());
357357
}
358358

359+
// Build mapping of user-session -> role (label)
360+
Map<String, String> roleByUserMap = new HashMap<>();
361+
List<CreateParticipantResponse> allResponses = participantOrchestrator.getAllParticipantResponses();
362+
for (CreateParticipantResponse r : allResponses) {
363+
if (r.getUserId() != null && r.getSessionId() != null && r.getRole() != null) {
364+
roleByUserMap.put(r.getUserId() + "-" + r.getSessionId(), r.getRole().getLabel());
365+
}
366+
}
367+
// Merge any roles tracked in BrowserEmulatorClient internal maps
368+
Map<String, io.openvidu.loadtest.models.testcase.Role> clientRoles = browserEmulatorClient.getPerUserRoles();
369+
if (clientRoles != null) {
370+
for (Map.Entry<String, io.openvidu.loadtest.models.testcase.Role> e : clientRoles.entrySet()) {
371+
if (e.getValue() != null) {
372+
roleByUserMap.putIfAbsent(e.getKey(), e.getValue().getLabel());
373+
}
374+
}
375+
}
376+
// Fallback: if topology is N:N, assume PUBLISHER for any missing entries
377+
if (testCase != null && testCase.getTopology() != null && testCase.getTopology().toString().startsWith("N:N")) {
378+
for (String key : userSuccessTimestamps.keySet()) {
379+
roleByUserMap.putIfAbsent(key, "PUBLISHER");
380+
}
381+
}
382+
359383
ResultReport rr = new ResultReport().setTotalParticipants(participantOrchestrator.getTotalParticipants())
360384
.setNumSessionsCompleted(participantOrchestrator.getSessionsCompleted())
361385
.setNumSessionsCreated(participantOrchestrator.getSessionNumber())
@@ -371,15 +395,16 @@ private void saveResultReport(TestCase testCase, String participantsBySession, C
371395
.setTimePerWorker(shutdownOrchestrator.getWorkerTimes())
372396
.setTimePerRecordingWorker(shutdownOrchestrator.getRecordingWorkerTimes())
373397
.setUserStartTimes(participantOrchestrator.getUserStartTimes())
374-
.setUserSuccessTimestamps(userSuccessTimestamps)
375-
.setParticipantResponses(participantOrchestrator.getAllParticipantResponses())
398+
.setUserSuccessTimestamps(userSuccessTimestamps)
399+
.setParticipantResponses(participantOrchestrator.getAllParticipantResponses())
400+
.setRoleByUser(roleByUserMap)
376401
.setUserRetryCounts(browserEmulatorClient.getPerUserRetryCounts())
377402
.setUserRetryAttempts(browserEmulatorClient.getPerUserRetryAttempts())
378403
.build();
379404

380405
allReports.add(rr);
381406

382-
io.exportResultsTxtOnly(rr);
407+
io.exportResultsTxtOnly(rr);
383408

384409
}
385410

loadtest-controller/src/main/java/io/openvidu/loadtest/utils/HtmlReportGenerator.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import io.openvidu.loadtest.models.testcase.ResultReport;
3030
import io.openvidu.loadtest.services.BrowserEmulatorClient.RetryAttempt;
31+
import io.openvidu.loadtest.models.testcase.CreateParticipantResponse;
3132

3233
@Component
3334
public class HtmlReportGenerator {
@@ -139,6 +140,25 @@ private List<Map<String, Object>> buildUserRows(ResultReport result) {
139140
return List.of();
140141
}
141142

143+
// Build a map of user-session -> role (PUBLISHER/SUBSCRIBER).
144+
// Prefer precomputed roles from the ResultReport if present, otherwise
145+
// fall back to participant responses.
146+
Map<String, String> roleByUser = new java.util.HashMap<>();
147+
if (result.getRoleByUser() != null) {
148+
roleByUser.putAll(result.getRoleByUser());
149+
}
150+
List<CreateParticipantResponse> participantResponses = result.getParticipantResponses() != null
151+
? result.getParticipantResponses()
152+
: List.of();
153+
for (CreateParticipantResponse r : participantResponses) {
154+
if (r.getUserId() != null && r.getSessionId() != null) {
155+
String k = r.getUserId() + "-" + r.getSessionId();
156+
if (r.getRole() != null) {
157+
roleByUser.putIfAbsent(k, r.getRole().getLabel());
158+
}
159+
}
160+
}
161+
142162
Map<String, Integer> userRetryCounts = result.getUserRetryCounts() != null ? result.getUserRetryCounts()
143163
: Map.of();
144164
Map<String, List<RetryAttempt>> userRetryAttempts = result.getUserRetryAttempts() != null
@@ -164,7 +184,9 @@ private List<Map<String, Object>> buildUserRows(ResultReport result) {
164184
retryDetailId = baseId + "-" + suffix;
165185
}
166186
existingDetailIds.add(retryDetailId);
167-
rows.add(objectRow("userId", info.userId, "sessionId", info.sessionId,
187+
String roleLabel = roleByUser.getOrDefault(key, "-");
188+
rows.add(objectRow("userId", info.userId, "sessionId", info.sessionId,
189+
"type", roleLabel,
168190
"joinDate", formatDate(info.joinDate),
169191
"retries", info.retries,
170192
"ifRetriesGreaterThanZero", info.retries > 0,

loadtest-controller/src/main/resources/templates/report.mustache

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -675,15 +675,16 @@
675675
<tr>
676676
<th onclick="sortUserTable(this, 0, {{index}})">User <span class="sort-icon">↕</span></th>
677677
<th onclick="sortUserTable(this, 1, {{index}})">Session <span class="sort-icon">↕</span></th>
678-
<th onclick="sortUserTable(this, 2, {{index}})">Join Date <span class="sort-icon">↕</span></th>
679-
<th onclick="sortUserTable(this, 3, {{index}})">Retries <span class="sort-icon">↕</span></th>
678+
<th onclick="sortUserTable(this, 2, {{index}})">Type <span class="sort-icon">↕</span></th>
679+
<th onclick="sortUserTable(this, 3, {{index}})">Join Date <span class="sort-icon">↕</span></th>
680+
<th onclick="sortUserTable(this, 4, {{index}})">Retries <span class="sort-icon">↕</span></th>
680681
<th>Retry Details</th>
681682
</tr>
682683
</thead>
683684
<tbody id="userTableBody-{{index}}">
684685
{{#userRows}}
685686
<tr class="user-row" data-detail-id="{{retryDetailId}}">
686-
<td>{{userId}}</td><td>{{sessionId}}</td><td>{{joinDate}}</td>
687+
<td>{{userId}}</td><td>{{sessionId}}</td><td>{{type}}</td><td>{{joinDate}}</td>
687688
<td>{{#ifRetriesGreaterThanZero}}<span class="error-count medium">{{retries}}</span>{{/ifRetriesGreaterThanZero}}{{^ifRetriesGreaterThanZero}}{{retries}}{{/ifRetriesGreaterThanZero}}</td>
688689
<td>
689690
{{#hasRetryAttempts}}
@@ -694,7 +695,7 @@
694695
</tr>
695696
{{#hasRetryAttempts}}
696697
<tr id="{{retryDetailId}}" class="retry-details-row">
697-
<td colspan="5">
698+
<td colspan="6">
698699
<div class="retry-details-wrap">
699700
<table class="retry-attempts-table">
700701
<thead>
@@ -720,7 +721,7 @@
720721
{{/hasRetryAttempts}}
721722
{{/userRows}}
722723
{{^userRows}}
723-
<tr><td colspan="5" class="no-data">No participants</td></tr>
724+
<tr><td colspan="6" class="no-data">No participants</td></tr>
724725
{{/userRows}}
725726
</tbody>
726727
</table>
@@ -767,7 +768,7 @@
767768
const dir = sortDirection[key] ? 1 : -1;
768769
document.querySelectorAll('#user-connections-table-' + tabIdx + ' th').forEach(h => h.classList.remove('sorted'));
769770
th.classList.add('sorted');
770-
const dateColumns = [2];
771+
const dateColumns = [3];
771772
rows.sort((a, b) => {
772773
const aVal = a.children[colIndex].textContent.trim();
773774
const bVal = b.children[colIndex].textContent.trim();
@@ -820,8 +821,8 @@
820821
}
821822
if (s.allUserRows.length > 0) {
822823
s.filteredUsers = [...s.allUserRows].sort((a, b) => {
823-
const aVal = a.children[2].textContent.trim();
824-
const bVal = b.children[2].textContent.trim();
824+
const aVal = a.children[3].textContent.trim();
825+
const bVal = b.children[3].textContent.trim();
825826
if (aVal === '-' || aVal === '') return 1;
826827
if (bVal === '-' || bVal === '') return -1;
827828
const aDate = new Date(aVal.replace(' ', 'T'));
@@ -830,7 +831,7 @@
830831
return aVal.localeCompare(bVal);
831832
});
832833
sortDirection['user-' + tabIdx] = true;
833-
const joinDateHeader = document.querySelectorAll('#user-connections-table-' + tabIdx + ' th')[2];
834+
const joinDateHeader = document.querySelectorAll('#user-connections-table-' + tabIdx + ' th')[3];
834835
if (joinDateHeader) joinDateHeader.classList.add('sorted');
835836
updatePagination(tabIdx);
836837
}

loadtest-controller/src/test/java/io/openvidu/loadtest/integration/AwsScaleMultiFailureIntegrationTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,11 +509,13 @@ private void validateUserRetryCounts(Document doc) {
509509
Map<String, Element> userRowsByKey = new HashMap<>();
510510
for (Element row : userRows) {
511511
Elements cells = row.select("td");
512-
if (cells.size() >= 5) {
512+
// After adding the 'Type' column the table now has at least 6 columns
513+
if (cells.size() >= 6) {
513514
String userId = cells.get(0).text().trim();
514515
String sessionId = cells.get(1).text().trim();
515516
String userKey = userId + "-" + sessionId;
516-
userRetries.put(userKey, parseRetriesFromCell(cells.get(3), userKey));
517+
// Retries column shifted from index 3 -> 4 due to inserted 'Type' column
518+
userRetries.put(userKey, parseRetriesFromCell(cells.get(4), userKey));
517519
userRowsByKey.put(userKey, row);
518520
}
519521
}

0 commit comments

Comments
 (0)