Skip to content

Commit 9029d3b

Browse files
authored
Merge pull request #19 from bcdevtools/feat/monitoring
feat: add new API for internal monitoring
2 parents f26c1c2 + 32e61c6 commit 9029d3b

File tree

7 files changed

+138
-0
lines changed

7 files changed

+138
-0
lines changed

cmd/start_web.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
const (
1414
flagPort = "port"
1515
flagAuthorizationToken = "authorization-token"
16+
flagDisks = "disks"
1617
flagDebug = "debug"
1718

1819
flagBrand = "brand"
@@ -52,6 +53,7 @@ func GetStartWebCmd() *cobra.Command {
5253
nodeHomeDirectory := strings.TrimSpace(args[0])
5354
port, _ := cmd.Flags().GetUint16(flagPort)
5455
authorizationToken, _ := cmd.Flags().GetString(flagAuthorizationToken)
56+
disks, _ := cmd.Flags().GetStringSlice(flagDisks)
5557
debug, _ := cmd.Flags().GetBool(flagDebug)
5658

5759
brand, _ := cmd.Flags().GetString(flagBrand)
@@ -82,6 +84,35 @@ func GetStartWebCmd() *cobra.Command {
8284
return
8385
}
8486

87+
if disks == nil || len(disks) == 0 {
88+
utils.ExitWithErrorMsgf("ERR: disks is required, use --%s flag to set it\n", flagDisks)
89+
return
90+
}
91+
for _, disk := range disks {
92+
disk := strings.TrimSpace(disk)
93+
if !strings.HasPrefix(disk, "/") {
94+
utils.ExitWithErrorMsgf("ERR: disk must be absolute path, correct the --%s flag\n", flagDisks)
95+
return
96+
}
97+
if strings.HasPrefix(disk, "/dev") {
98+
utils.ExitWithErrorMsgf("ERR: disk must be path, not device, correct the --%s flag\n", flagDisks)
99+
return
100+
}
101+
_, exists, isDir, err := utils.FileInfo(disk)
102+
if err != nil {
103+
utils.ExitWithErrorMsgf("ERR: disk %s is invalid, correct the --%s flag\n", disk, flagDisks)
104+
return
105+
}
106+
if !exists {
107+
utils.ExitWithErrorMsgf("ERR: disk %s does not exists, correct the --%s flag\n", disk, flagDisks)
108+
return
109+
}
110+
if !isDir {
111+
utils.ExitWithErrorMsgf("ERR: disk %s is not a directory, correct the --%s flag\n", disk, flagDisks)
112+
return
113+
}
114+
}
115+
85116
brand = strings.TrimSpace(brand)
86117
if brand == "" {
87118
utils.ExitWithErrorMsgf("ERR: brand is required, use --%s flag to set it\n", flagBrand)
@@ -170,6 +201,7 @@ func GetStartWebCmd() *cobra.Command {
170201
web_server.StartWebServer(webtypes.Config{
171202
Port: port,
172203
AuthorizeToken: authorizationToken,
204+
Disks: disks,
173205
NodeHome: nodeHomeDirectory,
174206
Debug: debug,
175207

@@ -195,6 +227,7 @@ func GetStartWebCmd() *cobra.Command {
195227

196228
cmd.Flags().Uint16(flagPort, defaultWebPort, "port to bind Web service to")
197229
cmd.Flags().StringP(flagAuthorizationToken, "a", "", "authorization token")
230+
cmd.Flags().StringSlice(flagDisks, []string{"/"}, "disks to monitor, must be path, mount-point, not device")
198231
cmd.Flags().Bool(flagDebug, false, "enable debug mode")
199232

200233
cmd.Flags().String(flagBrand, defaultBrand, "brand")

services/web_server/gin_wrapper/gin_wrapper.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,17 @@ func (w GinWrapper) Binder() *GinBinder {
3838
func (w GinWrapper) Config() types.Config {
3939
return w.c.MustGet(constants.GinConfig).(types.Config)
4040
}
41+
42+
func (w GinWrapper) IsAuthorizedRequest() bool {
43+
cfg := w.Config()
44+
if cfg.AuthorizeToken == "" {
45+
return false
46+
}
47+
48+
token := w.c.GetHeader("VN-Authorization")
49+
if token == "" {
50+
return false
51+
}
52+
53+
return token == cfg.AuthorizeToken
54+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package web_server
2+
3+
import (
4+
"github.com/bcdevtools/node-management/utils"
5+
"github.com/gin-gonic/gin"
6+
"github.com/shirou/gopsutil/v3/cpu"
7+
"github.com/shirou/gopsutil/v3/disk"
8+
"github.com/shirou/gopsutil/v3/mem"
9+
"math"
10+
)
11+
12+
func HandleApiInternalMonitoringStats(c *gin.Context) {
13+
w := wrapGin(c)
14+
15+
cfg := w.Config()
16+
17+
cpuInfo := make(map[string]any)
18+
vmInfo := make(map[string]any)
19+
disks := make([]map[string]any, 0)
20+
21+
pCore, errPCore := cpu.Counts(false)
22+
if errPCore == nil {
23+
cpuInfo["physical_cores"] = pCore
24+
}
25+
26+
lCore, errLCore := cpu.Counts(true)
27+
if errLCore == nil {
28+
cpuInfo["logical_cores"] = lCore
29+
}
30+
31+
cpusPercent, errCpuPercent := cpu.Percent(0, false)
32+
if errCpuPercent == nil {
33+
cpuInfo["used_percent"] = normalizePercentage(cpusPercent[0])
34+
}
35+
36+
vm, errVm := mem.VirtualMemory()
37+
if errVm == nil {
38+
vmInfo["total"] = convertByteToGb(vm.Total)
39+
vmInfo["used"] = convertByteToGb(vm.Used)
40+
vmInfo["used_percent"] = normalizePercentage(vm.UsedPercent)
41+
}
42+
43+
for _, _disk := range cfg.Disks {
44+
du, err := disk.Usage(_disk)
45+
if err != nil {
46+
utils.PrintlnStdErr("ERR: failed to get disk spec", "disk", _disk, "error", err.Error())
47+
continue
48+
}
49+
50+
disks = append(disks, map[string]any{
51+
"mount": _disk,
52+
"total": convertByteToGb(du.Total),
53+
"used": convertByteToGb(du.Used),
54+
"used_percent": normalizePercentage(du.UsedPercent),
55+
})
56+
}
57+
58+
w.PrepareDefaultSuccessResponse(map[string]any{
59+
"cpu": cpuInfo,
60+
"ram": vmInfo,
61+
"disks": disks,
62+
}).SendResponse()
63+
}
64+
65+
func convertByteToGb(byteCount uint64) float64 {
66+
result := float64(byteCount) / 1024 / 1024 / 1024
67+
result = math.Round(result*100) / 100
68+
return result
69+
}
70+
71+
func normalizePercentage(percent float64) float64 {
72+
return float64(int(percent*100)) / 100
73+
}
File renamed without changes.

services/web_server/types/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "path"
55
type Config struct {
66
Port uint16
77
AuthorizeToken string
8+
Disks []string
89
NodeHome string
910
Debug bool
1011

services/web_server/web_server.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
statikfs "github.com/rakyll/statik/fs"
1313
"html/template"
1414
"net/http"
15+
"strings"
1516
)
1617

1718
func StartWebServer(cfg webtypes.Config) {
@@ -31,6 +32,21 @@ func StartWebServer(cfg webtypes.Config) {
3132
r.Use(func(c *gin.Context) {
3233
c.Set(constants.GinConfig, cfg)
3334
})
35+
r.Use(func(c *gin.Context) {
36+
if strings.HasPrefix(strings.TrimPrefix(c.Request.URL.Path, "/"), "api/internal") {
37+
w := wrapGin(c)
38+
if !w.IsAuthorizedRequest() {
39+
w.PrepareDefaultErrorResponse().
40+
WithHttpStatusCode(http.StatusForbidden).
41+
WithResult("invalid authentication token").
42+
SendResponse()
43+
c.Abort()
44+
return
45+
}
46+
}
47+
48+
c.Next()
49+
})
3450

3551
const (
3652
engineDelimsLeft = "{[{"
@@ -57,6 +73,7 @@ func StartWebServer(cfg webtypes.Config) {
5773

5874
// API
5975
r.GET("/api/node/live-peers", HandleApiNodeLivePeers)
76+
r.GET("/api/internal/monitoring/stats", HandleApiInternalMonitoringStats)
6077

6178
// Web
6279
r.GET("/", HandleWebIndex)

0 commit comments

Comments
 (0)