Skip to content

Commit

Permalink
feat: introduce runnable metrics (#2)
Browse files Browse the repository at this point in the history
* feat: update spec

* feat: send empty value

* feat: implement values for service self

* chore: install modules in ci

* chore: adjust ci

* chore: adjust path in ci

* chore: adjust docker image tag

* chore: build in ci with jar

* feat: add uptime
  • Loading branch information
c100k authored Mar 24, 2024
1 parent b767ccf commit ff4fb8b
Show file tree
Hide file tree
Showing 11 changed files with 446 additions and 10 deletions.
32 changes: 32 additions & 0 deletions data/servers.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
"fqdn": "server01.mycompany.com",
"id": "123",
"ipv4": "192.168.0.26",
"metric": [
{
"label": "CPU",
"ratio": null,
"thresholds": [65, 85],
"unit": "%",
"value": 28
},
{
"label": "RAM",
"ratio": 0.125,
"thresholds": [3000, 3800],
"unit": "MB",
"value": 512
}
],
"name": "server01",
"scopes": {
"geo": {
Expand All @@ -28,6 +44,22 @@
"fqdn": "server02.mycompany.com",
"id": "456",
"ipv4": "192.168.0.27",
"metrics": [
{
"label": "CPU",
"ratio": null,
"thresholds": [65, 85],
"unit": "%",
"value": 82
},
{
"label": "RAM",
"ratio": 0.25,
"thresholds": [3000, 3800],
"unit": "GB",
"value": 1
}
],
"name": "server02",
"scopes": {
"geo": {
Expand Down
8 changes: 7 additions & 1 deletion impl/http-server-go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ module http-server-go
go 1.22.0

require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gorilla/handlers v1.5.2 // direct
github.com/gorilla/mux v1.8.1 // direct
)

require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/mackerelio/go-osstat v0.2.4 // direct
)

require golang.org/x/sys v0.18.0 // indirect
4 changes: 4 additions & 0 deletions impl/http-server-go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs=
github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2 changes: 2 additions & 0 deletions impl/http-server-go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func main() {
panic(fmt.Sprintf("Invalid serviceImpl : %s", config.serviceImpl))
}

logger.Info(fmt.Sprintf("Using serviceImpl : %s", config.serviceImpl))

router := mux.NewRouter()

router.Use(logMiddleware(config, logger))
Expand Down
106 changes: 106 additions & 0 deletions impl/http-server-go/service_self.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ import (
"log/slog"
"openapi"
"os"
"time"

"github.com/mackerelio/go-osstat/cpu"
"github.com/mackerelio/go-osstat/memory"
"github.com/mackerelio/go-osstat/uptime"
)

type ServiceSelf struct {
config *Config
logger *slog.Logger
}

const CPU_METRIC_LABEL = "CPU"
const CPU_METRIC_UNIT = "%"
const MEMORY_METRIC_LABEL = "RAM"
const MEMORY_METRIC_UNIT = "MB"
const MEMORY_METRIC_UNIT_AS_BYTES = 10e6
const THRESHOLD_WARNING = 0.75
const THRESHOLD_DANGER = 0.85
const UPTIME_METRIC_LABEL = "Uptime"

func (service ServiceSelf) list(params *openapi.ListRunnablesQueryParams) (*openapi.ListResRunnable, *ServiceError) {
config := service.config

Expand All @@ -22,12 +36,30 @@ func (service ServiceSelf) list(params *openapi.ListRunnablesQueryParams) (*open
}
}

metrics := []openapi.RunnableMetric{}

cpuValue, cpuTotal, err := getCPUStats()
if err == nil {
metrics = append(metrics, *buildCPUMetric(*cpuValue, *cpuTotal))
}

memory, err := memory.Get()
if err == nil {
metrics = append(metrics, *buildMemoryMetric(memory.Used, memory.Total))
}

uptime, err := uptime.Get()
if err == nil {
metrics = append(metrics, *buildUptimeMetric(uptime))
}

items := []openapi.Runnable{
*openapi.NewRunnable(
*openapi.NewNullableString(&config.runnableFlavor),
*openapi.NewNullableString(&config.runnableFQDN),
config.runnableId,
*openapi.NewNullableString(&config.runnableIPv4),
metrics,
nameFromHostname(config),
*openapi.NewRunnableScopes(
*openapi.NewNullableRunnableScope(
Expand Down Expand Up @@ -93,13 +125,87 @@ func (service ServiceSelf) stop(id string) (*openapi.RunnableOperationRes, *Serv
return openapi.NewRunnableOperationRes(*openapi.NewNullableString(nil)), nil
}

func buildCPUMetric(used uint64, total uint64) *openapi.RunnableMetric {
value := roundToCloser(float64(used) / float64(total) * 100)

metric := openapi.NewRunnableMetric(
*openapi.NewNullableString(ptr(CPU_METRIC_LABEL)),
*openapi.NewNullableFloat64(nil),
[]float64{THRESHOLD_WARNING, THRESHOLD_DANGER},
*openapi.NewNullableString(ptr(CPU_METRIC_UNIT)),
*openapi.NewNullableFloat64(&value),
)

return metric
}

func buildMemoryMetric(used uint64, total uint64) *openapi.RunnableMetric {
valueInBytes := float64(used)

ratio := roundToCloser(valueInBytes / float64(total))
warning := roundToCloser(THRESHOLD_WARNING * float64(total) / MEMORY_METRIC_UNIT_AS_BYTES)
danger := roundToCloser(THRESHOLD_DANGER * float64(total) / MEMORY_METRIC_UNIT_AS_BYTES)
value := roundToCloser(valueInBytes / MEMORY_METRIC_UNIT_AS_BYTES)

metric := openapi.NewRunnableMetric(
*openapi.NewNullableString(ptr(MEMORY_METRIC_LABEL)),
*openapi.NewNullableFloat64(&ratio),
[]float64{warning, danger},
*openapi.NewNullableString(ptr(MEMORY_METRIC_UNIT)),
*openapi.NewNullableFloat64(&value),
)

return metric
}

func buildUptimeMetric(uptime time.Duration) *openapi.RunnableMetric {
value := uptime.Hours()
unit := "h"
if value < 1.0 {
value = uptime.Minutes()
unit = "min"
if value < 1.0 {
value = uptime.Seconds()
unit = "s"
}
}
value = roundToCloser(value)

metric := openapi.NewRunnableMetric(
*openapi.NewNullableString(ptr(UPTIME_METRIC_LABEL)),
*openapi.NewNullableFloat64(nil),
[]float64{},
*openapi.NewNullableString(&unit),
*openapi.NewNullableFloat64(&value),
)

return metric
}

func checkThatRunnableExists(config *Config, id string) *ServiceError {
if id != config.runnableId {
return &ServiceError{HttpStatus: 404, Message: Err404Runnable}
}
return nil
}

func getCPUStats() (*uint64, *uint64, error) {
before, err := cpu.Get()
if err != nil {
return nil, nil, err
}
time.Sleep(time.Duration(1) * time.Second)
after, err := cpu.Get()
if err != nil {
return nil, nil, err
}

total := after.Total - before.Total
value := after.User - before.User

return &value, &total, nil
}

func nameFromHostname(config *Config) string {
name := config.runnableNameFallback
hostname, err := os.Hostname()
Expand Down
89 changes: 89 additions & 0 deletions impl/http-server-go/service_self_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"testing"
"time"
)

func TestBuildCPUMetric(t *testing.T) {
// Given
used := uint64(23)
total := uint64(1201)

// When
metric := buildCPUMetric(used, total)
value := *metric.Value.Get()

// Then
expectedValue := 1.92
if value != expectedValue {
t.Fatalf("Expected value to be %f, actual %f", expectedValue, value)
}
}

func TestBuildMemoryMetric(t *testing.T) {
// Given
used := uint64(14333321216)
total := uint64(17176596480)

// When
metric := buildMemoryMetric(used, total)
ratio := *metric.Ratio.Get()
thresholds := metric.Thresholds
value := *metric.Value.Get()

// Then
expectedRatio := 0.83
if ratio != expectedRatio {
t.Fatalf("Expected ratio to be %f, actual %f", expectedRatio, ratio)
}
expectedThresholds := []float64{1288.24, 1460.01}
if thresholds[0] != expectedThresholds[0] {
t.Fatalf("Expected thresholds[0] to be %f, actual %f", expectedThresholds[0], thresholds[0])
}
if thresholds[1] != expectedThresholds[1] {
t.Fatalf("Expected thresholds[1] to be %f, actual %f", expectedThresholds[1], thresholds[1])
}
expectedValue := 1433.33
if value != expectedValue {
t.Fatalf("Expected value to be %f, actual %f", expectedValue, value)
}
}

func TestBuildUptimeMetric(t *testing.T) {
// Given
uptime, _ := time.ParseDuration("0h34m")

// When
metric := buildUptimeMetric(uptime)
value := *metric.Value.Get()
unit := *metric.Unit.Get()

// Then
expectedValue := 34.0
if value != expectedValue {
t.Fatalf("Expected value to be %f, actual %f", expectedValue, value)
}
expectedUnit := "min"
if unit != expectedUnit {
t.Fatalf("Expected unit to be %s, actual %s", expectedUnit, unit)
}

// Given
uptime, _ = time.ParseDuration("2h34m")

// When
metric = buildUptimeMetric(uptime)
value = *metric.Value.Get()
unit = *metric.Unit.Get()

// Then
expectedValue = 2.57
if value != expectedValue {
t.Fatalf("Expected value to be %f, actual %f", expectedValue, value)
}
expectedUnit = "h"
if unit != expectedUnit {
t.Fatalf("Expected unit to be %s, actual %s", expectedUnit, unit)
}
}
13 changes: 12 additions & 1 deletion impl/http-server-go/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package main

import "strconv"
import (
"math"
"strconv"
)

func parseInt(raw *string) *int32 {
if raw == nil {
Expand All @@ -16,3 +19,11 @@ func parseInt(raw *string) *int32 {

return &asInt32
}

func ptr[T float64 | string](v T) *T {
return &v
}

func roundToCloser(v float64) float64 {
return math.Round(v*100) / 100
}
8 changes: 8 additions & 0 deletions impl/http-server-go/vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ github.com/gorilla/handlers
# github.com/gorilla/mux v1.8.1
## explicit; go 1.20
github.com/gorilla/mux
# github.com/mackerelio/go-osstat v0.2.4
## explicit; go 1.18
github.com/mackerelio/go-osstat/cpu
github.com/mackerelio/go-osstat/memory
github.com/mackerelio/go-osstat/uptime
# golang.org/x/sys v0.18.0
## explicit; go 1.18
golang.org/x/sys/unix
Loading

0 comments on commit ff4fb8b

Please sign in to comment.