mirror of
https://git.phc.dm.unipi.it/aziis98/go-stats-server.git
synced 2026-10-07 13:04:50 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c86785ecdf | ||
|
|
7ff45091c7 | ||
|
|
994cff1190 | ||
|
|
c85ec78793 | ||
|
|
10938c1d98 | ||
|
|
631af1cda6 | ||
|
|
c488d41ac0 | ||
|
|
0a375c0990 |
@@ -12,12 +12,12 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.21
|
||||
go-version-file: './go.mod'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
||||
@@ -1,64 +1,87 @@
|
||||
# Go Stats Server
|
||||
|
||||
This Go project implements a simple TCP server that responds to custom commands over the network. The server performs various system-related tasks and provides
|
||||
information such as CPU usage, memory status, network statistics, storage details, and system uptime.
|
||||
This Go project implements a simple TCP server that responds to custom commands over an internal network. The server performs various system-related tasks and provides information such as CPU usage, memory status, network statistics, storage details, and system uptime.
|
||||
|
||||
## Commands
|
||||
|
||||
The protocol supports the following commands:
|
||||
|
||||
- `cpu` — returns the CPU usage percentage
|
||||
- `memory` — returns the memory usage in MB
|
||||
- `network` — returns the network usage in bytes
|
||||
- `storage` — returns the storage usage in GB
|
||||
- `uptime` — returns the system uptime in seconds
|
||||
- `temperature` — returns the CPU temperature of Big and Little cores in Celsius
|
||||
|
||||
[TODO: check if the command docs are correct]
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Build the Server:**
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=0 GOARCH=<arch> go build -a -ldflags '-s -w' -o ./out/stats-server main.go
|
||||
```bash shell
|
||||
$ CGO_ENABLED=0 GOARCH=<arch> go build -a -ldflags '-s -w' -o ./out/stats-server main.go
|
||||
```
|
||||
|
||||
2. **Run the Server:**
|
||||
|
||||
```bash
|
||||
./out/stats-server
|
||||
```bash shell
|
||||
$ ./out/stats-server serve
|
||||
```
|
||||
|
||||
3. **Connect to the Server:** Use a TCP client to connect to the server on port 12345. You can send commands like "cpu," "memory," "network," "storage,"
|
||||
"uptime," and "exit."
|
||||
3. **Connect to the Server:** Use a TCP client to connect to the server on port 12345. You can send commands like "cpu", "memory", "network", "storage" or
|
||||
"uptime".
|
||||
|
||||
Example using `nc`:
|
||||
|
||||
```bash
|
||||
echo "cpu" | nc localhost 12345
|
||||
```bash shell
|
||||
$ time ( echo "cpu" | nc <hostname> 12345 )
|
||||
```
|
||||
|
||||
or using golang
|
||||
or directly using golang:
|
||||
|
||||
```go
|
||||
import "net"
|
||||
conn, _ := net.Dial("tcp", "<hostname>:12345")
|
||||
conn.Write([]byte("cpu\n"))
|
||||
|
||||
func main() {
|
||||
conn, err := net.Dial("tcp", "localhost:12345")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.Write([]byte("cpu"))
|
||||
}
|
||||
data, _ := io.RealAll(conn)
|
||||
log.Printf("data: %s", data)
|
||||
```
|
||||
|
||||
## GitHub Actions Workflow
|
||||
|
||||
The included GitHub Actions workflow automates the build and release process. On each push to the main branch, the workflow builds the Go program, creates a
|
||||
GitHub release, and uploads the compiled binary as an artifact.
|
||||
The included GitHub Actions workflow automates the build and release process. On each push to the main branch, the workflow builds the Go program, creates a GitHub release, and uploads the compiled binary as an artifact.
|
||||
|
||||
## Downloading the Artifact
|
||||
## Self-Installing Binary
|
||||
|
||||
TODO
|
||||
|
||||
<!--
|
||||
To download the compiled binary on multiple machines using GNU Parallel, use the following `wget` command:
|
||||
Assuming you have a cluster of machines, you can use the following commands to download, install and setup systemd services for the latest version of the binary on all nodes. You must have root access to all nodes.
|
||||
|
||||
```bash
|
||||
cat machines.txt | parallel --slf - 'wget -qO- https://github.com/aziis98/go-stats-server/releases/latest/download/stats-server | tar -xz -C /path/to/destination/'
|
||||
$ parallel --nonall --slf nodes.txt 'mkdir -p cluster'
|
||||
$ parallel --nonall --slf nodes.txt 'wget -qO- https://github.com/aziis98/go-stats-server/releases/latest/download/stats-server > ./cluster/stats-server'
|
||||
$ parallel --nonall --slf nodes.txt 'chmod -v +x ./cluster/stats-server'
|
||||
|
||||
# setup systemd services on all nodes
|
||||
$ parallel --nonall --slf nodes.txt './cluster/stats-server setup'
|
||||
|
||||
# setup systemd service on a single node
|
||||
$ ssh root@<node>
|
||||
$ cd cluster
|
||||
$ ./stats-server setup
|
||||
|
||||
# trying out the new binary using netcat
|
||||
$ time ( echo '<command>' | nc <node> 12345 )
|
||||
|
||||
# stop and disable the systemd service on all nodes
|
||||
$ parallel --nonall --slf nodes.txt 'systemctl disable --now stats-server.service'
|
||||
```
|
||||
|
||||
Replace "your-username" and "your-repo" with your GitHub username and repository name. Adjust "/path/to/destination/" to the desired destination on the target machines.
|
||||
## Credits
|
||||
|
||||
Feel free to explore and customize the server to suit your needs. If you encounter any issues or have suggestions for improvements, please open an issue on this repository. Contributions are welcome!
|
||||
-->
|
||||
- The shell code for the commands was originally made by [@BachoSeven](https://github.com/bachoseven)
|
||||
|
||||
<https://git.phc.dm.unipi.it/phc/cluster-dashboard/src/branch/main/backend/scripts>
|
||||
|
||||
- Most of the code was initially made with the help of ChatGPT in this conversation
|
||||
|
||||
<https://chat.openai.com/share/40247aa0-76e9-4d9a-b0fa-356a5f51f208>
|
||||
@@ -25,12 +25,54 @@ ExecStart=/usr/local/bin/stats-server serve
|
||||
WantedBy=default.target
|
||||
`)
|
||||
|
||||
// cmdStrHelper removes indentation and newlines from a shell command string for
|
||||
// better readability.
|
||||
//
|
||||
// Precisely, it first trims the string, then splits it by newlines, then trims
|
||||
// each line, then joins them back with a space.
|
||||
func cmdStrHelper(command string) string {
|
||||
lines := strings.Split(strings.TrimSpace(command), "\n")
|
||||
for i, line := range lines {
|
||||
lines[i] = strings.TrimSpace(line)
|
||||
}
|
||||
|
||||
return strings.Join(lines, " ")
|
||||
}
|
||||
|
||||
// commands is a map of commands that can be run on the server, originally made
|
||||
// by @BachoSeven
|
||||
var commands = map[string]string{
|
||||
"cpu": `top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | sed "s/^/100 - /" | bc`,
|
||||
"memory": `free -m | awk '/Mem/{print $3 " " $2}'`,
|
||||
"network": `cat /sys/class/net/[e]*/statistics/{r,t}x_bytes`,
|
||||
"storage": `df -Ph | grep mmcblk0p5 | awk '{print $2 " " $3}' | sed 's/G//g'`,
|
||||
"uptime": `cut -f1 -d. /proc/uptime`,
|
||||
"cpu": cmdStrHelper(`
|
||||
top -bn1
|
||||
| grep "Cpu(s)"
|
||||
| sed "s/.*, *\([0-9.]*\)%* id.*/\1/"
|
||||
| sed "s/^/100 - /"
|
||||
| bc
|
||||
`),
|
||||
"memory": cmdStrHelper(`
|
||||
free -m
|
||||
| awk '/Mem/{print $3 " " $2}'
|
||||
`),
|
||||
"network": cmdStrHelper(`
|
||||
cat /sys/class/net/[e]*/statistics/{r,t}x_bytes
|
||||
`),
|
||||
"storage": cmdStrHelper(`
|
||||
df -Ph
|
||||
| grep mmcblk0p5
|
||||
| awk '{print $2 " " $3}'
|
||||
| sed 's/G//g'
|
||||
`),
|
||||
"uptime": cmdStrHelper(`
|
||||
cut -f1 -d. /proc/uptime
|
||||
`),
|
||||
"temperature": cmdStrHelper(`
|
||||
paste
|
||||
<(cat /sys/class/thermal/thermal_zone*/type)
|
||||
<(awk
|
||||
'{printf "%.3f\n", $1/1000}'
|
||||
/sys/class/thermal/thermal_zone*/temp
|
||||
)
|
||||
`),
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -78,22 +120,25 @@ func handleConnection(conn net.Conn) {
|
||||
|
||||
func runCommand(args ...string) error {
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("%w: %s", err, output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupSystemdService() error {
|
||||
// copy itself to /usr/bin
|
||||
if _, err := os.Stat("/usr/local/bin/stats-server"); err == nil {
|
||||
log.Println("binary already exists in /usr/local/bin, updating...")
|
||||
}
|
||||
|
||||
log.Println("copying itself to /usr/local/bin/stats-server")
|
||||
if err := runCommand("cp", os.Args[0], "/usr/local/bin/stats-server"); err != nil {
|
||||
if err := runCommand("cp", "-f", os.Args[0], "/usr/local/bin/stats-server"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write systemd service unit
|
||||
log.Println("generating /etc/systemd/system/stats-server.service")
|
||||
log.Println("writing unit to /etc/systemd/system/stats-server.service")
|
||||
if err := os.WriteFile(
|
||||
"/etc/systemd/system/stats-server.service",
|
||||
[]byte(systemdServiceUnit),
|
||||
@@ -102,15 +147,18 @@ func setupSystemdService() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// reload systemd
|
||||
log.Println("running systemctl daemon-reload")
|
||||
if err := runCommand("systemctl", "daemon-reload"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// enable and start the service
|
||||
log.Println("running systemctl enable --now stats-server.service")
|
||||
if err := runCommand("systemctl", "enable", "--now", "stats-server.service"); err != nil {
|
||||
log.Println("running systemctl enable stats-server.service")
|
||||
if err := runCommand("systemctl", "enable", "stats-server.service"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("running systemctl (re)start stats-server.service")
|
||||
if err := runCommand("systemctl", "restart", "stats-server.service"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -142,11 +190,29 @@ func startTCPServer() error {
|
||||
}
|
||||
}
|
||||
|
||||
func showHelp() {
|
||||
fmt.Println("usage: stats-server [setup|serve]")
|
||||
fmt.Println(" setup: setup systemd service")
|
||||
fmt.Println(" serve: start tcp server")
|
||||
var helpText = strings.TrimSpace(`
|
||||
usage: stats-server [setup|serve]
|
||||
|
||||
This is a simple tcp server that returns system stats, it can be run as a
|
||||
systemd service or as a standalone server.
|
||||
|
||||
protocol commands:
|
||||
cpu returns cpu usage
|
||||
memory returns memory usage
|
||||
network returns network usage
|
||||
storage returns storage usage
|
||||
uptime returns uptime
|
||||
|
||||
subcommands:
|
||||
setup auto-install and setup systemd service
|
||||
serve start tcp server
|
||||
|
||||
config, environment variables:
|
||||
HOST tcp host to bind to (default: :12345)
|
||||
`)
|
||||
|
||||
func showHelp() {
|
||||
fmt.Println(helpText)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -157,17 +223,21 @@ func main() {
|
||||
|
||||
switch os.Args[1] {
|
||||
case "setup":
|
||||
log.Println("starting setup")
|
||||
if err := setupSystemdService(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("setup complete")
|
||||
log.Println("setup completed")
|
||||
|
||||
case "serve":
|
||||
log.Println("starting server")
|
||||
if err := startTCPServer(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("server exited")
|
||||
|
||||
default:
|
||||
showHelp()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user