Node index finalization

This commit is contained in:
Adam Štrauch 2020-07-21 23:11:56 +02:00
parent e6f8d49cc8
commit c45c27ac38
Signed by: cx
GPG Key ID: 018304FFA8988F8D
7 changed files with 191 additions and 93 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
rosti.db rosti.db
node-api node-api
.history/ .history/
api-node-17.http

22
main.go
View File

@ -9,7 +9,7 @@ import (
"github.com/rosti-cz/node-api/apps" "github.com/rosti-cz/node-api/apps"
"github.com/rosti-cz/node-api/common" "github.com/rosti-cz/node-api/common"
"github.com/rosti-cz/node-api/docker" "github.com/rosti-cz/node-api/docker"
"github.com/rosti-cz/node-api/nodes" "github.com/rosti-cz/node-api/node"
) )
// JSONIndent Indendation of JSON output format // JSONIndent Indendation of JSON output format
@ -18,8 +18,6 @@ const JSONIndent = " "
func main() { func main() {
// Close database at the end // Close database at the end
db := common.GetDBConnection() db := common.GetDBConnection()
// db.AutoMigrate(apps.Label{})
// db.AutoMigrate(apps.App{})
defer db.Close() defer db.Close()
// Templating // Templating
@ -27,11 +25,24 @@ func main() {
// Stats loop // Stats loop
go func() { go func() {
for {
err := gatherContainerStats() err := gatherContainerStats()
if err != nil { if err != nil {
log.Println("LOOP ERROR:", err.Error()) log.Println("LOOP ERROR:", err.Error())
} }
time.Sleep(5 * time.Minute) time.Sleep(5 * time.Minute)
}
}()
// Node stats
go func() {
for {
err := node.Log()
if err != nil {
log.Println("NODE PERFORMANCE LOG ERROR:", err.Error())
}
time.Sleep(5 * time.Minute)
}
}() }()
// API // API
@ -324,7 +335,10 @@ func main() {
// Return info about the node including performance index // Return info about the node including performance index
e.GET("/v1/node", func(c echo.Context) error { e.GET("/v1/node", func(c echo.Context) error {
node := nodes.GetNodeInfo() node, err := node.GetNodeInfo()
if err != nil {
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
}
return c.JSON(http.StatusOK, node) return c.JSON(http.StatusOK, node)
}) })

134
node/load.go Normal file
View File

@ -0,0 +1,134 @@
package node
import (
"github.com/rosti-cz/node-api/common"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
)
const history = 72 * 3600 / 300 // 3 days, one record every five minutes
func init() {
db := common.GetDBConnection()
db.AutoMigrate(PerformanceLog{})
}
// Log creates a record for all important metrics used as
func Log() error {
performanceLog := PerformanceLog{}
// Load
loadStats, err := load.Avg()
if err != nil {
return err
}
performanceLog.Load1 = loadStats.Load1
performanceLog.Load5 = loadStats.Load5
performanceLog.Load15 = loadStats.Load15
// Memory
memoryStat, err := mem.VirtualMemory()
if err != nil {
return err
}
performanceLog.Memory = memoryStat.UsedPercent / 100.0
// Disk space
diskUsage, err := disk.Usage("/srv")
if err != nil {
return err
}
performanceLog.DiskSpaceUsage = diskUsage.UsedPercent / 100.0
// Save
db := common.GetDBConnection()
err = db.Create(&performanceLog).Error
if err != nil {
return err
}
// and clean
// we have to use this stupid approach because DELETE doesn't support ORDER BY and LIMIT
toDeleteLogs := []PerformanceLog{}
err = db.Order("id DESC").Limit("99").Offset(history).Find(&toDeleteLogs).Error
if err != nil {
return err
}
for _, toDeleteLog := range toDeleteLogs {
err = db.Delete(&toDeleteLog).Error
if err != nil {
return err
}
}
return nil
}
// Index returns number from 0 to 1 where 0 means least loaded and 1 maximally loaded.
// It uses history of last 72 hours
func index() (*Node, error) {
node := Node{
Index: 1.0,
}
// Number of CPUs
cpus, err := cpu.Counts(true)
if err != nil {
return &node, err
}
db := common.GetDBConnection()
logs := []PerformanceLog{}
err = db.Find(&logs).Error
if err != nil {
return &node, err
}
var totalLoad1 float64
var totalLoad5 float64
var totalLoad15 float64
var totalDiskSpaceUsage float64
var totalMemory float64
// If there is no record we have to wait until it is, until then the server is "full"
if len(logs) == 0 {
return &node, nil
}
for _, log := range logs {
totalLoad1 += log.Load1
totalLoad5 += log.Load5
totalLoad15 += log.Load15
totalDiskSpaceUsage += log.DiskSpaceUsage
totalMemory += log.Memory
}
node.Load1Index = totalLoad1 / float64(len(logs)) / float64(cpus)
node.Load5Index = totalLoad5 / float64(len(logs)) / float64(cpus)
node.Load15Index = totalLoad15 / float64(len(logs)) / float64(cpus)
node.MemoryIndex = totalMemory / float64(len(logs))
node.DiskSpaceIndex = totalDiskSpaceUsage / float64(len(logs))
var indexes []float64
indexes = append(indexes, node.Load5Index)
indexes = append(indexes, node.MemoryIndex)
indexes = append(indexes, node.DiskSpaceIndex)
finalIndex := float64(0)
for _, index := range indexes {
if index > finalIndex {
finalIndex = index
}
}
node.Index = finalIndex
return &node, nil
}

7
node/main.go Normal file
View File

@ -0,0 +1,7 @@
package node
// GetNodeInfo returns information about this node
func GetNodeInfo() (*Node, error) {
node, err := index()
return node, err
}

27
node/types.go Normal file
View File

@ -0,0 +1,27 @@
package node
import "github.com/jinzhu/gorm"
// Node keeps info about server
type Node struct {
Index float64 `json:"index"` // 0 is empty server, 1 means full server
Load1Index float64 `json:"load1_index"` // 0 is empty server, 1 means resource fully used
Load5Index float64 `json:"load5_index"` // 0 is empty server, 1 means resource fully used
Load15Index float64 `json:"load15_index"` // 0 is empty server, 1 means resource fully used
MemoryIndex float64 `json:"memory_index"` // 0 is empty server, 1 means resource fully used
DiskSpaceIndex float64 `json:"disk_space_index"` // 0 is empty server, 1 means resource fully used
// TODO not implemented
SoldMemory int `json:"-"` // allocated to containers
}
// PerformanceLog keeps track of performance history
type PerformanceLog struct {
gorm.Model
Load1 float64
Load5 float64
Load15 float64
DiskSpaceUsage float64
Memory float64
}

View File

@ -1,66 +0,0 @@
package nodes
import (
"log"
"github.com/jinzhu/gorm"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
)
// PerformanceLog keeps track of performance history
type PerformanceLog struct {
gorm.Model
Load1 float64
Load5 float64
Load15 float64
DiskSpaceUsage float64
Memory float64
}
// Log creates a record for all important metrics used as
func Log() error {
performanceLog := PerformanceLog{}
// Load
loadStats, err := load.Avg()
if err != nil {
return err
}
performanceLog.Load1 = loadStats.Load1
performanceLog.Load5 = loadStats.Load5
performanceLog.Load15 = loadStats.Load15
// Memory
memoryStat, err := mem.VirtualMemory()
if err != nil {
return err
}
performanceLog.Memory = memoryStat.UsedPercent / 100.0
// Disk space
diskUsage, err := disk.Usage("/srv")
if err != nil {
return err
}
performanceLog.DiskSpaceUsage = diskUsage.UsedPercent / 100.0
return nil
}
// Index returns number from 0 to 1 where 0 means least loaded and 1 maximally loaded.
// It uses history of last 72 hours
func Index() (float64, error) {
// Number of CPUs
cpus, err := cpu.Counts(true)
if err != nil {
return 1.0, err
}
log.Println(cpus)
return 1.0, err
}

View File

@ -1,19 +0,0 @@
package nodes
// Node keeps info about server
type Node struct {
OccupationIndex float32 // bigger number bigger load, number over 1 means full server
Memory int
SoldMemory int // allocated to containers
LoadOverDay float32 // average load 5 in last 24 hours
DiskSpaceUsed int // Usage of overall disk space
}
// GetNodeInfo returns information about this node
func GetNodeInfo() *Node {
node := Node{
OccupationIndex: 0,
}
return &node
}