banyand/observability/system.go (121 lines of code) (raw):
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package observability
import (
"encoding/json"
"net/http"
"os"
"sync"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/mem"
"github.com/apache/skywalking-banyandb/pkg/logger"
)
var systemInfoInstance *SystemInfo
func init() {
systemInfoInstance = &SystemInfo{
Addresses: make(map[string]string),
DiskUsages: make(map[string]*DiskUsage),
}
}
// UpdateAddress updates the address of the given name.
func UpdateAddress(name, address string) {
systemInfoInstance.Lock()
defer systemInfoInstance.Unlock()
systemInfoInstance.Addresses[name] = address
}
func getAddresses() map[string]string {
systemInfoInstance.RLock()
defer systemInfoInstance.RUnlock()
return systemInfoInstance.Addresses
}
// UpdatePath updates a path to monitoring its disk usage.
func UpdatePath(path string) {
systemInfoInstance.Lock()
defer systemInfoInstance.Unlock()
systemInfoInstance.DiskUsages[path] = nil
}
func getPath() map[string]*DiskUsage {
systemInfoInstance.RLock()
defer systemInfoInstance.RUnlock()
return systemInfoInstance.DiskUsages
}
// SystemInfo represents the system information of a node.
type SystemInfo struct {
Addresses map[string]string `json:"addresses"`
DiskUsages map[string]*DiskUsage `json:"disk_usages"`
NodeID string `json:"node_id"`
Hostname string `json:"hostname"`
Roles []string `json:"roles"`
Uptime uint64 `json:"uptime"`
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage float64 `json:"memory_usage"`
sync.RWMutex
}
// DiskUsage represents the disk usage for a given path.
type DiskUsage struct {
Capacity uint64 `json:"capacity"`
Used uint64 `json:"used"`
}
// ErrorResponse represents the error response.
type ErrorResponse struct {
Message string `json:"message"`
OriginalError string `json:"original_error,omitempty"`
}
func init() {
mux.HandleFunc("/system", systemInfoHandler)
}
func systemInfoHandler(w http.ResponseWriter, _ *http.Request) {
hostname, _ := os.Hostname()
uptime, _ := getUptime()
cpuUsage, _ := getCPUUsage()
memoryUsage, _ := getMemoryUsage()
systemInfo := &SystemInfo{
NodeID: "1",
Roles: []string{"meta", "ingest", "query", "data"},
Hostname: hostname,
Uptime: uptime,
CPUUsage: cpuUsage,
MemoryUsage: memoryUsage,
Addresses: getAddresses(),
DiskUsages: make(map[string]*DiskUsage),
}
for k := range getPath() {
usage, _ := getDiskUsage(k)
systemInfo.DiskUsages[k] = &usage
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode([]*SystemInfo{systemInfo}); err != nil {
w.WriteHeader(http.StatusInternalServerError)
errorResponse := &ErrorResponse{
Message: "Error encoding JSON response",
OriginalError: err.Error(),
}
if err := json.NewEncoder(w).Encode(errorResponse); err != nil {
logger.GetLogger().Error().Err(err).Msg("Error encoding JSON response")
}
}
}
func getUptime() (uint64, error) {
uptime, err := host.Uptime()
if err != nil {
return 0, err
}
return uptime, nil
}
func getCPUUsage() (float64, error) {
percentages, err := cpu.Percent(0, false)
if err != nil {
return 0, err
}
return percentages[0], nil
}
func getMemoryUsage() (float64, error) {
vm, err := mem.VirtualMemory()
if err != nil {
return 0, err
}
return vm.UsedPercent, nil
}
func getDiskUsage(path string) (DiskUsage, error) {
usage, err := disk.Usage(path)
if err != nil {
return DiskUsage{}, err
}
return DiskUsage{Capacity: usage.Total, Used: usage.Used}, nil
}