From: Kotresh HR Date: Mon, 1 Jun 2026 09:29:31 +0000 (+0530) Subject: mgr/mirroring: Reorder and format metrics output X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=4c19167719caeab7de059bb1ba983e3f16afb5b3;p=ceph.git mgr/mirroring: Reorder and format metrics output Reorder and format status output to match the output of asok interface peer_status command. Return metrics under metrics//peer/ to match the asok peer_status layout, including {"metrics": {}} when no peers are configured. Fixes: https://tracker.ceph.com/issues/76686 Signed-off-by: Kotresh HR --- diff --git a/src/pybind/mgr/mirroring/fs/metrics/format.py b/src/pybind/mgr/mirroring/fs/metrics/format.py new file mode 100644 index 00000000000..b208151c167 --- /dev/null +++ b/src/pybind/mgr/mirroring/fs/metrics/format.py @@ -0,0 +1,113 @@ +# Matches the format_time function used in peer_status +def _format_time(total_seconds_d): + total_seconds = int(round(total_seconds_d)) + days = total_seconds // 86400 + total_seconds %= 86400 + hours = total_seconds // 3600 + total_seconds %= 3600 + minutes = total_seconds // 60 + seconds = total_seconds % 60 + if days > 0: + return (f'{days}d {hours:02d}h {minutes:02d}m {seconds:02d}s') + if hours > 0: + return f'{hours}h {minutes:02d}m {seconds:02d}s' + if minutes > 0: + return f'{minutes}m {seconds:02d}s' + return f'{seconds}s' + + +# Matches the format_bytes function used in peer_status +def _format_bytes(bytes_val): + kib = 1024.0 + mib = kib * 1024.0 + gib = mib * 1024.0 + tib = gib * 1024.0 + pib = tib * 1024.0 + if bytes_val >= pib: + return f'{bytes_val / pib:.2f} PiB' + if bytes_val >= tib: + return f'{bytes_val / tib:.2f} TiB' + if bytes_val >= gib: + return f'{bytes_val / gib:.2f} GiB' + if bytes_val >= mib: + return f'{bytes_val / mib:.2f} MiB' + if bytes_val >= kib: + return f'{bytes_val / kib:.2f} KiB' + return f'{bytes_val:.2f} B' + + +def _order_dict(d, keys): + ordered = {} + for key in keys: + if key in d: + ordered[key] = d[key] + for key, val in d.items(): + if key not in ordered: + ordered[key] = val + return ordered + + +def _order_current_syncing_snap(snap): + if not isinstance(snap, dict): + return snap + snap = dict(snap) + crawl = snap.get('crawl') + if isinstance(crawl, dict): + snap['crawl'] = _order_dict(crawl, ('state', 'duration')) + dq_wait = snap.get('datasync_queue_wait') + if isinstance(dq_wait, dict): + snap['datasync_queue_wait'] = _order_dict(dq_wait, ('state', 'duration')) + bytes_obj = snap.get('bytes') + if isinstance(bytes_obj, dict): + snap['bytes'] = _order_dict( + bytes_obj, ('sync_bytes', 'total_bytes', 'sync_percent')) + files_obj = snap.get('files') + if isinstance(files_obj, dict): + snap['files'] = _order_dict( + files_obj, ('sync_files', 'total_files', 'sync_percent')) + return _order_dict( + snap, ('id', 'name', 'sync-mode', 'avg_read_throughput_bytes', + 'avg_write_throughput_bytes', 'crawl', 'datasync_queue_wait', + 'bytes', 'files', 'eta')) + + +def format_peer_status_metrics(metrics, dir_path, peer_uuid, stat): + metrics.setdefault(dir_path, {}).setdefault('peer', {})[peer_uuid] = stat + + +# to match the output of peer_status +def format_and_order_sync_stat_for_display(stat): + if not isinstance(stat, dict): + return stat + + out = dict(stat) + last_synced_snap = out.get('last_synced_snap') + if isinstance(last_synced_snap, dict): + snap = dict(last_synced_snap) + for key in ('crawl_duration', 'datasync_queue_wait_duration', + 'sync_duration'): + val = snap.get(key) + if isinstance(val, (int, float)): + snap[key] = _format_time(val) + sync_bytes = snap.get('sync_bytes') + if isinstance(sync_bytes, (int, float)): + snap['sync_bytes'] = _format_bytes(sync_bytes) + sync_time_stamp = snap.get('sync_time_stamp') + if isinstance(sync_time_stamp, (int, float)): + snap['sync_time_stamp'] = f'{sync_time_stamp:.6f}s' + out['last_synced_snap'] = _order_dict( + snap, ('id', 'name', 'crawl_duration', + 'datasync_queue_wait_duration', 'sync_duration', + 'sync_time_stamp', 'sync_bytes', 'sync_files')) + + current_syncing_snap = out.get('current_syncing_snap') + if isinstance(current_syncing_snap, dict): + out['current_syncing_snap'] = _order_current_syncing_snap( + current_syncing_snap) + + out.pop('_instance_id', None) + + return _order_dict( + out, ('state', 'failure_reason', 'current_syncing_snap', + 'last_synced_snap', 'snaps_synced', 'snaps_deleted', + 'snaps_renamed', 'metrics_updated_at')) diff --git a/src/pybind/mgr/mirroring/fs/metrics/load.py b/src/pybind/mgr/mirroring/fs/metrics/load.py index 8eff6a5c819..f5aed3a28a5 100644 --- a/src/pybind/mgr/mirroring/fs/metrics/load.py +++ b/src/pybind/mgr/mirroring/fs/metrics/load.py @@ -14,14 +14,11 @@ from ..utils import ( get_metadata_pool, parse_sync_stat_omap_key, ) +from .format import format_and_order_sync_stat_for_display, format_peer_status_metrics log = logging.getLogger(__name__) -def nest_sync_stat_in_metrics(metrics, dir_path, peer_uuid, stat): - metrics.setdefault(dir_path, {}).setdefault('peer', {})[peer_uuid] = stat - - def load_sync_stat_by_keys(ioctx, keys): stats: Dict[str, Any] = {} if not keys: @@ -88,7 +85,9 @@ def load_sync_stat_metrics(ioctx, filesystem, peer_uuid=None): except (json.JSONDecodeError, UnicodeDecodeError) as e: log.warning(f'failed to decode sync stat for key {key}: {e}') continue - nest_sync_stat_in_metrics(metrics, dir_path, peer, stat) + format_peer_status_metrics( + metrics, dir_path, peer, + format_and_order_sync_stat_for_display(stat)) start = omap_vals.popitem()[0] except rados.Error as e: log.error(f'failed to read sync stat omap: {e}') diff --git a/src/pybind/mgr/mirroring/fs/snapshot_mirror.py b/src/pybind/mgr/mirroring/fs/snapshot_mirror.py index e25a0b19458..1504b8831ef 100644 --- a/src/pybind/mgr/mirroring/fs/snapshot_mirror.py +++ b/src/pybind/mgr/mirroring/fs/snapshot_mirror.py @@ -22,7 +22,8 @@ from .utils import INSTANCE_ID_PREFIX, MIRROR_OBJECT_NAME, Finisher, \ AsyncOpTracker, get_metadata_pool, norm_path, connect_to_filesystem, \ disconnect_from_filesystem, sync_stat_omap_key from .metrics import load as metrics_load -from .metrics.load import nest_sync_stat_in_metrics +from .metrics.format import format_and_order_sync_stat_for_display, \ + format_peer_status_metrics from .exception import MirrorException from .dir_map.create import create_mirror_object from .dir_map.load import load_dir_map, load_instances @@ -776,7 +777,7 @@ class FSSnapshotMirror: f'directory {dir_path} is not mirrored') peers = self.get_filesystem_peers(filesystem) if not peers: - return 0, json.dumps({}), '' + return 0, json.dumps({'metrics': {}}, indent=4), '' if peer_uuid: if peer_uuid not in peers: @@ -796,12 +797,14 @@ class FSSnapshotMirror: for peer in peers: omap_key = sync_stat_omap_key(filesystem, peer, dir_path) if omap_key in omap_stats: - nest_sync_stat_in_metrics(metrics, dir_path, peer, - omap_stats[omap_key]) + format_peer_status_metrics( + metrics, dir_path, peer, + format_and_order_sync_stat_for_display( + omap_stats[omap_key])) else: metrics = metrics_load.load_sync_stat_metrics( ioctx, filesystem, peer_uuid) - return 0, json.dumps(metrics, indent=4), '' + return 0, json.dumps({'metrics': metrics}, indent=4), '' except MirrorException as me: return me.args[0], '', me.args[1]