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

(BEDS-461) implement get user machine metrics #889

Merged
merged 1 commit into from
Sep 26, 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
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>;
Loading