Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: add square size stats #241

Merged
merged 2 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading