[API] Run scorecard queries concurrently (#339)
### Summary Before pull request, in `GET /api/v1/scorecards`, the queries for each scorecard were being executed sequentially. This PR changes the handler to run all those queries concurrently. Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/336
This commit is contained in:
parent
fe574754eb
commit
69251f136d
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
|
@ -350,46 +351,89 @@ func (r *Repository) buildFindVolumeQuery(q *ChainActivityQuery) string {
|
|||
}
|
||||
|
||||
func (r *Repository) GetScorecards(ctx context.Context) (*Scorecards, error) {
|
||||
tvl, err := r.tvl.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get tvl")
|
||||
}
|
||||
|
||||
messages24h, err := r.getMessages24h(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query 24h messages: %w", err)
|
||||
}
|
||||
// This function launches one goroutine for each scorecard.
|
||||
//
|
||||
// We use a `sync.WaitGroup` to block until all goroutines are done.
|
||||
var wg sync.WaitGroup
|
||||
|
||||
totalTxCount, err := r.getTotalTxCount(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query total tx count by portal bridge")
|
||||
}
|
||||
var messages24h, tvl, totalTxCount, totalTxVolume, txCount24h, volume24h string
|
||||
|
||||
totalTxVolume, err := r.getTotalTxVolume(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query tx volume by portal bridge")
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
messages24h, err = r.getMessages24h(ctx)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to query 24h messages", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
txCount24h, err := r.getTxCount24h(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query 24h transactions: %w", err)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
tvl, err = r.tvl.Get(ctx)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to get tvl", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
volume24h, err := r.getVolume24h(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query 24h volume: %w", err)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
totalTxCount, err = r.getTotalTxCount(ctx)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to tx count", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// build the result and return
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
totalTxVolume, err = r.getTotalTxVolume(ctx)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to get total tx volume", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
txCount24h, err = r.getTxCount24h(ctx)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to get 24h transactions", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
volume24h, err = r.getVolume24h(ctx)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to get 24h volume", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Each of the queries synchronized by this wait group has a context timeout.
|
||||
//
|
||||
// Hence, this call to `wg.Wait()` will not block indefinitely as long as the
|
||||
// context timeouts are properly handled in each goroutine.
|
||||
wg.Wait()
|
||||
|
||||
// Build the result and return
|
||||
scorecards := Scorecards{
|
||||
Messages24h: messages24h,
|
||||
TotalTxCount: totalTxCount,
|
||||
TotalTxVolume: totalTxVolume,
|
||||
Tvl: tvl,
|
||||
TxCount24h: txCount24h,
|
||||
TxCount24h: txCount24h,
|
||||
Volume24h: volume24h,
|
||||
}
|
||||
|
||||
return &scorecards, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ func NewTVL(p2pNetwork string, cache wormscanCache.Cache, tvlKey string, expirat
|
|||
|
||||
// Get get tvl value from cache if exists or call wormhole api to get tvl value and set the in cache for t.expiration time.
|
||||
func (t *Tvl) Get(ctx context.Context) (string, error) {
|
||||
// get tvl from cache
|
||||
|
||||
// Get tvl from cache
|
||||
tvl, err := t.cache.Get(ctx, t.tvlKey)
|
||||
if err == nil {
|
||||
return tvl, nil
|
||||
|
@ -42,8 +43,8 @@ func (t *Tvl) Get(ctx context.Context) (string, error) {
|
|||
zap.String("key", t.tvlKey))
|
||||
}
|
||||
|
||||
// get tvl from wormhole api
|
||||
tvlUSD, err := t.api.GetNotionalUSD([]string{"all"})
|
||||
// Get tvl from wormhole api
|
||||
tvlUSD, err := t.api.GetNotionalUSD(ctx, []string{"all"})
|
||||
if err != nil {
|
||||
t.logger.Error("error getting tvl from wormhole api",
|
||||
zap.Error(err))
|
||||
|
@ -52,7 +53,7 @@ func (t *Tvl) Get(ctx context.Context) (string, error) {
|
|||
return "", errs.ErrNotFound
|
||||
}
|
||||
|
||||
// set tvl in cache with t.expiration time
|
||||
// Set tvl in cache with t.expiration time
|
||||
err = t.cache.Set(ctx, t.tvlKey, *tvlUSD, t.expiration)
|
||||
if err != nil {
|
||||
t.logger.Error("error setting tvl in cache",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tvl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
|
@ -28,23 +29,28 @@ func NewTvlAPI(net string) *TvlAPI {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *TvlAPI) GetNotionalUSD(ids []string) (*string, error) {
|
||||
func (c *TvlAPI) GetNotionalUSD(ctx context.Context, ids []string) (*string, error) {
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, c.url, nil)
|
||||
// Build the request
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send it
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// Read response body
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract TVL from the response
|
||||
tvl := gjson.Get(string(body), "AllTime.\\*.\\*.Notional")
|
||||
response := tvl.String()
|
||||
return &response, nil
|
||||
|
|
Loading…
Reference in New Issue