Skip to content

Commit cd32fd3

Browse files
authored
Merge pull request #262 from SiaFoundation/christopher/add-block-time-metrics
Add block time metrics
2 parents d5868fb + c90da1c commit cd32fd3

File tree

6 files changed

+130
-0
lines changed

6 files changed

+130
-0
lines changed

api/client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,12 @@ func (c *Client) HostMetrics() (resp explorer.HostMetrics, err error) {
280280
return
281281
}
282282

283+
// BlockTimeMetrics returns the average block time during various intervals.
284+
func (c *Client) BlockTimeMetrics() (resp explorer.BlockTimeMetrics, err error) {
285+
err = c.c.GET(context.Background(), "/metrics/blocktime", &resp)
286+
return
287+
}
288+
283289
// Search returns what type of object an ID is.
284290
func (c *Client) Search(id string) (resp explorer.SearchType, err error) {
285291
err = c.c.GET(context.Background(), fmt.Sprintf("/search/%s", id), &resp)

api/server.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type (
5454
BestTip(height uint64) (types.ChainIndex, error)
5555
Metrics(id types.BlockID) (explorer.Metrics, error)
5656
HostMetrics() (explorer.HostMetrics, error)
57+
BlockTimeMetrics() (explorer.BlockTimeMetrics, error)
5758
Transactions(ids []types.TransactionID) ([]explorer.Transaction, error)
5859
TransactionChainIndices(id types.TransactionID, offset, limit uint64) ([]types.ChainIndex, error)
5960
V2Transactions(ids []types.TransactionID) ([]explorer.V2Transaction, error)
@@ -299,6 +300,14 @@ func (s *server) hostMetricsHandler(jc jape.Context) {
299300
jc.Encode(metrics)
300301
}
301302

303+
func (s *server) blockTimeMetricsHandler(jc jape.Context) {
304+
metrics, err := s.e.BlockTimeMetrics()
305+
if jc.Check("failed to get block time metrics", err) != nil {
306+
return
307+
}
308+
jc.Encode(metrics)
309+
}
310+
302311
func (s *server) blocksIDHandler(jc jape.Context) {
303312
var id types.BlockID
304313
if jc.DecodeParam("id", &id) != nil {
@@ -885,6 +894,7 @@ func NewServer(e Explorer, cm ChainManager, s Syncer, ex exchangerates.Source, a
885894
"GET /metrics/block": srv.blocksMetricsHandler,
886895
"GET /metrics/block/:id": srv.blocksMetricsIDHandler,
887896
"GET /metrics/host": srv.hostMetricsHandler,
897+
"GET /metrics/blocktime": srv.blockTimeMetricsHandler,
888898

889899
"POST /hosts": srv.hostsHandler,
890900

explorer/explorer.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ type Store interface {
8989
Metrics(id types.BlockID) (Metrics, error)
9090
LastSuccessScan() (time.Time, error)
9191
HostMetrics() (HostMetrics, error)
92+
BlockTimeMetrics(blockTime time.Duration) (BlockTimeMetrics, error)
9293
Transactions(ids []types.TransactionID) ([]Transaction, error)
9394
TransactionChainIndices(txid types.TransactionID, offset, limit uint64) ([]types.ChainIndex, error)
9495
V2Transactions(ids []types.TransactionID) ([]V2Transaction, error)
@@ -319,6 +320,11 @@ func (e *Explorer) HostMetrics() (HostMetrics, error) {
319320
return e.s.HostMetrics()
320321
}
321322

323+
// BlockTimeMetrics returns the average block time during various intervals.
324+
func (e *Explorer) BlockTimeMetrics() (BlockTimeMetrics, error) {
325+
return e.s.BlockTimeMetrics(e.cm.TipState().Network.BlockInterval)
326+
}
327+
322328
// Transactions returns the transactions with the specified IDs.
323329
func (e *Explorer) Transactions(ids []types.TransactionID) ([]Transaction, error) {
324330
return e.s.Transactions(ids)

explorer/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,3 +605,10 @@ type HostQuery struct {
605605
AcceptContracts *bool `json:"acceptContracts,omitempty"`
606606
Online *bool `json:"online,omitempty"`
607607
}
608+
609+
// BlockTimeMetrics represents the average block time during various intervals.
610+
type BlockTimeMetrics struct {
611+
Day time.Duration
612+
Week time.Duration
613+
Month time.Duration
614+
}

persist/sqlite/metrics.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,53 @@ func (s *Store) HostMetrics() (result explorer.HostMetrics, err error) {
221221
})
222222
return
223223
}
224+
225+
// BlockTimeMetrics implements explorer.Store.
226+
func (s *Store) BlockTimeMetrics(blockTime time.Duration) (result explorer.BlockTimeMetrics, err error) {
227+
err = s.transaction(func(tx *txn) error {
228+
const day = 24 * time.Hour
229+
monthBlocks := int64(30*day) / int64(blockTime)
230+
231+
rows, err := tx.Query(`SELECT timestamp FROM blocks ORDER BY height DESC LIMIT ?`, monthBlocks)
232+
if err != nil {
233+
return fmt.Errorf("failed to query block timestamps: %w", err)
234+
}
235+
defer rows.Close()
236+
237+
timestamps := make([]time.Time, 0, monthBlocks)
238+
for rows.Next() {
239+
var timestamp time.Time
240+
if err := rows.Scan(decode(&timestamp)); err != nil {
241+
return fmt.Errorf("failed to scan block timestamp: %w", err)
242+
}
243+
timestamps = append(timestamps, timestamp)
244+
}
245+
if err := rows.Err(); err != nil {
246+
return fmt.Errorf("failed to retrieve block timestamp rows: %w", err)
247+
}
248+
249+
now := time.Now()
250+
intervals := []time.Time{now.Add(-day), now.Add(-(7 * day)), now.Add(-(30 * day))}
251+
252+
sums := make([]time.Duration, len(intervals))
253+
counts := make([]int64, len(intervals))
254+
for i := range len(timestamps) - 1 {
255+
timestamp := timestamps[i]
256+
// descending order, so larger timestamps is first
257+
delta := timestamps[i].Sub(timestamps[i+1])
258+
259+
for j, interval := range intervals {
260+
if timestamp.After(interval) {
261+
sums[j] += delta
262+
counts[j]++
263+
}
264+
}
265+
}
266+
result.Day = sums[0] / time.Duration(max(1, counts[0]))
267+
result.Week = sums[1] / time.Duration(max(1, counts[1]))
268+
result.Month = sums[2] / time.Duration(max(1, counts[2]))
269+
270+
return nil
271+
})
272+
return
273+
}

persist/sqlite/metrics_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package sqlite
22

33
import (
44
"testing"
5+
"time"
56

67
"go.sia.tech/core/consensus"
78
proto4 "go.sia.tech/core/rhp/v4"
89
"go.sia.tech/core/types"
910
"go.sia.tech/explored/explorer"
1011
"go.sia.tech/explored/internal/testutil"
12+
"lukechampine.com/frand"
1113
)
1214

1315
func TestMetrics(t *testing.T) {
@@ -436,3 +438,52 @@ func TestV2Metrics(t *testing.T) {
436438

437439
assertMetrics(metricsGenesis)
438440
}
441+
442+
func BenchmarkBlockTimeMetrics(b *testing.B) {
443+
n := newTestChain(b, false, nil)
444+
445+
const month = 30 * 24 * time.Hour
446+
const blockTime = 10 * time.Minute
447+
448+
now := time.Now().Add(-month)
449+
err := n.db.transaction(func(tx *txn) error {
450+
blockStmt, err := tx.Prepare(`INSERT INTO blocks(id, height, parent_id, nonce, timestamp, leaf_index) VALUES (?, ?, ?, ?, ?, ?)`)
451+
if err != nil {
452+
return err
453+
}
454+
defer blockStmt.Close()
455+
456+
var parentID types.BlockID
457+
nonce, leafIndex := encode(uint64(0)), encode(uint64(0))
458+
for i := range 500000 {
459+
if i%10000 == 0 {
460+
b.Log("Adding block:", i)
461+
}
462+
id := types.BlockID(frand.Entropy256())
463+
if _, err := blockStmt.Exec(encode(id), i, encode(parentID), nonce, encode(now), leafIndex); err != nil {
464+
b.Fatal(err)
465+
}
466+
467+
parentID = id
468+
now = now.Add(blockTime)
469+
}
470+
return nil
471+
})
472+
if err != nil {
473+
b.Fatal(err)
474+
}
475+
476+
for b.Loop() {
477+
blockTimes, err := n.db.BlockTimeMetrics(blockTime)
478+
if err != nil {
479+
b.Fatal(err)
480+
}
481+
if blockTimes.Day != blockTime {
482+
b.Fatalf("expected %v average block time for past day, got %v", blockTime, blockTimes.Day)
483+
} else if blockTimes.Week != blockTime {
484+
b.Fatalf("expected %v average block time for past week, got %v", blockTime, blockTimes.Week)
485+
} else if blockTimes.Month != blockTime {
486+
b.Fatalf("expected %v average block time for past month, got %v", blockTime, blockTimes.Month)
487+
}
488+
}
489+
}

0 commit comments

Comments
 (0)