Skip to content

Commit 4e48c39

Browse files
authored
Merge pull request #7 from algorandfoundation/feat/round-time-tps
feat: TPS and average round times
2 parents 3e27b6b + 2cda040 commit 4e48c39

File tree

4 files changed

+107
-70
lines changed

4 files changed

+107
-70
lines changed

internal/block.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"errors"
6+
"github.com/algorandfoundation/hack-tui/api"
7+
"time"
8+
)
9+
10+
func GetBlock(ctx context.Context, client *api.ClientWithResponses, round uint64) (map[string]interface{}, error) {
11+
12+
var format api.GetBlockParamsFormat = "json"
13+
block, err := client.GetBlockWithResponse(ctx, int(round), &api.GetBlockParams{
14+
Format: &format,
15+
})
16+
if err != nil {
17+
return nil, err
18+
}
19+
20+
if block.StatusCode() != 200 {
21+
return nil, errors.New("invalid status code")
22+
}
23+
24+
return block.JSON200.Block, nil
25+
}
26+
27+
type BlockMetrics struct {
28+
AvgTime time.Duration
29+
TPS float64
30+
}
31+
32+
func GetBlockMetrics(ctx context.Context, client *api.ClientWithResponses, round uint64, window int) (*BlockMetrics, error) {
33+
var avgs BlockMetrics
34+
if round < uint64(window) {
35+
avgs.AvgTime = 0
36+
avgs.TPS = 0
37+
return &avgs, nil
38+
}
39+
var format api.GetBlockParamsFormat = "json"
40+
a, err := client.GetBlockWithResponse(ctx, int(round), &api.GetBlockParams{
41+
Format: &format,
42+
})
43+
if err != nil {
44+
return nil, err
45+
}
46+
b, err := client.GetBlockWithResponse(ctx, int(round)-window, &api.GetBlockParams{
47+
Format: &format,
48+
})
49+
if err != nil {
50+
return nil, err
51+
}
52+
// Push to the transactions count list
53+
aTimestamp := time.Duration(a.JSON200.Block["ts"].(float64)) * time.Second
54+
bTimestamp := time.Duration(b.JSON200.Block["ts"].(float64)) * time.Second
55+
//
56+
aTransactions := a.JSON200.Block["tc"].(float64)
57+
bTransactions := b.JSON200.Block["tc"].(float64)
58+
59+
avgs.AvgTime = time.Duration((int(aTimestamp - bTimestamp)) / window)
60+
avgs.TPS = (aTransactions - bTransactions) / (float64(window) * avgs.AvgTime.Seconds())
61+
62+
return &avgs, nil
63+
}

internal/block_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"github.com/algorandfoundation/hack-tui/api"
6+
"testing"
7+
"time"
8+
)
9+
10+
func Test_GetBlockMetrics(t *testing.T) {
11+
window := 1000000
12+
13+
expectedAvg := time.Duration(2856041000)
14+
15+
client, err := api.NewClientWithResponses("https://mainnet-api.4160.nodely.dev:443")
16+
17+
metrics, err := GetBlockMetrics(context.Background(), client, uint64(42000000), window)
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
22+
if metrics.AvgTime != expectedAvg {
23+
t.Fatal("expected time to be", expectedAvg, "got", metrics.AvgTime)
24+
}
25+
26+
expectedTPS := 25.318608871511294
27+
28+
if metrics.TPS != expectedTPS {
29+
t.Fatal("expected tps to be", expectedTPS, "got", metrics.TPS)
30+
}
31+
}

internal/state.go

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"errors"
66
"github.com/algorandfoundation/hack-tui/api"
7-
"time"
87
)
98

109
type StateModel struct {
@@ -17,25 +16,12 @@ type StateModel struct {
1716
Watching bool
1817
}
1918

20-
func getAverage(data []float64) float64 {
21-
sum := 0.0
22-
for _, element := range data {
23-
sum += element
24-
}
25-
return sum / (float64(len(data)))
26-
}
27-
func getAverageDuration(timings []time.Duration) time.Duration {
28-
sum := 0.0
29-
for _, element := range timings {
30-
sum += element.Seconds()
31-
}
32-
avg := sum / (float64(len(timings)))
33-
return time.Duration(avg * float64(time.Second))
34-
}
35-
3619
// TODO: allow context to handle loop
3720
func (s *StateModel) Watch(cb func(model *StateModel, err error), ctx context.Context, client *api.ClientWithResponses) {
3821
s.Watching = true
22+
if s.Metrics.Window == 0 {
23+
s.Metrics.Window = 100
24+
}
3925

4026
err := s.Status.Fetch(ctx, client)
4127
if err != nil {
@@ -44,61 +30,33 @@ func (s *StateModel) Watch(cb func(model *StateModel, err error), ctx context.Co
4430

4531
lastRound := s.Status.LastRound
4632

47-
// Collection of Round Durations
48-
timings := make([]time.Duration, 0)
49-
// Collection of Transaction Counts
50-
txns := make([]float64, 0)
51-
5233
for {
5334
if !s.Watching {
5435
break
5536
}
56-
// Collect Time of Round
57-
startTime := time.Now()
5837
status, err := client.WaitForBlockWithResponse(ctx, int(lastRound))
5938
if err != nil {
6039
cb(nil, err)
6140
}
6241
if status.StatusCode() != 200 {
6342
cb(nil, errors.New(status.Status()))
6443
}
65-
// Store round timing
66-
endTime := time.Now()
67-
dur := endTime.Sub(startTime)
68-
timings = append(timings, dur)
6944

7045
// Update Status
7146
s.Status.LastRound = uint64(status.JSON200.LastRound)
7247

7348
// Fetch Keys
7449
s.UpdateKeys(ctx, client)
7550

76-
// Fetch Block
77-
var format api.GetBlockParamsFormat = "json"
78-
block, err := client.GetBlockWithResponse(ctx, int(lastRound), &api.GetBlockParams{
79-
Format: &format,
80-
})
81-
if err != nil {
82-
cb(nil, err)
83-
}
84-
85-
// Check for transactions
86-
if block.JSON200.Block["txns"] != nil {
87-
// Get the average duration in seconds (TPS)
88-
txnCount := float64(len(block.JSON200.Block["txns"].([]any)))
89-
txns = append(txns, txnCount/getAverageDuration(timings).Seconds())
90-
} else {
91-
txns = append(txns, 0)
92-
}
93-
94-
// Fetch RX/TX every 5th round
51+
// Run Round Averages and RX/TX every 5 rounds
9552
if s.Status.LastRound%5 == 0 {
96-
s.UpdateMetrics(ctx, client, timings, txns)
97-
}
98-
// Trim data
99-
if len(timings) >= 100 {
100-
timings = timings[1:]
101-
txns = txns[1:]
53+
bm, err := GetBlockMetrics(ctx, client, s.Status.LastRound, s.Metrics.Window)
54+
if err != nil {
55+
cb(nil, err)
56+
}
57+
s.Metrics.RoundTime = bm.AvgTime
58+
s.Metrics.TPS = bm.TPS
59+
s.UpdateMetricsFromRPC(ctx, client)
10260
}
10361

10462
lastRound = s.Status.LastRound
@@ -110,20 +68,7 @@ func (s *StateModel) Stop() {
11068
s.Watching = false
11169
}
11270

113-
func (s *StateModel) UpdateMetrics(
114-
ctx context.Context,
115-
client *api.ClientWithResponses,
116-
timings []time.Duration,
117-
txns []float64,
118-
) {
119-
if s == nil {
120-
panic("StateModel is nil while UpdateMetrics is called")
121-
}
122-
// Set Metrics
123-
s.Metrics.RoundTime = getAverageDuration(timings)
124-
s.Metrics.Window = len(timings)
125-
s.Metrics.TPS = getAverage(txns)
126-
71+
func (s *StateModel) UpdateMetricsFromRPC(ctx context.Context, client *api.ClientWithResponses) {
12772
// Fetch RX/TX
12873
res, err := GetMetrics(ctx, client)
12974
if err != nil {
@@ -135,7 +80,6 @@ func (s *StateModel) UpdateMetrics(
13580
s.Metrics.RX = res["algod_network_received_bytes_total"]
13681
}
13782
}
138-
13983
func (s *StateModel) UpdateAccounts() {
14084
s.Accounts = AccountsFromState(s)
14185
}

internal/status.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88

99
// StatusModel represents a status response from algod.Status
1010
type StatusModel struct {
11-
Metrics MetricsModel
1211
State string
1312
Version string
1413
Network string
@@ -19,7 +18,7 @@ type StatusModel struct {
1918

2019
// String prints the last round value
2120
func (m *StatusModel) String() string {
22-
return fmt.Sprintf("\nLastRound: %d\nRoundTime: %f \nTPS: %f", m.LastRound, m.Metrics.RoundTime.Seconds(), m.Metrics.TPS)
21+
return fmt.Sprintf("\nLastRound: %d\n", m.LastRound)
2322
}
2423

2524
// Fetch handles algod.Status

0 commit comments

Comments
 (0)