From 61b429f22328ec3129e59f204d7d9d5c76b0bec4 Mon Sep 17 00:00:00 2001 From: Mohith Kumar Thummaluru Date: Wed, 2 Jul 2025 11:59:18 +0000 Subject: [PATCH] PCP: PMDA: Introduce new PMDA for RDS This commit adds a new PMDA (Performance Metrics Domain Agent) for Reliable Datagram Sockets (RDS). It exports key metrics including connection information, socket and connection statistics, and details of send, receive, and retransmit queues for performance analysis using Performance Co-Pilot (PCP). This PMDA is intended to aid in diagnosing network-related issues on systems using RDS over Infiniband or TCP. Signed-off-by: Mohith Kumar Thummaluru --- build/rpm/pcp.spec.in | 22 +- build/rpm/redhat.spec | 23 +- src/pmdas/GNUmakefile | 2 +- src/pmdas/rds/GNUmakefile | 52 ++++ src/pmdas/rds/Install | 35 +++ src/pmdas/rds/Remove | 35 +++ src/pmdas/rds/domain.h | 1 + src/pmdas/rds/modules/ping.py | 320 ++++++++++++++++++++++ src/pmdas/rds/modules/rds_info.py | 407 ++++++++++++++++++++++++++++ src/pmdas/rds/modules/rds_ping.py | 262 ++++++++++++++++++ src/pmdas/rds/pmdards.python | 436 ++++++++++++++++++++++++++++++ src/pmdas/rds/pmns | 1 + src/pmdas/rds/pyprep | 23 ++ src/pmns/stdpmid.pcp | 3 +- 14 files changed, 1618 insertions(+), 4 deletions(-) create mode 100644 src/pmdas/rds/GNUmakefile create mode 100755 src/pmdas/rds/Install create mode 100755 src/pmdas/rds/Remove create mode 100644 src/pmdas/rds/domain.h create mode 100755 src/pmdas/rds/modules/ping.py create mode 100755 src/pmdas/rds/modules/rds_info.py create mode 100755 src/pmdas/rds/modules/rds_ping.py create mode 100755 src/pmdas/rds/pmdards.python create mode 100644 src/pmdas/rds/pmns create mode 100644 src/pmdas/rds/pyprep diff --git a/build/rpm/pcp.spec.in b/build/rpm/pcp.spec.in index d2600c181b..375332e24b 100755 --- a/build/rpm/pcp.spec.in +++ b/build/rpm/pcp.spec.in @@ -1598,6 +1598,20 @@ This package contains the PCP Performance Metrics Domain Agent (PMDA) for collecting statistics for nVidia RDMA over Converged Ethernet (RoCE) devices. # end pcp-pmda-rocestat +# +# pcp-pmda-rds +# +%package pmda-rds +License: GPL-2.0-or-later +Summary: Performance Co-Pilot (PCP) metrics for RDS protocol +URL: https://pcp.io +Requires: pcp = @package_version@ pcp-libs = @package_version@ +Requires: python3-pcp +%description pmda-rds +This package contains the PCP Performance Metrics Domain Agent (PMDA) for +collecting statistics for RDS connections +# end pcp-pmda-rds + # # pcp-pmda-openvswitch # @@ -2268,6 +2282,7 @@ basic_manifest | keep '(etc/pcp|pmdas)/podman(/|$)' >pcp-pmda-podman-files basic_manifest | keep '(etc/pcp|pmdas)/postfix(/|$)' >pcp-pmda-postfix-files basic_manifest | keep '(etc/pcp|pmdas)/postgresql(/|$)' >pcp-pmda-postgresql-files basic_manifest | keep '(etc/pcp|pmdas)/rabbitmq(/|$)' >pcp-pmda-rabbitmq-files +basic_manifest | keep '(etc/pcp|pmdas)/rds(/|$)' >pcp-pmda-rds-files basic_manifest | keep '(etc/pcp|pmdas)/redis(/|$)' >pcp-pmda-redis-files basic_manifest | keep '(etc/pcp|pmdas)/resctrl(/|$)|sys-fs-resctrl' >pcp-pmda-resctrl-files basic_manifest | keep '(etc/pcp|pmdas)/rocestat(/|$)' >pcp-pmda-rocestat-files @@ -2308,7 +2323,7 @@ for pmda_package in \ nutcracker nvidia \ openmetrics openvswitch oracle \ pdns perfevent podman postfix postgresql \ - rabbitmq redis resctrl rocestat roomtemp rsyslog \ + rabbitmq rds redis resctrl rocestat roomtemp rsyslog \ samba sendmail shping slurm smart snmp \ sockets statsd summary systemd \ unbound uwsgi \ @@ -2744,6 +2759,9 @@ done %preun pmda-rocestat %{pmda_remove "$1" "rocestat"} +%preun pmda-rds +%{pmda_remove "$1" "rds"} + %preun pmda-uwsgi %{pmda_remove "$1" "uwsgi"} %endif @@ -3072,6 +3090,8 @@ fi %files pmda-rocestat -f pcp-pmda-rocestat-files.rpm +%files pmda-rds -f pcp-pmda-rds-files.rpm + %files pmda-uwsgi -f pcp-pmda-uwsgi-files.rpm %files export-pcp2graphite -f pcp-export-pcp2graphite-files.rpm diff --git a/build/rpm/redhat.spec b/build/rpm/redhat.spec index 821e0354fa..0c30ef370f 100644 --- a/build/rpm/redhat.spec +++ b/build/rpm/redhat.spec @@ -1759,6 +1759,21 @@ collecting statistics for nVidia RDMA over Converged Ethernet (RoCE) devices. # end pcp-pmda-rocestat %endif +# +# pcp-pmda-rds +# +%package pmda-rds +License: GPL-2.0-or-later +Summary: Performance Co-Pilot (PCP) metrics for RDS protocol +URL: https://pcp.io +Requires: pcp = %{version}-%{release} pcp-libs = %{version}-%{release} +Requires: python3-pcp +%description pmda-rds +This package contains the PCP Performance Metrics Domain Agent (PMDA) for +collecting statistics for RDS connections. +# end pcp-pmda-rds +%endif + %if !%{disable_mongodb} # # pcp-pmda-mongodb @@ -2478,6 +2493,7 @@ basic_manifest | keep '(etc/pcp|pmdas)/podman(/|$)' >pcp-pmda-podman-files basic_manifest | keep '(etc/pcp|pmdas)/postfix(/|$)' >pcp-pmda-postfix-files basic_manifest | keep '(etc/pcp|pmdas)/postgresql(/|$)' >pcp-pmda-postgresql-files basic_manifest | keep '(etc/pcp|pmdas)/rabbitmq(/|$)' >pcp-pmda-rabbitmq-files +basic_manifest | keep '(etc/pcp|pmdas)/rds(/|$)' >pcp-pmda-rds-files basic_manifest | keep '(etc/pcp|pmdas)/redis(/|$)' >pcp-pmda-redis-files basic_manifest | keep '(etc/pcp|pmdas)/resctrl(/|$)|sys-fs-resctrl' >pcp-pmda-resctrl-files basic_manifest | keep '(etc/pcp|pmdas)/rocestat(/|$)' >pcp-pmda-rocestat-files @@ -2519,7 +2535,7 @@ for pmda_package in \ nutcracker nvidia \ openmetrics openvswitch oracle \ pdns perfevent podman postfix postgresql \ - rabbitmq redis resctrl rocestat roomtemp rpm rsyslog \ + rabbitmq rds redis resctrl rocestat roomtemp rpm rsyslog \ samba sendmail shping slurm smart snmp \ sockets statsd summary systemd \ unbound uwsgi \ @@ -2913,6 +2929,9 @@ exit 0 %preun pmda-rocestat %{pmda_remove "$1" "rocestat"} +%preun pmda-rds +%{pmda_remove "$1" "rds"} + %endif %preun pmda-apache @@ -3263,6 +3282,8 @@ fi %files pmda-rocestat -f pcp-pmda-rocestat-files.rpm +%files pmda-rds -f pcp-pmda-rds-files.rpm + %files pmda-uwsgi -f pcp-pmda-uwsgi-files.rpm %files export-pcp2graphite -f pcp-export-pcp2graphite-files.rpm diff --git a/src/pmdas/GNUmakefile b/src/pmdas/GNUmakefile index d61ec32360..4f37862263 100644 --- a/src/pmdas/GNUmakefile +++ b/src/pmdas/GNUmakefile @@ -39,7 +39,7 @@ PLPMDAS = bonding netfilter zimbra postgresql \ PYPMDAS = bcc gluster zswap unbound mic haproxy \ json libvirt lio openmetrics elasticsearch \ bpftrace mssql netcheck rabbitmq openvswitch \ - nfsclient mongodb uwsgi rocestat hdb + nfsclient mongodb uwsgi rocestat hdb rds SUBDIRS = $(CPMDAS) $(PLPMDAS) $(PYPMDAS) LDIRT = pmcd.conf diff --git a/src/pmdas/rds/GNUmakefile b/src/pmdas/rds/GNUmakefile new file mode 100644 index 0000000000..3c0f1c68a9 --- /dev/null +++ b/src/pmdas/rds/GNUmakefile @@ -0,0 +1,52 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# + +TOPDIR = ../../.. +include $(TOPDIR)/src/include/builddefs + +IAM = rds +PYSCRIPT = pmda$(IAM).python +LDIRT = domain.h root pmns $(IAM).log +DOMAIN = GLUSTER + +PMDAADMDIR = $(PCP_PMDASADM_DIR)/$(IAM) +PMDATMPDIR = $(PCP_PMDAS_DIR)/$(IAM) + +MAN_SECTION = 1 +MAN_PAGES = pmda$(IAM).$(MAN_SECTION) +MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION) + +default_pcp default: build-me + +include $(BUILDRULES) + +ifeq "$(HAVE_PYTHON)" "true" +build-me: check_domain +install_pcp install: default + $(INSTALL) -m 755 -d $(PMDAADMDIR) + $(INSTALL) -m 755 -d $(PMDATMPDIR) + $(INSTALL) -m 755 -t $(PMDATMPDIR) Install Remove $(PYSCRIPT) $(PMDAADMDIR) + @$(INSTALL_MAN) +else +build-me: +install_pcp install: + @$(INSTALL_MAN) +endif + +check_domain: ../../pmns/stdpmid + $(DOMAIN_PYTHONRULE) + +check:: $(MAN_PAGES) + $(MANLINT) $^ diff --git a/src/pmdas/rds/Install b/src/pmdas/rds/Install new file mode 100755 index 0000000000..5eada91604 --- /dev/null +++ b/src/pmdas/rds/Install @@ -0,0 +1,35 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# + +. $PCP_DIR/etc/pcp.env +. $PCP_SHARE_DIR/lib/pmdaproc.sh + +iam=rds +python_opt=true +daemon_opt=false + +# Prepare PMDA Python files (needed for easier packaging) +$PCP_PYTHON_PROG $PCP_PMDAS_DIR/rds/pyprep + +# +# See pmcd(1) man page. PMDA starts up in the "not ready" state. +# When it has finished starting up, it sends a PM_ERR_PMDAREADY +# error PDU to PMCD to indicate it's ready to start processing +# requests. + +ipc_prot="binary notready" +pmdaSetup +pmdaInstall +exit diff --git a/src/pmdas/rds/Remove b/src/pmdas/rds/Remove new file mode 100755 index 0000000000..a77324f961 --- /dev/null +++ b/src/pmdas/rds/Remove @@ -0,0 +1,35 @@ +# +# Copyright (c) 2025 Oracle and/or its affiliates. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# Remove the rds PMDA +# + +# source the PCP configuration environment variables +. $PCP_DIR/etc/pcp.env + +# Get the common procedures and variable assignments +# +. $PCP_SHARE_DIR/lib/pmdaproc.sh + +# The name of the PMDA +# +iam=rds + +# Do it +# +pmdaSetup +pmdaRemove + + +exit diff --git a/src/pmdas/rds/domain.h b/src/pmdas/rds/domain.h new file mode 100644 index 0000000000..925639b909 --- /dev/null +++ b/src/pmdas/rds/domain.h @@ -0,0 +1 @@ +#define RDS 256 diff --git a/src/pmdas/rds/modules/ping.py b/src/pmdas/rds/modules/ping.py new file mode 100755 index 0000000000..9bcb8e3811 --- /dev/null +++ b/src/pmdas/rds/modules/ping.py @@ -0,0 +1,320 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +""" +Python implementation of ping +""" +from collections import defaultdict +import json +import socket +import sys +import os +import time +import select +from datetime import datetime +import argparse +import struct +import ipaddress +import threading +import signal +import re +import concurrent.futures + +sys.path.append("/var/lib/pcp/pmdas/rds/modules") +from rds_info import RdsInfo + +ICMP_ECHO = 8 +ICMP_MAX_RECV = 2048 +RDS_INFO_LEN = 176 +SOL_RDS = 276 +RDS_INFO_IB_CONNECTIONS = 10008 + +pings_sent = 0 +pending = 0 +own_id = os.getpid() & 0xFFFF +pings_json = {} + + +def signal_handler(sig, frame): + sys.exit(0) + + +def create_socket(): + try: + sock_fd = socket.socket( + socket.AF_INET, socket.SOCK_RAW, 1) # 1 for ICMP + return sock_fd + except socket.error as err: + print("socket creation failed with error %s" % (err)) + return None + + +def bind_socket(sock_fd, src_ip): + src_port = 0 + try: + sock_fd.bind((src_ip, src_port)) + except socket.error as err: + print("socket bind failed with error %s" % (err)) + + +def calculate_checksum(source_string): + countTo = (int(len(source_string) / 2)) * 2 + total = 0 + count = 0 + + # Handle bytes in pairs (decoding as short ints) + loByte = 0 + hiByte = 0 + while count < countTo: + if sys.byteorder == "little": + loByte = source_string[count] + hiByte = source_string[count + 1] + else: + loByte = source_string[count + 1] + hiByte = source_string[count] + total = total + ((hiByte) * 256 + (loByte)) + count += 2 + + # Handle last byte if applicable (odd-number of bytes) + # Endianness should be irrelevant in this case + if countTo < len(source_string): # Check for odd length + loByte = source_string[len(source_string) - 1] + total += loByte + + total &= 0xffffffff # Truncate total to 32 bits (a variance from ping.c, which + # uses signed ints, but overflow is unlikely in ping) + + total = (total >> 16) + (total & 0xffff) # Add high 16 bits to low 16 bits + total += (total >> 16) # Add carry from above (if any) + answer = ~total & 0xffff # Invert and truncate to 16 bits + answer = socket.htons(answer) + + return answer + + +def create_rds_socket(): + try: + sock_fd = socket.socket(socket.PF_RDS, socket.SOCK_SEQPACKET, 0) + return sock_fd + except socket.error as err: + print("socket creation failed with error %s" % (err)) + return None + + +def htosi(data, signed): + hex_string = data.hex() + return int.from_bytes(bytes.fromhex(hex_string), byteorder='little', signed=signed) + + +def send_ping(sock_fd, src_ip, dst_ip_list, send_timestamps): + seq_number = 0 + global own_id + global pings_sent + + for dst_ip in dst_ip_list: + checksum = 0 + header = struct.pack("!BBHHH", ICMP_ECHO, 0, + checksum, own_id, seq_number) + checksum = calculate_checksum(header) + header = struct.pack("!BBHHH", ICMP_ECHO, 0, + checksum, own_id, seq_number) + packet = header + try: + sock_fd.sendto(packet, (dst_ip, 1)) + send_timestamps[dst_ip] = datetime.now() + except socket.error as err: + print("socket send failed with error %s" % (err)) + # remove the added entry from the list + del send_timestamps[dst_ip] + pings_sent = pings_sent + 1 + + +def recv_pong(sock_fd, src_ip, num_dest, timeout, send_timestamps, print_all): + + inputs = [sock_fd] + outputs = [] + latencies_info = "" + pongs_rcvd = 0 + sock_fd.settimeout(timeout) + + select_timeout = timeout + + select_timeout_msec = select_timeout * 1000 + while True: + select_start_time = int(round(time.time() * 1000)) + readable, writable, exceptional = select.select( + inputs, outputs, inputs, select_timeout) + + for _ in readable: + try: + packet_data, (ipaddr, _) = sock_fd.recvfrom(ICMP_MAX_RECV) + icmp_header = struct.unpack("!BBHHH", packet_data[20:28]) + + if icmp_header[3] == own_id: + latency = datetime.now() - send_timestamps[ipaddr] + lat_data = ("%s %s - %.3f |" % + (src_ip, ipaddr, latency.microseconds)) + latencies_info = latencies_info + lat_data + pongs_rcvd = pongs_rcvd + 1 + conn = "%s-%s" % (src_ip, ipaddr) + pings_json[conn] = latency.microseconds + del send_timestamps[ipaddr] + except KeyError: + continue + + curr_time = int(round(time.time() * 1000)) + time_elapsed = curr_time - select_start_time + select_timeout_msec = select_timeout_msec - time_elapsed + if select_timeout_msec < 0: + for keys, _ in send_timestamps.items(): + lat_data = ("%s %s - timeout |" % (src_ip, keys)) + latencies_info = latencies_info + lat_data + conn = "%s-%s" % (src_ip, keys) + pings_json[conn] = -1 + break + + select_timeout = select_timeout_msec/1000 + + if num_dest - pongs_rcvd == 0: + break + + if not (readable or writable or exceptional): + for keys, _ in send_timestamps.items(): + lat_data = ("%s %s - timeout |" % (src_ip, keys)) + latencies_info = latencies_info + lat_data + conn = "%s-%s" % (src_ip, keys) + pings_json[conn] = -1 + + break + + if not print_all: + print(latencies_info) + + +def get_each_entry_len(data): + try: + rds_info_len = data[-4:].decode() + except ValueError: + rds_info_len = data[-4:-2].decode() + + if '\x00' in rds_info_len: + rds_info_len = ' '.join(rds_info_len.split('\x00')) + data = data[:-4] + + rds_info_len = re.findall(r'\d+', rds_info_len) + rds_info_len = int(rds_info_len[-1]) + + return data, rds_info_len + +def ping_destinations(src_ip, dest_ip_list, timeout): + sock_fd = create_socket() + bind_socket(sock_fd, src_ip) + send_timestamps = {} + num_dest = len(dest_ip_list) + send_ping(sock_fd, src_ip, dest_ip_list, send_timestamps) + recv_pong(sock_fd, src_ip, num_dest, timeout, send_timestamps, True) + sock_fd.close() + + +def ping_all_avlbl_dest(timeout): + rds_info = RdsInfo() + rds_connections = rds_info.main('-I').split("\n") + + conns_dict = defaultdict(list) + + for conn in rds_connections: + saddr, daddr = conn.split()[0:2] + if(validate_ip(saddr) == 0 and validate_ip(daddr) == 0): + conns_dict[saddr].append(daddr) + + with concurrent.futures.ThreadPoolExecutor() as executor: + for saddr, dest_ip_list in conns_dict.items(): + executor.submit(ping_destinations, saddr, dest_ip_list, timeout) + + res = json.dumps(pings_json) + return res + + +def validate_ip(ip_addr_str): + try: + ipaddress.ip_address(ip_addr_str) + return 0 + except ValueError: + print("Invalid IP %s" % ip_addr_str) + return 1 + + +def ping(args): + dest_ip_list = [str(item) for item in args.list.split(' ')] + for ip_addr in dest_ip_list: + if validate_ip(ip_addr): + print("Invalid IP being passed %s. Please rectify." % (ip_addr)) + sys.exit(1) + + sock_fd = create_socket() + bind_socket(sock_fd, args.source_ip) + + num_destinations = len(dest_ip_list) + send_timestamps = {} + + thread2 = threading.Thread(target=recv_pong, args=( + sock_fd, args.source_ip, num_destinations, args.timeout, send_timestamps, False)) + thread2.start() + + thread1 = threading.Thread(target=send_ping, args=( + sock_fd, args.source_ip, dest_ip_list, send_timestamps)) + thread1.start() + + thread2.join() + thread1.join() + + sock_fd.close() + + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument(dest='destination_ip', + help="destination ip is mandatory") + parser = argparse.ArgumentParser(description='python version of ping utility.', + epilog="sample usage: ping.py -d \"dst_ip_1 dst_ip_2\" -I src_ip") + parser.add_argument('-d', '--list', type=str) + parser.add_argument('-I', '--source_ip', type=str) + parser.add_argument('-t', '--timeout', type=float, default=3) + parser.add_argument('-a', '--auto', action='store_true') + parser.add_argument('-c', '--count', type=int, default=-1) + + args = parser.parse_args() + + if args.auto: + ping_all_avlbl_dest(args.timeout) + print(pings_json) + sys.exit(1) + + else: + if args.count == -1: + while True: + ping(args) + time.sleep(1) + else: + iterations = args.count + while iterations > 0: + ping(args) + iterations = iterations - 1 + if iterations: + time.sleep(1) + + +signal.signal(signal.SIGINT, signal_handler) + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/src/pmdas/rds/modules/rds_info.py b/src/pmdas/rds/modules/rds_info.py new file mode 100755 index 0000000000..55a5822f37 --- /dev/null +++ b/src/pmdas/rds/modules/rds_info.py @@ -0,0 +1,407 @@ +# Copyright (c) 2025 Oracle and/or its affiliates. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +import sys +import struct +import psutil +import socket +import ctypes +import binascii +from datetime import datetime + +# Constants for socket operations +PF_RDS = 21 +SOCK_SEQPACKET = 5 +SOL_RDS = 276 + +RDS_INFO_COUNTERS = 10000 +RDS_INFO_CONNECTIONS = 10001 +RDS_INFO_SEND_MESSAGES = 10003 +RDS_INFO_RETRANS_MESSAGES = 10004 +RDS_INFO_RECV_MESSAGES = 10005 +RDS_INFO_SOCKETS = 10006 +RDS_INFO_TCP_SOCKETS = 10007 +RDS_INFO_IB_CONNECTIONS = 10008 +RDS_INFO_CONN_PATHS = 10020 + +# Connection drop reasons +CONN_DROP_REASONS = [ + "--", "user reset", "invalid connection state", "failure to move to DOWN state", + "connection destroy", "conn_connect failure", "hb timeout", "reconnect timeout", + "cancel operation on socket", "race between ESTABLISHED event and drop", + "conn is not in CONNECTING state", "qp event", "incoming REQ in CONN_UP state", + "incoming REQ in CONNECTING state", "passive setup_qp failure", + "rdma_accept failure", "active setup_qp failure", "rdma_connect failure", + "resolve_route failure", "detected rdma_cm_id mismatch", "ROUTE_ERROR event", + "ADDR_ERROR event", "CONNECT_ERROR or UNREACHABLE or DEVICE_REMOVE event", + "CONSUMER_DEFINED reject", "REJECTED event", "ADDR_CHANGE event", + "DISCONNECTED event", "TIMEWAIT_EXIT event", "post_recv failure", + "send_ack failure", "no header in incoming msg", "corrupted header in incoming msg", + "fragment header mismatch", "recv completion error", "send completion error", + "post_send failure", "rds_rdma module unload", "active bonding failover", + "corresponding loopback conn drop", "active bonding failback", "sk_state to TCP_CLOSE", + "tcp_send failure" +] + +class RdsInfo: + """Class to implement standard RDS info operations.""" + + libc = ctypes.CDLL("libc.so.6", use_errno=True) + + def create_rds_socket(self): + sock_fd = self.libc.socket(PF_RDS, SOCK_SEQPACKET, 0) + if sock_fd < 0: + return None + return sock_fd + + def get_rds_info_data(self, sock_fd, query_type): + """Retrieve RDS information data.""" + data_len = ctypes.c_int(0) + res = self.libc.getsockopt(sock_fd, SOL_RDS, query_type, None, ctypes.byref(data_len)) + if res < 0: + data_buffer = ctypes.create_string_buffer(int(data_len.value)) + res = self.libc.getsockopt(sock_fd, SOL_RDS, query_type, data_buffer, ctypes.byref(data_len)) + if res < 0: + return None + + return data_buffer.raw[:data_len.value], res + + return None, None + + @staticmethod + def little_endian_to_unsigned(hex_string, bit): + """Convert little-endian byte sequence to an unsigned integer.""" + bit_format = "") + print("Available options:") + for key, value in infos.items(): + print(f" {key}: {value['description']}") + return "" + + sock_fd = self.create_rds_socket() + res = infos[option]["method"](sock_fd) + self.libc.close(sock_fd) + return res + +if __name__ == "__main__": + # Ensure an argument is passed + if len(sys.argv) != 2: + print("Usage: python rds_info.py