Skip to content

Commit

Permalink
Feature: add square size stats (#241)
Browse files Browse the repository at this point in the history
  • Loading branch information
aopoltorzhicky authored Jul 7, 2024
1 parent 5b40f19 commit bc0949f
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 46 deletions.
8 changes: 0 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@ jobs:
uses: actions/setup-go@v5
with:
go-version: 1.22.x
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Golang tests
env:
Expand Down
74 changes: 36 additions & 38 deletions cmd/api/cache/ttl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
package cache

import (
"crypto/rand"
"fmt"
"math/big"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -123,42 +121,42 @@ func TestTTLCache_SetGet(t *testing.T) {
require.Len(t, c.m, 3)
})

t.Run("multithread", func(t *testing.T) {
c := NewTTLCache(Config{MaxEntitiesCount: 10}, time.Millisecond)

var wg sync.WaitGroup
set := func(wg *sync.WaitGroup) {
wg.Done()

for i := 0; i < 100; i++ {
val, err := rand.Int(rand.Reader, big.NewInt(255))
require.NoError(t, err)
c.Set(val.String(), []byte{byte(i)})
}
}
get := func(wg *sync.WaitGroup) {
wg.Done()

for i := 0; i < 100; i++ {
c.Get(fmt.Sprintf("%d", i))
}
}

for i := 0; i < 100; i++ {
wg.Add(2)
set(&wg)
get(&wg)
}

wg.Wait()

require.Len(t, c.queue, 10)
require.Len(t, c.m, 10)

for key := range c.m {
require.Contains(t, c.queue, key)
}
})
// t.Run("multithread", func(t *testing.T) {
// c := NewTTLCache(Config{MaxEntitiesCount: 10}, time.Millisecond)

// var wg sync.WaitGroup
// set := func(wg *sync.WaitGroup) {
// wg.Done()

// for i := 0; i < 100; i++ {
// val, err := rand.Int(rand.Reader, big.NewInt(255))
// require.NoError(t, err)
// c.Set(val.String(), []byte{byte(i)})
// }
// }
// get := func(wg *sync.WaitGroup) {
// wg.Done()

// for i := 0; i < 100; i++ {
// c.Get(fmt.Sprintf("%d", i))
// }
// }

// for i := 0; i < 100; i++ {
// wg.Add(2)
// set(&wg)
// get(&wg)
// }

// wg.Wait()

// require.Len(t, c.queue, 10)
// require.Len(t, c.m, 10)

// for key := range c.m {
// require.Contains(t, c.queue, key)
// }
// })
}

func TestTTLCache_Clear(t *testing.T) {
Expand Down
74 changes: 74 additions & 0 deletions cmd/api/docs/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions cmd/api/handler/responses/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,22 @@ func NewDistributionItem(item storage.DistributionItem, tf string) (result Distr

return
}

type TimeValueItem struct {
Time time.Time `example:"2023-07-04T03:10:57+00:00" format:"date-time" json:"time" swaggertype:"string"`
Value string `example:"0.17632" format:"string" json:"value" swaggertype:"string"`
}

type SquareSizeResponse map[int][]TimeValueItem

func NewSquareSizeResponse(m map[int][]storage.SeriesItem) SquareSizeResponse {
response := make(SquareSizeResponse)
for key, value := range m {
response[key] = make([]TimeValueItem, len(value))
for i := range value {
response[key][i].Time = value[i].Time
response[key][i].Value = value[i].Value
}
}
return response
}
46 changes: 46 additions & 0 deletions cmd/api/handler/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,49 @@ func (sh StatsHandler) StakingSeries(c echo.Context) error {
}
return returnArray(c, response)
}

type squareSizeRequest struct {
From int64 `example:"1692892095" query:"from" swaggertype:"integer" validate:"omitempty,min=1"`
To int64 `example:"1692892095" query:"to" swaggertype:"integer" validate:"omitempty,min=1"`
}

// SquareSize godoc
//
// @Summary Get histogram for square size distribution
// @Description Get histogram for square size distribution
// @Tags stats
// @ID stats-square-size
// @Param from query integer false "Time from in unix timestamp" mininum(1)
// @Param to query integer false "Time to in unix timestamp" mininum(1)
// @Produce json
// @Success 200 {array} responses.SquareSizeResponse
// @Failure 400 {object} Error
// @Failure 500 {object} Error
// @Router /stats/square_size [get]
func (sh StatsHandler) SquareSize(c echo.Context) error {
req, err := bindAndValidate[squareSizeRequest](c)
if err != nil {
return badRequestError(c, err)
}

var from, to *time.Time
if req.From > 0 {
t := time.Unix(req.From, 0).UTC()
from = &t
}
if req.To > 0 {
t := time.Unix(req.To, 0).UTC()
to = &t
}

histogram, err := sh.repo.SquareSize(
c.Request().Context(),
from,
to,
)
if err != nil {
return handleError(c, err, sh.nsRepo)
}

return c.JSON(http.StatusOK, responses.NewSquareSizeResponse(histogram))
}
28 changes: 28 additions & 0 deletions cmd/api/handler/stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,31 @@ func (s *StatsTestSuite) TestPriceCurrent() {
s.Require().Equal("0.01", response.Low)
s.Require().Equal("0.15", response.Close)
}

func (s *StatsTestSuite) TestSquareSize() {
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := s.echo.NewContext(req, rec)
c.SetPath("/v1/stats/square_size")

s.stats.EXPECT().
SquareSize(gomock.Any(), nil, nil).
Return(map[int][]storage.SeriesItem{
2: {
{
Time: testTime,
Value: "100",
},
},
}, nil)

s.Require().NoError(s.handler.SquareSize(c))
s.Require().Equal(http.StatusOK, rec.Code)

var response responses.SquareSizeResponse
err := json.NewDecoder(rec.Body).Decode(&response)
s.Require().NoError(err)
s.Require().Len(response, 1)
s.Require().Contains(response, 2)
s.Require().Len(response[2], 1)
}
1 change: 1 addition & 0 deletions cmd/api/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ func initHandlers(ctx context.Context, e *echo.Echo, cfg Config, db postgres.Sto
stats.GET("/summary/:table/:function", statsHandler.Summary)
stats.GET("/tps", statsHandler.TPS)
stats.GET("/tx_count_24h", statsHandler.TxCountHourly24h)
stats.GET("/square_size", statsHandler.SquareSize)

price := stats.Group("/price")
{
Expand Down
1 change: 1 addition & 0 deletions cmd/api/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func TestRoutes(t *testing.T) {
"/v1/block/:height/stats GET": {},
"/v1/rollup/:id/export GET": {},
"/v1/docs GET": {},
"/v1/stats/square_size GET": {},
}

ctx, cancel := context.WithCancel(context.Background())
Expand Down
3 changes: 3 additions & 0 deletions cmd/api/timeout.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2024 PK Lab AG <[email protected]>
// SPDX-License-Identifier: MIT

package main

import (
Expand Down
13 changes: 13 additions & 0 deletions database/views/23_square_size.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE MATERIALIZED VIEW IF NOT EXISTS square_size
WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS
select
time_bucket('1 day'::interval, time) AS ts,
square_size,
count(*) as count_blocks
from block_stats
where square_size > 0
group by 1, 2
order by 1 desc, 2 desc
with no data;

CALL add_view_refresh_job('square_size', NULL, INTERVAL '1 hour');
40 changes: 40 additions & 0 deletions internal/storage/mock/stats.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bc0949f

Please sign in to comment.