Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ac16864
added show and config commands
kanza-latif Oct 14, 2024
f4811d1
resolved the errors
kanza-latif Oct 14, 2024
b8d7274
resolved the errors
kanza-latif Oct 14, 2024
636f30a
added test filw
kanza-latif Oct 21, 2024
81df5f0
updated test cases
kanza-latif Oct 23, 2024
1ad1222
updated test cases
kanza-latif Oct 23, 2024
29afad4
updated the mock db
kanza-latif Oct 28, 2024
aecbc72
removed pre-commit errors
kanza-latif Oct 28, 2024
2d066ee
resolivng pre-commit errors
kanza-latif Oct 28, 2024
0cf9043
resolivng pre-commit errors
kanza-latif Oct 28, 2024
52d7c0a
added mock db in config file
kanza-latif Oct 28, 2024
43f1f9c
removed incomaptibilities from the config and test files
kanza-latif Oct 28, 2024
b4716c3
removed incomaptibilities from the config and test files
kanza-latif Oct 28, 2024
b17f226
updated the test file
kanza-latif Oct 28, 2024
a6680d9
updated the test file
kanza-latif Oct 28, 2024
732612f
corrected the failing test case
kanza-latif Oct 28, 2024
7b17295
added testfile for show commands
kanza-latif Nov 5, 2024
5e72f6b
added testfile for show commands
kanza-latif Nov 5, 2024
a51c84d
removed the commented import
kanza-latif Nov 6, 2024
07fbf15
Add show memory-stats CLI command with Dict2Obj handling, clean outpu…
Arham-Nasir Nov 13, 2024
9ddaf96
updated config and test files
kanza-latif Nov 25, 2024
5c6b013
updated config and test files
kanza-latif Nov 25, 2024
81be860
update CLI commands for memory statistics configuration and management.
Arham-Nasir Dec 26, 2024
02848eb
Add show commands for memory statistics and configuration
Arham-Nasir Dec 26, 2024
4aab773
Add memory statistics monitoring commands to documentation.
Arham-Nasir Dec 26, 2024
bc8c240
Merge branch 'master' into show-config
Arham-Nasir Dec 26, 2024
c47b61b
Implement single DB connection for config commands
Arham-Nasir Dec 30, 2024
cf7f6d0
add closelog to ensure that connection is closed
Arham-Nasir Jan 7, 2025
dab65d4
update and restructure CLI documentation
Arham-Nasir Jan 14, 2025
9974f39
Refactor exception handling, improve type safety, optimize retry logi…
Arham-Nasir Feb 10, 2025
b33678e
Remove generic except Exception and handle specific errors
Arham-Nasir Feb 12, 2025
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
331 changes: 331 additions & 0 deletions config/memory_statistics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
# Standard library imports
import syslog

# Third-party imports
import click

# Type hints
from typing import Tuple, Optional

# Local imports
from swsscommon.swsscommon import ConfigDBConnector

# Constants
MEMORY_STATISTICS_TABLE = "MEMORY_STATISTICS"
MEMORY_STATISTICS_KEY = "memory_statistics"

SAMPLING_INTERVAL_MIN = 3
SAMPLING_INTERVAL_MAX = 15
RETENTION_PERIOD_MIN = 1
RETENTION_PERIOD_MAX = 30

DEFAULT_SAMPLING_INTERVAL = 5 # minutes
DEFAULT_RETENTION_PERIOD = 15 # days

syslog.openlog("memory_statistics_config", syslog.LOG_PID | syslog.LOG_CONS, syslog.LOG_USER)


def log_to_syslog(message: str, level: int = syslog.LOG_INFO) -> None:
"""
Logs a message to the system log (syslog) with error handling.

This function logs the provided message to syslog at the specified level.
It handles potential errors such as system-related issues (OSError) and
invalid parameters (ValueError) by displaying appropriate error messages.

Args:
message (str): The message to log.
level (int, optional): The log level (default is syslog.LOG_INFO).
"""
try:
syslog.syslog(level, message)
except OSError as e:
click.echo(f"System error while logging to syslog: {e}", err=True)
except ValueError as e:
click.echo(f"Invalid syslog parameters: {e}", err=True)


def generate_error_message(error_type: str, error: Exception) -> str:
"""
Generates a formatted error message for logging and user feedback.

Args:
error_type (str): A short description of the error type.
error (Exception): The actual exception object.

Returns:
str: A formatted error message string.
"""
return f"{error_type}: {error}"


def validate_range(value: int, min_val: int, max_val: int) -> bool:
"""
Validates whether a given integer value falls within a specified range.

Args:
value (int): The value to validate.
min_val (int): The minimum allowable value.
max_val (int): The maximum allowable value.

Returns:
bool: True if the value is within range, False otherwise.
"""
return min_val <= value <= max_val


class MemoryStatisticsDB:
"""
Singleton class for managing the connection to the memory statistics configuration database.
"""
_instance = None
_db = None

def __new__(cls):
"""
Creates and returns a singleton instance of MemoryStatisticsDB.

Returns:
MemoryStatisticsDB: The singleton instance.
"""
if cls._instance is None:
cls._instance = super(MemoryStatisticsDB, cls).__new__(cls)
cls._connect_db()
return cls._instance

@classmethod
def _connect_db(cls):
"""
Establishes a connection to the ConfigDB database.

Logs an error if the connection fails.
"""
try:
cls._db = ConfigDBConnector()
cls._db.connect()
except RuntimeError as e:
log_to_syslog(f"ConfigDB connection failed: {e}", syslog.LOG_ERR)
cls._db = None

@classmethod
def get_db(cls):
"""
Retrieves the database connection instance, reconnecting if necessary.

Returns:
ConfigDBConnector: The active database connection.

Raises:
RuntimeError: If the database connection is unavailable.
"""
if cls._db is None:
cls._connect_db()
if cls._db is None:
raise RuntimeError("Database connection unavailable")
return cls._db


def update_memory_statistics_status(enabled: bool) -> Tuple[bool, Optional[str]]:
"""
Updates the enable/disable status of memory statistics in the configuration database.

This function modifies the configuration database to enable or disable
memory statistics collection based on the provided status. It also logs
the action and returns a tuple indicating whether the operation was successful.

Args:
enabled (bool): True to enable memory statistics, False to disable.

Returns:
Tuple[bool, Optional[str]]: A tuple containing success status and an optional error message.
"""
try:
db = MemoryStatisticsDB.get_db()

db.mod_entry(MEMORY_STATISTICS_TABLE, MEMORY_STATISTICS_KEY, {"enabled": str(enabled).lower()})
msg = f"Memory statistics feature {'enabled' if enabled else 'disabled'} successfully."
click.echo(msg)
log_to_syslog(msg)
return True, None
except (KeyError, ConnectionError, RuntimeError) as e:
error_msg = generate_error_message(f"Failed to {'enable' if enabled else 'disable'} memory statistics", e)

click.echo(error_msg, err=True)
log_to_syslog(error_msg, syslog.LOG_ERR)
return False, error_msg


@click.group(help="Tool to manage memory statistics configuration.")
def cli():
"""
Memory statistics configuration tool.

This command-line interface (CLI) allows users to configure and manage
memory statistics settings such as enabling/disabling the feature and
modifying parameters like the sampling interval and retention period.
"""
pass


@cli.group(help="Commands to configure system settings.")
def config():
"""
Configuration commands for managing memory statistics.

Example:
$ config memory-stats enable
$ config memory-stats sampling-interval 5
"""
pass


@config.group(name='memory-stats', help="Manage memory statistics collection settings.")
def memory_stats():
"""Configure memory statistics collection and settings.

This group contains commands to enable/disable memory statistics collection
and configure related parameters.

Examples:
Enable memory statistics:
$ config memory-stats enable

Set sampling interval to 5 minutes:
$ config memory-stats sampling-interval 5

Set retention period to 7 days:
$ config memory-stats retention-period 7

Disable memory statistics:
$ config memory-stats disable
"""
pass


@memory_stats.command(name='enable')
def memory_stats_enable():
"""Enable memory statistics collection.

This command enables the collection of memory statistics on the device.
It updates the configuration and reminds the user to run 'config save'
to persist changes.

Example:
$ config memory-stats enable
Memory statistics feature enabled successfully.
Reminder: Please run 'config save' to persist changes.
"""
success, error = update_memory_statistics_status(True)
if success:
click.echo("Reminder: Please run 'config save' to persist changes.")
log_to_syslog("Memory statistics enabled. Reminder to run 'config save'")


@memory_stats.command(name='disable')
def memory_stats_disable():
"""Disable memory statistics collection.

This command disables the collection of memory statistics on the device.
It updates the configuration and reminds the user to run 'config save'
to persist changes.

Example:
$ config memory-stats disable
Memory statistics feature disabled successfully.
Reminder: Please run 'config save' to persist changes.
"""
success, error = update_memory_statistics_status(False)
if success:
click.echo("Reminder: Please run 'config save' to persist changes.")
log_to_syslog("Memory statistics disabled. Reminder to run 'config save'")


@memory_stats.command(name='sampling-interval')
@click.argument("interval", type=int)
def memory_stats_sampling_interval(interval: int):
"""
Configure the sampling interval for memory statistics collection.

This command updates the interval at which memory statistics are collected.
The interval must be between 3 and 15 minutes.

Args:
interval (int): The desired sampling interval in minutes.

Examples:
Set sampling interval to 5 minutes:
$ config memory-stats sampling-interval 5
Sampling interval set to 5 minutes successfully.
Reminder: Please run 'config save' to persist changes.

Invalid interval example:
$ config memory-stats sampling-interval 20
Error: Sampling interval must be between 3 and 15 minutes.
"""
if not validate_range(interval, SAMPLING_INTERVAL_MIN, SAMPLING_INTERVAL_MAX):
error_msg = f"Error: Sampling interval must be between {SAMPLING_INTERVAL_MIN} and {SAMPLING_INTERVAL_MAX}."
click.echo(error_msg, err=True)
log_to_syslog(error_msg, syslog.LOG_ERR)
return

try:
db = MemoryStatisticsDB.get_db()
db.mod_entry(MEMORY_STATISTICS_TABLE, MEMORY_STATISTICS_KEY, {"sampling_interval": str(interval)})
success_msg = f"Sampling interval set to {interval} minutes successfully."
click.echo(success_msg)
log_to_syslog(success_msg)
click.echo("Reminder: Please run 'config save' to persist changes.")
except (KeyError, ConnectionError, ValueError, RuntimeError) as e:
error_msg = generate_error_message(f"{type(e).__name__} setting sampling interval", e)
click.echo(error_msg, err=True)
log_to_syslog(error_msg, syslog.LOG_ERR)
return


@memory_stats.command(name='retention-period')
@click.argument("period", type=int)
def memory_stats_retention_period(period: int):
"""
Configure the retention period for memory statistics storage.

This command sets the number of days memory statistics should be retained.
The retention period must be between 1 and 30 days.

Args:
period (int): The desired retention period in days.

Examples:
Set retention period to 7 days:
$ config memory-stats retention-period 7
Retention period set to 7 days successfully.
Reminder: Please run 'config save' to persist changes.

Invalid period example:
$ config memory-stats retention-period 45
Error: Retention period must be between 1 and 30 days.
"""
if not validate_range(period, RETENTION_PERIOD_MIN, RETENTION_PERIOD_MAX):
error_msg = f"Error: Retention period must be between {RETENTION_PERIOD_MIN} and {RETENTION_PERIOD_MAX}."
click.echo(error_msg, err=True)
log_to_syslog(error_msg, syslog.LOG_ERR)
return

try:
db = MemoryStatisticsDB.get_db()
db.mod_entry(MEMORY_STATISTICS_TABLE, MEMORY_STATISTICS_KEY, {"retention_period": str(period)})
success_msg = f"Retention period set to {period} days successfully."
click.echo(success_msg)
log_to_syslog(success_msg)
click.echo("Reminder: Please run 'config save' to persist changes.")
except (KeyError, ConnectionError, ValueError, RuntimeError) as e:
error_msg = generate_error_message(f"{type(e).__name__} setting retention period", e)
click.echo(error_msg, err=True)
log_to_syslog(error_msg, syslog.LOG_ERR)
return


if __name__ == "__main__":
try:
cli()
finally:
syslog.closelog()
Loading
Loading