Skip to content

Commit

Permalink
Merge pull request #78 from leosunmo/heap-stats
Browse files Browse the repository at this point in the history
Heap stats
  • Loading branch information
Nick Canzoneri authored Sep 30, 2020
2 parents dc3a2fd + 986fed2 commit bbf7d16
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 0 deletions.
96 changes: 96 additions & 0 deletions es.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ type Node struct {
DiskPercent string
}

// Holds a subset of information from the _nodes/stats endpoint: https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html
type NodeStats struct {
Name string
Role string
JVMStats NodeJVM
}

// Holds information about an Elasticsearch node's JVM settings. From _nodes/stats/jvm: https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-stats.html
type NodeJVM struct {
HeapUsedBytes int `json:"heap_used_in_bytes"`
HeapUsedPercentage int `json:"heap_used_percent"`
HeapMaxBytes int `json:"heap_max_in_bytes"`
NonHeapUsedBytes int `json:"non_heap_used_in_bytes"`
NonHeapCommittedBytes int `json:"non_heap_committed_in_bytes"`
}

// DiskAllocation holds disk allocation information per node, based on _cat/allocationAPI: https://www.elastic.co/guide/en/elasticsearch/reference/5.6/cat-allocation.html
type DiskAllocation struct {
Name string `json:"name"`
Expand Down Expand Up @@ -537,6 +553,86 @@ func (c *Client) GetNodeAllocations() ([]Node, error) {
return nodes, nil
}

//Get all the nodes' JVM Heap statistics.
//
//Use case: You want to see how much heap each node is using and their max heap size.

func (c *Client) GetNodeJVMStats() ([]NodeStats, error) {

// NodeStats is not the top level of "nodes" as the individual node name
// is the key of each node. Eg. "nodes.H1iBOLqqToyT8CHF9C0W0w.name = es-node-1".
// This is tricky to unmarshal to struct, so let gjson deal with it.

var nodesStats []NodeStats
// Get node stats/jvm
agent := c.buildGetRequest("_nodes/stats/jvm")
bytes, err := handleErrWithBytes(agent)
if err != nil {
return nil, err
}

nodesRes := gjson.GetBytes(bytes, "nodes")

var itErr error
// Iterate over each node.
nodesRes.ForEach(func(key, value gjson.Result) bool {
var jvmStats NodeJVM
memString := value.Get("jvm.mem").String()
err = json.Unmarshal([]byte(memString), &jvmStats)
if err != nil {
itErr = fmt.Errorf("failed to unmarshal mem stats: %w", err)
return false
}

// Let's grab the nodes role(s). Different format depending on version
var role string

if value.Get("attributes.master").Exists() {
// Probably Elasticsearch 1.7
masterRole := value.Get("attributes.master").String()
dataRole := value.Get("attributes.data").String()

if dataRole != "false" {
role = "d"
}
if masterRole == "true" {
role = "M" + role
}
}

if value.Get("roles").Exists() {
// Probably Elasticsearch 5+

// Elasticsearch 5,6 and 7 has quite a few roles, let's collect them
roleRes := value.Get("roles").Array()
for _, res := range roleRes {
sr := res.String()
if sr == "master" {
role = "M" + role
continue
}
role = role + sr[:1]
}
}
nodeStat := NodeStats{
Name: value.Get("name").String(),
Role: role,
JVMStats: jvmStats,
}

nodesStats = append(nodesStats, nodeStat)

return true
})

if itErr != nil {
return nil, itErr
}

return nodesStats, nil

}

//Get all the indices in the cluster.
//
//Use case: You want to see some basic info on all the indices of the cluster.
Expand Down
68 changes: 68 additions & 0 deletions pkg/cli/heap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cli

import (
"fmt"
"os"
"sort"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(cmdNodeHeap)
}

var cmdNodeHeap = &cobra.Command{
Use: "heap",
Short: "Display the node heap stats.",
Long: `Show node heap stats and settings.`,
Run: func(cmd *cobra.Command, args []string) {

v := getClient()

nodeStats, err := v.GetNodeJVMStats()

if err != nil {
fmt.Printf("Error getting node JVM stats: %s\n", err)
os.Exit(1)
}

var header []string
var rows [][]string
header = []string{"Name", "Role", "Heap Max", "Heap Used", "Heap %", "Non-Heap Committed", "Non-Heap Used"}
rows = [][]string{}
sort.Slice(nodeStats, func(i, j int) bool {
return nodeStats[i].Name < nodeStats[j].Name
})
for _, node := range nodeStats {
row := []string{
node.Name,
node.Role,
byteCountSI(int64(node.JVMStats.HeapMaxBytes)),
byteCountSI(int64(node.JVMStats.HeapUsedBytes)),
fmt.Sprintf("%d %%", node.JVMStats.HeapUsedPercentage),
byteCountSI(int64(node.JVMStats.NonHeapCommittedBytes)),
byteCountSI(int64(node.JVMStats.NonHeapUsedBytes)),
}

rows = append(rows, row)
}

table := renderTable(rows, header)
fmt.Println(table)
},
}

func byteCountSI(b int64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB",
float64(b)/float64(div), "kMGTPE"[exp])
}

0 comments on commit bbf7d16

Please sign in to comment.