Commit 319ff52e authored by luxq's avatar luxq

add code

parents
Pipeline #886 canceled with stages
.idea
build
.PHONY: default all agent server
GOBIN = $(shell pwd)/build/bin
default: all
all: server agent
server:
go build $(BUILD_FLAGS) -v -o=${GOBIN}/nmserver ./server
agent:
go build $(BUILD_FLAGS) -v -o=${GOBIN}/nmagent ./agent
# Node Monitor
A simple node monitoring system with Agent and Server components written in Go.
## Features
- **Agent**: Collects system metrics (CPU, Memory, Disk usage) and reports to server every 5 seconds
- **Server**: Receives reports from agents and provides a web dashboard
## Usage
### Start the Server
```bash
cd server
go run main.go -port=8080
```
### Start the Agent
```bash
cd agent
go run . -server=http://localhost:8080 -interval=5s
```
### View Dashboard
Open `http://localhost:8080` in your browser to see the monitoring dashboard.
## API Endpoints
- `POST /api/report` - Endpoint for agents to report status
- `GET /` - Web dashboard showing all nodes
## Dependencies
- `github.com/shirou/gopsutil/v3` - System metrics collection
## Installation
```bash
go mod tidy
```
## Monitoring System Documentation
This documentation provides an overview of the monitoring system, including its components, usage, API endpoints, dependencies, and installation instructions.
### Components
The monitoring system consists of two main components:
1. **Agent**: A lightweight process that runs on each node, collecting system metrics and reporting them to the server.
2. **Server**: The central component that receives reports from agents, stores the data, and provides a web-based dashboard for monitoring.
### Usage
To use the monitoring system, follow these steps:
1. **Start the Server**: Navigate to the `server` directory and run `go run main.go -port=8080` to start the server on port 8080.
2. **Start the Agent**: Navigate to the `agent` directory and run `go run . -server=http://localhost:8080 -interval=5s` to start the agent, pointing it to the server's address.
3. **View Dashboard**: Open a web browser and go to `http://localhost:8080` to access the monitoring dashboard.
### API Endpoints
The monitoring system provides the following API endpoints:
- `POST /api/report`: This endpoint is used by agents to report the status of the node. The agent sends a JSON payload containing the system metrics to this endpoint.
- `GET /`: This endpoint serves the web dashboard, which displays the status of all nodes being monitored.
### Dependencies
The monitoring system relies on the following external package:
- `github.com/shirou/gopsutil/v3`: This package is used for collecting system metrics such as CPU, memory, and disk usage.
### Installation
To install the monitoring system, execute the following command:
```bash
go mod tidy
```
This command will download and install the necessary dependencies for the project.
### License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"github.com/ethereum/go-ethereum/ethclient"
"log"
"net/http"
"time"
"nodemonitor/types"
)
var (
serverURL = flag.String("server", "http://localhost:8080", "Server URL")
interval = flag.Duration("interval", 5*time.Second, "Report interval")
localRpc = flag.String("local-rpc", "", "Local Node RPC URL")
nameTag = flag.String("name-tag", "", "Name tag")
)
func main() {
flag.Parse()
if *nameTag == "" {
log.Fatal("name-tag is required")
}
nodeID := *nameTag
log.Printf("Starting agent with node ID: %s", nodeID)
log.Printf("Reporting to server: %s", *serverURL)
ticker := time.NewTicker(*interval)
defer ticker.Stop()
var nodeClient *ethclient.Client
var err error
if len(*localRpc) > 0 {
nodeClient, err = ethclient.Dial(*localRpc)
if err != nil {
log.Fatalf("Failed to connect to local node RPC: %v", err)
}
}
for {
status := collectNodeStatus(nodeID, nodeClient)
if err := reportToServer(status); err != nil {
log.Printf("Failed to report to server: %v", err)
} else {
log.Printf("Successfully reported status")
}
<-ticker.C
}
}
func collectNodeStatus(nodeID string, nodeClient *ethclient.Client) types.NodeStatus {
cpuUsage := getCPUUsage()
memUsage, memTotal, memFree := getMemoryInfo()
diskUsage, diskTotal, diskFree := getDiskInfo()
privateIP := getPrivateIP()
publicIP := getPublicIP()
nodeHeight := getNodeHeight(nodeClient)
return types.NodeStatus{
NodeID: nodeID,
CPUUsage: cpuUsage,
MemoryUsage: memUsage,
MemoryTotal: memTotal,
MemoryFree: memFree,
DiskUsage: diskUsage,
DiskTotal: diskTotal,
DiskFree: diskFree,
PrivateIP: privateIP,
PublicIP: publicIP,
Height: nodeHeight,
Timestamp: time.Now(),
}
}
func reportToServer(status types.NodeStatus) error {
data, err := json.Marshal(status)
if err != nil {
return fmt.Errorf("failed to marshal status: %v", err)
}
resp, err := http.Post(*serverURL+"/api/report", "application/json", bytes.NewBuffer(data))
if err != nil {
return fmt.Errorf("failed to post to server: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("server returned status: %d", resp.StatusCode)
}
return nil
}
package main
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/ethclient"
"io"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/mem"
)
// getPrivateIP gets the local private IP address
func getPrivateIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return "unknown"
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()
}
// getPublicIP gets the public IP address
func getPublicIP() string {
resp, err := http.Get("https://api.ipify.org?format=text")
if err != nil {
return "unknown"
}
defer resp.Body.Close()
ip, err := io.ReadAll(resp.Body)
if err != nil {
return "unknown"
}
return strings.TrimSpace(string(ip))
}
func getNodeHeight(nodeClient *ethclient.Client) string {
if nodeClient == nil {
return "N/A"
}
height, err := nodeClient.BlockNumber(context.Background())
if err != nil {
return fmt.Sprintf("fetch error:%s", err)
}
return strconv.FormatUint(height, 10)
}
// getCPUUsage gets CPU usage percentage
func getCPUUsage() float64 {
percent, err := cpu.Percent(time.Second, false)
if err != nil || len(percent) == 0 {
return 0
}
return percent[0]
}
// getMemoryInfo gets memory usage information
func getMemoryInfo() (float64, uint64, uint64) {
v, err := mem.VirtualMemory()
if err != nil {
return 0, 0, 0
}
return v.UsedPercent, v.Total, v.Available
}
// getDiskInfo gets disk usage information
func getDiskInfo() (float64, uint64, uint64) {
usage, err := disk.Usage("/")
if err != nil {
return 0, 0, 0
}
return usage.UsedPercent, usage.Total, usage.Free
}
// getNodeID generates a unique node identifier
func getNodeID() string {
hostname, err := os.Hostname()
if err != nil {
hostname = "unknown"
}
return fmt.Sprintf("%s-%s", hostname, getPrivateIP())
}
module nodemonitor
go 1.23.0
toolchain go1.24.0
require (
github.com/ethereum/go-ethereum v1.16.2
github.com/shirou/gopsutil/v3 v3.23.12
)
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/consensys/gnark-crypto v0.18.0 // indirect
github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect
github.com/ethereum/go-verkle v0.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/supranational/blst v0.3.14 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
)
This diff is collapsed.
package main
import (
"encoding/json"
"flag"
"fmt"
"html/template"
"log"
"net/http"
"sort"
"sync"
"time"
"nodemonitor/types"
)
var (
port = flag.String("port", "8080", "Server port")
)
type Server struct {
nodes map[string]*types.NodeStatus
mutex sync.RWMutex
}
func NewServer() *Server {
return &Server{
nodes: make(map[string]*types.NodeStatus),
}
}
func (s *Server) handleReport(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var status types.NodeStatus
if err := json.NewDecoder(r.Body).Decode(&status); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
status.LastHeartbeat = time.Now()
s.mutex.Lock()
s.nodes[status.NodeID] = &status
s.mutex.Unlock()
log.Printf("Received report from node: %s", status.NodeID)
w.WriteHeader(http.StatusOK)
}
func (s *Server) handleNodes(w http.ResponseWriter, r *http.Request) {
s.mutex.RLock()
nodes := make([]*types.NodeStatus, 0, len(s.nodes))
for _, node := range s.nodes {
nodes = append(nodes, node)
}
s.mutex.RUnlock()
// Sort by node ID
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].NodeID < nodes[j].NodeID
})
tmpl := `
<!DOCTYPE html>
<html>
<head>
<title>Node Monitor</title>
<style>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.offline { background-color: #ffebee; }
.online { background-color: #e8f5e8; }
</style>
</head>
<body>
<h1>Node Monitor Dashboard</h1>
<p>Last updated: {{.UpdateTime}}</p>
<table>
<tr>
<th>Node ID</th>
<th>Status</th>
<th>Height</th>
<th>CPU Usage (%)</th>
<th>Memory Usage (%)</th>
<th>Memory (Free/Total)</th>
<th>Disk Usage (%)</th>
<th>Disk (Free/Total)</th>
<th>Private IP</th>
<th>Public IP</th>
<th>Last Report</th>
</tr>
{{range .Nodes}}
<tr class="{{.StatusClass}}">
<td>{{.NodeID}}</td>
<td>{{.Status}}</td>
<td>{{.Height}}</td>
<td>{{printf "%.2f" .CPUUsage}}</td>
<td>{{printf "%.2f" .MemoryUsage}}</td>
<td>{{.MemoryInfo}}</td>
<td>{{printf "%.2f" .DiskUsage}}</td>
<td>{{.DiskInfo}}</td>
<td>{{.PrivateIP}}</td>
<td>{{.PublicIP}}</td>
<td>{{.LastReport}}</td>
</tr>
{{end}}
</table>
</body>
</html>
`
type NodeView struct {
*types.NodeStatus
Status string
StatusClass string
MemoryInfo string
DiskInfo string
LastReport string
}
nodeViews := make([]NodeView, len(nodes))
for i, node := range nodes {
isOnline := time.Since(node.LastHeartbeat) < 30*time.Second
status := "Online"
statusClass := "online"
if !isOnline {
status = "Offline"
statusClass = "offline"
}
nodeViews[i] = NodeView{
NodeStatus: node,
Status: status,
StatusClass: statusClass,
MemoryInfo: fmt.Sprintf("%.2f GB / %.2f GB", float64(node.MemoryFree)/(1024*1024*1024), float64(node.MemoryTotal)/(1024*1024*1024)),
DiskInfo: fmt.Sprintf("%.2f GB / %.2f GB", float64(node.DiskFree)/(1024*1024*1024), float64(node.DiskTotal)/(1024*1024*1024)),
LastReport: node.Timestamp.Format("2006-01-02 15:04:05"),
}
}
data := struct {
Nodes []NodeView
UpdateTime string
}{
Nodes: nodeViews,
UpdateTime: time.Now().Format("2006-01-02 15:04:05"),
}
t, err := template.New("nodes").Parse(tmpl)
if err != nil {
http.Error(w, "Template error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html")
if err := t.Execute(w, data); err != nil {
http.Error(w, "Template execution error", http.StatusInternalServerError)
return
}
}
func main() {
flag.Parse()
server := NewServer()
http.HandleFunc("/api/report", server.handleReport)
http.HandleFunc("/", server.handleNodes)
log.Printf("Server starting on port %s", *port)
log.Fatal(http.ListenAndServe(":"+*port, nil))
}
package types
import "time"
// NodeStatus represents the status information of a node
type NodeStatus struct {
NodeID string `json:"node_id"`
CPUUsage float64 `json:"cpu_usage"` // CPU usage percentage
MemoryUsage float64 `json:"memory_usage"` // Memory usage percentage
MemoryTotal uint64 `json:"memory_total"` // Total memory in bytes
MemoryFree uint64 `json:"memory_free"` // Free memory in bytes
DiskUsage float64 `json:"disk_usage"` // Disk usage percentage
DiskTotal uint64 `json:"disk_total"` // Total disk space in bytes
DiskFree uint64 `json:"disk_free"` // Free disk space in bytes
PrivateIP string `json:"private_ip"` // Internal IP address
PublicIP string `json:"public_ip"` // External IP address
Height string `json:"height"` // Node height (e.g., blockchain height)
Timestamp time.Time `json:"timestamp"` // Report timestamp
LastHeartbeat time.Time `json:"last_heartbeat"` // Last heartbeat time
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment