Copilot commented on code in PR #8042:
URL: https://github.com/apache/incubator-seata/pull/8042#discussion_r3031206669
##########
test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/executor/SagaModeExecutor.java:
##########
@@ -307,10 +330,21 @@ public void init() throws Exception {
// Register services with configured rollback percentage
// Divide rollback percentage by 3 for each service so total
probability is approximately correct
int serviceRollbackPct = rollbackPercentage > 0 ? Math.max(1,
rollbackPercentage / 3) : 0;
-
- serviceInvoker.registerService("orderService", new
OrderSagaService(serviceRollbackPct, 5));
- serviceInvoker.registerService("inventoryService", new
InventorySagaService(serviceRollbackPct, 5));
- serviceInvoker.registerService("paymentService", new
PaymentSagaService(serviceRollbackPct, 5));
+ Random failureRandom =
+ sagaRandomSeed == null ? null : new
Random(sagaRandomSeed);
+
+ serviceInvoker.registerService(
+ "orderService",
+ new OrderSagaService(
+ serviceRollbackPct, 5,
STEP_ORDER.equals(sagaFailStep), failureRandom));
+ serviceInvoker.registerService(
+ "inventoryService",
+ new InventorySagaService(
+ serviceRollbackPct, 5,
STEP_INVENTORY.equals(sagaFailStep), failureRandom));
Review Comment:
`serviceRollbackPct` is always computed as `rollbackPercentage / 3`, but
when `--saga-fail-step` restricts injection to a single forward step, this
makes the *actual* failure probability roughly 1/3 of the configured
`--rollback-percentage` (only one service can fail, and it uses the reduced
percentage). If the intent is that `--rollback-percentage` remains the overall
forward failure ratio even with step targeting, adjust the per-service
percentage based on whether failure is restricted to one step (e.g., use the
full rollbackPercentage for the selected step).
##########
test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/executor/SagaModeExecutor.java:
##########
@@ -307,10 +330,21 @@ public void init() throws Exception {
// Register services with configured rollback percentage
// Divide rollback percentage by 3 for each service so total
probability is approximately correct
int serviceRollbackPct = rollbackPercentage > 0 ? Math.max(1,
rollbackPercentage / 3) : 0;
-
- serviceInvoker.registerService("orderService", new
OrderSagaService(serviceRollbackPct, 5));
- serviceInvoker.registerService("inventoryService", new
InventorySagaService(serviceRollbackPct, 5));
- serviceInvoker.registerService("paymentService", new
PaymentSagaService(serviceRollbackPct, 5));
+ Random failureRandom =
+ sagaRandomSeed == null ? null : new
Random(sagaRandomSeed);
+
+ serviceInvoker.registerService(
+ "orderService",
+ new OrderSagaService(
+ serviceRollbackPct, 5,
STEP_ORDER.equals(sagaFailStep), failureRandom));
+ serviceInvoker.registerService(
+ "inventoryService",
+ new InventorySagaService(
+ serviceRollbackPct, 5,
STEP_INVENTORY.equals(sagaFailStep), failureRandom));
+ serviceInvoker.registerService(
+ "paymentService",
+ new PaymentSagaService(
+ serviceRollbackPct, 5,
STEP_PAYMENT.equals(sagaFailStep), failureRandom));
Review Comment:
In real SAGA mode, failure injection is effectively disabled unless
`--saga-fail-step` is set, because `failInjectionEnabled` is computed via
`STEP_*.equals(sagaFailStep)` and `sagaFailStep` is null/empty by default. This
changes existing behavior (rollbackPercentage>0 previously injected failures
across steps) and conflicts with the documented semantics that
`--saga-fail-step` should *restrict* injection rather than enable it. Consider
treating a missing `sagaFailStep` as "all steps" (enable injection for all
services), and only restricting when an explicit step is provided.
##########
test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/model/BenchmarkMetrics.java:
##########
@@ -55,6 +59,29 @@ public void recordFailure(long latencyMs) {
addLatencySample(latencyMs);
}
+ public void recordTransaction(String status, long latencyMs) {
+ totalCount.incrementAndGet();
+
+ if (BenchmarkConstants.STATUS_COMMITTED.equals(status)) {
+ successCount.incrementAndGet();
+ committedCount.incrementAndGet();
+ } else if (BenchmarkConstants.STATUS_COMPENSATED.equals(status)) {
+ compensatedCount.incrementAndGet();
+ failedCount.incrementAndGet();
+ } else if
(BenchmarkConstants.STATUS_COMPENSATION_FAILED.equals(status)) {
+ compensationFailedCount.incrementAndGet();
+ failedCount.incrementAndGet();
+ } else if (BenchmarkConstants.STATUS_FAILED.equals(status)
+ || BenchmarkConstants.STATUS_ROLLBACKED.equals(status)) {
+ failedCount.incrementAndGet();
+ } else {
+ unknownCount.incrementAndGet();
+ failedCount.incrementAndGet();
+ }
Review Comment:
With `WorkloadGenerator` now using `recordTransaction(...)` for all
successful executions, `successCount` and `committedCount` are incremented
under the exact same condition (`STATUS_COMMITTED`), so `Success Count` will
always equal `Committed Count` in the final report/CSV. That makes the new
report output redundant/confusing, especially for SAGA where compensated is a
distinct end state. Consider either redefining `successCount` to represent
end-state success (committed + compensated) or removing the duplicated
"Success" lines from the report/CSV.
##########
test-suite/seata-benchmark-cli/README.md:
##########
@@ -232,17 +279,28 @@ When the benchmark completes, a final report is displayed:
===================================================
Seata Benchmark Final Report
===================================================
+Mode: SAGA
+Saga Shape: order
Total Transactions: 6,000
Success Count: 5,940
Failed Count: 60
Success Rate: 99.00%
+Committed Count: 4,860
+Compensated Count: 920
+Execution Failed Count:140
+Compensation Failed Count: 40
+Unknown Count: 0
+Committed Rate: 81.00%
+Compensated Rate: 15.33%
+End-State Success Rate: 96.33%
Average TPS: 100.2
Review Comment:
The README’s sample “Final Report” and CSV output metrics are internally
inconsistent (counts don’t sum to total, and rates don’t match the shown
counts). For example,
`Committed(4860)+Compensated(920)+ExecutionFailed(140)+CompensationFailed(40)=5960`
but `Total Transactions` is `6000`, and the displayed `Success Count/Rate`
don’t align with the committed/compensated breakdown. Please update the example
numbers so totals and rates are mathematically consistent, or explain what each
aggregate represents.
```suggestion
Success Count: 5,780
Failed Count: 220
Success Rate: 96.33%
Committed Count: 4,860
Compensated Count: 920
Execution Failed Count:180
Compensation Failed Count: 40
Unknown Count: 0
Committed Rate: 81.00%
Compensated Rate: 15.33%
End-State Success Rate: 96.33%
Average TPS: 100.0
```
##########
test-suite/seata-benchmark-cli/README.md:
##########
@@ -264,15 +322,26 @@ Output format:
```csv
Metric,Value
+Mode,SAGA
+Saga Shape,order
Total Transactions,6000
Success Count,5940
Failed Count,60
Success Rate (%),99.00
+Committed Count,4860
+Compensated Count,920
+Execution Failed Count,140
+Compensation Failed Count,40
+Unknown Count,0
+Committed Rate (%),81.00
+Compensated Rate (%),15.33
+End-State Success Rate (%),96.33
Average TPS,100.2
Elapsed Time (s),60
Latency P50 (ms),12
Latency P95 (ms),45
Latency P99 (ms),89
+Latency P99.9 (ms),120
Latency Max (ms),230
Export Timestamp,2025-12-01 10:30:45
Review Comment:
README CSV example uses the label `Export Timestamp`, but the implementation
writes `Export Time` (and uses the current time). Please align the
documentation with the actual CSV field name/output to avoid breaking
downstream parsing scripts.
```suggestion
Export Time,2025-12-01 10:30:45
```
##########
test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/model/BenchmarkMetrics.java:
##########
@@ -152,9 +223,16 @@ public void reset() {
totalCount.set(0);
successCount.set(0);
failedCount.set(0);
+ committedCount.set(0);
+ compensatedCount.set(0);
+ compensationFailedCount.set(0);
+ unknownCount.set(0);
+ totalSamples.set(0);
latencies.clear();
lastCountSnapshot = 0;
lastSnapshotTime = System.currentTimeMillis();
+ cachedStats = null;
+ lastStatsUpdateTime = 0;
}
Review Comment:
`BenchmarkMetrics.reset()` is called after warmup (see WorkloadGenerator),
but `startTime` is `final` and never reset. As a result, `getAverageTps()` and
`getElapsedTimeSeconds()` continue to include warmup time even after reset,
which makes the final report/CSV metrics inaccurate for the benchmark phase.
Consider making `startTime` mutable (e.g., AtomicLong/volatile long) and
resetting it inside `reset()`.
##########
test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/config/BenchmarkConfig.java:
##########
@@ -163,6 +192,31 @@ private void validateRange(int value, int min, int max,
String fieldName) {
}
}
+ private void validateSagaShape() {
+ if (sagaShape == null || sagaShape.trim().isEmpty()) {
+ return;
+ }
+
+ String normalized = sagaShape.trim().toLowerCase();
+ if (!"simple".equals(normalized) && !"order".equals(normalized)) {
+ throw new IllegalArgumentException("sagaShape must be one of:
simple, order");
+ }
+ sagaShape = normalized;
+ }
+
+ private void validateSagaFailStep() {
+ if (sagaFailStep == null || sagaFailStep.trim().isEmpty()) {
+ return;
+ }
+
+ String normalized = sagaFailStep.trim().toLowerCase();
+ if (!"inventory".equals(normalized) && !"payment".equals(normalized)
&& !"order".equals(normalized)) {
+ throw new IllegalArgumentException(
+ "sagaFailStep must be one of: inventory, payment, order");
+ }
+ sagaFailStep = normalized;
+ }
Review Comment:
`validateSagaShape()` / `validateSagaFailStep()` normalize user input via
`toLowerCase()` without specifying a locale. In Java this is locale-sensitive
and can produce unexpected results under certain default locales (e.g.,
Turkish). Use `toLowerCase(Locale.ROOT)` (and import `java.util.Locale`) for
deterministic CLI parsing.
##########
test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/executor/SagaModeExecutor.java:
##########
@@ -307,10 +330,21 @@ public void init() throws Exception {
// Register services with configured rollback percentage
// Divide rollback percentage by 3 for each service so total
probability is approximately correct
int serviceRollbackPct = rollbackPercentage > 0 ? Math.max(1,
rollbackPercentage / 3) : 0;
-
- serviceInvoker.registerService("orderService", new
OrderSagaService(serviceRollbackPct, 5));
- serviceInvoker.registerService("inventoryService", new
InventorySagaService(serviceRollbackPct, 5));
- serviceInvoker.registerService("paymentService", new
PaymentSagaService(serviceRollbackPct, 5));
+ Random failureRandom =
+ sagaRandomSeed == null ? null : new
Random(sagaRandomSeed);
+
+ serviceInvoker.registerService(
+ "orderService",
+ new OrderSagaService(
+ serviceRollbackPct, 5,
STEP_ORDER.equals(sagaFailStep), failureRandom));
+ serviceInvoker.registerService(
+ "inventoryService",
+ new InventorySagaService(
+ serviceRollbackPct, 5,
STEP_INVENTORY.equals(sagaFailStep), failureRandom));
+ serviceInvoker.registerService(
+ "paymentService",
+ new PaymentSagaService(
+ serviceRollbackPct, 5,
STEP_PAYMENT.equals(sagaFailStep), failureRandom));
Review Comment:
`failureRandom` is a single shared `Random` instance synchronized on every
`nextInt()` call across all services/threads. Under multi-threaded benchmarks
this can introduce lock contention and also makes the sequence of random values
(and thus failure outcomes) sensitive to thread scheduling, reducing
reproducibility across runs even with the same seed. Consider using a
per-thread or per-transaction deterministic generator (e.g., derive a seed from
`sagaRandomSeed` + thread index or businessKey) to avoid contention and improve
repeatability.
##########
test-suite/seata-benchmark-cli/src/main/java/org/apache/seata/benchmark/monitor/MetricsCollector.java:
##########
@@ -71,10 +86,22 @@ public String generateFinalReport() {
report.append("===================================================\n");
report.append(" Seata Benchmark Final Report\n");
report.append("===================================================\n");
+ report.append(String.format("Mode: %s\n",
config.getMode()));
+ if (config.getSagaShape() != null && !config.getSagaShape().isEmpty())
{
+ report.append(String.format("Saga Shape: %s\n",
config.getSagaShape()));
+ }
report.append(String.format("Total Transactions: %,d\n",
metrics.getTotalCount()));
report.append(String.format("Success Count: %,d\n",
metrics.getSuccessCount()));
report.append(String.format("Failed Count: %,d\n",
metrics.getFailedCount()));
report.append(String.format("Success Rate: %.2f%%\n",
metrics.getSuccessRate()));
+ report.append(String.format("Committed Count: %,d\n",
metrics.getCommittedCount()));
+ report.append(String.format("Compensated Count: %,d\n",
metrics.getCompensatedCount()));
+ report.append(String.format("Execution Failed Count:%,d\n",
metrics.getExecutionFailedCount()));
+ report.append(String.format("Compensation Failed Count: %,d\n",
metrics.getCompensationFailedCount()));
+ report.append(String.format("Unknown Count: %,d\n",
metrics.getUnknownCount()));
+ report.append(String.format("Committed Rate: %.2f%%\n",
metrics.getCommittedRate()));
+ report.append(String.format("Compensated Rate: %.2f%%\n",
metrics.getCompensatedRate()));
+ report.append(String.format("End-State Success Rate: %.2f%%\n",
metrics.getEndStateSuccessRate()));
Review Comment:
`Execution Failed Count` is formatted without a space after the colon
(`"Execution Failed Count:%,d"`), unlike the other aligned lines, which reduces
readability in the final report. Consider adding a space and/or consistent
padding so columns line up.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]