Skip to content

Commit

Permalink
(BEDS-461) implement get user machine metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
LuccaBitfly committed Sep 26, 2024
1 parent 7d35b43 commit be26d51
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 23 deletions.
1 change: 1 addition & 0 deletions backend/pkg/api/data_access/data_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type DataAccessor interface {
ProtocolRepository
RatelimitRepository
HealthzRepository
MachineRepository

StartDataAccessServices()
Close()
Expand Down
19 changes: 19 additions & 0 deletions backend/pkg/api/data_access/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
"fmt"
"math/rand/v2"
"reflect"
"slices"
"time"

"github.com/go-faker/faker/v4"
"github.com/go-faker/faker/v4/pkg/options"
"github.com/gobitfly/beaconchain/pkg/api/enums"
"github.com/gobitfly/beaconchain/pkg/api/types"
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/userservice"
"github.com/shopspring/decimal"
Expand Down Expand Up @@ -656,3 +658,20 @@ func (d *DummyService) IncrementBundleDeliveryCount(ctx context.Context, bundleV
func (d *DummyService) GetValidatorDashboardMobileWidget(ctx context.Context, dashboardId t.VDBIdPrimary) (*t.MobileWidgetData, error) {
return getDummyStruct[t.MobileWidgetData]()
}

func (d *DummyService) GetUserMachineMetrics(ctx context.Context, userID uint64, limit uint64, offset uint64) (*types.MachineMetricsData, error) {
data, err := getDummyStruct[types.MachineMetricsData]()
if err != nil {
return nil, err
}
data.SystemMetrics = slices.SortedFunc(slices.Values(data.SystemMetrics), func(i, j *t.MachineMetricSystem) int {
return int(i.Timestamp) - int(j.Timestamp)
})
data.ValidatorMetrics = slices.SortedFunc(slices.Values(data.ValidatorMetrics), func(i, j *t.MachineMetricValidator) int {
return int(i.Timestamp) - int(j.Timestamp)
})
data.NodeMetrics = slices.SortedFunc(slices.Values(data.NodeMetrics), func(i, j *t.MachineMetricNode) int {
return int(i.Timestamp) - int(j.Timestamp)
})
return data, nil
}
15 changes: 15 additions & 0 deletions backend/pkg/api/data_access/machine_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dataaccess

import (
"context"

"github.com/gobitfly/beaconchain/pkg/api/types"
)

type MachineRepository interface {
GetUserMachineMetrics(context context.Context, userID uint64, limit uint64, offset uint64) (*types.MachineMetricsData, error)
}

func (d *DataAccessService) GetUserMachineMetrics(ctx context.Context, userID uint64, limit uint64, offset uint64) (*types.MachineMetricsData, error) {
return d.dummy.GetUserMachineMetrics(ctx, userID, limit, offset)
}
17 changes: 10 additions & 7 deletions backend/pkg/api/handlers/auth.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"cmp"
"context"
"errors"
"fmt"
Expand Down Expand Up @@ -181,13 +182,15 @@ const authHeaderPrefix = "Bearer "

func (h *HandlerService) GetUserIdByApiKey(r *http.Request) (uint64, error) {
// TODO: store user id in context during ratelimting and use it here
var apiKey string
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, authHeaderPrefix) {
apiKey = strings.TrimPrefix(authHeader, authHeaderPrefix)
} else {
apiKey = r.URL.Query().Get("api_key")
}
query := r.URL.Query()
header := r.Header
apiKey := cmp.Or(
strings.TrimPrefix(header.Get("Authorization"), authHeaderPrefix),
header.Get("X-Api-Key"),
query.Get("api_key"),
query.Get("apiKey"),
query.Get("apikey"),
)
if apiKey == "" {
return 0, newUnauthorizedErr("missing api key")
}
Expand Down
22 changes: 10 additions & 12 deletions backend/pkg/api/handlers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ import (
)

type HandlerService struct {
dai dataaccess.DataAccessor
scs *scs.SessionManager
dai dataaccess.DataAccessor
scs *scs.SessionManager
isPostMachineMetricsEnabled bool // if more config options are needed, consider having the whole config in here
}

func NewHandlerService(dataAccessor dataaccess.DataAccessor, sessionManager *scs.SessionManager) *HandlerService {
func NewHandlerService(dataAccessor dataaccess.DataAccessor, sessionManager *scs.SessionManager, enablePostMachineMetrics bool) *HandlerService {
if allNetworks == nil {
networks, err := dataAccessor.GetAllNetworks()
if err != nil {
Expand All @@ -43,8 +44,9 @@ func NewHandlerService(dataAccessor dataaccess.DataAccessor, sessionManager *scs
}

return &HandlerService{
dai: dataAccessor,
scs: sessionManager,
dai: dataAccessor,
scs: sessionManager,
isPostMachineMetricsEnabled: enablePostMachineMetrics,
}
}

Expand Down Expand Up @@ -525,14 +527,10 @@ func checkEnum[T enums.EnumFactory[T]](v *validationError, enumString string, na
return enum
}

// checkEnumIsAllowed checks if the given enum is in the list of allowed enums.
func checkEnumIsAllowed[T enums.EnumFactory[T]](v *validationError, enum T, allowed []T, name string) {
if enums.IsInvalidEnum(enum) {
v.add(name, "parameter is missing or invalid, please check the API documentation")
return
}
// better func name would be
func checkValueInAllowed[T cmp.Ordered](v *validationError, value T, allowed []T, name string) {
for _, a := range allowed {
if enum.Int() == a.Int() {
if cmp.Compare(value, a) == 0 {
return
}
}
Expand Down
37 changes: 37 additions & 0 deletions backend/pkg/api/handlers/machine_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package handlers

import (
"net/http"

"github.com/gobitfly/beaconchain/pkg/api/types"
)

func (h *HandlerService) InternalGetUserMachineMetrics(w http.ResponseWriter, r *http.Request) {
h.PublicGetUserMachineMetrics(w, r)
}

func (h *HandlerService) PublicGetUserMachineMetrics(w http.ResponseWriter, r *http.Request) {
var v validationError
userId, err := GetUserIdByContext(r)
if err != nil {
handleErr(w, r, err)
return
}
q := r.URL.Query()
offset := v.checkUint(q.Get("offset"), "offset")
limit := uint64(180)
if limitParam := q.Get("limit"); limitParam != "" {
limit = v.checkUint(limitParam, "limit")
}

data, err := h.dai.GetUserMachineMetrics(r.Context(), userId, limit, offset)
if err != nil {
handleErr(w, r, err)
return
}
response := types.GetUserMachineMetricsRespone{
Data: *data,
}

returnOk(w, r, response)
}
6 changes: 3 additions & 3 deletions backend/pkg/api/handlers/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,7 @@ func (h *HandlerService) PublicGetValidatorDashboardSummary(w http.ResponseWrite

period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period")
// allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h
checkEnumIsAllowed(&v, period, summaryAllowedPeriods, "period")
checkValueInAllowed(&v, period, summaryAllowedPeriods, "period")
if v.hasErrors() {
handleErr(w, r, v)
return
Expand Down Expand Up @@ -1065,7 +1065,7 @@ func (h *HandlerService) PublicGetValidatorDashboardGroupSummary(w http.Response
groupId := v.checkGroupId(vars["group_id"], forbidEmpty)
period := checkEnum[enums.TimePeriod](&v, r.URL.Query().Get("period"), "period")
// allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h
checkEnumIsAllowed(&v, period, summaryAllowedPeriods, "period")
checkValueInAllowed(&v, period, summaryAllowedPeriods, "period")
if v.hasErrors() {
handleErr(w, r, v)
return
Expand Down Expand Up @@ -1160,7 +1160,7 @@ func (h *HandlerService) PublicGetValidatorDashboardSummaryValidators(w http.Res
period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period")
// allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h
allowedPeriods := []enums.TimePeriod{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
checkEnumIsAllowed(&v, period, allowedPeriods, "period")
checkValueInAllowed(&v, period, allowedPeriods, "period")
if v.hasErrors() {
handleErr(w, r, v)
return
Expand Down
4 changes: 3 additions & 1 deletion backend/pkg/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func NewApiRouter(dataAccessor dataaccess.DataAccessor, cfg *types.Config) *mux.
if !(cfg.Frontend.CsrfInsecure || cfg.Frontend.Debug) {
internalRouter.Use(getCsrfProtectionMiddleware(cfg), csrfInjecterMiddleware)
}
handlerService := handlers.NewHandlerService(dataAccessor, sessionManager)
handlerService := handlers.NewHandlerService(dataAccessor, sessionManager, !cfg.Frontend.DisableStatsInserts)

// store user id in context, if available
publicRouter.Use(handlers.GetUserIdStoreMiddleware(handlerService.GetUserIdByApiKey))
Expand Down Expand Up @@ -123,6 +123,8 @@ func addRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Ro
{http.MethodGet, "/users/me/dashboards", hs.PublicGetUserDashboards, hs.InternalGetUserDashboards},
{http.MethodPut, "/users/me/notifications/settings/paired-devices/{client_id}/token", nil, hs.InternalPostUsersMeNotificationSettingsPairedDevicesToken},

{http.MethodGet, "/users/me/machine-metrics", hs.PublicGetUserMachineMetrics, hs.InternalGetUserMachineMetrics},

{http.MethodPost, "/search", nil, hs.InternalPostSearch},

{http.MethodPost, "/account-dashboards", hs.PublicPostAccountDashboards, hs.InternalPostAccountDashboards},
Expand Down
79 changes: 79 additions & 0 deletions backend/pkg/api/types/machine_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package types

type MachineMetricSystem struct {
Timestamp uint64 `json:"timestamp,omitempty" faker:"boundary_start=1725166800, boundary_end=1725177600"`
ExporterVersion string `json:"exporter_version,omitempty"`
// system
CpuCores uint64 `json:"cpu_cores,omitempty"`
CpuThreads uint64 `json:"cpu_threads,omitempty"`
CpuNodeSystemSecondsTotal uint64 `json:"cpu_node_system_seconds_total,omitempty"`
CpuNodeUserSecondsTotal uint64 `json:"cpu_node_user_seconds_total,omitempty"`
CpuNodeIowaitSecondsTotal uint64 `json:"cpu_node_iowait_seconds_total,omitempty"`
CpuNodeIdleSecondsTotal uint64 `json:"cpu_node_idle_seconds_total,omitempty"`
MemoryNodeBytesTotal uint64 `json:"memory_node_bytes_total,omitempty"`
MemoryNodeBytesFree uint64 `json:"memory_node_bytes_free,omitempty"`
MemoryNodeBytesCached uint64 `json:"memory_node_bytes_cached,omitempty"`
MemoryNodeBytesBuffers uint64 `json:"memory_node_bytes_buffers,omitempty"`
DiskNodeBytesTotal uint64 `json:"disk_node_bytes_total,omitempty"`
DiskNodeBytesFree uint64 `json:"disk_node_bytes_free,omitempty"`
DiskNodeIoSeconds uint64 `json:"disk_node_io_seconds,omitempty"`
DiskNodeReadsTotal uint64 `json:"disk_node_reads_total,omitempty"`
DiskNodeWritesTotal uint64 `json:"disk_node_writes_total,omitempty"`
NetworkNodeBytesTotalReceive uint64 `json:"network_node_bytes_total_receive,omitempty"`
NetworkNodeBytesTotalTransmit uint64 `json:"network_node_bytes_total_transmit,omitempty"`
MiscNodeBootTsSeconds uint64 `json:"misc_node_boot_ts_seconds,omitempty"`
MiscOs string `json:"misc_os,omitempty"`
// do not store in bigtable but include them in generated model
Machine *string `json:"machine,omitempty"`
}

type MachineMetricValidator struct {
Timestamp uint64 `json:"timestamp,omitempty" faker:"boundary_start=1725166800, boundary_end=1725177600"`
ExporterVersion string `json:"exporter_version,omitempty"`
// process
CpuProcessSecondsTotal uint64 `json:"cpu_process_seconds_total,omitempty"`
MemoryProcessBytes uint64 `json:"memory_process_bytes,omitempty"`
ClientName string `json:"client_name,omitempty"`
ClientVersion string `json:"client_version,omitempty"`
ClientBuild uint64 `json:"client_build,omitempty"`
SyncEth2FallbackConfigured bool `json:"sync_eth2_fallback_configured,omitempty"`
SyncEth2FallbackConnected bool `json:"sync_eth2_fallback_connected,omitempty"`
// validator
ValidatorTotal uint64 `json:"validator_total,omitempty"`
ValidatorActive uint64 `json:"validator_active,omitempty"`
// do not store in bigtable but include them in generated model
Machine *string `json:"machine,omitempty"`
}

type MachineMetricNode struct {
Timestamp uint64 `json:"timestamp,omitempty" faker:"boundary_start=1725166800, boundary_end=1725177600"`
ExporterVersion string `json:"exporter_version,omitempty"`
// process
CpuProcessSecondsTotal uint64 `json:"cpu_process_seconds_total,omitempty"`
MemoryProcessBytes uint64 `json:"memory_process_bytes,omitempty"`
ClientName string `json:"client_name,omitempty"`
ClientVersion string `json:"client_version,omitempty"`
ClientBuild uint64 `json:"client_build,omitempty"`
SyncEth2FallbackConfigured bool `json:"sync_eth2_fallback_configured,omitempty"`
SyncEth2FallbackConnected bool `json:"sync_eth2_fallback_connected,omitempty"`
// node
DiskBeaconchainBytesTotal uint64 `json:"disk_beaconchain_bytes_total,omitempty"`
NetworkLibp2PBytesTotalReceive uint64 `json:"network_libp2p_bytes_total_receive,omitempty"`
NetworkLibp2PBytesTotalTransmit uint64 `json:"network_libp2p_bytes_total_transmit,omitempty"`
NetworkPeersConnected uint64 `json:"network_peers_connected,omitempty"`
SyncEth1Connected bool `json:"sync_eth1_connected,omitempty"`
SyncEth2Synced bool `json:"sync_eth2_synced,omitempty"`
SyncBeaconHeadSlot uint64 `json:"sync_beacon_head_slot,omitempty"`
SyncEth1FallbackConfigured bool `json:"sync_eth1_fallback_configured,omitempty"`
SyncEth1FallbackConnected bool `json:"sync_eth1_fallback_connected,omitempty"`
// do not store in bigtable but include them in generated model
Machine *string `json:"machine,omitempty"`
}

type MachineMetricsData struct {
SystemMetrics []*MachineMetricSystem `json:"system_metrics" faker:"slice_len=30"`
ValidatorMetrics []*MachineMetricValidator `json:"validator_metrics" faker:"slice_len=30"`
NodeMetrics []*MachineMetricNode `json:"node_metrics" faker:"slice_len=30"`
}

type GetUserMachineMetricsRespone ApiDataResponse[MachineMetricsData]
96 changes: 96 additions & 0 deletions frontend/types/api/machine_metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Code generated by tygo. DO NOT EDIT.
/* eslint-disable */
import type { ApiDataResponse } from './common'

//////////
// source: machine_metrics.go

export interface MachineMetricSystem {
timestamp?: number /* uint64 */;
exporter_version?: string;
/**
* system
*/
cpu_cores?: number /* uint64 */;
cpu_threads?: number /* uint64 */;
cpu_node_system_seconds_total?: number /* uint64 */;
cpu_node_user_seconds_total?: number /* uint64 */;
cpu_node_iowait_seconds_total?: number /* uint64 */;
cpu_node_idle_seconds_total?: number /* uint64 */;
memory_node_bytes_total?: number /* uint64 */;
memory_node_bytes_free?: number /* uint64 */;
memory_node_bytes_cached?: number /* uint64 */;
memory_node_bytes_buffers?: number /* uint64 */;
disk_node_bytes_total?: number /* uint64 */;
disk_node_bytes_free?: number /* uint64 */;
disk_node_io_seconds?: number /* uint64 */;
disk_node_reads_total?: number /* uint64 */;
disk_node_writes_total?: number /* uint64 */;
network_node_bytes_total_receive?: number /* uint64 */;
network_node_bytes_total_transmit?: number /* uint64 */;
misc_node_boot_ts_seconds?: number /* uint64 */;
misc_os?: string;
/**
* do not store in bigtable but include them in generated model
*/
machine?: string;
}
export interface MachineMetricValidator {
timestamp?: number /* uint64 */;
exporter_version?: string;
/**
* process
*/
cpu_process_seconds_total?: number /* uint64 */;
memory_process_bytes?: number /* uint64 */;
client_name?: string;
client_version?: string;
client_build?: number /* uint64 */;
sync_eth2_fallback_configured?: boolean;
sync_eth2_fallback_connected?: boolean;
/**
* validator
*/
validator_total?: number /* uint64 */;
validator_active?: number /* uint64 */;
/**
* do not store in bigtable but include them in generated model
*/
machine?: string;
}
export interface MachineMetricNode {
timestamp?: number /* uint64 */;
exporter_version?: string;
/**
* process
*/
cpu_process_seconds_total?: number /* uint64 */;
memory_process_bytes?: number /* uint64 */;
client_name?: string;
client_version?: string;
client_build?: number /* uint64 */;
sync_eth2_fallback_configured?: boolean;
sync_eth2_fallback_connected?: boolean;
/**
* node
*/
disk_beaconchain_bytes_total?: number /* uint64 */;
network_libp2p_bytes_total_receive?: number /* uint64 */;
network_libp2p_bytes_total_transmit?: number /* uint64 */;
network_peers_connected?: number /* uint64 */;
sync_eth1_connected?: boolean;
sync_eth2_synced?: boolean;
sync_beacon_head_slot?: number /* uint64 */;
sync_eth1_fallback_configured?: boolean;
sync_eth1_fallback_connected?: boolean;
/**
* do not store in bigtable but include them in generated model
*/
machine?: string;
}
export interface MachineMetricsData {
system_metrics: (MachineMetricSystem | undefined)[];
validator_metrics: (MachineMetricValidator | undefined)[];
node_metrics: (MachineMetricNode | undefined)[];
}
export type GetUserMachineMetricsRespone = ApiDataResponse<MachineMetricsData>;

0 comments on commit be26d51

Please sign in to comment.