mirror of
https://codeberg.org/scip/esctl.git
synced 2026-08-24 14:14:23 +02:00
207 lines
5.1 KiB
Go
207 lines
5.1 KiB
Go
/*
|
|
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"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"codeberg.org/scip/esctl/pkg/cfg"
|
|
"codeberg.org/scip/esctl/pkg/printer"
|
|
"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 {
|
|
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 {
|
|
table.Entries[idx] = []any{
|
|
*node.Name,
|
|
*node.Ip,
|
|
*node.Load1M,
|
|
*node.Load5M,
|
|
*node.Load15M,
|
|
node.RamPercent,
|
|
node.HeapPercent,
|
|
}
|
|
}
|
|
|
|
table.Sort()
|
|
|
|
if err := table.Print(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
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"]
|
|
|
|
table.Entries = [][]any{
|
|
{"Id", id},
|
|
{"Name", nodename},
|
|
{"Kubernetes node", k8snode},
|
|
{"Ip address", info.Ip},
|
|
{"Node rank", *stat.AdaptiveSelection[id].Rank},
|
|
{"JVM", info.Jvm.VmName + " " + info.Jvm.Version},
|
|
{"JVM Started", time.UnixMilli(info.Jvm.StartTimeInMillis)},
|
|
{"OS", info.Os.PrettyName + " " + info.Os.Version},
|
|
{"Node roles", roles},
|
|
{"Node version", info.Version},
|
|
{"HTTP clients", *stat.Http.CurrentOpen},
|
|
{"CPUs", *info.Os.AllocatedProcessors},
|
|
{"Load 15m/5m/1m", fmt.Sprintf("%.2f/%.2f/%.2f",
|
|
stat.Os.Cpu.LoadAverage["15m"],
|
|
stat.Os.Cpu.LoadAverage["5m"],
|
|
stat.Os.Cpu.LoadAverage["1m"],
|
|
)},
|
|
{"Open FD's", *stat.Process.OpenFileDescriptors},
|
|
{"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]
|
|
table.Entries = append(table.Entries, [][]any{
|
|
{"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 {
|
|
return fmt.Errorf("failed to get node stats: %w", esErrorString(err))
|
|
}
|
|
|
|
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,
|
|
strconv.FormatInt(*client.Id, 10),
|
|
time.UnixMilli(*client.LastRequestTimeMillis).String(),
|
|
*client.RemoteAddress,
|
|
uri,
|
|
)
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
table.Sort()
|
|
|
|
return table.Print()
|
|
}
|