Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Please mark backwards incompatible changes with an exclamation mark at the start

## [Unreleased]

### Added
- The `#nodes` method to the `Elasticsearch::Stats` class. This method gives the
user access to the node-related statistics of the Elasticsearch cluster.

## [28.1.0] - 2025-05-19

### Added
Expand Down
32 changes: 32 additions & 0 deletions documentation/source/user_guidelines/elasticsearch/stats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,35 @@ The ``Stats::Index`` objects have the following methods:

``#name``
The name of the index.

#nodes
------

This method gives you access to node-related statistics. The method returns an
instance of the ``Stats::Nodes`` class, which in turn allows you to access
information on each of the nodes that make up the Elasticsearch cluster through
the following methods:

#size
+++++

This method returns the number of nodes in the cluster.

#all
++++

This method returns an ``Enumerator`` whose elements are instances of the
``Stats::Node`` class, one for each of the nodes in the cluster.

The ``Stats::Node`` class has the following methods:

``#name``
The name of the node. (Usually a random string of numbers and letters)

``#storage``
The method returns an instance of ``Stats::Node::Storage``, a class which
offers information about the storage of the node. ``Storage`` objects can be
added together to calculate the total storage of the cluster.

The ``Storage`` classes provides three methods that return number of bytes:
``#total``, ``#free`` and ``#available``.
28 changes: 24 additions & 4 deletions lib/jay_api/elasticsearch/stats.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

require_relative 'stats/index'
require_relative 'stats/indices'
require_relative 'stats/node'
require_relative 'stats/nodes'

module JayAPI
module Elasticsearch
Expand All @@ -21,19 +23,37 @@ def initialize(transport_client)
# to the Statistics API endpoint fails.
def indices
# DO NOT MEMOIZE! Leave it to the caller.
::JayAPI::Elasticsearch::Stats::Indices.new(response['indices'])
::JayAPI::Elasticsearch::Stats::Indices.new(indices_stats['indices'])
end

# @return [JayAPI::Elasticsearch::Stats::Nodes] Information about the
# nodes that make up the Elasticsearch cluster.
# @raise [Elasticsearch::Transport::Transport::ServerError] If the request
# to the Statistics API endpoint fails.
def nodes
# DO NOT MEMOIZE! Leave it to the caller.
::JayAPI::Elasticsearch::Stats::Nodes.new(nodes_stats['nodes'])
end

private

# @return [Hash] The Hash with the statistics returned by the
# Elasticsearch cluster.
# @return [Hash] The Hash with the index-related statistics returned by
# the Elasticsearch cluster.
# @raise [Elasticsearch::Transport::Transport::ServerError] If the
# request fails.
def response
def indices_stats
# DO NOT MEMOIZE! Leave it to the caller.
transport_client.indices.stats
end

# @return [Hash] The Hash with the node-related statistics returned by the
# Elasticsearch cluster.
# @raise [Elasticsearch::Transport::Transport::ServerError] If the
# request fails.
def nodes_stats
# DO NOT MEMOIZE! Leave it to the caller.
transport_client.nodes.stats
end
end
end
end
15 changes: 15 additions & 0 deletions lib/jay_api/elasticsearch/stats/errors/stats_data_not_available.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require_relative '../../../errors/error'

module JayAPI
module Elasticsearch
class Stats
module Errors
# An error to be raised when a particular Stats element is requested for
# which there is no data in the response received from the cluster.
class StatsDataNotAvailable < ::JayAPI::Errors::Error; end
end
end
end
end
45 changes: 45 additions & 0 deletions lib/jay_api/elasticsearch/stats/node.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

require_relative 'errors/stats_data_not_available'
require_relative 'node/storage'

module JayAPI
module Elasticsearch
class Stats
# Holds information about one of the nodes in the Elasticsearch cluster.
class Node
attr_reader :name

# @param [String] name The name of the node.
# @param [Hash] data Information about the node.
def initialize(name, data)
@name = name
@data = data
end

# @return [JayAPI::Elasticsearch::Stats::Node::Storage] Storage
# information about the node.
# @raise [JayAPI::Elasticsearch::Stats::Errors::StatsDataNotAvailable]
# If there is no storage information for the node.
def storage
@storage ||= ::JayAPI::Elasticsearch::Stats::Node::Storage.new(fs_totals)
end

private

attr_reader :data

# @return [Hash] Aggregated information about the +Node+'s
# filesystem.
# @raise [JayAPI::Elasticsearch::Stats::Errors::StatsDataNotAvailable]
# If there is no filesystem information for the node.
def fs_totals
@fs_totals ||= data.dig('fs', 'total') || raise(
::JayAPI::Elasticsearch::Stats::Errors::StatsDataNotAvailable,
"Filesystem data not available for node #{name}"
)
end
end
end
end
end
55 changes: 55 additions & 0 deletions lib/jay_api/elasticsearch/stats/node/storage.rb
Comment thread
sergio-bobillier marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

module JayAPI
module Elasticsearch
class Stats
class Node
# Holds storage information related to one of the nodes in the
# Elasticsearch cluster.
class Storage
Comment thread
sergio-bobillier marked this conversation as resolved.
TOTAL_KEY = 'total_in_bytes'
FREE_KEY = 'free_in_bytes'
AVAILABLE_KEY = 'available_in_bytes'
Comment thread
sergio-bobillier marked this conversation as resolved.

# @param [Hash] data Data about the Node's storage.
def initialize(data)
@data = data
end

# @return [Integer] The total size of the storage (in bytes)
def total
@total ||= data[TOTAL_KEY]
end

# @return [Integer] The total number of bytes that are free on the
# node.
def free
@free ||= data[FREE_KEY]
end

# @return [Integer] The total number of bytes that are available on
# the node. In general this is equal to #free, but not always.
Comment thread
sergio-bobillier marked this conversation as resolved.
def available
@available ||= data[AVAILABLE_KEY]
end

# @return [self] A new instance of the class with the added storage of
# the receiver and +other+.
def +(other)
raise ArgumentError, "Cannot add #{self.class} and #{other.class} together" unless other.is_a?(self.class)

self.class.new(
TOTAL_KEY => total + other.total,
FREE_KEY => free + other.free,
AVAILABLE_KEY => available + other.available
)
end

private

attr_reader :data
end
end
end
end
end
46 changes: 46 additions & 0 deletions lib/jay_api/elasticsearch/stats/nodes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'forwardable'

require_relative 'node'

module JayAPI
module Elasticsearch
class Stats
# Provides access to the list of nodes returned by Elasticsearch's Stats API
class Nodes
extend Forwardable

def_delegator :nodes, :size

# @param [Hash] nodes Information about the nodes in the Elasticsearch
# cluster.
def initialize(nodes)
@nodes = nodes
end

# @return [Enumerator::Lazy<JayAPI::Elasticsearch::Stats::Node>] A lazy
# enumerator of +Node+ objects, one for each of the nodes.
def all
@all ||= with_lazy_instantiation { nodes }
end

private

attr_reader :nodes

def build_node(args)

Check failure on line 32 in lib/jay_api/elasticsearch/stats/nodes.rb

View workflow job for this annotation

GitHub Actions / lint

[reek] reported by reviewdog 🐶 UtilityFunction: JayAPI::Elasticsearch::Stats::Nodes#build_node doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/v6.1.4/docs/Utility-Function.md] Raw Output: lib/jay_api/elasticsearch/stats/nodes.rb:32: UtilityFunction: JayAPI::Elasticsearch::Stats::Nodes#build_node doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/v6.1.4/docs/Utility-Function.md]
::JayAPI::Elasticsearch::Stats::Node.new(*args)
end

# Calls the given block and turns its return value into a lazy
# enumerator that instantiates a +Node+ object for each of the
# elements of the collection returned by the block.
# @return [Enumerator::Lazy<JayAPI::Elasticsearch::Stats::Node>]
Comment thread
sergio-bobillier marked this conversation as resolved.
def with_lazy_instantiation(&block)
block.call.lazy.map(&method(:build_node))
end
end
end
end
end
90 changes: 90 additions & 0 deletions spec/jay_api/elasticsearch/stats/node/storage_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

require 'jay_api/elasticsearch/stats/node/storage'

RSpec.describe JayAPI::Elasticsearch::Stats::Node::Storage do
subject(:storage) { described_class.new(data) }

let(:data) do
{
'total_in_bytes' => 5_126_127_616,
'free_in_bytes' => 4_576_702_464,
'available_in_bytes' => 4_559_925_248
}
end

describe '#total' do
subject(:method_call) { storage.total }

it 'returns the total number of bytes' do
expect(method_call).to eq(5_126_127_616)
end
end

describe '#free' do
subject(:method_call) { storage.free }

it 'returns the total number of free bytes' do
expect(method_call).to eq(4_576_702_464)
end
end

describe '#available' do
subject(:method_call) { storage.available }

it 'returns the total number of available bytes' do
expect(method_call).to eq(4_559_925_248)
end
end

describe '#+' do
subject(:method_call) { storage + other }

let(:other) do
described_class.new(
'total_in_bytes' => 316_863_741_952,
'free_in_bytes' => 25_923_690_496,
'available_in_bytes' => 25_906_913_280
)
end

context "when 'other' is not an instance of #{described_class}" do
let(:other) do
{
'total_in_bytes' => 316_863_741_952,
'free_in_bytes' => 25_923_690_496,
'available_in_bytes' => 25_906_913_280
}
end

it 'raises an ArgumentError' do
expect { method_call }.to raise_error(
ArgumentError,
'Cannot add JayAPI::Elasticsearch::Stats::Node::Storage and Hash together'
)
end
end

it 'does not return the receiver nor other' do
expect(method_call).not_to be(storage)
expect(method_call).not_to be(other)
method_call
end

it "returns a new instance of #{described_class}" do
expect(method_call).to be_a(described_class)
end

it "returns an instance of #{described_class} with the expected number of total bytes" do
expect(method_call.total).to eq(321_989_869_568)
end

it "returns an instance of #{described_class} with the expected number of free bytes" do
expect(method_call.free).to eq(30_500_392_960)
end

it "returns an instance of #{described_class} with the expected number of available bytes" do
expect(method_call.available).to eq(30_466_838_528)
end
end
end
Loading