Skip to content

Commit de1471d

Browse files
authored
feat: TOOLS-3076 Add info transactions command for MRT metrics (#362)
* feat: Add MRT transaction metrics support in InfoController and templates - Introduced InfoTransactionsController to handle MRT metrics. - Updated InfoController to include transactions in the controller map. - Added MRT metrics template for displaying transaction statistics. - Enhanced CliView to render MRT transaction metrics with integrated set statistics. * refactor: Add MRT monitor and provisionals metrics views Introduces new commands and templates for displaying MRT monitor and provisionals metrics per namespace. Adds 'do_monitors' and 'do_provisionals' methods to InfoTransactionsController, corresponding templates in templates.py, and rendering logic in CliView. This enhances transaction metrics visibility by separating monitor and provisional statistics. * feat: Remove info_transactions_mrt method from CliView Deleted the info_transactions_mrt static method and related code from CliView, consolidating MRT set metrics handling into the main namespace stats logic. This simplifies the codebase and removes redundant functionality. * feat: TOOLS-3076 Remove MRT metrics sheet and update monitor fields Deleted the info_transactions_mrt_sheet definition and updated field keys in info_transactions_monitors_sheet to use 'mrt_monitors' and 'mrt_data_used_bytes' instead of 'mrt_set_objects' and 'mrt_set_data_used_bytes'. This streamlines MRT metrics handling and aligns field names with current data sources. * feat: TOOLS-3076 Add MRT metrics display to info controller Introduces InfoTransactionsController to display Multi-Record Transaction (MRT) metrics for each namespace, including monitors and provisionals. Updates controller_map to include the new transactions controller. * feat: TOOLS-3076 Add tests for MRT monitor and provisional metrics views Added unit tests for CliView.info_transactions_monitors and CliView.info_transactions_provisionals, covering normal cases, empty stats, exceptions, and 'with_' modifiers. These tests verify correct merging of stats, handling of exceptions, and proper calls to cluster and render methods. * Rename MRT metric key in namespace stats Changed the key 'mrt_data_used_bytes' to 'pseudo_mrt_monitor_used_bytes' when adding set metrics to namespace stats for improved clarity. * Remove most formatters from transaction sheets Formatters for alerts have been removed from most fields in the transaction monitor and provisional sheets, except for a yellow alert on 'Active' monitors. Also, the 'Storage' field now uses 'pseudo_mrt_monitor_used_bytes' instead of 'mrt_data_used_bytes'. * feat: Add red alert formatting for mrt monitor thresholds Introduces red alert formatters to the monitor count and storage fields in the transactions monitors sheet, highlighting when values exceed stop-writes thresholds. Also updates view logic to include 'stop-writes-count' and 'stop-writes-size' in namespace stats, and adds debug print statements for set and namespace stats. * fix: Update test data with stop-writes and pseudo MRT fields Added 'stop-writes-count', 'stop-writes-size', and 'pseudo_mrt_monitor_used_bytes' fields to test data in CliViewTest. Updated assertions to reflect new field names and values for improved test coverage. * feat: Add yellow alert thresholds and clean up debug prints Introduces yellow alert formatters for transaction monitor fields at 70% of stop-writes thresholds and adjusts red alert thresholds to 90%. Removes leftover debug print statements from view.py for cleaner output. * feat: Fetch MRT SET Stats only for info transactions Moved merging of <ERO~MRT set statistics into namespace stats from the view layer to the controller layer for both collectinfo and live cluster flows. Updated constants to define MRT_SET, simplified view and test signatures, and improved consistency in metric handling. * feat: TOOLS-3076 Update metrics to use on strong consistency term Revised command help and logic to clarify that transaction metrics are displayed only for namespaces with 'strong-consistency' enabled. Updated controllers to use get_strong_consistency_namespace and added checks for empty results. Renamed metric titles in the view to reflect transaction focus instead of MRT. * fix: TOOLS-3076 Update test titles for transaction metrics Renamed test titles from 'MRT Monitor Metrics' and 'MRT Provisionals Metrics' to 'Transaction Monitor Metrics' and 'Transaction Provisionals Metrics' for clarity and consistency in unit tests. * feat: Add method to fetch strong consistency namespaces Introduces get_strong_consistency_namespace to retrieve statistics for namespaces with strong consistency enabled. Supports filtering by nodes and namespace modifiers, and optionally flips the output structure. * Add method to filter strong consistency namespaces Introduced get_strong_consistency_namespace to return only namespaces with strong consistency enabled. This helps in isolating and analyzing relevant namespace statistics. * fix: Refactor strong consistency namespace stats handling Updated the monitors and provisionals commands to use get_strong_consistency_namespace instead of get_namespace, and improved merging of MRT set metrics into namespace stats. Added logging for cases where no strong consistency namespaces are found. * fix: Remove unused print_dict import Eliminated the import of print_dict from lib.health.util in info_controller.py as it was not used in the file. * fix: Update transaction metrics help text for clarity Revised help descriptions to specify that transaction metrics apply to 'strong-consistency' enabled namespaces, replacing references to MRT (Multi-Record Transaction) for improved clarity. * feat: Added tcs around strong-consistency and general fixes Updated info_controller logic to always add set metrics to namespace stats, defaulting to 0 when set data is missing or invalid. This prevents missing keys and ensures consistent output for downstream consumers and tests. * feat: Parallelize set statistics retrieval for namespaces Replaces sequential fetching of <ERO~MRT set statistics for each namespace with concurrent asyncio tasks, improving performance by gathering data from all nodes in parallel and merging results back into namespace statistics. * Remove unused aggregator arguments from transaction sheets Eliminated redundant 'aggregator=Aggregators.sum()' parameters from fields in info_transactions_monitors_sheet and info_transactions_provisionals_sheet. These aggregators were not required for the current usage and their removal simplifies the code.
1 parent fd6c9d9 commit de1471d

File tree

9 files changed

+894
-3
lines changed

9 files changed

+894
-3
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,7 @@ tramp
148148
/auto/
149149

150150
# vscode user settings
151-
.vscode
151+
.vscode
152+
153+
# cursor
154+
.cursor

lib/collectinfo_analyzer/get_controller.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,23 @@ def get_namespace(self, for_mods=None):
232232
] = self.log_handler.info_statistics(stanza=constants.STAT_NAMESPACE)
233233
return filter_3rd_level_keys(stats, for_mods)
234234

235+
236+
def get_strong_consistency_namespace(self, for_mods=None):
237+
stats: TimestampDict[
238+
NodeDict[NamespaceDict[dict[str, str]]]
239+
] = self.log_handler.info_statistics(stanza=constants.STAT_NAMESPACE)
240+
filtered_stats = filter_3rd_level_keys(stats, for_mods)
241+
242+
for _, nodes_data in filtered_stats.items():
243+
for _, namespaces in nodes_data.items():
244+
for namespace in list(namespaces.keys()):
245+
# Skip if namespace data is missing or doesn't have strong consistency
246+
if (not namespaces[namespace] or
247+
namespaces[namespace].get("strong-consistency", "false").lower() != 'true'):
248+
del namespaces[namespace]
249+
250+
return filtered_stats
251+
235252
def get_sets(self, for_mods=None, flip=False):
236253
set_filter: list[str] | None = None
237254
namespaces_filter: list[str] | None = None

lib/collectinfo_analyzer/info_controller.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
GetStatisticsController,
1919
)
2020
from lib.utils import constants, util, version
21+
import logging
22+
logger = logging.getLogger(__name__)
2123

2224
from .collectinfo_command_controller import CollectinfoCommandController
2325
from lib.base_controller import ShellException
@@ -35,7 +37,10 @@ def __init__(self):
3537
self.modifiers = set(["for"])
3638
self.stats_getter = GetStatisticsController(self.log_handler)
3739
self.config_getter = GetConfigController(self.log_handler)
38-
self.controller_map = dict(namespace=InfoNamespaceController)
40+
self.controller_map = dict(
41+
namespace=InfoNamespaceController,
42+
transactions=InfoTransactionsController
43+
)
3944

4045
@CommandHelp("Displays network, namespace, and xdr summary information.")
4146
async def _do_default(self, line):
@@ -297,3 +302,85 @@ def do_object(self, line):
297302
timestamp=timestamp,
298303
**self.mods,
299304
)
305+
306+
307+
@CommandHelp(
308+
"Displays transaction metrics for each 'strong-consistency' enabled namespace.",
309+
)
310+
class InfoTransactionsController(CollectinfoCommandController):
311+
def __init__(self):
312+
self.modifiers = set()
313+
self.stats_getter = GetStatisticsController(self.log_handler)
314+
315+
@CommandHelp(
316+
"Displays monitors and provisionals information for transactions in each 'strong-consistency' enabled namespace.",
317+
)
318+
def _do_default(self, line):
319+
self.do_monitors(line)
320+
self.do_provisionals(line)
321+
322+
@CommandHelp(
323+
"Displays monitor-related transaction metrics for each 'strong-consistency' enabled namespace.",
324+
)
325+
def do_monitors(self, line):
326+
# Get namespace statistics which contain MRT metrics
327+
ns_stats = self.stats_getter.get_strong_consistency_namespace()
328+
329+
for timestamp in sorted(ns_stats.keys()):
330+
if not ns_stats[timestamp]:
331+
continue
332+
333+
namespaces = set()
334+
for _, node_stats in ns_stats[timestamp].items():
335+
namespaces.update(node_stats.keys())
336+
337+
# Check if any strong consistency namespaces were found
338+
if not namespaces:
339+
logger.debug("No namespaces with strong consistency enabled were found at %s", timestamp)
340+
continue
341+
342+
for namespace in namespaces:
343+
set_data = self.stats_getter.get_sets(for_mods=[namespace, constants.MRT_SET])
344+
345+
if timestamp in set_data:
346+
for node_id, sets_dict in set_data[timestamp].items():
347+
if node_id not in ns_stats[timestamp] or namespace not in ns_stats[timestamp][node_id]:
348+
continue
349+
set_stats = sets_dict.get((namespace, constants.MRT_SET))
350+
# Always add set metrics to namespace stats, defaulting to 0 if not present
351+
ns_stats[timestamp][node_id][namespace]["pseudo_mrt_monitor_used_bytes"] = int(set_stats.get("data_used_bytes", 0) if set_stats else 0)
352+
ns_stats[timestamp][node_id][namespace]["stop-writes-count"] = int(set_stats.get("stop-writes-count", 0) if set_stats else 0)
353+
ns_stats[timestamp][node_id][namespace]["stop-writes-size"] = int(set_stats.get("stop-writes-size", 0) if set_stats else 0)
354+
355+
self.view.info_transactions_monitors(
356+
ns_stats[timestamp],
357+
self.log_handler.get_cinfo_log_at(timestamp=timestamp),
358+
timestamp=timestamp,
359+
**self.mods,
360+
)
361+
362+
@CommandHelp(
363+
"Displays provisional-related transaction metrics for each 'strong-consistency' enabled namespace.",
364+
)
365+
def do_provisionals(self, line):
366+
# Get namespace statistics which contain MRT metrics
367+
ns_stats = self.stats_getter.get_strong_consistency_namespace()
368+
369+
for timestamp in sorted(ns_stats.keys()):
370+
if not ns_stats[timestamp]:
371+
continue
372+
373+
namespaces = set()
374+
for _, node_stats in ns_stats[timestamp].items():
375+
namespaces.update(node_stats.keys())
376+
377+
if not namespaces:
378+
logger.debug("No namespaces with strong consistency enabled were found at %s", timestamp)
379+
continue
380+
381+
self.view.info_transactions_provisionals(
382+
ns_stats[timestamp],
383+
self.log_handler.get_cinfo_log_at(timestamp=timestamp),
384+
timestamp=timestamp,
385+
**self.mods,
386+
)

lib/live_cluster/get_controller.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,49 @@ async def get_namespace(self, flip=False, nodes="all", for_mods=[]):
620620

621621
return ns_stats
622622

623+
async def get_strong_consistency_namespace(self, flip=False, nodes="all", for_mods: list[str] | None = None):
624+
"""
625+
Get statistics for namespaces with strong consistency enabled.
626+
627+
Args:
628+
flip: Whether to flip the output structure (default: False)
629+
nodes: Nodes to query (default: "all")
630+
for_mods: Optional list of namespace filters (default: None)
631+
632+
Returns:
633+
Dictionary of namespace statistics for strong consistency namespaces
634+
"""
635+
namespace_set = set(await _get_all_namespaces(self.cluster, nodes))
636+
namespace_list = list(util.filter_list(namespace_set, for_mods))
637+
tasks = [
638+
asyncio.create_task(
639+
self.cluster.info_namespace_statistics(namespace, nodes=nodes)
640+
)
641+
for namespace in namespace_list
642+
]
643+
ns_stats = {}
644+
for namespace, stat_task in zip(namespace_list, tasks):
645+
ns_stats[namespace] = await stat_task
646+
647+
if isinstance(ns_stats[namespace], Exception):
648+
continue
649+
650+
for node in list(ns_stats[namespace].keys()):
651+
if not ns_stats[namespace][node] or isinstance(
652+
ns_stats[namespace][node], Exception
653+
):
654+
ns_stats[namespace].pop(node)
655+
continue
656+
657+
strong_consistency = ns_stats[namespace][node].get("strong-consistency", "false").lower() == 'true'
658+
if not strong_consistency:
659+
ns_stats[namespace].pop(node)
660+
661+
if not flip:
662+
return util.flip_keys(ns_stats)
663+
664+
return ns_stats
665+
623666
async def get_sindex(
624667
self, flip=False, nodes="all", for_mods: list[str] | None = None
625668
):

lib/live_cluster/info_controller.py

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@
4343
class InfoController(LiveClusterCommandController):
4444
def __init__(self):
4545
self.modifiers = set(["with", "for"])
46-
self.controller_map = dict(namespace=InfoNamespaceController)
46+
self.controller_map = dict(
47+
namespace=InfoNamespaceController,
48+
transactions=InfoTransactionsController
49+
)
4750
self.config_getter = GetConfigController(self.cluster)
4851
self.stat_getter = GetStatisticsController(self.cluster)
4952

@@ -299,3 +302,104 @@ async def do_object(self, line):
299302
return util.callable(
300303
self.view.info_namespace_object, stats, rack_ids, self.cluster, **self.mods
301304
)
305+
306+
307+
@CommandHelp(
308+
"Displays transaction metrics for each 'strong-consistency' enabled namespace.",
309+
usage=f"[{ModifierUsageHelp.WITH}]",
310+
modifiers=(with_modifier_help,),
311+
)
312+
class InfoTransactionsController(LiveClusterCommandController):
313+
def __init__(self, get_futures=False):
314+
self.modifiers = set(["with"])
315+
self.get_futures = get_futures
316+
self.stats_getter = GetStatisticsController(self.cluster)
317+
318+
@CommandHelp(
319+
"Displays monitors and provisionals information for transactions in each 'strong-consistency' enabled namespace.",
320+
)
321+
async def _do_default(self, line):
322+
tasks = await asyncio.gather(
323+
self.do_monitors(line),
324+
self.do_provisionals(line),
325+
)
326+
if self.get_futures:
327+
# Wrapped to prevent base class from calling result.
328+
return dict(futures=tasks)
329+
330+
return tasks
331+
332+
@CommandHelp(
333+
"Displays monitor-related transaction metrics for each 'strong-consistency' enabled namespace.",
334+
usage=f"[{ModifierUsageHelp.WITH}]",
335+
modifiers=(with_modifier_help,),
336+
)
337+
async def do_monitors(self, line):
338+
# Get namespace statistics which contain MRT metrics
339+
ns_stats = await self.stats_getter.get_strong_consistency_namespace(nodes=self.nodes)
340+
341+
# Collect all namespaces from all nodes
342+
namespaces = set()
343+
for node_id, node_stats in ns_stats.items():
344+
namespaces.update(node_stats.keys())
345+
346+
# If no namespaces with strong consistency enabled were found, return
347+
if not namespaces:
348+
logger.debug("No namespaces with strong consistency enabled were found for do_monitors")
349+
return
350+
351+
# Get <ERO~MRT set statistics for all namespaces from all nodes concurrently
352+
set_stats_futures = [
353+
asyncio.create_task(
354+
self.cluster.info_set_statistics(namespace, constants.MRT_SET, nodes=self.nodes)
355+
)
356+
for namespace in namespaces
357+
]
358+
all_set_data = await asyncio.gather(*set_stats_futures)
359+
360+
# Map the results back to their namespaces and merge into ns_stats
361+
for namespace, set_data in zip(namespaces, all_set_data):
362+
for node_id, set_stats in set_data.items():
363+
if (
364+
isinstance(set_stats, Exception)
365+
or node_id not in ns_stats
366+
or namespace not in ns_stats[node_id]
367+
):
368+
continue
369+
370+
# Add set metrics to namespace stats with prefixed names
371+
ns_stats[node_id][namespace]["pseudo_mrt_monitor_used_bytes"] = int(set_stats.get("data_used_bytes", 0) if set_stats else 0)
372+
ns_stats[node_id][namespace]["stop-writes-count"] = int(set_stats.get("stop-writes-count", 0) if set_stats else 0)
373+
ns_stats[node_id][namespace]["stop-writes-size"] = int(set_stats.get("stop-writes-size", 0) if set_stats else 0)
374+
375+
return util.callable(
376+
self.view.info_transactions_monitors,
377+
ns_stats,
378+
self.cluster,
379+
**self.mods,
380+
)
381+
382+
@CommandHelp(
383+
"Displays provisional-related transaction metrics for each 'strong-consistency' enabled namespace.",
384+
usage=f"[{ModifierUsageHelp.WITH}]",
385+
modifiers=(with_modifier_help,),
386+
)
387+
async def do_provisionals(self, line):
388+
# Get namespace statistics which contain MRT metrics
389+
ns_stats = await self.stats_getter.get_strong_consistency_namespace(nodes=self.nodes)
390+
391+
# Check if any strong consistency namespaces were found
392+
namespaces = set()
393+
for _, node_stats in ns_stats.items():
394+
namespaces.update(node_stats.keys())
395+
396+
if not namespaces:
397+
logger.debug("No namespaces with strong consistency enabled were found for do_provisionals")
398+
return
399+
400+
return util.callable(
401+
self.view.info_transactions_provisionals,
402+
ns_stats,
403+
self.cluster,
404+
**self.mods,
405+
)

lib/utils/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
COLLECTINFO_SEPERATOR = "\n====ASCOLLECTINFO====\n"
7676
COLLECTINFO_PROGRESS_MSG = "Data collection for %s%s in progress..."
7777

78+
MRT_SET = "<ERO~MRT"
7879

7980
class Enumeration(set):
8081
def __getattr__(self, name):

0 commit comments

Comments
 (0)