Files
scylladb/test/nodetool/test_info.py
Avi Kivity f3eade2f62 treewide: relicense to ScyllaDB-Source-Available-1.0
Drop the AGPL license in favor of a source-available license.
See the blog post [1] for details.

[1] https://www.scylladb.com/2024/12/18/why-were-moving-to-a-source-available-license/
2024-12-18 17:45:13 +02:00

217 lines
8.2 KiB
Python

#
# Copyright 2023-present ScyllaDB
#
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
#
import random
from typing import NamedTuple
import pytest
from test.nodetool.rest_api_mock import expected_request
from test.nodetool.utils import format_size
class moving_average(NamedTuple):
rates: list[float]
mean_rate: float
count: int
def to_json(self):
return {
'rates': self.rates,
'mean_rate': self.mean_rate,
'count': self.count
}
@staticmethod
def make(count=0):
# 1, 5, 15 minutes rates
rates = [random.random() for _ in (1, 5, 15)]
# the mean rate from startup
mean_rate = random.random()
return moving_average(rates, mean_rate, count)
class cache_metrics(NamedTuple):
entries: int
size: int
capacity: int
hits: moving_average
requests: moving_average
hit_rate: float
save_period: int
def normalize_output(output):
# Scylla does not run a JVM, so it the memory usage exposed by MemoryMXBean
# is meaningless to it.
normalized = ''
for line in output.split('\n'):
if line.startswith('Heap Memory'):
continue
if line.startswith('Uptime'):
continue
# cassandra nodetool use KiB and KB interchangeably, let's
# normalize these two prefixes
for iec, si in [('KiB', 'KB'), ('MiB', 'MB'), ('GiB', 'GB')]:
line = line.replace(iec, si)
normalized += f'{line}\n'
return normalized
@pytest.mark.parametrize("display_all_tokens",
[
True,
False,
]
)
def test_info(request, nodetool, display_all_tokens):
host_id = 'hostid0'
endpoint = '127.0.0.1'
endpoint_to_host_id = {endpoint: host_id}
generation_number = 42
load = 42424242
uptime = 12345
datacenter = 'dc0'
rack = 'rack0'
nr_exceptions = 0
join_ring = True
caches = {
'key': cache_metrics(98, 123, 1220, moving_average.make(), moving_average.make(), 0.42, 20),
'row': cache_metrics(42, 223, 1200, moving_average.make(), moving_average.make(), 0.43, 20),
'counter': cache_metrics(13, 523, 3200, moving_average.make(), moving_average.make(), 0.44, 20),
}
off_heap_mem_usages = {
'memtable_off_heap_size': 1234,
'bloom_filter_off_heap_memory_used': 1235,
'index_summary_off_heap_memory_used': 1236,
'compression_metadata_off_heap_memory_used': 1237,
}
tables = [('ks0', 'cf0', 'ColumnFamilies'),
('ks0', 'cf1', 'ColumnFamilies')]
tokens = ['-9217327499541836964',
'9066719992055809912',
'50927788561116407']
expected_requests = [
expected_request('GET', '/storage_service/gossiping', response=True),
expected_request('GET', '/storage_service/hostid/local', response=host_id),
expected_request('GET', '/storage_service/rpc_server', response=False),
expected_request('GET', '/storage_service/native_transport', response=True),
expected_request('GET', '/storage_service/load', response=load),
expected_request('GET', '/storage_service/generation_number', response=generation_number),
]
is_scylla = request.config.getoption('nodetool') == 'scylla'
if is_scylla:
expected_requests.append(
expected_request('GET', '/system/uptime_ms', response=uptime))
else:
# cassandra nodetool uses RuntimeMXBean
pass
expected_requests.append(
expected_request('GET', '/column_family/',
multiple=expected_request.MULTIPLE,
response=[{'ks': ks, 'cf': cf, 'type': type_} for ks, cf, type_ in tables]))
off_heap_mem_used = 0
for ks, cf, _ in tables:
for name, mem_used in off_heap_mem_usages.items():
off_heap_mem_used += mem_used
expected_requests.append(
expected_request('GET', f'/column_family/metrics/{name}/{ks}:{cf}',
response=mem_used))
expected_requests += [
expected_request('GET', '/snitch/datacenter', multiple=expected_request.ANY, response=datacenter),
expected_request('GET', '/snitch/rack', multiple=expected_request.ANY, response=rack),
expected_request('GET', '/storage_service/metrics/exceptions', response=nr_exceptions),
]
for name, cache in caches.items():
expected_requests += [
expected_request('GET', f'/cache_service/metrics/{name}/entries',
response=cache.entries),
expected_request('GET', f'/cache_service/metrics/{name}/size',
response=cache.size),
expected_request('GET', f'/cache_service/metrics/{name}/capacity',
response=cache.capacity),
expected_request('GET', f'/cache_service/metrics/{name}/hits_moving_avrage',
multiple=expected_request.ANY,
response=cache.hits.to_json()),
expected_request('GET', f'/cache_service/metrics/{name}/requests_moving_avrage',
multiple=expected_request.ANY,
response=cache.requests.to_json()),
expected_request('GET', f'/cache_service/metrics/{name}/hit_rate',
response=cache.hit_rate),
expected_request('GET', f'/cache_service/{name}_cache_save_period',
response=cache.save_period)
]
expected_requests.append(
expected_request('GET', '/storage_service/join_ring',
response=join_ring))
if join_ring:
if is_scylla:
expected_requests.append(
expected_request('GET', '/storage_service/tokens',
response=tokens))
else:
expected_requests += [
expected_request('GET', '/storage_service/host_id',
response=[{'key': endpoint, 'value': hostid}
for endpoint, hostid in endpoint_to_host_id.items()]),
expected_request('GET', '/storage_service/hostid/local', response=host_id),
expected_request('GET', f'/storage_service/tokens/{endpoint}',
response=tokens)
]
mem_used = 0.0
mem_max = 0.0
off_heap_mem_used_in_mb = off_heap_mem_used / 1024 / 1024
expected_output = f'''\
{'ID':<23}: {host_id}
{'Gossip active':<23}: true
{'Thrift active':<23}: false
{'Native Transport active':<23}: true
{'Load':<23}: {format_size(load)}
{'Generation No':<23}: {generation_number}
{'Uptime (seconds)':<23}: {uptime}
{'Heap Memory (MB)':<23}: {mem_used:.2f} / {mem_max:.2f}
{'Off Heap Memory (MB)':<23}: {off_heap_mem_used_in_mb:.2f}
{'Data Center':<23}: {datacenter}
{'Rack':<23}: {rack}
{'Exceptions':<23}: {nr_exceptions}
'''
for n, c in caches.items():
name = f'{n.capitalize()} Cache'
expected_output += (f'{name:<23}: '
f'entries {c.entries}, size {format_size(c.size)}, '
f'capacity {format_size(c.capacity)}, '
f'{c.hits.count} hits, {c.requests.count} requests, '
f'{c.hit_rate:.3f} recent hit rate, '
f'{c.save_period} save period in seconds\n')
percent_repaired = 0.0
expected_output += f"{'Percent Repaired':<23}: {percent_repaired:.1f}%\n"
if join_ring:
name = 'Token'
if display_all_tokens:
for token in tokens:
expected_output += f'{name:<23}: {token}\n'
else:
nr_tokens = len(tokens)
expected_output += f'{name:<23}: (invoke with -T/--tokens to see all {nr_tokens} tokens)\n'
else:
expected_output += f'{name:<23}: (invoke with -T/--tokens to see all {nr_tokens} tokens)\n'
args = []
if display_all_tokens:
args.append('--tokens')
res = nodetool("info", *args, expected_requests=expected_requests)
assert normalize_output(res.stdout) == normalize_output(expected_output)