Files
esctl/pkg/es/node.go

232 lines
6.2 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-07 23:46:43 +02:00
"strconv"
2026-07-01 13:13:16 +02:00
"strings"
"time"
"codeberg.org/scip/esctl/pkg/cfg"
"codeberg.org/scip/esctl/pkg/printer"
)
func NodeList(conf *cfg.Config) error {
// get nodes
nodes, err := conf.DefaultCluster.ES().Cat.Nodes().Do(context.Background())
if err != nil {
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()
2026-07-07 23:46:43 +02:00
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 {
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 {
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 {
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-08 13:56:55 +02:00
// FIXME: not implemented upstream
2026-07-08 13:44:45 +02:00
// see: https://github.com/elastic/go-elasticsearch/issues/1526
2026-07-08 15:02:17 +02:00
// {"Allocated shards", stat.Allocations.XXX},
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-08 11:47:07 +02:00
{"HTTP sesssions current/total", fmt.Sprintf("%d/%d",
*stat.Http.CurrentOpen,
*stat.Http.TotalOpened,
)},
{"Traffic rx/tx",
2026-07-08 13:44:45 +02:00
printer.ByteString(*stat.Transport.RxSizeInBytes) + " / " + printer.ByteString(*stat.Transport.TxSizeInBytes)},
2026-07-08 11:47:07 +02:00
{"Response time avg", time.Duration(*stat.AdaptiveSelection[id].AvgResponseTimeNs)},
2026-07-01 13:13:16 +02:00
{"Memory usage (used/avail)",
2026-07-08 13:44:45 +02:00
printer.ByteString(*stat.Os.Mem.UsedInBytes) + " / " + printer.ByteString(*stat.Os.Mem.TotalInBytes)},
2026-07-08 11:47:07 +02:00
{"Search queries current/total", fmt.Sprintf("%d/%d",
stat.Indices.Search.QueryCurrent,
stat.Indices.Search.QueryTotal,
)},
{"Search efficiency", stat.Indices.Search.QueryTimeInMillis / stat.Indices.Search.QueryTotal},
{"Docs count", stat.Indices.Docs.Count},
{"Merges current/total", fmt.Sprintf("%d/%d",
stat.Indices.Merges.Current,
stat.Indices.Merges.Total,
)},
{"Merge docs count current/total", fmt.Sprintf("%d/%d",
stat.Indices.Merges.CurrentDocs,
stat.Indices.Merges.TotalDocs,
)},
{"Merge size current/total", fmt.Sprintf("%s/%s",
2026-07-08 13:44:45 +02:00
printer.ByteString(stat.Indices.Merges.CurrentSizeInBytes),
printer.ByteString(stat.Indices.Merges.TotalSizeInBytes),
2026-07-08 11:47:07 +02:00
)},
{"CircuitBreaker trip count", *stat.Breakers["fielddata"].Tripped},
2026-07-01 13:13:16 +02:00
}
if len(stat.Fs.Data) > 0 {
fs := stat.Fs.Data[0]
2026-07-08 11:47:07 +02:00
table.AddRow("Storage usage (used/avail)",
2026-07-08 13:44:45 +02:00
printer.ByteString(*fs.AvailableInBytes)+" / "+printer.ByteString(*fs.TotalInBytes))
2026-07-08 11:47:07 +02:00
table.AddRow("Storage mount", *fs.Mount)
2026-07-01 13:13:16 +02:00
}
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 {
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,
2026-07-07 23:46:43 +02:00
strconv.FormatInt(*client.Id, 10),
2026-07-01 13:13:16 +02:00
time.UnixMilli(*client.LastRequestTimeMillis).String(),
*client.RemoteAddress,
uri,
)
}
}
break
}
table.Sort()
2026-07-07 23:46:43 +02:00
2026-07-01 13:13:16 +02:00
return table.Print()
}