diff --git a/cmd/api/handler/address_test.go b/cmd/api/handler/address_test.go index 16200fde..d5f42361 100644 --- a/cmd/api/handler/address_test.go +++ b/cmd/api/handler/address_test.go @@ -31,6 +31,7 @@ var ( TotalTx: 14149240, TotalAccounts: 123123, TotalNamespaces: 123, + TotalBlobsSize: 1000, } testAddress = "celestia1jc92qdnty48pafummfr8ava2tjtuhfdw774w60" testHashAddress = []byte{0x96, 0xa, 0xa0, 0x36, 0x6b, 0x25, 0x4e, 0x1e, 0xa7, 0x9b, 0xda, 0x46, 0x7e, 0xb3, 0xaa, 0x5c, 0x97, 0xcb, 0xa5, 0xae} diff --git a/cmd/api/handler/responses/stats.go b/cmd/api/handler/responses/stats.go index f3561e9d..8f3ba42b 100644 --- a/cmd/api/handler/responses/stats.go +++ b/cmd/api/handler/responses/stats.go @@ -64,3 +64,15 @@ func NewGasPriceCandle(item storage.GasCandle) GasPriceCandle { func formatFoat64(value float64) string { return strconv.FormatFloat(value, 'f', -1, 64) } + +type NamespaceUsage struct { + Name string `example:"00112233" format:"string" json:"name" swaggertype:"string"` + Size int64 `example:"1283518" format:"integer" json:"size" swaggertype:"number"` +} + +func NewNamespaceUsage(ns storage.Namespace) NamespaceUsage { + return NamespaceUsage{ + Name: ns.String(), + Size: ns.Size, + } +} diff --git a/cmd/api/handler/stats.go b/cmd/api/handler/stats.go index 011f493f..30588ddb 100644 --- a/cmd/api/handler/stats.go +++ b/cmd/api/handler/stats.go @@ -9,16 +9,21 @@ import ( "github.com/celenium-io/celestia-indexer/cmd/api/handler/responses" "github.com/celenium-io/celestia-indexer/internal/storage" + sdk "github.com/dipdup-net/indexer-sdk/pkg/storage" "github.com/labstack/echo/v4" ) type StatsHandler struct { - repo storage.IStats + repo storage.IStats + nsRepo storage.INamespace + state storage.IState } -func NewStatsHandler(repo storage.IStats) StatsHandler { +func NewStatsHandler(repo storage.IStats, nsRepo storage.INamespace, state storage.IState) StatsHandler { return StatsHandler{ - repo: repo, + repo: repo, + nsRepo: nsRepo, + state: state, } } @@ -208,3 +213,42 @@ func (sh StatsHandler) GasPriceHourly(c echo.Context) error { } return returnArray(c, response) } + +// NamespaceUsage godoc +// +// @Summary Get namespaces with sorting by size. +// @Description Get namespaces with sorting by size. Returns top 100 namespaces. Namespaces which is not included to top 100 grouped into 'others' item +// @Tags stats +// @ID stats-namespace-usage +// @Produce json +// @Success 200 {array} responses.NamespaceUsage +// @Failure 500 {object} Error +// @Router /v1/stats/namespace/usage [get] +func (sh StatsHandler) NamespaceUsage(c echo.Context) error { + namespaces, err := sh.nsRepo.Active(c.Request().Context(), "size", 100) + if err != nil { + return internalServerError(c, err) + } + + var top100Size int64 + response := make([]responses.NamespaceUsage, len(namespaces)) + for i := range namespaces { + response[i] = responses.NewNamespaceUsage(namespaces[i]) + top100Size += response[i].Size + } + + state, err := sh.state.List(c.Request().Context(), 1, 0, sdk.SortOrderAsc) + if err != nil { + return internalServerError(c, err) + } + if len(state) == 0 { + return returnArray(c, response) + } + + response = append(response, responses.NamespaceUsage{ + Name: "others", + Size: state[0].TotalBlobsSize - top100Size, + }) + + return returnArray(c, response) +} diff --git a/cmd/api/handler/stats_test.go b/cmd/api/handler/stats_test.go index c6930065..ef662d29 100644 --- a/cmd/api/handler/stats_test.go +++ b/cmd/api/handler/stats_test.go @@ -14,6 +14,7 @@ import ( "github.com/celenium-io/celestia-indexer/cmd/api/handler/responses" "github.com/celenium-io/celestia-indexer/internal/storage" "github.com/celenium-io/celestia-indexer/internal/storage/mock" + sdk "github.com/dipdup-net/indexer-sdk/pkg/storage" "github.com/labstack/echo/v4" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" @@ -23,6 +24,8 @@ import ( type StatsTestSuite struct { suite.Suite stats *mock.MockIStats + ns *mock.MockINamespace + state *mock.MockIState echo *echo.Echo handler StatsHandler ctrl *gomock.Controller @@ -34,7 +37,9 @@ func (s *StatsTestSuite) SetupSuite() { s.echo.Validator = NewCelestiaApiValidator() s.ctrl = gomock.NewController(s.T()) s.stats = mock.NewMockIStats(s.ctrl) - s.handler = NewStatsHandler(s.stats) + s.ns = mock.NewMockINamespace(s.ctrl) + s.state = mock.NewMockIState(s.ctrl) + s.handler = NewStatsHandler(s.stats, s.ns, s.state) } // TearDownSuite - @@ -296,3 +301,38 @@ func (s *StatsTestSuite) TestGasPriceHourly() { s.Require().EqualValues("0.11151539708265802", item.GasEfficiency) s.Require().True(testTime.Equal(item.Time)) } + +func (s *StatsTestSuite) TestNamespaceUsage() { + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := s.echo.NewContext(req, rec) + c.SetPath("/v1/stats/namespace/usage") + + s.ns.EXPECT(). + Active(gomock.Any(), "size", 100). + Return([]storage.Namespace{ + testNamespace, + }, nil) + + s.state.EXPECT(). + List(gomock.Any(), uint64(1), uint64(0), sdk.SortOrderAsc). + Return([]*storage.State{ + &testState, + }, nil) + + s.Require().NoError(s.handler.NamespaceUsage(c)) + s.Require().Equal(http.StatusOK, rec.Code) + + var response []responses.NamespaceUsage + err := json.NewDecoder(rec.Body).Decode(&response) + s.Require().NoError(err) + s.Require().Len(response, 2) + + item0 := response[0] + s.Require().Equal(testNamespace.String(), item0.Name) + s.Require().Equal(testNamespace.Size, item0.Size) + + item1 := response[1] + s.Require().Equal("others", item1.Name) + s.Require().EqualValues(900, item1.Size) +} diff --git a/cmd/api/init.go b/cmd/api/init.go index 51d0ac8c..e327f88c 100644 --- a/cmd/api/init.go +++ b/cmd/api/init.go @@ -13,7 +13,6 @@ import ( "time" "github.com/celenium-io/celestia-indexer/cmd/api/cache" - _ "github.com/celenium-io/celestia-indexer/cmd/api/docs" "github.com/celenium-io/celestia-indexer/cmd/api/handler" "github.com/celenium-io/celestia-indexer/cmd/api/handler/responses" "github.com/celenium-io/celestia-indexer/cmd/api/handler/websocket" @@ -302,7 +301,7 @@ func initHandlers(ctx context.Context, e *echo.Echo, cfg Config, db postgres.Sto namespaceByHash.GET("/:hash/:height/:commitment", namespaceHandlers.GetBlob) } - statsHandler := handler.NewStatsHandler(db.Stats) + statsHandler := handler.NewStatsHandler(db.Stats, db.Namespace, db.State) stats := v1.Group("/stats") { stats.GET("/summary/:table/:function", statsHandler.Summary) @@ -314,6 +313,10 @@ func initHandlers(ctx context.Context, e *echo.Echo, cfg Config, db postgres.Sto { gasPrice.GET("/hourly", statsHandler.GasPriceHourly) } + namespace := stats.Group("/namespace") + { + namespace.GET("/usage", statsHandler.NamespaceUsage) + } } gasHandler := handler.NewGasHandler()