From 11321ed5bda1692bd1ba1da88cccced752fb006e Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 15:24:17 +0200 Subject: [PATCH 01/48] Refactor tx_pool_latency_analysis configuration to include QPS parameter and reorder fields for improved clarity --- .../tasks/tx_pool_latency_analysis/config.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/config.go b/pkg/coordinator/tasks/tx_pool_latency_analysis/config.go index f8b0ea48..d1c4aee4 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/config.go +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/config.go @@ -3,18 +3,20 @@ package txpoollatencyanalysis type Config struct { PrivateKey string `yaml:"privateKey" json:"privateKey"` - TxCount int `yaml:"txCount" json:"txCount"` + QPS int `yaml:"qps" json:"qps"` + TxCount int `yaml:"txCount" json:"txCount"` MeasureInterval int `yaml:"measureInterval" json:"measureInterval"` - HighLatency int64 `yaml:"highLatency" json:"highLatency"` + HighLatency int64 `yaml:"highLatency" json:"highLatency"` FailOnHighLatency bool `yaml:"failOnHighLatency" json:"failOnHighLatency"` SecondsBeforeRunning int64 `yaml:"secondsBeforeRunning" json:"secondsBeforeRunning"` } func DefaultConfig() Config { return Config{ - TxCount: 1000, + QPS: 1000, + TxCount: 1000, MeasureInterval: 100, - HighLatency: 5000, // in microseconds + HighLatency: 5000, // in microseconds FailOnHighLatency: true, SecondsBeforeRunning: 0, } From 1eaed2fb45a4e5e89d0f00d5005f20e7eb0c64cd Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Wed, 11 Jun 2025 15:41:30 +0200 Subject: [PATCH 02/48] change task config --- .../tasks/tx_pool_latency_analysis/README.md | 25 ++++++++----------- .../tasks/tx_pool_latency_analysis/config.go | 10 +++----- .../tx_pool_throughput_analysis/README.md | 8 ++++-- .../tx_pool_throughput_analysis/config.go | 8 +++--- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/README.md b/pkg/coordinator/tasks/tx_pool_latency_analysis/README.md index a675b5c5..502c0fd2 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/README.md +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/README.md @@ -9,25 +9,21 @@ The `tx_pool_latency_analysis` task evaluates latency of transaction processing - **`privateKey`**: The private key of the account to use for sending transactions. -- **`txCount`**: - The total number of transactions to send. +- **`tps`**: + The total number of transactions to send in one second. + +- **`duration_s`**: + The test duration (the number of transactions to send is calculated as `tps * duration_s`). - **`measureInterval`**: The interval at which the script logs progress (e.g., every 100 transactions). -- **`highLatency`**: - The expected average transaction latency in milliseconds. - -- **`failOnHighLatency`**: - Whether the task should fail if the measured latency exceeds `highLatency`. - - ### Outputs - **`tx_count`**: The total number of transactions sent. -- **`avg_latency_ms`**: +- **`max_latency_ms`**: The average latency of the transactions in milliseconds. ### Defaults @@ -35,10 +31,11 @@ The `tx_pool_latency_analysis` task evaluates latency of transaction processing ```yaml - name: tx_pool_latency_analysis config: - txCount: 15000 + tps: 100 + duration_s: 10 measureInterval: 1000 - highLatency: 5000 - failOnHighLatency: false configVars: - privateKey: "tx_pool_latency_analysis" + privateKey: "walletPrivkey" ``` + + diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/config.go b/pkg/coordinator/tasks/tx_pool_latency_analysis/config.go index d1c4aee4..2f8ae791 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/config.go +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/config.go @@ -4,20 +4,16 @@ type Config struct { PrivateKey string `yaml:"privateKey" json:"privateKey"` QPS int `yaml:"qps" json:"qps"` - TxCount int `yaml:"txCount" json:"txCount"` + Duration_s int `yaml:"duration_s" json:"duration_s"` MeasureInterval int `yaml:"measureInterval" json:"measureInterval"` - HighLatency int64 `yaml:"highLatency" json:"highLatency"` - FailOnHighLatency bool `yaml:"failOnHighLatency" json:"failOnHighLatency"` SecondsBeforeRunning int64 `yaml:"secondsBeforeRunning" json:"secondsBeforeRunning"` } func DefaultConfig() Config { return Config{ - QPS: 1000, - TxCount: 1000, + QPS: 100, + Duration_s: 60, MeasureInterval: 100, - HighLatency: 5000, // in microseconds - FailOnHighLatency: true, SecondsBeforeRunning: 0, } } diff --git a/pkg/coordinator/tasks/tx_pool_throughput_analysis/README.md b/pkg/coordinator/tasks/tx_pool_throughput_analysis/README.md index b59eef7b..9c5c86a4 100644 --- a/pkg/coordinator/tasks/tx_pool_throughput_analysis/README.md +++ b/pkg/coordinator/tasks/tx_pool_throughput_analysis/README.md @@ -9,9 +9,12 @@ The `tx_pool_throughput_analysis` task evaluates the throughput of transaction p - **`privateKey`**: The private key of the account to use for sending transactions. -- **`qps`**: +- **`tps`**: The total number of transactions to send in one second. +- **`duration_s`**: + The test duration (the number of transactions to send is calculated as `tps * duration_s`). + - **`measureInterval`**: The interval at which the script logs progress (e.g., every 100 transactions). @@ -25,7 +28,8 @@ The `tx_pool_throughput_analysis` task evaluates the throughput of transaction p ```yaml - name: tx_pool_throughput_analysis config: - qps: 15000 + tps: 100 + duration_s: 10 measureInterval: 1000 configVars: privateKey: "walletPrivkey" diff --git a/pkg/coordinator/tasks/tx_pool_throughput_analysis/config.go b/pkg/coordinator/tasks/tx_pool_throughput_analysis/config.go index 60e0e70e..573905d1 100644 --- a/pkg/coordinator/tasks/tx_pool_throughput_analysis/config.go +++ b/pkg/coordinator/tasks/tx_pool_throughput_analysis/config.go @@ -3,15 +3,17 @@ package txpoolcheck type Config struct { PrivateKey string `yaml:"privateKey" json:"privateKey"` - QPS int `yaml:"qps" json:"qps"` + QPS int `yaml:"qps" json:"qps"` + Duration_s int `yaml:"duration_s" json:"duration_s"` MeasureInterval int `yaml:"measureInterval" json:"measureInterval"` SecondsBeforeRunning int `yaml:"secondsBeforeRunning" json:"secondsBeforeRunning"` } func DefaultConfig() Config { return Config{ - QPS: 1000, - MeasureInterval: 100, + QPS: 100, + Duration_s: 60, + MeasureInterval: 100, SecondsBeforeRunning: 0, } } From 31355d6cefd0d9306d2f3d1b0941dd23b3f90d8c Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 16:00:41 +0200 Subject: [PATCH 03/48] Enhance transaction latency analysis by implementing synchronized arrays for precise latency measurement and improving transaction sending logic. Added QPS-based transaction distribution and refined logging for better monitoring of transaction flow. --- .../tasks/tx_pool_latency_analysis/task.go | 152 +++++++++++++----- 1 file changed, 110 insertions(+), 42 deletions(-) diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go index 0d411d2b..b2ee0e9a 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go @@ -111,64 +111,130 @@ func (t *Task) Execute(ctx context.Context) error { defer conn.Close() - var totalLatency time.Duration - var latencies []time.Duration + // Create three synchronized arrays for precise latency calculation + sendTimestamps := make([]time.Time, t.config.QPS) + receiveTimestamps := make([]time.Time, t.config.QPS) var txs []*ethtypes.Transaction + hashToIndex := make(map[string]int) // Map hash to array index - for i := 0; i < t.config.TxCount; i++ { - tx, err := t.generateTransaction(ctx) - if err != nil { - t.logger.Errorf("Failed to create transaction: %v", err) - t.ctx.SetResult(types.TaskResultFailure) - return nil + startTime := time.Now() + isFailed := false + sentTxCount := 0 + + // Send transactions at QPS rate + go func() { + startExecTime := time.Now() + endTime := startExecTime.Add(time.Second) + + for i := range t.config.QPS { + if ctx.Err() != nil || isFailed { + return + } + + // Calculate how much time we have left + remainingTime := time.Until(endTime) + + // Calculate sleep time to distribute remaining transactions evenly + sleepTime := remainingTime / time.Duration(t.config.QPS-i) + + // Generate and send transaction + go func(index int) { + if ctx.Err() != nil || isFailed { + return + } + + tx, err := t.generateTransaction(ctx) + if err != nil { + t.logger.Errorf("Failed to create transaction: %v", err) + t.ctx.SetResult(types.TaskResultFailure) + isFailed = true + return + } + + startTx := time.Now() + + err = client.GetRPCClient().SendTransaction(ctx, tx) + if err != nil { + t.logger.Errorf("Failed to send transaction: %v. Nonce: %d. ", err, tx.Nonce()) + t.ctx.SetResult(types.TaskResultFailure) + isFailed = true + return + } + + // Store transaction data in synchronized arrays + txHash := tx.Hash().String() + sendTimestamps[index] = startTx + hashToIndex[txHash] = index + + txs = append(txs, tx) + sentTxCount++ + + if sentTxCount%t.config.MeasureInterval == 0 { + elapsed := time.Since(startTime) + t.logger.Infof("Sent %d transactions in %.2fs", sentTxCount, elapsed.Seconds()) + } + }(i) + + if isFailed { + return + } + + time.Sleep(sleepTime) } - startTx := time.Now() + execTime := time.Since(startExecTime) + t.logger.Infof("Time to generate and send %d transactions: %v", t.config.QPS, execTime) + }() + + // Read transactions back from peer to measure network latency + gotTx := 0 + for gotTx < t.config.QPS && !isFailed { + if ctx.Err() != nil { + break + } - err = client.GetRPCClient().SendTransaction(ctx, tx) + receivedTxs, err := conn.ReadTransactionMessages() if err != nil { - t.logger.Errorf("Failed to send transaction: %v. Nonce: %d. ", err, tx.Nonce()) + t.logger.Errorf("Failed to read transaction messages: %v", err) t.ctx.SetResult(types.TaskResultFailure) return nil } - txs = append(txs, tx) - - // Create a context with timeout for reading transaction messages - readCtx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - done := make(chan error, 1) - go func() { - _, readErr := conn.ReadTransactionMessages() - done <- readErr - }() - - select { - case err = <-done: - if err != nil { - t.logger.Errorf("Failed to read transaction messages: %v", err) - t.ctx.SetResult(types.TaskResultFailure) - return nil + // Process received transactions and store receive timestamps + for _, receivedTx := range *receivedTxs { + receivedHash := receivedTx.Hash().String() + if index, exists := hashToIndex[receivedHash]; exists { + receiveTimestamps[index] = time.Now() + gotTx++ } - case <-readCtx.Done(): - t.logger.Warnf("Timeout waiting for transaction message at index %d, retrying transaction", i) - i-- // Retry this transaction - continue } - latency := time.Since(startTx) - latencies = append(latencies, latency) - totalLatency += latency + if gotTx%t.config.MeasureInterval == 0 { + t.logger.Infof("Received %d transactions", gotTx) + } + } + + if isFailed { + return nil + } + + // Calculate latencies from synchronized arrays + var latencies []time.Duration + var totalLatency time.Duration - if (i+1)%t.config.MeasureInterval == 0 { - avgSoFar := totalLatency.Microseconds() / int64(i+1) - t.logger.Infof("Processed %d transactions, current avg latency: %dmus.", i+1, avgSoFar) + for i := range t.config.QPS { + if !receiveTimestamps[i].IsZero() && !sendTimestamps[i].IsZero() { + latency := receiveTimestamps[i].Sub(sendTimestamps[i]) + latencies = append(latencies, latency) + totalLatency += latency } } - avgLatency := totalLatency / time.Duration(t.config.TxCount) + avgLatency := time.Duration(0) + if len(latencies) > 0 { + avgLatency = totalLatency / time.Duration(len(latencies)) + } t.logger.Infof("Average transaction latency: %dmus", avgLatency.Microseconds()) // send to other clients, for speeding up tx mining @@ -261,7 +327,8 @@ func (t *Task) Execute(ctx context.Context) error { t.logger.Errorf("Transaction latency too high: %dmus (expected <= %dmus)", avgLatency.Microseconds(), t.config.HighLatency) t.ctx.SetResult(types.TaskResultFailure) } else { - t.ctx.Outputs.SetVar("tx_count", t.config.TxCount) + t.ctx.Outputs.SetVar("tx_count", len(latencies)) + t.ctx.Outputs.SetVar("qps", t.config.QPS) t.ctx.Outputs.SetVar("avg_latency_mus", avgLatency.Microseconds()) t.ctx.Outputs.SetVar("latencies", latenciesStats) @@ -269,7 +336,8 @@ func (t *Task) Execute(ctx context.Context) error { } outputs := map[string]interface{}{ - "tx_count": t.config.TxCount, + "tx_count": len(latencies), + "qps": t.config.QPS, "avg_latency_mus": avgLatency.Microseconds(), "tx_pool_latency_hdr_plot": plot, "latencies": latenciesStats, From c7fd1cf91fffa9a8035aad06d21548fa4f2a910a Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 16:29:38 +0200 Subject: [PATCH 04/48] Revert "Enhance transaction latency analysis by implementing synchronized arrays for precise latency measurement and improving transaction sending logic. Added QPS-based transaction distribution and refined logging for better monitoring of transaction flow." This reverts commit 31355d6cefd0d9306d2f3d1b0941dd23b3f90d8c. --- .../tasks/tx_pool_latency_analysis/task.go | 152 +++++------------- 1 file changed, 42 insertions(+), 110 deletions(-) diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go index b2ee0e9a..0d411d2b 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go @@ -111,130 +111,64 @@ func (t *Task) Execute(ctx context.Context) error { defer conn.Close() - // Create three synchronized arrays for precise latency calculation - sendTimestamps := make([]time.Time, t.config.QPS) - receiveTimestamps := make([]time.Time, t.config.QPS) + var totalLatency time.Duration + var latencies []time.Duration var txs []*ethtypes.Transaction - hashToIndex := make(map[string]int) // Map hash to array index - - startTime := time.Now() - isFailed := false - sentTxCount := 0 - - // Send transactions at QPS rate - go func() { - startExecTime := time.Now() - endTime := startExecTime.Add(time.Second) - for i := range t.config.QPS { - if ctx.Err() != nil || isFailed { - return - } - - // Calculate how much time we have left - remainingTime := time.Until(endTime) - - // Calculate sleep time to distribute remaining transactions evenly - sleepTime := remainingTime / time.Duration(t.config.QPS-i) - - // Generate and send transaction - go func(index int) { - if ctx.Err() != nil || isFailed { - return - } - - tx, err := t.generateTransaction(ctx) - if err != nil { - t.logger.Errorf("Failed to create transaction: %v", err) - t.ctx.SetResult(types.TaskResultFailure) - isFailed = true - return - } - - startTx := time.Now() - - err = client.GetRPCClient().SendTransaction(ctx, tx) - if err != nil { - t.logger.Errorf("Failed to send transaction: %v. Nonce: %d. ", err, tx.Nonce()) - t.ctx.SetResult(types.TaskResultFailure) - isFailed = true - return - } - - // Store transaction data in synchronized arrays - txHash := tx.Hash().String() - sendTimestamps[index] = startTx - hashToIndex[txHash] = index - - txs = append(txs, tx) - sentTxCount++ - - if sentTxCount%t.config.MeasureInterval == 0 { - elapsed := time.Since(startTime) - t.logger.Infof("Sent %d transactions in %.2fs", sentTxCount, elapsed.Seconds()) - } - }(i) - - if isFailed { - return - } - - time.Sleep(sleepTime) + for i := 0; i < t.config.TxCount; i++ { + tx, err := t.generateTransaction(ctx) + if err != nil { + t.logger.Errorf("Failed to create transaction: %v", err) + t.ctx.SetResult(types.TaskResultFailure) + return nil } - execTime := time.Since(startExecTime) - t.logger.Infof("Time to generate and send %d transactions: %v", t.config.QPS, execTime) - }() - - // Read transactions back from peer to measure network latency - gotTx := 0 - for gotTx < t.config.QPS && !isFailed { - if ctx.Err() != nil { - break - } + startTx := time.Now() - receivedTxs, err := conn.ReadTransactionMessages() + err = client.GetRPCClient().SendTransaction(ctx, tx) if err != nil { - t.logger.Errorf("Failed to read transaction messages: %v", err) + t.logger.Errorf("Failed to send transaction: %v. Nonce: %d. ", err, tx.Nonce()) t.ctx.SetResult(types.TaskResultFailure) return nil } - // Process received transactions and store receive timestamps - for _, receivedTx := range *receivedTxs { - receivedHash := receivedTx.Hash().String() - if index, exists := hashToIndex[receivedHash]; exists { - receiveTimestamps[index] = time.Now() - gotTx++ - } - } + txs = append(txs, tx) - if gotTx%t.config.MeasureInterval == 0 { - t.logger.Infof("Received %d transactions", gotTx) - } - } + // Create a context with timeout for reading transaction messages + readCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() - if isFailed { - return nil - } + done := make(chan error, 1) + go func() { + _, readErr := conn.ReadTransactionMessages() + done <- readErr + }() - // Calculate latencies from synchronized arrays - var latencies []time.Duration - var totalLatency time.Duration + select { + case err = <-done: + if err != nil { + t.logger.Errorf("Failed to read transaction messages: %v", err) + t.ctx.SetResult(types.TaskResultFailure) + return nil + } + case <-readCtx.Done(): + t.logger.Warnf("Timeout waiting for transaction message at index %d, retrying transaction", i) + i-- // Retry this transaction + continue + } + + latency := time.Since(startTx) + latencies = append(latencies, latency) + totalLatency += latency - for i := range t.config.QPS { - if !receiveTimestamps[i].IsZero() && !sendTimestamps[i].IsZero() { - latency := receiveTimestamps[i].Sub(sendTimestamps[i]) - latencies = append(latencies, latency) - totalLatency += latency + if (i+1)%t.config.MeasureInterval == 0 { + avgSoFar := totalLatency.Microseconds() / int64(i+1) + t.logger.Infof("Processed %d transactions, current avg latency: %dmus.", i+1, avgSoFar) } } - avgLatency := time.Duration(0) - if len(latencies) > 0 { - avgLatency = totalLatency / time.Duration(len(latencies)) - } + avgLatency := totalLatency / time.Duration(t.config.TxCount) t.logger.Infof("Average transaction latency: %dmus", avgLatency.Microseconds()) // send to other clients, for speeding up tx mining @@ -327,8 +261,7 @@ func (t *Task) Execute(ctx context.Context) error { t.logger.Errorf("Transaction latency too high: %dmus (expected <= %dmus)", avgLatency.Microseconds(), t.config.HighLatency) t.ctx.SetResult(types.TaskResultFailure) } else { - t.ctx.Outputs.SetVar("tx_count", len(latencies)) - t.ctx.Outputs.SetVar("qps", t.config.QPS) + t.ctx.Outputs.SetVar("tx_count", t.config.TxCount) t.ctx.Outputs.SetVar("avg_latency_mus", avgLatency.Microseconds()) t.ctx.Outputs.SetVar("latencies", latenciesStats) @@ -336,8 +269,7 @@ func (t *Task) Execute(ctx context.Context) error { } outputs := map[string]interface{}{ - "tx_count": len(latencies), - "qps": t.config.QPS, + "tx_count": t.config.TxCount, "avg_latency_mus": avgLatency.Microseconds(), "tx_pool_latency_hdr_plot": plot, "latencies": latenciesStats, From aaf38a76b39740db32874e060a3b083db3b0ac3e Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Wed, 11 Jun 2025 17:10:31 +0200 Subject: [PATCH 05/48] refactoring, check p2p message vs tx --- .../tasks/tx_pool_latency_analysis/README.md | 7 +- .../tasks/tx_pool_latency_analysis/task.go | 265 ++++++++++-------- 2 files changed, 154 insertions(+), 118 deletions(-) diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/README.md b/pkg/coordinator/tasks/tx_pool_latency_analysis/README.md index 502c0fd2..f23df3f7 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/README.md +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/README.md @@ -23,8 +23,11 @@ The `tx_pool_latency_analysis` task evaluates latency of transaction processing - **`tx_count`**: The total number of transactions sent. -- **`max_latency_ms`**: - The average latency of the transactions in milliseconds. +- **`max_latency_mus`**: + The max latency of the transactions in microseconds. + +- **`tx_pool_latency_hdr_plot`**: + The HDR plot of the transaction pool latency. ### Defaults diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go index 0d411d2b..fa7bedf7 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go @@ -7,7 +7,6 @@ import ( "fmt" "math/big" "math/rand" - "sort" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -111,67 +110,145 @@ func (t *Task) Execute(ctx context.Context) error { defer conn.Close() - var totalLatency time.Duration - var latencies []time.Duration - - var txs []*ethtypes.Transaction - - for i := 0; i < t.config.TxCount; i++ { - tx, err := t.generateTransaction(ctx) - if err != nil { - t.logger.Errorf("Failed to create transaction: %v", err) - t.ctx.SetResult(types.TaskResultFailure) - return nil + // Wait for the specified seconds before starting the task + if t.config.SecondsBeforeRunning > 0 { + t.logger.Infof("Waiting for %d seconds before starting the task...", t.config.SecondsBeforeRunning) + select { + case <-time.After(time.Duration(t.config.SecondsBeforeRunning) * time.Second): + t.logger.Infof("Starting task after waiting.") + case <-ctx.Done(): + t.logger.Warnf("Task cancelled before starting.") + return ctx.Err() } + } - startTx := time.Now() + // Prepare to collect transaction latencies + var totNumberOfTxes int = t.config.QPS * t.config.Duration_s + var txs []*ethtypes.Transaction = make([]*ethtypes.Transaction, totNumberOfTxes) + var txStartTime []time.Time = make([]time.Time, totNumberOfTxes) + var hashToIndex map[string]int = make(map[string]int) + var testDeadline time.Time = time.Now().Add(time.Duration(t.config.Duration_s+60) * time.Second) + var latenciesMus = make([]int64, totNumberOfTxes) + + startTime := time.Now() + isFailed := false + sentTxCount := 0 + + // Start generating and sending transactions + go func() { + startExecTime := time.Now() + endTime := startExecTime.Add(time.Second * time.Duration(t.config.Duration_s)) + + // Generate and send transactions + for i := 0; i < totNumberOfTxes; i++ { + // Calculate how much time we have left + remainingTime := time.Until(endTime) + + // Calculate sleep time to distribute remaining transactions evenly + sleepTime := remainingTime / time.Duration(totNumberOfTxes-i) + + // generate and send tx + go func(i int) { + if ctx.Err() != nil && !isFailed { + return + } + + tx, err := t.generateTransaction(ctx) + if err != nil { + t.logger.Errorf("Failed to create transaction: %v", err) + t.ctx.SetResult(types.TaskResultFailure) + isFailed = true + return + } + + txStartTime[i] = time.Now() + err = client.GetRPCClient().SendTransaction(ctx, tx) + if err != nil { + t.logger.WithField("client", client.GetName()).Errorf("Failed to send transaction: %v", err) + t.ctx.SetResult(types.TaskResultFailure) + isFailed = true + return + } + + txs[i] = tx + hashToIndex[tx.Hash().String()] = i + sentTxCount++ + + // log transaction sending + if sentTxCount%t.config.MeasureInterval == 0 { + elapsed := time.Since(startTime) + t.logger.Infof("Sent %d transactions in %.2fs", sentTxCount, elapsed.Seconds()) + } + + select { + case <-ctx.Done(): + t.logger.Warnf("Task cancelled, stopping transaction generation.") + return + default: + if time.Since(startTime) >= time.Duration(t.config.Duration_s)*time.Second { + t.logger.Infof("Reached duration limit, stopping transaction generation.") + return + } + } + + }(i) + + // Sleep to control the QPS + if i < totNumberOfTxes-1 { + if sleepTime > 0 { + time.Sleep(sleepTime) + } else { + t.logger.Warnf("Remaining time is negative, skipping sleep") + } + } - err = client.GetRPCClient().SendTransaction(ctx, tx) - if err != nil { - t.logger.Errorf("Failed to send transaction: %v. Nonce: %d. ", err, tx.Nonce()) - t.ctx.SetResult(types.TaskResultFailure) - return nil + if (i+1)%t.config.MeasureInterval == 0 { + t.logger.Infof("%d transactions sent", i+1) + } } + }() + + // Wait P2P event messages + func() { + var receivedEvents int = 0 + for { + txes, err := conn.ReadTransactionMessages() + if err == nil { + t.logger.Errorf("Failed reading p2p events: %v", err) + t.ctx.SetResult(types.TaskResultFailure) + return + } - txs = append(txs, tx) - - // Create a context with timeout for reading transaction messages - readCtx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - done := make(chan error, 1) - go func() { - _, readErr := conn.ReadTransactionMessages() - done <- readErr - }() + for _, tx := range *txes { + tx_index := hashToIndex[tx.Hash().String()] + latenciesMus[tx_index] = time.Now().Sub(txStartTime[tx_index]).Microseconds() + receivedEvents++ - select { - case err = <-done: - if err != nil { - t.logger.Errorf("Failed to read transaction messages: %v", err) - t.ctx.SetResult(types.TaskResultFailure) - return nil + if receivedEvents%t.config.MeasureInterval == 0 { + t.logger.Infof("Received %d p2p events", sentTxCount) + } } - case <-readCtx.Done(): - t.logger.Warnf("Timeout waiting for transaction message at index %d, retrying transaction", i) - i-- // Retry this transaction - continue - } - latency := time.Since(startTx) - latencies = append(latencies, latency) - totalLatency += latency + if receivedEvents == totNumberOfTxes { + t.logger.Infof("Reading of p2p events finished") + return + } - if (i+1)%t.config.MeasureInterval == 0 { - avgSoFar := totalLatency.Microseconds() / int64(i+1) - t.logger.Infof("Processed %d transactions, current avg latency: %dmus.", i+1, avgSoFar) + select { + case <-ctx.Done(): + t.logger.Warnf("Task cancelled, stopping reading p2p events.") + return + default: + // check test deadline + if time.Now().After(testDeadline) { + t.logger.Warnf("Reached duration limit, stopping reading p2p events.") + return + } + } } - } - - avgLatency := totalLatency / time.Duration(t.config.TxCount) - t.logger.Infof("Average transaction latency: %dmus", avgLatency.Microseconds()) + }() - // send to other clients, for speeding up tx mining + // Send txes to other clients, for speeding up tx mining for _, tx := range txs { for _, otherClient := range executionClients { if otherClient.GetName() == client.GetName() { @@ -182,22 +259,28 @@ func (t *Task) Execute(ctx context.Context) error { } } - // Convert latencies to microseconds for processing - latenciesMus := make([]int64, len(latencies)) - for i, latency := range latencies { - latenciesMus[i] = latency.Microseconds() + // Check if the context was cancelled or other errors occurred + if ctx.Err() != nil && !isFailed { + return nil + } + + // Check if we received all transactions p2p events + notReceivedP2PEventCount := 0 + for i := 0; i < totNumberOfTxes; i++ { + if latenciesMus[i] == 0 { + notReceivedP2PEventCount++ + // Assign a default value for missing P2P events + latenciesMus[i] = (time.Duration(t.config.Duration_s) * time.Second).Microseconds() + } + } + if notReceivedP2PEventCount > 0 { + t.logger.Warnf("Missed %d p2p events, assigned test dureation as latency", notReceivedP2PEventCount) } // Calculate statistics - var totalLatencyMus int64 var maxLatency int64 = 0 var minLatency int64 = 0 - if len(latenciesMus) > 0 { - minLatency = latenciesMus[0] - } - for _, lat := range latenciesMus { - totalLatencyMus += lat if lat > maxLatency { maxLatency = lat } @@ -206,49 +289,6 @@ func (t *Task) Execute(ctx context.Context) error { } } - // Calculate mean - var meanLatency float64 = 0 - if len(latenciesMus) > 0 { - meanLatency = float64(totalLatencyMus) / float64(len(latenciesMus)) - } - - // Sort for percentiles - sortedLatencies := make([]int64, len(latenciesMus)) - copy(sortedLatencies, latenciesMus) - sort.Slice(sortedLatencies, func(i, j int) bool { - return sortedLatencies[i] < sortedLatencies[j] - }) - - // Calculate percentiles - percentile50th := float64(0) - percentile90th := float64(0) - percentile95th := float64(0) - percentile99th := float64(0) - - if len(sortedLatencies) > 0 { - getPercentile := func(pct float64) float64 { - idx := int(float64(len(sortedLatencies)-1) * pct / 100) - return float64(sortedLatencies[idx]) - } - - percentile50th = getPercentile(50) - percentile90th = getPercentile(90) - percentile95th = getPercentile(95) - percentile99th = getPercentile(99) - } - - // Create statistics map for output - latenciesStats := map[string]float64{ - "total": float64(totalLatencyMus), - "mean": meanLatency, - "50th": percentile50th, - "90th": percentile90th, - "95th": percentile95th, - "99th": percentile99th, - "max": float64(maxLatency), - "min": float64(minLatency), - } - // Generate HDR plot plot, err := hdr.HdrPlot(latenciesMus) if err != nil { @@ -257,22 +297,15 @@ func (t *Task) Execute(ctx context.Context) error { return nil } - if t.config.FailOnHighLatency && avgLatency.Microseconds() > t.config.HighLatency { - t.logger.Errorf("Transaction latency too high: %dmus (expected <= %dmus)", avgLatency.Microseconds(), t.config.HighLatency) - t.ctx.SetResult(types.TaskResultFailure) - } else { - t.ctx.Outputs.SetVar("tx_count", t.config.TxCount) - t.ctx.Outputs.SetVar("avg_latency_mus", avgLatency.Microseconds()) - t.ctx.Outputs.SetVar("latencies", latenciesStats) + t.ctx.Outputs.SetVar("tx_count", totNumberOfTxes) + t.ctx.Outputs.SetVar("max_latency_mus", maxLatency) - t.ctx.SetResult(types.TaskResultSuccess) - } + t.ctx.SetResult(types.TaskResultSuccess) outputs := map[string]interface{}{ - "tx_count": t.config.TxCount, - "avg_latency_mus": avgLatency.Microseconds(), + "tx_count": totNumberOfTxes, + "max_latency_mus": maxLatency, "tx_pool_latency_hdr_plot": plot, - "latencies": latenciesStats, } outputsJSON, _ := json.Marshal(outputs) From 7e3a25d79541d420e96eb5fcba78bc3931d8cf33 Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Wed, 11 Jun 2025 17:12:09 +0200 Subject: [PATCH 06/48] initial refactoring (to complete) --- .../tx_pool_throughput_analysis/README.md | 7 ++- .../tasks/tx_pool_throughput_analysis/task.go | 46 +++++++++++++------ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/pkg/coordinator/tasks/tx_pool_throughput_analysis/README.md b/pkg/coordinator/tasks/tx_pool_throughput_analysis/README.md index 9c5c86a4..43c49c3a 100644 --- a/pkg/coordinator/tasks/tx_pool_throughput_analysis/README.md +++ b/pkg/coordinator/tasks/tx_pool_throughput_analysis/README.md @@ -20,8 +20,11 @@ The `tx_pool_throughput_analysis` task evaluates the throughput of transaction p ### Outputs -- **`total_time_mus`**: - The total time taken to send the transactions in microseconds. +- **`tx_count`**: + The total number of transactions sent. + +- **`max_latency_mus`**: + The max latency of the transactions in microseconds. ### Defaults diff --git a/pkg/coordinator/tasks/tx_pool_throughput_analysis/task.go b/pkg/coordinator/tasks/tx_pool_throughput_analysis/task.go index 11f7f5dd..8653e3d3 100644 --- a/pkg/coordinator/tasks/tx_pool_throughput_analysis/task.go +++ b/pkg/coordinator/tasks/tx_pool_throughput_analysis/task.go @@ -110,7 +110,21 @@ func (t *Task) Execute(ctx context.Context) error { defer conn.Close() - var txs []*ethtypes.Transaction + // Wait for the specified seconds before starting the task + if t.config.SecondsBeforeRunning > 0 { + t.logger.Infof("Waiting for %d seconds before starting the task...", t.config.SecondsBeforeRunning) + select { + case <-time.After(time.Duration(t.config.SecondsBeforeRunning) * time.Second): + t.logger.Infof("Starting task after waiting.") + case <-ctx.Done(): + t.logger.Warnf("Task cancelled before starting.") + return ctx.Err() + } + } + + // Prepare to send transactions + var totNumberOfTxes int = t.config.QPS * t.config.Duration_s + var tx_events []*ethtypes.Transaction = make([]*ethtypes.Transaction, totNumberOfTxes) startTime := time.Now() isFailed := false @@ -120,14 +134,15 @@ func (t *Task) Execute(ctx context.Context) error { startExecTime := time.Now() endTime := startExecTime.Add(time.Second) - for i := range t.config.QPS { + // Generate and send transactions + for i := 0; i < totNumberOfTxes; i++ { // Calculate how much time we have left remainingTime := time.Until(endTime) // Calculate sleep time to distribute remaining transactions evenly sleepTime := remainingTime / time.Duration(t.config.QPS-i) - // generate and sign tx + // generate and send tx go func() { if ctx.Err() != nil && !isFailed { return @@ -141,13 +156,6 @@ func (t *Task) Execute(ctx context.Context) error { return } - sentTxCount++ - - if sentTxCount%t.config.MeasureInterval == 0 { - elapsed := time.Since(startTime) - t.logger.Infof("Sent %d transactions in %.2fs", sentTxCount, elapsed.Seconds()) - } - err = client.GetRPCClient().SendTransaction(ctx, tx) if err != nil { t.logger.WithField("client", client.GetName()).Errorf("Failed to send transaction: %v", err) @@ -156,7 +164,15 @@ func (t *Task) Execute(ctx context.Context) error { return } - txs = append(txs, tx) + sentTxCount++ + + // log transaction sending + if sentTxCount%t.config.MeasureInterval == 0 { + elapsed := time.Since(startTime) + t.logger.Infof("Sent %d transactions in %.2fs", sentTxCount, elapsed.Seconds()) + } + + tx_events = append(tx_events, tx) }() if isFailed { @@ -214,8 +230,8 @@ func (t *Task) Execute(ctx context.Context) error { } // Re-send transactions to the original client - for i := 0; i < missingTxCount && i < len(txs); i++ { - err = client.GetRPCClient().SendTransaction(ctx, txs[i]) + for i := 0; i < missingTxCount && i < len(tx_events); i++ { + err = client.GetRPCClient().SendTransaction(ctx, tx_events[i]) if err != nil { t.logger.WithError(err).Errorf("Failed to re-send transaction message, error: %v", err) t.ctx.SetResult(types.TaskResultFailure) @@ -232,7 +248,7 @@ func (t *Task) Execute(ctx context.Context) error { } t.logger.Infof("Got %d transactions", gotTx) - t.logger.Infof("Tx/s: (%d txs processed): %.2f / s \n", gotTx, float64(t.config.MeasureInterval)*float64(time.Second)/float64(time.Since(lastMeasureTime))) + t.logger.Infof("Tx/s: (%d tx_events processed): %.2f / s \n", gotTx, float64(t.config.MeasureInterval)*float64(time.Second)/float64(time.Since(lastMeasureTime))) lastMeasureTime = time.Now() } @@ -241,7 +257,7 @@ func (t *Task) Execute(ctx context.Context) error { t.logger.Infof("Total time for %d transactions: %.2fs", sentTxCount, totalTime.Seconds()) // send to other clients, for speeding up tx mining - for _, tx := range txs { + for _, tx := range tx_events { for _, otherClient := range executionClients { if otherClient.GetName() == client.GetName() { continue From 68a79da09905269a913578d096c93a61b451983e Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 17:13:38 +0200 Subject: [PATCH 07/48] Refactor tx_pool_latency_analysis task configuration to use QPS and adjust parameters for improved performance and clarity --- playbooks/dev/tx-pool-check-short.yaml | 33 +++++++++++++------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/playbooks/dev/tx-pool-check-short.yaml b/playbooks/dev/tx-pool-check-short.yaml index 99cb470f..7299cad9 100644 --- a/playbooks/dev/tx-pool-check-short.yaml +++ b/playbooks/dev/tx-pool-check-short.yaml @@ -5,26 +5,25 @@ disable: false config: walletPrivkey: "" tasks: -- name: tx_pool_throughput_analysis - title: "Check transaction pool throughput with 10.000 transactions" - timeout: 30m - config: - qps: 10000 - measureInterval: 1000 - configVars: - privateKey: "walletPrivkey" -- name: tx_pool_clean - title: "Clean transaction pool" - timeout: 30m - config: - waitTime: 5 +# - name: tx_pool_throughput_analysis +# title: "Check transaction pool throughput with 10.000 transactions" +# timeout: 30m +# config: +# qps: 10000 +# measureInterval: 1000 +# configVars: +# privateKey: "walletPrivkey" +# - name: tx_pool_clean +# title: "Clean transaction pool" +# timeout: 30m +# config: +# waitTime: 5 - name: tx_pool_latency_analysis title: "Check transaction pool latency with 10.000 transactions" timeout: 30m config: - txCount: 10000 - measureInterval: 1000 - highLatency: 3500 - failOnHighLatency: true + qps: 100 + duration_s: 10 + measureInterval: 100 configVars: privateKey: "walletPrivkey" From 7c9edb4725f645f679c0de383df7544dc2252384 Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Wed, 11 Jun 2025 17:22:35 +0200 Subject: [PATCH 08/48] solve concurrency problem --- .../tasks/tx_pool_latency_analysis/task.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go index fa7bedf7..c8253974 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go @@ -126,7 +126,6 @@ func (t *Task) Execute(ctx context.Context) error { var totNumberOfTxes int = t.config.QPS * t.config.Duration_s var txs []*ethtypes.Transaction = make([]*ethtypes.Transaction, totNumberOfTxes) var txStartTime []time.Time = make([]time.Time, totNumberOfTxes) - var hashToIndex map[string]int = make(map[string]int) var testDeadline time.Time = time.Now().Add(time.Duration(t.config.Duration_s+60) * time.Second) var latenciesMus = make([]int64, totNumberOfTxes) @@ -153,7 +152,7 @@ func (t *Task) Execute(ctx context.Context) error { return } - tx, err := t.generateTransaction(ctx) + tx, err := t.generateTransaction(ctx, i) if err != nil { t.logger.Errorf("Failed to create transaction: %v", err) t.ctx.SetResult(types.TaskResultFailure) @@ -171,7 +170,7 @@ func (t *Task) Execute(ctx context.Context) error { } txs[i] = tx - hashToIndex[tx.Hash().String()] = i + //hashToIndex[tx.Hash().String()] = i sentTxCount++ // log transaction sending @@ -220,7 +219,15 @@ func (t *Task) Execute(ctx context.Context) error { } for _, tx := range *txes { - tx_index := hashToIndex[tx.Hash().String()] + tx_data := tx.Data() + // read tx_data that is in the format "tx_index:" + var tx_index int + _, err := fmt.Sscanf(string(tx_data), "tx_index:%d", &tx_index) + if err != nil { + t.logger.Errorf("Failed to parse transaction data: %v", err) + t.ctx.SetResult(types.TaskResultFailure) + return + } latenciesMus[tx_index] = time.Now().Sub(txStartTime[tx_index]).Microseconds() receivedEvents++ @@ -357,7 +364,7 @@ func (t *Task) getTcpConn(ctx context.Context, client *execution.Client) (*sentr return conn, nil } -func (t *Task) generateTransaction(ctx context.Context) (*ethtypes.Transaction, error) { +func (t *Task) generateTransaction(ctx context.Context, i int) (*ethtypes.Transaction, error) { tx, err := t.wallet.BuildTransaction(ctx, func(_ context.Context, nonce uint64, _ bind.SignerFn) (*ethtypes.Transaction, error) { addr := t.wallet.GetAddress() toAddr := &addr @@ -377,7 +384,7 @@ func (t *Task) generateTransaction(ctx context.Context) (*ethtypes.Transaction, Gas: 50000, To: toAddr, Value: txAmount, - Data: []byte{}, + Data: []byte(fmt.Sprintf("tx_index:%d", i)), } return ethtypes.NewTx(txObj), nil From 80e805ce063ca22dc521fc4694b90b3cea233ae0 Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Wed, 11 Jun 2025 17:24:16 +0200 Subject: [PATCH 09/48] small fixes --- pkg/coordinator/tasks/tx_pool_latency_analysis/task.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go index c8253974..1d5e7cc0 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go @@ -228,7 +228,7 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.SetResult(types.TaskResultFailure) return } - latenciesMus[tx_index] = time.Now().Sub(txStartTime[tx_index]).Microseconds() + latenciesMus[tx_index] = time.Since(txStartTime[tx_index]).Microseconds() receivedEvents++ if receivedEvents%t.config.MeasureInterval == 0 { @@ -374,9 +374,7 @@ func (t *Task) generateTransaction(ctx context.Context, i int) (*ethtypes.Transa feeCap := &helper.BigInt{Value: *big.NewInt(100000000000)} // 100 Gwei tipCap := &helper.BigInt{Value: *big.NewInt(1000000000)} // 1 Gwei - var txObj ethtypes.TxData - - txObj = ðtypes.DynamicFeeTx{ + txObj := ðtypes.DynamicFeeTx{ ChainID: t.ctx.Scheduler.GetServices().ClientPool().GetExecutionPool().GetBlockCache().GetChainID(), Nonce: nonce, GasTipCap: &tipCap.Value, From ec057e121a4c06e099fdddef4261b31c993ab4ac Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Wed, 11 Jun 2025 17:31:50 +0200 Subject: [PATCH 10/48] check tx index range --- pkg/coordinator/tasks/tx_pool_latency_analysis/task.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go index 1d5e7cc0..08a393f0 100644 --- a/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go +++ b/pkg/coordinator/tasks/tx_pool_latency_analysis/task.go @@ -228,6 +228,11 @@ func (t *Task) Execute(ctx context.Context) error { t.ctx.SetResult(types.TaskResultFailure) return } + if tx_index < 0 || tx_index >= totNumberOfTxes { + t.logger.Errorf("Transaction index out of range: %d", tx_index) + t.ctx.SetResult(types.TaskResultFailure) + return + } latenciesMus[tx_index] = time.Since(txStartTime[tx_index]).Microseconds() receivedEvents++ From 24abc5dd3584ff040cb76a1626552cbf433a74ba Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 17:43:46 +0200 Subject: [PATCH 11/48] fix(get_task_result_api) --- pkg/coordinator/web/api/get_task_result_api.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/coordinator/web/api/get_task_result_api.go b/pkg/coordinator/web/api/get_task_result_api.go index 188f2ab4..69598c70 100644 --- a/pkg/coordinator/web/api/get_task_result_api.go +++ b/pkg/coordinator/web/api/get_task_result_api.go @@ -9,9 +9,9 @@ import ( "strings" "time" - "github.com/gorilla/mux" "github.com/noku-team/assertoor/pkg/coordinator/db" "github.com/noku-team/assertoor/pkg/coordinator/types" + "github.com/gorilla/mux" ) // GetTaskResult godoc @@ -92,18 +92,20 @@ func (ah *APIHandler) GetTaskResult(w http.ResponseWriter, r *http.Request) { } // Check if view parameter is set - viewMode := r.URL.Query().Has("view") + downloadMode := r.URL.Query().Has("download") // Determine content type contentType := "application/octet-stream" - if viewMode { + if !downloadMode { ext := strings.ToLower(filepath.Ext(resultFile.Name)) switch ext { case ".txt", ".log", ".yaml", ".yml", ".json", ".md": contentType = "text/plain" case ".html", ".htm": contentType = "text/html" + case ".css": + contentType = "text/css" case ".png": contentType = "image/png" case ".jpg", ".jpeg": @@ -118,9 +120,9 @@ func (ah *APIHandler) GetTaskResult(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", contentType) - if !viewMode { + if downloadMode { w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", resultFile.Name)) } http.ServeContent(w, r, resultFile.Name, time.Now(), bytes.NewReader(resultFile.Data)) -} +} \ No newline at end of file From f467aec892fd12ae4c07dfde14175add158d426d Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 17:44:31 +0200 Subject: [PATCH 12/48] fix(server) --- pkg/coordinator/web/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/coordinator/web/server.go b/pkg/coordinator/web/server.go index 11e04682..768f476a 100644 --- a/pkg/coordinator/web/server.go +++ b/pkg/coordinator/web/server.go @@ -7,11 +7,11 @@ import ( "net/http" "strings" - "github.com/gorilla/mux" coordinator_types "github.com/noku-team/assertoor/pkg/coordinator/types" "github.com/noku-team/assertoor/pkg/coordinator/web/api" "github.com/noku-team/assertoor/pkg/coordinator/web/handlers" "github.com/noku-team/assertoor/pkg/coordinator/web/types" + "github.com/gorilla/mux" "github.com/sirupsen/logrus" httpSwagger "github.com/swaggo/http-swagger" "github.com/urfave/negroni" @@ -96,6 +96,7 @@ func (ws *Server) ConfigureRoutes(frontendConfig *types.FrontendConfig, apiConfi ws.router.HandleFunc("/api/v1/test_runs/delete", apiHandler.PostTestRunsDelete).Methods("POST") ws.router.HandleFunc("/api/v1/test_run/{runId}/cancel", apiHandler.PostTestRunCancel).Methods("POST") ws.router.HandleFunc("/api/v1/test_run/{runId}/details", apiHandler.GetTestRunDetails).Methods("GET") + ws.router.HandleFunc("/api/v1/test_run/{runId}/task/{taskIndex}/details", apiHandler.GetTestRunTaskDetails).Methods("GET") ws.router.HandleFunc("/api/v1/test_run/{runId}/task/{taskId}/result/{resultType}/{fileId:.*}", apiHandler.GetTaskResult).Methods("GET") } } @@ -183,4 +184,4 @@ func (ws *Server) getSwaggerHandler(logger logrus.FieldLogger, fh *handlers.Fron } } }) -} +} \ No newline at end of file From 4797f116b4fe0865d4b4a08cddb68e4564a0ea98 Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 17:45:18 +0200 Subject: [PATCH 13/48] fix(execution-spec-tests-execute.yaml) --- .../dev/execution-spec-tests-execute.yaml | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/playbooks/dev/execution-spec-tests-execute.yaml b/playbooks/dev/execution-spec-tests-execute.yaml index fcfaf0d3..24277e44 100644 --- a/playbooks/dev/execution-spec-tests-execute.yaml +++ b/playbooks/dev/execution-spec-tests-execute.yaml @@ -175,33 +175,33 @@ tasks: cp report.json ${ASSERTOOR_RESULT_DIR}/report.json cp report.html ${ASSERTOOR_RESULT_DIR}/report.html + mkdir -p ${ASSERTOOR_RESULT_DIR}/assets + curl -o ${ASSERTOOR_RESULT_DIR}/assets/style.css https://raw.githubusercontent.com/noku-team/assertoor/refs/heads/master/res/execution-spec-tests-styles.css REPORT_JSON=$(cat report.json | jq -c '[.tests[] | {"nodeid": .nodeid, "outcome": .outcome, "setupDuration": .setup.duration, "callDuration": .call.duration, "teardownDuration": .teardown.duration}]') + TOTAL_TESTS=$(echo "$REPORT_JSON" | jq '. | length') + PASSED_TESTS=$(echo "$REPORT_JSON" | jq '[.[] | select(.outcome == "passed")] | length') + echo "Total tests: $TOTAL_TESTS" + echo "Passed tests: $PASSED_TESTS" - echo "::set-output-json reportJSON ${REPORT_JSON}" + # Check if tests passed + echo "::set-output passedTests ${PASSED_TESTS}" + echo "::set-output totalTests ${TOTAL_TESTS}" exit 0 - - name: run_task_matrix - title: "Show test results" - configVars: - matrixValues: "tasks.execute.outputs.reportJSON" + + - name: run_shell + title: "Check test results (${{tasks.execute.outputs.passedTests}} / ${{tasks.execute.outputs.totalTests}} passed)" + id: check + if: runTests config: - runConcurrent: true - matrixVar: "testResult" - task: - name: run_shell - title: "${{testResult.nodeid}}" - config: - shell: bash - envVars: - TEST_RESULT: testResult - command: | - DURATION_SECONDS=$(echo $TEST_RESULT | jq -r '(.setup.duration // 0) + (.call.duration // 0) + (.teardown.duration // 0)') - echo "::set-output-json customRunTimeSeconds ${DURATION_SECONDS}" - echo "::set-output-json execTestResult ${TEST_RESULT}" - if $(echo $TEST_RESULT | jq -e '.outcome == "passed"') ; then - echo "Test passed" - else - echo "Test failed" - exit 1 - fi + shell: bash + envVars: + passedTests: "tasks.execute.outputs.passedTests" + totalTests: "tasks.execute.outputs.totalTests" + command: | + set -e + if [ "${passedTests}" != "${totalTests}" ]; then + echo "Some tests failed, see report.html in the task above for details" + exit 1 + fi \ No newline at end of file From 76c21e94f8a28e2f153b7a32588ee5d9c6beccad Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 17:46:18 +0200 Subject: [PATCH 14/48] fix(build-release) --- .github/workflows/build-release.yml | 225 ++++++++++++++-------------- 1 file changed, 113 insertions(+), 112 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 038bb8a9..687ce115 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -1,3 +1,4 @@ + name: Build Release on: @@ -18,133 +19,133 @@ jobs: ref: ${{ github.sha }} release: "v${{ inputs.version }}" docker: true - docker_repository: "ethpandaops/assertoor" + docker_repository: "noku-team/assertoor" docker_tag_prefix: "v${{ inputs.version }}" additional_tags: "['v${{ inputs.version }}','latest']" secrets: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - + create_release: name: Create Release needs: [build_binaries] runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 100 - ref: ${{ github.sha }} - - name: "Generate release changelog" - id: changelog - run: | - git fetch --tags - prev_tag=$(git tag --sort=-version:refname | grep -e "^v[0-9.]*$" | head -n 1) - echo "previous release: $prev_tag" - if [ "$prev_tag" ]; then - changelog=$(git log --oneline --no-decorate $prev_tag..HEAD) - else - changelog=$(git log --oneline --no-decorate) - fi - echo "changelog<> $GITHUB_OUTPUT - echo " - ${changelog//$'\n'/$'\n' - }" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 100 + ref: ${{ github.sha }} + - name: "Generate release changelog" + id: changelog + run: | + git fetch --tags + prev_tag=$(git tag --sort=-version:refname | grep -e "^v[0-9.]*$" | head -n 1) + echo "previous release: $prev_tag" + if [ "$prev_tag" ]; then + changelog=$(git log --oneline --no-decorate $prev_tag..HEAD) + else + changelog=$(git log --oneline --no-decorate) + fi + echo "changelog<> $GITHUB_OUTPUT + echo " - ${changelog//$'\n'/$'\n' - }" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT # download build artifacts - name: "Download build artifacts" uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - # create draft release - - name: Create latest release - uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4 - id: create_release - with: - draft: true - prerelease: false - release_name: "v${{ inputs.version }}" - tag_name: "v${{ inputs.version }}" - body: | - ### Changes - ${{ steps.changelog.outputs.changelog }} - - ### Release Artifacts - Please read through the [wiki](https://github.com/ethpandaops/assertoor/wiki) for setup & configuration instructions. - | Release File | Description | - | ------------- | ------------- | - | [assertoor_${{ inputs.version }}_windows_amd64.zip](https://github.com/ethpandaops/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_windows_amd64.zip) | assertoor executables for windows/amd64 | - | [assertoor_${{ inputs.version }}_linux_amd64.tar.gz](https://github.com/ethpandaops/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_linux_amd64.tar.gz) | assertoor executables for linux/amd64 | - | [assertoor_${{ inputs.version }}_linux_arm64.tar.gz](https://github.com/ethpandaops/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_linux_arm64.tar.gz) | assertoor executables for linux/arm64 | - | [assertoor_${{ inputs.version }}_darwin_amd64.tar.gz](https://github.com/ethpandaops/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_darwin_amd64.tar.gz) | assertoor executable for macos/amd64 | - | [assertoor_${{ inputs.version }}_darwin_arm64.tar.gz](https://github.com/ethpandaops/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_darwin_arm64.tar.gz) | assertoor executable for macos/arm64 | - env: - GITHUB_TOKEN: ${{ github.token }} - - # generate & upload release artifacts - - name: "Generate release package: assertoor_${{ inputs.version }}_windows_amd64.zip" - run: | - cd assertoor_windows_amd64 - zip -r -q ../assertoor_${{ inputs.version }}_windows_amd64.zip . - - name: "Upload release artifact: assertoor_${{ inputs.version }}_windows_amd64.zip" - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./assertoor_${{ inputs.version }}_windows_amd64.zip - asset_name: assertoor_${{ inputs.version }}_windows_amd64.zip - asset_content_type: application/octet-stream - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: "Generate release package: assertoor_${{ inputs.version }}_linux_amd64.tar.gz" - run: | - cd assertoor_linux_amd64 - tar -czf ../assertoor_${{ inputs.version }}_linux_amd64.tar.gz . - - name: "Upload release artifact: assertoor_${{ inputs.version }}_linux_amd64.tar.gz" - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./assertoor_${{ inputs.version }}_linux_amd64.tar.gz - asset_name: assertoor_${{ inputs.version }}_linux_amd64.tar.gz - asset_content_type: application/octet-stream - env: - GITHUB_TOKEN: ${{ github.token }} + # create draft release + - name: Create latest release + uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4 + id: create_release + with: + draft: true + prerelease: false + release_name: "v${{ inputs.version }}" + tag_name: "v${{ inputs.version }}" + body: | + ### Changes + ${{ steps.changelog.outputs.changelog }} - - name: "Generate release package: assertoor_${{ inputs.version }}_linux_arm64.tar.gz" - run: | - cd assertoor_linux_arm64 - tar -czf ../assertoor_${{ inputs.version }}_linux_arm64.tar.gz . - - name: "Upload release artifact: assertoor_${{ inputs.version }}_linux_arm64.tar.gz" - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./assertoor_${{ inputs.version }}_linux_arm64.tar.gz - asset_name: assertoor_${{ inputs.version }}_linux_arm64.tar.gz - asset_content_type: application/octet-stream - env: - GITHUB_TOKEN: ${{ github.token }} + ### Release Artifacts + Please read through the [wiki](https://github.com/noku-team/assertoor/wiki) for setup & configuration instructions. + | Release File | Description | + | ------------- | ------------- | + | [assertoor_${{ inputs.version }}_windows_amd64.zip](https://github.com/noku-team/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_windows_amd64.zip) | assertoor executables for windows/amd64 | + | [assertoor_${{ inputs.version }}_linux_amd64.tar.gz](https://github.com/noku-team/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_linux_amd64.tar.gz) | assertoor executables for linux/amd64 | + | [assertoor_${{ inputs.version }}_linux_arm64.tar.gz](https://github.com/noku-team/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_linux_arm64.tar.gz) | assertoor executables for linux/arm64 | + | [assertoor_${{ inputs.version }}_darwin_amd64.tar.gz](https://github.com/noku-team/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_darwin_amd64.tar.gz) | assertoor executable for macos/amd64 | + | [assertoor_${{ inputs.version }}_darwin_arm64.tar.gz](https://github.com/noku-team/assertoor/releases/download/v${{ inputs.version }}/assertoor_${{ inputs.version }}_darwin_arm64.tar.gz) | assertoor executable for macos/arm64 | + env: + GITHUB_TOKEN: ${{ github.token }} - - name: "Generate release package: assertoor_${{ inputs.version }}_darwin_amd64.tar.gz" - run: | - cd assertoor_darwin_amd64 - tar -czf ../assertoor_${{ inputs.version }}_darwin_amd64.tar.gz . - - name: "Upload release artifact: assertoor_${{ inputs.version }}_darwin_amd64.tar.gz" - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./assertoor_${{ inputs.version }}_darwin_amd64.tar.gz - asset_name: assertoor_${{ inputs.version }}_darwin_amd64.tar.gz - asset_content_type: application/octet-stream - env: - GITHUB_TOKEN: ${{ github.token }} + # generate & upload release artifacts + - name: "Generate release package: assertoor_${{ inputs.version }}_windows_amd64.zip" + run: | + cd assertoor_windows_amd64 + zip -r -q ../assertoor_${{ inputs.version }}_windows_amd64.zip . + - name: "Upload release artifact: assertoor_${{ inputs.version }}_windows_amd64.zip" + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./assertoor_${{ inputs.version }}_windows_amd64.zip + asset_name: assertoor_${{ inputs.version }}_windows_amd64.zip + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: "Generate release package: assertoor_${{ inputs.version }}_linux_amd64.tar.gz" + run: | + cd assertoor_linux_amd64 + tar -czf ../assertoor_${{ inputs.version }}_linux_amd64.tar.gz . + - name: "Upload release artifact: assertoor_${{ inputs.version }}_linux_amd64.tar.gz" + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./assertoor_${{ inputs.version }}_linux_amd64.tar.gz + asset_name: assertoor_${{ inputs.version }}_linux_amd64.tar.gz + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: "Generate release package: assertoor_${{ inputs.version }}_linux_arm64.tar.gz" + run: | + cd assertoor_linux_arm64 + tar -czf ../assertoor_${{ inputs.version }}_linux_arm64.tar.gz . + - name: "Upload release artifact: assertoor_${{ inputs.version }}_linux_arm64.tar.gz" + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./assertoor_${{ inputs.version }}_linux_arm64.tar.gz + asset_name: assertoor_${{ inputs.version }}_linux_arm64.tar.gz + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: "Generate release package: assertoor_${{ inputs.version }}_darwin_amd64.tar.gz" + run: | + cd assertoor_darwin_amd64 + tar -czf ../assertoor_${{ inputs.version }}_darwin_amd64.tar.gz . + - name: "Upload release artifact: assertoor_${{ inputs.version }}_darwin_amd64.tar.gz" + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./assertoor_${{ inputs.version }}_darwin_amd64.tar.gz + asset_name: assertoor_${{ inputs.version }}_darwin_amd64.tar.gz + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ github.token }} - - name: "Generate release package: assertoor_${{ inputs.version }}_darwin_arm64.tar.gz" - run: | - cd assertoor_darwin_arm64 - tar -czf ../assertoor_${{ inputs.version }}_darwin_arm64.tar.gz . - - name: "Upload release artifact: assertoor_${{ inputs.version }}_darwin_arm64.tar.gz" - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./assertoor_${{ inputs.version }}_darwin_arm64.tar.gz - asset_name: assertoor_${{ inputs.version }}_darwin_arm64.tar.gz - asset_content_type: application/octet-stream - env: - GITHUB_TOKEN: ${{ github.token }} + - name: "Generate release package: assertoor_${{ inputs.version }}_darwin_arm64.tar.gz" + run: | + cd assertoor_darwin_arm64 + tar -czf ../assertoor_${{ inputs.version }}_darwin_arm64.tar.gz . + - name: "Upload release artifact: assertoor_${{ inputs.version }}_darwin_arm64.tar.gz" + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2 + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./assertoor_${{ inputs.version }}_darwin_arm64.tar.gz + asset_name: assertoor_${{ inputs.version }}_darwin_arm64.tar.gz + asset_content_type: application/octet-stream + env: + GITHUB_TOKEN: ${{ github.token }} \ No newline at end of file From fe2616ff819cf1df5a1a2e3b6b946f81315c6507 Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 17:47:44 +0200 Subject: [PATCH 15/48] fix(build-release) --- .github/workflows/build-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 687ce115..57f0a78a 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -148,4 +148,4 @@ jobs: asset_name: assertoor_${{ inputs.version }}_darwin_arm64.tar.gz asset_content_type: application/octet-stream env: - GITHUB_TOKEN: ${{ github.token }} \ No newline at end of file + GITHUB_TOKEN: ${{ github.token }} From f5ecd52eea75092f647a7cdca3f188170c2c9c51 Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 17:50:41 +0200 Subject: [PATCH 16/48] feat(res/execution-spec-tests-styles.css) --- res/execution-spec-tests-styles.css | 341 ++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 res/execution-spec-tests-styles.css diff --git a/res/execution-spec-tests-styles.css b/res/execution-spec-tests-styles.css new file mode 100644 index 00000000..6b4a108b --- /dev/null +++ b/res/execution-spec-tests-styles.css @@ -0,0 +1,341 @@ +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 12px; + /* do not increase min-width as some may use split screens */ + min-width: 800px; + color: #999; +} + +h1 { + font-size: 24px; + color: black; +} + +h2 { + font-size: 16px; + color: black; +} + +p { + color: black; +} + +a { + color: #999; +} + +table { + border-collapse: collapse; +} + +/****************************** + * SUMMARY INFORMATION + ******************************/ +#environment td { + padding: 5px; + border: 1px solid #e6e6e6; + vertical-align: top; +} + +#environment tr:nth-child(odd) { + background-color: #f6f6f6; +} + +#environment ul { + margin: 0; + padding: 0 20px; +} + +/****************************** + * TEST RESULT COLORS + ******************************/ +span.passed, +.passed .col-result { + color: green; +} + +span.skipped, +span.xfailed, +span.rerun, +.skipped .col-result, +.xfailed .col-result, +.rerun .col-result { + color: orange; +} + +span.error, +span.failed, +span.xpassed, +.error .col-result, +.failed .col-result, +.xpassed .col-result { + color: red; +} + +.col-links__extra { + margin-right: 3px; +} + +/****************************** + * RESULTS TABLE + * + * 1. Table Layout + * 2. Extra + * 3. Sorting items + * + ******************************/ +/*------------------ + * 1. Table Layout + *------------------*/ +#results-table { + border: 1px solid #e6e6e6; + color: #999; + font-size: 12px; + width: 100%; +} + +#results-table th, +#results-table td { + padding: 5px; + border: 1px solid #e6e6e6; + text-align: left; +} + +#results-table th { + font-weight: bold; +} + +/*------------------ + * 2. Extra + *------------------*/ +.logwrapper { + max-height: 230px; + overflow-y: scroll; + background-color: #e6e6e6; +} + +.logwrapper.expanded { + max-height: none; +} + +.logwrapper.expanded .logexpander:after { + content: "collapse [-]"; +} + +.logwrapper .logexpander { + z-index: 1; + position: sticky; + top: 10px; + width: max-content; + border: 1px solid; + border-radius: 3px; + padding: 5px 7px; + margin: 10px 0 10px calc(100% - 80px); + cursor: pointer; + background-color: #e6e6e6; +} + +.logwrapper .logexpander:after { + content: "expand [+]"; +} + +.logwrapper .logexpander:hover { + color: #000; + border-color: #000; +} + +.logwrapper .log { + min-height: 40px; + position: relative; + top: -50px; + height: calc(100% + 50px); + border: 1px solid #e6e6e6; + color: black; + display: block; + font-family: "Courier New", Courier, monospace; + padding: 5px; + padding-right: 80px; + white-space: pre-wrap; +} + +div.media { + border: 1px solid #e6e6e6; + float: right; + height: 240px; + margin: 0 5px; + overflow: hidden; + width: 320px; +} + +.media-container { + display: grid; + grid-template-columns: 25px auto 25px; + align-items: center; + flex: 1 1; + overflow: hidden; + height: 200px; +} + +.media-container--fullscreen { + grid-template-columns: 0px auto 0px; +} + +.media-container__nav--right, +.media-container__nav--left { + text-align: center; + cursor: pointer; +} + +.media-container__viewport { + cursor: pointer; + text-align: center; + height: inherit; +} + +.media-container__viewport img, +.media-container__viewport video { + object-fit: cover; + width: 100%; + max-height: 100%; +} + +.media__name, +.media__counter { + display: flex; + flex-direction: row; + justify-content: space-around; + flex: 0 0 25px; + align-items: center; +} + +.collapsible td:not(.col-links) { + cursor: pointer; +} + +.collapsible td:not(.col-links):hover::after { + color: #bbb; + font-style: italic; + cursor: pointer; +} + +.col-result { + width: 130px; +} + +.col-result:hover::after { + content: " (hide details)"; +} + +.col-result.collapsed:hover::after { + content: " (show details)"; +} + +#environment-header h2:hover::after { + content: " (hide details)"; + color: #bbb; + font-style: italic; + cursor: pointer; + font-size: 12px; +} + +#environment-header.collapsed h2:hover::after { + content: " (show details)"; + color: #bbb; + font-style: italic; + cursor: pointer; + font-size: 12px; +} + +/*------------------ + * 3. Sorting items + *------------------*/ +.sortable { + cursor: pointer; +} + +.sortable.desc:after { + content: " "; + position: relative; + left: 5px; + bottom: -12.5px; + border: 10px solid #4caf50; + border-bottom: 0; + border-left-color: transparent; + border-right-color: transparent; +} + +.sortable.asc:after { + content: " "; + position: relative; + left: 5px; + bottom: 12.5px; + border: 10px solid #4caf50; + border-top: 0; + border-left-color: transparent; + border-right-color: transparent; +} + +.hidden, +.summary__reload__button.hidden { + display: none; +} + +.summary__data { + flex: 0 0 550px; +} + +.summary__reload { + flex: 1 1; + display: flex; + justify-content: center; +} + +.summary__reload__button { + flex: 0 0 300px; + display: flex; + color: white; + font-weight: bold; + background-color: #4caf50; + text-align: center; + justify-content: center; + align-items: center; + border-radius: 3px; + cursor: pointer; +} + +.summary__reload__button:hover { + background-color: #46a049; +} + +.summary__spacer { + flex: 0 0 550px; +} + +.controls { + display: flex; + justify-content: space-between; +} + +.filters, +.collapse { + display: flex; + align-items: center; +} + +.filters button, +.collapse button { + color: #999; + border: none; + background: none; + cursor: pointer; + text-decoration: underline; +} + +.filters button:hover, +.collapse button:hover { + color: #ccc; +} + +.filter__label { + margin-right: 10px; +} \ No newline at end of file From 3f92c9144aaf3423e3fd61d01c37cdf579b26bc1 Mon Sep 17 00:00:00 2001 From: tosettil-polimi Date: Wed, 11 Jun 2025 17:51:24 +0200 Subject: [PATCH 17/48] feat(test_run) --- .../web/templates/test_run/test_run.html | 997 +++++++++++++----- 1 file changed, 762 insertions(+), 235 deletions(-) diff --git a/pkg/coordinator/web/templates/test_run/test_run.html b/pkg/coordinator/web/templates/test_run/test_run.html index 3d356839..0882ed07 100644 --- a/pkg/coordinator/web/templates/test_run/test_run.html +++ b/pkg/coordinator/web/templates/test_run/test_run.html @@ -1,6 +1,13 @@ {{ define "page" }} -
-

Test Run {{ .RunID }}: {{ .Name }}

+ + + + + + + +
+

Test Run {{ .RunID }}: {{ html "" }}{{ html "" }}

@@ -8,69 +15,64 @@

Test Run {{ .RunID }}: {{ .Name }}

- + - {{ if .IsStarted }} + {{ html "" }} - + - {{ end }} - {{ if .IsCompleted }} + {{ html "" }} + {{ html "" }} - + - {{ end }} + {{ html "" }} - +
Test ID: - {{ .TestID }} -
Test Status: - {{ if eq .Status "pending" }} + {{ html "" }} Pending - {{ else if eq .Status "running" }} + {{ html "" }} + {{ html "" }} Running - {{ else if eq .Status "success" }} + {{ html "" }} + {{ html "" }} Success - {{ else if eq .Status "failure" }} + {{ html "" }} + {{ html "" }} Failed - {{ else if eq .Status "aborted" }} + {{ html "" }} + {{ html "" }} Cancelled - {{ else }} - - {{ .Status }} - - {{ end }} + {{ html "" }} + {{ html "" }} + + {{ html "" }}
Start Time: - {{ formatDateTime .StartTime.UTC }} -
Finish Time: - {{ formatDateTime .StopTime.UTC }} -
Timeout: - {{ .Timeout }} -
@@ -89,241 +91,224 @@
Tasks
- - - {{ $isSecTrimmed := .IsSecTrimmed }} - {{ range $i, $task := .Tasks }} - + + {{ html "" }} +
- {{ range $l, $graph := $task.GraphLevels }} -
- {{ if gt $graph 1 }} + {{ html "" }} +
+ {{ html "" }}
- {{ end }} + {{ html "" }}
- {{ end }} - {{ if $task.HasChildren }} -
-
- {{ end }} + {{ html "" }}
- - {{ $task.Index }} - +
- {{ $task.Name }} - {{ $task.Title }} + + - {{ if $task.HasRunTime }}{{ $task.RunTime }}{{ else }}?{{ end }} - {{ if $task.HasTimeout }} / {{ $task.Timeout }}{{ end }} - {{ if $task.HasCustomRunTime}} - - ({{ $task.CustomRunTime}}) - - {{ end }} + + {{ html "" }} + / + {{ html "" }} + {{ html "" }} + + () + + {{ html "" }} - {{ if eq $task.Result "success" }} + {{ html "" }} - {{ else if eq $task.Result "failure" }} + {{ html "" }} + {{ html "" }} - {{ else }} + {{ html "" }} + {{ html "" }} - {{ end }} + {{ html "" }} - {{ if eq $task.Status "pending" }} + {{ html "" }} - {{ else if eq $task.Status "running" }} + {{ html "" }} + {{ html "" }} - {{ end }} + {{ html "" }} + + + - -
+ {{ html "" }} +
- {{ if $task.IsStarted }} + {{ html "" }} - + - {{ end }} - {{ if $task.IsCompleted }} + {{ html "" }} + {{ html "" }} - + - {{ end }} - {{ if not (eq $task.ResultError "") }} - - - - - {{ end }} + {{ html "" }}
Status: - {{ if eq $task.Status "pending" }} + {{ html "" }} Pending - {{ else if eq $task.Status "running" }} + {{ html "" }} + {{ html "" }} Running - {{ else if eq $task.Status "complete" }} + {{ html "" }} + {{ html "" }} Complete - {{ else }} + {{ html "" }} + {{ html "" }} - {{ $task.Status }} + - {{ end }} + {{ html "" }}
Result: - {{ if eq $task.Result "success" }} + {{ html "" }} Success - {{ else if eq $task.Result "failure" }} + {{ html "" }} + {{ html "" }} Failure - {{ else }} + {{ html "" }} + {{ html "" }} None - {{ end }} + {{ html "" }}
Start Time:{{ formatDateTime $task.StartTime.UTC }}
Finish Time:{{ formatDateTime $task.StopTime.UTC }}
Error Result: -
{{ .ResultError }}
-
- {{ if not $isSecTrimmed }} - {{ if $task.IsStarted }} -