Files
esctl/pkg/es/node.go

204 lines
5.1 KiB
Go
Raw Normal View History

/*
Copyright © 2026 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package es
import (
"context"
"fmt"
"log/slog"
2026-07-01 13:13:16 +02:00
"strings"
"time"
"codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/printer"
2026-07-01 13:13:16 +02:00
"github.com/dustin/go-humanize"
)
func NodeList(conf *cfg.Config) error {
// get nodes
nodes, err := conf.DefaultCluster.ES().Cat.Nodes().Do(context.Background())
if err != nil {
2026-07-07 09:34:36 +02:00
return fmt.Errorf("failed to get nodes: %w", esErrorString(err))
}
slog.Debug("ES result", "nodes", nodes)
table := printer.NewTable(conf, 7, len(nodes))
table.Addheaders("name", "ip", "load1m", "load5m", "load15m", "ram %", "heap %")
for idx, node := range nodes {
2026-07-07 07:29:03 +02:00
table.Entries[idx] = []any{
*node.Name,
*node.Ip,
*node.Load1M,
*node.Load5M,
*node.Load15M,
2026-07-07 07:29:03 +02:00
node.RamPercent,
node.HeapPercent,
}
}
table.Sort()
if err := table.Print(); err != nil {
return err
}
return nil
}
2026-07-01 13:13:16 +02:00
func NodeNames(conf *cfg.Config) ([]string, error) {
nodes, err := conf.DefaultCluster.ES().Cat.Nodes().
Do(context.Background())
if err != nil {
2026-07-07 09:34:36 +02:00
return nil, fmt.Errorf("failed to get nodes: %w", esErrorString(err))
2026-07-01 13:13:16 +02:00
}
slog.Debug("ES result", "nodes", nodes)
nodelist := make([]string, len(nodes))
for idx, node := range nodes {
nodelist[idx] = *node.Name
}
return nodelist, err
}
// FIXME: adding the settings metric leads to json unmarshall error:
// https://github.com/elastic/go-elasticsearch/issues/1524
func NodeShow(conf *cfg.Config, nodename string) error {
res, err := conf.DefaultCluster.ES().Nodes.Info().
NodeId(nodename).
Metric("os, jvm, thread_pool, remote_cluster_server").
Do(context.Background())
if err != nil {
2026-07-07 09:34:36 +02:00
return fmt.Errorf("failed to get node info: %w", esErrorString(err))
2026-07-01 13:13:16 +02:00
}
slog.Debug("ES result", "node", res)
stats, err := conf.DefaultCluster.ES().Nodes.Stats().
NodeId(nodename).
Do(context.Background())
if err != nil {
2026-07-07 09:34:36 +02:00
return fmt.Errorf("failed to get node stats: %w", esErrorString(err))
2026-07-01 13:13:16 +02:00
}
slog.Debug("ES result", "stat", stats)
for id, info := range res.Nodes {
stat := stats.Nodes[id]
table := printer.NewTable(conf, 2, 0)
table.Addheaders(nodename+" property", "value")
roles := make([]string, len(info.Roles))
for idx, role := range info.Roles {
roles[idx] = role.Name
}
k8snode := info.Attributes["k8s_node_name"]
2026-07-07 07:29:03 +02:00
table.Entries = [][]any{
2026-07-01 13:13:16 +02:00
{"Id", id},
{"Name", nodename},
{"Kubernetes node", k8snode},
{"Ip address", info.Ip},
{"Node rank", *stat.AdaptiveSelection[id].Rank},
{"JVM", info.Jvm.VmName + " " + info.Jvm.Version},
2026-07-07 07:29:03 +02:00
{"JVM Started", time.UnixMilli(info.Jvm.StartTimeInMillis)},
2026-07-01 13:13:16 +02:00
{"OS", info.Os.PrettyName + " " + info.Os.Version},
2026-07-07 07:29:03 +02:00
{"Node roles", roles},
2026-07-01 13:13:16 +02:00
{"Node version", info.Version},
2026-07-07 07:29:03 +02:00
{"HTTP clients", *stat.Http.CurrentOpen},
{"CPUs", *info.Os.AllocatedProcessors},
2026-07-01 13:13:16 +02:00
{"Load 15m/5m/1m", fmt.Sprintf("%.2f/%.2f/%.2f",
stat.Os.Cpu.LoadAverage["15m"],
stat.Os.Cpu.LoadAverage["5m"],
stat.Os.Cpu.LoadAverage["1m"],
)},
2026-07-07 07:29:03 +02:00
{"Open FD's", *stat.Process.OpenFileDescriptors},
2026-07-01 13:13:16 +02:00
{"Response time avg", fmt.Sprintf("%dns", *stat.AdaptiveSelection[id].AvgResponseTimeNs)},
{"Memory usage (used/avail)",
humanize.Bytes(uint64(*stat.Os.Mem.UsedInBytes)) + " / " + humanize.Bytes(uint64(*stat.Os.Mem.TotalInBytes))},
}
if len(stat.Fs.Data) > 0 {
fs := stat.Fs.Data[0]
2026-07-07 07:29:03 +02:00
table.Entries = append(table.Entries, [][]any{
2026-07-01 13:13:16 +02:00
{"Storage usage (used/avail)",
humanize.Bytes(uint64(*fs.AvailableInBytes)) + " / " + humanize.Bytes(uint64(*fs.TotalInBytes))},
{"Storage mount", *fs.Mount},
}...)
}
if err := table.Print(); err != nil {
return err
}
}
return nil
}
func NodeClients(conf *cfg.Config, nodename string) error {
stats, err := conf.DefaultCluster.ES().Nodes.Stats().
NodeId(nodename).
Do(context.Background())
if err != nil {
2026-07-07 09:34:36 +02:00
return fmt.Errorf("failed to get node stats: %w", esErrorString(err))
2026-07-01 13:13:16 +02:00
}
slog.Debug("ES result", "stat", stats)
table := printer.NewTable(conf, 5, 0)
table.Addheaders("agent", "id", "when", "from host", "url")
for _, stat := range stats.Nodes {
for _, client := range stat.Http.Clients {
if client.ClosedTimeMillis == nil {
agent := ""
if client.Agent != nil {
agent = *client.Agent
}
uri := ""
if client.LastUri != nil {
uri = *client.LastUri
if !conf.All {
parts := strings.Split(uri, "?")
uri = parts[0]
}
}
table.AddRow(
agent,
fmt.Sprintf("%d", *client.Id),
time.UnixMilli(*client.LastRequestTimeMillis).String(),
*client.RemoteAddress,
uri,
)
}
}
break
}
table.Sort()
return table.Print()
}