diff --git a/couchdb/README.md b/couchdb/README.md index c3932285..b07e7088 100644 --- a/couchdb/README.md +++ b/couchdb/README.md @@ -118,6 +118,12 @@ https://support.site24x7.com/portal/en/kb/articles/run-python-plugin-scripts-in- | bulk_requests | Number of bulk document write requests received | | temporary_view_reads | Number of temporary (ad-hoc) view reads | | clients_requesting_changes | Number of clients currently requesting change feeds | +| lru_skips | Number of times the Least Recently Used (LRU) cache was skipped due to full capacity | +| emits | Number of key-value pairs emitted during map function execution | +| vdu_rejects | Number of document updates rejected by the validate_doc_update function | +| view_timeouts | Number of view requests that timed out | +| map_doc | Number of documents processed by map functions | + ## Performance | **Metric Name** | **Description** | |------------------|------------------| diff --git a/couchdb/couchdb.py b/couchdb/couchdb.py index 68b78b70..59c4d7da 100755 --- a/couchdb/couchdb.py +++ b/couchdb/couchdb.py @@ -74,7 +74,7 @@ 'view_reads', 'bulk_requests', 'temporary_view_reads', - 'clients_requesting_changes' + 'clients_requesting_changes','lru_skips','vdu_rejects','map_doc','emits','view_timeouts' ] }, "Performance": { @@ -222,7 +222,7 @@ def collect_couchdb(host, port, user, password): # -------- HTTP Other Metrics -------- httpd = stats.get('httpd') if httpd: - for key in ['view_reads','bulk_requests','temporary_view_reads','clients_requesting_changes']: + for key in ['view_reads','bulk_requests','temporary_view_reads','clients_requesting_changes','lru_skips','vdu_rejects','map_doc','emits','view_timeouts']: if key in httpd: data[key] = httpd[key].get('value', 0) else: diff --git a/couchserver/README.md b/couchserver/README.md index f3222aa5..a07a559f 100644 --- a/couchserver/README.md +++ b/couchserver/README.md @@ -13,6 +13,20 @@ Learn more https://www.site24x7.com/plugins/couchbase-monitoring.html - Download and install the latest version of the [Site24x7 agent](https://www.site24x7.com/app/client#/admin/inventory/add-monitor) in the server where you plan to run the plugin. - Download and install Python version 3 or higher. +## Create a new user and grant read-only access permission + +```bash +curl -u USERNAME:PASSWORD -X PUT \ +http://localhost:8091/settings/rbac/users/local/your_username \ +-d 'password=your_password&roles=cluster_admin,data_reader[*],query_select[*]' +``` +## Verify the new user + +Run the following command to check that the user was created successfully: + +```bash +curl -u USERNAME:PASSWORD http://localhost:8091/settings/rbac/users/local/your_username +``` ### Plugin Installation @@ -20,19 +34,20 @@ Learn more https://www.site24x7.com/plugins/couchbase-monitoring.html - Download the below files and place it under the "couchserver" directory. - wget https://raw.githubusercontent.com/site24x7/plugins/master/couchserver/couchserver.py - + wget https://raw.githubusercontent.com/site24x7/plugins/master/couchserver/couchserver.py && sed -i "1s|^.*|#! $(which python3)|" couchserver.py + wget https://raw.githubusercontent.com/site24x7/plugins/master/couchserver/couchserver.cfg - Edit the couchserver.py file with appropriate arguments and Execute the below command to check for the valid JSON output: - python couchserver.py + python3 couchserver.py --host 127.0.0.1 --port 8091 --user your_username --password your_password + #### Linux - Follow the steps in [this article](https://support.site24x7.com/portal/en/kb/articles/updating-python-path-in-a-plugin-script-for-linux-servers) to update the Python path in the couchserver.py script. - Place the "couchserver" folder under Site24x7 Linux Agent plugin directory : - Linux -> /opt/site24x7/monagent/plugins/couchserver + Linux -> /opt/site24x7/monagent/plugins #### Windows @@ -41,4 +56,75 @@ https://support.site24x7.com/portal/en/kb/articles/run-python-plugin-scripts-in- - Move the folder "couchserver" under Site24x7 Windows Agent plugin directory: - Windows -> C:\Program Files (x86)\Site24x7\WinAgent\monitoring\Plugins\couchserver + Windows -> C:\Program Files (x86)\Site24x7\WinAgent\monitoring\Plugins + + +## Metrics Captured + +| Metric Name | Description | +|-----------------------|-----------------------------------------------------------------------------| +| Bucket Items | Total number of documents/items in a bucket. | +| Cmd Get | Number of key-value GET operations performed per second. | +| Curr Items | Current number of active items in memory for the node. | +| Ops Queued | Number of operations currently queued and waiting to be processed. | +| Quota Total HDD | Total disk space quota allocated for Couchbase data. | +| Quota Total Ram Node | Total RAM quota assigned per node. | +| Quota Used Ram | Amount of RAM currently used by all buckets. | +| Quota Used Ram Node | RAM used by buckets on a specific node. | +| Total Bucket Items | Total items across all buckets in the cluster. | +| Total HDD | Total physical disk space available for Couchbase data. | +| Total Ram | Total physical RAM available on the cluster nodes. | +| Used HDD | Amount of disk space currently used by Couchbase data. | +| Used Ram | Amount of RAM currently used across the cluster. | +| Used by Data HDD | Percentage of disk space used specifically by bucket data (excluding indexes and metadata). | + +## Bucket + +| Metric Name | Description | +|-------------------|-----------------------------------------------------------------------------| +| Bucket Ops | Number of operations (reads, writes, deletes) per second on the bucket. | +| Get Hits | Number of successful GET operations (cache hits) per second. | +| Get Misses | Number of GET operations that did not find the document in memory. | +| Hit Ratio | Percentage of GET operations served from memory cache. | +| Evictions | Number of items removed from memory to free space for new data. | +| Cache Miss Rate | Percentage of GET operations that missed the cache. | +| Create Ops | Number of document creation operations per second. | +| Update Ops | Number of document update operations per second. | +| Bg Fetches | Number of background fetches from disk to memory per second. | +| Data Disk Size | Disk space used by the bucket's data (in MB). | +| Actual Disk Size | Total disk space used by the bucket, including metadata and overhead (in MB).| +| Doc Fragmentation | Percentage of fragmentation in the bucket data files. | +| Write Queue | Number of write operations waiting in the queue to be processed. | +| Bucket Mem Used | Amount of RAM used by this bucket (in MB). | + +## Memory + +| Metric Name | Description | +|--------------------------------------|-----------------------------------------------------------------------------| +| Mem High Water | Maximum memory used by the node before triggering memory management actions.| +| Mem Low Water | Minimum memory threshold before the node can reclaim memory. | +| Mem Overhead | Memory used internally by Couchbase for metadata and system operations (in MB). | +| Key Value Size | RAM used by key-value data on the node (in MB). | +| Node Cmd Get | Number of GET commands received by the node per second. | +| Node Get Hits | Number of successful GET operations (cache hits) at the node level. | +| Node Items | Number of active items in the node's memory. | +| Node Items Total | Total items including replicas on the node. | +| Node Bg Fetches | Number of background fetches from disk to memory on the node per second. | +| Node Mem Used | RAM used by the node for key-value storage (in MB). | +| Mem Actual Used | Actual RAM used by the node including overhead and active data (in MB). | +| Mem Actual Free | Actual free RAM available on the node (in MB). | +| Page Faults | Number of memory page faults encountered by the node. | +| Virtual Bucket Active Resident Items Ratio | Percentage of active items currently in memory (resident in RAM). | + +## Query + +| Metric Name | Description | +|-----------------------|-----------------------------------------------------------------------------| +| Query Elapsed Time | Total time taken to process the query, including network and planning (in ms). | +| Query Execution Time | Time taken by the query engine to actually execute the query (in ms). | +| Query Result Count | Number of rows/documents returned by the query. | +| Query Result Size | Size of the query result set (in bytes). | +| Query Service Load | Load on the query service, indicating how busy the query engine is. | + +## Sample Image : +image diff --git a/couchserver/couchserver.cfg b/couchserver/couchserver.cfg new file mode 100644 index 00000000..f2c31e3b --- /dev/null +++ b/couchserver/couchserver.cfg @@ -0,0 +1,5 @@ +[couchbase_config] +host = "localhost" +port = "8091" +user = "USERNAME" +password = "PASSWORD" diff --git a/couchserver/couchserver.py b/couchserver/couchserver.py index 2ad9fbf8..4cbedd50 100755 --- a/couchserver/couchserver.py +++ b/couchserver/couchserver.py @@ -1,94 +1,294 @@ -#!/usr/bin/python +#!/usr/bin/python3 import json +import requests +import argparse -import sys - -#if any changes to this plugin kindly increment the plugin version here. -PLUGIN_VERSION=1 - -#Setting this to true will alert you when there is a network problem while posting plugin data to server -HEARTBEAT="true" - -#Config Section -COUCHBASE_SERVER_HOST='127.0.0.1' - -COUCHBASE_SERVER_PORT="8091" - -COUCHBASE_SERVER_STATS_URI="pools/default" - -COUCHBASE_SERVER_USERNAME=None - -COUCHBASE_SERVER_PASSWORD=None - -REALM=None - -DEFAULT_TIMEOUT=30 - -METRICS_UNITS = {'hdd.ram':'MB',"hdd.quotaTotal": "MB", "hdd.total": "MB","hdd.used": "MB","hdd.usedByData": "MB","ram.quotaTotalPerNode": "MB","ram.quotaUsed": "MB","ram.quotaUsedPerNode": "MB","ram.total":"MB","ram.used": "MB"} - -PYTHON_MAJOR_VERSION = sys.version_info[0] - -if PYTHON_MAJOR_VERSION == 3: - import urllib - import urllib.request as connector -elif PYTHON_MAJOR_VERSION == 2: - import urllib2 as connector - -def metricCollector(): - data = {} - - #defaults - data['plugin_version'] = PLUGIN_VERSION - - data['heartbeat_required']=HEARTBEAT - - data['units']=METRICS_UNITS - - URL = "http://"+COUCHBASE_SERVER_HOST+":"+COUCHBASE_SERVER_PORT+"/"+COUCHBASE_SERVER_STATS_URI - - try: - - if COUCHBASE_SERVER_USERNAME and COUCHBASE_SERVER_PASSWORD: - password_mgr = connector.HTTPPasswordMgrWithDefaultRealm() - password_mgr.add_password(REALM, URL, COUCHBASE_SERVER_USERNAME, COUCHBASE_SERVER_PASSWORD) - auth_handler = connector.HTTPBasicAuthHandler(password_mgr) - opener = connector.build_opener(auth_handler) - connector.install_opener(opener) - response = connector.urlopen(URL, timeout=10) - byte_responseData = response.read() - str_responseData = byte_responseData.decode('UTF-8') - - couchserver_dict = json.loads(str_responseData) - data['hdd.total']=couchserver_dict['storageTotals']['hdd']['total'] - data['hdd.quotaTotal']=couchserver_dict['storageTotals']['hdd']['quotaTotal'] - data['hdd.usedByData']=couchserver_dict['storageTotals']['hdd']['usedByData'] - data['hdd.used']=couchserver_dict['storageTotals']['hdd']['used'] - - data['ram.used']=couchserver_dict['storageTotals']['ram']['used'] - data['ram.quotaUsed']=couchserver_dict['storageTotals']['ram']['quotaUsed'] - data['ram.quotaUsedPerNode']=couchserver_dict['storageTotals']['ram']['quotaUsedPerNode'] - data['ram.quotaTotalPerNode']=couchserver_dict['storageTotals']['ram']['quotaTotalPerNode'] - data['ram.total']=couchserver_dict['storageTotals']['ram']['total'] - for item in data: - if '.' in item: - data[item]=convertBytesToMB(data[item]) - - except Exception as e: - data['status']=0 - data['msg']=str(e) - - return data - -def convertBytesToMB(v): +PLUGIN_VERSION = 1 +HEARTBEAT = "true" + +# ------------------------- +# Metric Units +# ------------------------- +UNITS = { + "Total HDD": "MB", + "Quota Total HDD": "MB", + "Used HDD": "MB", + "Used by Data HDD": "%", + "Total Ram": "MB", + "Used Ram": "MB", + "Quota Used Ram": "MB", + "Quota Used Ram Node": "MB", + "Quota Total Ram Node": "MB", + "Bucket Ops": "ops/sec", + "Hit Ratio": "%", + "Cache Miss Rate": "%", + "Data Disk Size": "MB", + "Actual Disk Size": "MB", + "Doc Fragmentation": "%", + "Bucket Mem Used": "MB", + "Mem High Water": "%", + "Mem Low Water": "%", + "Mem Overhead": "MB", + "Key Value Size": "MB", + "Node Mem Used": "MB", + "Mem Actual Used": "MB", + "Mem Actual Free": "MB", + "Index Disk Size": "MB", + "Index Ram Used": "MB", + "Index Fragmentation": "%", + "Query Elapsed Time": "ms", + "Query Execution Time": "ms", + "Query Result Size": "bytes", + "Xdcr Ops": "ops/sec", + "Xdcr Meta Latency": "ms", + "Virtual Bucket Active Resident Items Ratio": "%", + "Ops Queued": "ops/sec", + "Cmd Get": "ops/sec", + "Get Hits": "ops/sec", + "Ep Bg Fetched": "items/sec", + "Mem Used": "MB", + "Mem Actual Free": "MB", + "Mem Actual Used": "MB", + "Cache Hits": "ops/sec", + "Cache Misses": "ops/sec", + "Disk Write Queue": "ops/sec", + "Disk Read Queue": "ops/sec" +} + +# ------------------------- +# Tabs +# ------------------------- +TABS = { + "Bucket": { + "order": 0, + "tablist": [ + "Bucket_Table","Bucket Ops","Get Hits","Get Misses","Hit Ratio","Evictions","Cache Miss Rate", + "Create Ops","Update Ops","Bg Fetches","Data Disk Size","Actual Disk Size", + "Doc Fragmentation","Write Queue","Bucket Mem Used" + ] + }, + "Memory": { + "order": 1, + "tablist": [ + "Mem High Water","Mem Low Water","Mem Overhead","Key Value Size","Node Cmd Get","Node Get Hits", + "Node Items","Node Items Total","Node Bg Fetches","Node Mem Used", + "Mem Actual Used","Mem Actual Free","Page Faults","Virtual Bucket Active Resident Items Ratio" + ] + }, + "Index": { + "order": 2, + "tablist": [ + "Index Docs Pending","Index Docs Queued","Index Docs Failed","Index Docs Indexed", + "Index Disk Size","Index Ram Used","Index Fragmentation" + ] + }, + "Query": { + "order": 3, + "tablist": [ + "Query Elapsed Time","Query Execution Time","Query Result Count","Query Result Size","Query Service Load", + "Xdcr Ops","Xdcr Docs Queue","Xdcr Meta Latency", + "Dcp Replica Items","Dcp Xdcr Items","Dcp View Items" + ] + } +} + +# ------------------------- +# Helper Functions +# ------------------------- +def bytes_to_mb(value): + try: + return round(float(value) / 1024 / 1024, 2) + except: + return 0 + +def safe_get(d, key): + if d and key in d: + return d[key] + return None + +def fetch_query_metrics(host, port, user, password, bucket): + query_port=8093 + url = f"http://{host}:{query_port}/query/service" + query = f'SELECT COUNT(*) FROM `{bucket}`;' + payload = {"statement": query} + metrics = {} + try: + r = requests.post(url, auth=(user, password), json=payload, timeout=10) + r.raise_for_status() + resp = r.json() + m = resp.get("metrics", {}) + # Convert elapsedTime, executionTime strings (like '12.5ms') to float ms + def to_ms(t): + try: + if isinstance(t, str) and t.endswith("ms"): + return float(t[:-2]) + return float(t) + except: + return 0 + metrics['Query Elapsed Time'] = to_ms(m.get("elapsedTime")) + metrics['Query Execution Time'] = to_ms(m.get("executionTime")) + metrics['Query Result Count'] = m.get("resultCount", 0) + metrics['Query Result Size'] = m.get("resultSize", 0) + metrics['Query Service Load'] = m.get("serviceLoad", 0) + except Exception as e: + metrics['msg'] = f"Query metrics error for bucket {bucket}: {str(e)}" + return metrics + +# ------------------------- +# Main collection function +# ------------------------- +def collect_couchbase(host, port, user, password): + data = {} try: - byte_s=float(v) - kilobytes=byte_s/1024; - megabytes=kilobytes/1024; - v=round(megabytes,2) + base_url = f"http://{host}:{port}/pools/default" + r = requests.get(base_url, auth=(user, password), timeout=10) + stats = r.json() + + # -------- Bucket Metrics -------- + bucket_names = stats.get('bucketNames', []) + bucket_table = [] + + for bucket_info in bucket_names: + bucket_name = bucket_info.get('bucketName') + if bucket_name: + bucket_url = f"{base_url}/buckets/{bucket_name}" + r_bucket = requests.get(bucket_url, auth=(user,password), timeout=10) + bucket = r_bucket.json() + basic = bucket.get('basicStats', {}) + if basic: + data['Bucket Items'] = basic.get('itemCount', 0) + data['Total Bucket Items'] = basic.get('itemCount', 0) + data['Bucket Ops'] = basic.get('opsPerSec', 0) + data['Get Hits'] = basic.get('get_hits', 0) + data['Get Misses'] = basic.get('get_misses', 0) + data['Hit Ratio'] = basic.get('hitRatio', 0) + data['Evictions'] = basic.get('evictions', 0) + data['Cache Miss Rate'] = basic.get('cacheMissRate', 0) + data['Create Ops'] = basic.get('opsCreate', 0) + data['Update Ops'] = basic.get('opsUpdate', 0) + data['Bg Fetches'] = basic.get('bgFetched', 0) + data['Data Disk Size'] = bytes_to_mb(basic.get('diskUsed', 0)) + data['Actual Disk Size'] = bytes_to_mb(basic.get('diskUsed', 0)) + data['Doc Fragmentation'] = basic.get('docFragmentation', 0) + data['Write Queue'] = basic.get('opsQueued', 0) + data['Bucket Mem Used'] = bytes_to_mb(basic.get('memUsed', 0)) + + # -------- Added for Bucket Table -------- + bucket_table.append({ + "name": bucket_name, + "Item_Count": basic.get('itemCount', 0), + "Ops_Per_Sec": basic.get('opsPerSec', 0) + }) + + # -------- Query Metrics (bucket specific) -------- + query_metrics = fetch_query_metrics(host, port, user, password, bucket_name) + data.update(query_metrics) + + # Attach the table to data + data["Bucket_Table"] = bucket_table # <--- added table output + + # -------- Memory / Storage Totals -------- + storage_totals = stats.get('storageTotals', {}) + ram = storage_totals.get('ram', {}) + hdd = storage_totals.get('hdd', {}) + + if ram: + data['Total Ram'] = bytes_to_mb(ram.get('total', 0)) + data['Used Ram'] = bytes_to_mb(ram.get('used', 0)) + data['Quota Used Ram'] = bytes_to_mb(ram.get('quotaUsed', 0)) + data['Quota Used Ram Node'] = bytes_to_mb(ram.get('quotaUsedPerNode', 0)) + data['Quota Total Ram Node'] = bytes_to_mb(ram.get('quotaTotalPerNode', 0)) + + if hdd: + data['Total HDD'] = bytes_to_mb(hdd.get('total', 0)) + data['Quota Total HDD'] = bytes_to_mb(hdd.get('quotaTotal', 0)) + data['Used HDD'] = bytes_to_mb(hdd.get('used', 0)) + data['Used by Data HDD'] = bytes_to_mb(hdd.get('usedByData', 0)) + + # -------- Node Interesting Stats -------- + nodes = stats.get('nodes', []) + for node in nodes: + interesting = node.get('interestingStats', {}) + if interesting: + data['Mem High Water'] = interesting.get('mem_high_watermark', 0) + data['Mem Low Water'] = interesting.get('mem_low_watermark', 0) + data['Mem Overhead'] = bytes_to_mb(interesting.get('mem_overhead', 0)) + data['Key Value Size'] = bytes_to_mb(interesting.get('mem_used', 0)) + data['Node Cmd Get'] = interesting.get('cmd_get', 0) + data['Node Get Hits'] = interesting.get('get_hits', 0) + data['Node Items'] = interesting.get('curr_items', 0) + data['Node Items Total'] = interesting.get('curr_items_tot', 0) + data['Node Bg Fetches'] = interesting.get('ep_bg_fetched', 0) + data['Node Mem Used'] = bytes_to_mb(interesting.get('mem_used', 0)) + data['Page Faults'] = interesting.get('page_faults', 0) + data['Virtual Bucket Active Resident Items Ratio'] = interesting.get('vb_active_resident_items_ratio', 0) + data['Cmd Get'] = interesting.get('cmd_get', 0) + data['Curr Items'] = interesting.get('curr_items', 0) + data['Ops Queued'] = interesting.get('ops_queued', 0) + + data['Mem Actual Used'] = bytes_to_mb(stats.get('memActualUsed', 0)) + data['Mem Actual Free'] = bytes_to_mb(stats.get('memActualFree', 0)) + + # -------- Index Metrics -------- + indexes = stats.get('indexes', []) + for index in indexes: + if index: + data['Index Docs Pending'] = index.get('docsPending', 0) + data['Index Docs Queued'] = index.get('docsQueued', 0) + data['Index Docs Failed'] = index.get('docsFailed', 0) + data['Index Docs Indexed'] = index.get('docsIndexed', 0) + data['Index Disk Size'] = bytes_to_mb(index.get('diskSize', 0)) + data['Index Ram Used'] = bytes_to_mb(index.get('ramUsed', 0)) + data['Index Fragmentation'] = index.get('fragmentation', 0) + + # -------- XDCR / DCP Metrics -------- + xdcr = stats.get('xdcr', {}) + if xdcr: + if 'ops' in xdcr: + data['Xdcr Ops'] = xdcr['ops'] + if 'docsQueued' in xdcr: + data['Xdcr Docs Queue'] = xdcr['docsQueued'] + if 'metaLatency' in xdcr: + data['Xdcr Meta Latency'] = xdcr['metaLatency'] + + dcp = stats.get('dcp', {}) + if dcp: + if 'replicaItems' in dcp: + data['Dcp Replica Items'] = dcp['replicaItems'] + if 'xdcrItems' in dcp: + data['Dcp Xdcr Items'] = dcp['xdcrItems'] + if 'viewItems' in dcp: + data['Dcp View Items'] = dcp['viewItems'] + except Exception as e: - pass - return v + data['msg'] = f"Couchbase Error: {str(e)}" + return data + +# ------------------------- +# Main +# ------------------------- +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", default="8091") + parser.add_argument("--user", default="admin") + parser.add_argument("--password", default="password") + args = parser.parse_args() + + output = { + "plugin_version": PLUGIN_VERSION, + "heartbeat_required": HEARTBEAT, + "units": UNITS + } + + # Couchbase metrics + output.update(collect_couchbase(args.host, args.port, args.user, args.password)) + + # Tabs at the end + output["tabs"] = TABS + + print(json.dumps(output, indent=4)) if __name__ == "__main__": - print(json.dumps(metricCollector(), indent=4, sort_keys=True)) + main() diff --git a/port/port.cfg b/port/port.cfg index 50dc5883..69fb142d 100644 --- a/port/port.cfg +++ b/port/port.cfg @@ -1,2 +1,3 @@ -[port_check] -port=22 \ No newline at end of file +[PORT_PLUGIN] +port = 22 +duration = 5 diff --git a/port/port.py b/port/port.py index 05e620b7..4b42acb1 100644 --- a/port/port.py +++ b/port/port.py @@ -1,105 +1,118 @@ #!/usr/bin/python3 - +import json +import os +import sys import time -import psutil import socket -import json import argparse -from psutil import cpu_count -def monitor_port_metrics(port, duration): - num_cores = psutil.cpu_count() - start_time = time.time() - end_time = start_time + duration +PLUGIN_VERSION = "1" +HEARTBEAT = "true" + +plugin_rs = {} +metric_units = { + "Connection Latency": "ms", + "Cpu Usage": "%", + "Memory Usage": "mb", + "Throughput Sent": "bytes", + "Throughput Received": "bytes" +} +plugin_rs['plugin_version'] = PLUGIN_VERSION +plugin_rs['heartbeat_required'] = HEARTBEAT + +try: + plugin_script_path = os.path.dirname(os.path.realpath(__file__)) + sys.path.insert(0, plugin_script_path) + + try: + import psutil + except Exception as e: + plugin_rs['status'] = 0 + plugin_rs['msg'] = f"psutil module not installed or failed to load: {str(e)}" + print(json.dumps(plugin_rs, indent=4)) + sys.exit(1) + + # ------------------------------- + # Argument Parsing (instead of cfg file) + # ------------------------------- + parser = argparse.ArgumentParser(description="Monitor a port and gather metrics.") + parser.add_argument("--port", type=int, required=True, help="The port number to monitor") + parser.add_argument("--duration", type=int, default=5, help="Duration to monitor in seconds (default: 5)") + args = parser.parse_args() + + port_to_monitor = args.port + duration = args.duration + # ------------------------------- + # Metrics dictionary metrics = { - "plugin_version": 1, - "heartbeat_required": True, - "Connection Latency": None, + "plugin_version": PLUGIN_VERSION, + "heartbeat_required": HEARTBEAT, + "units": metric_units, + "Connection Latency": 0, "Cpu Usage": 0, "Memory Usage": 0, "Active Connections": 0, "Connection Rate": 0, "Port Status Text": "closed", "Processes Count": 0, - "Throughput Sent": 0, - "Throughput Received": 0, - "units":{ - "Connection Latency":"ms", - "Cpu Usage":"%", - "Memory Usage":"mb", - "Throughput Sent": "bytes", - "Throughput Received": "bytes" - } + "Throughput Sent": 0, + "Throughput Received": 0, } connections_established = 0 - initial_io = psutil.net_io_counters() + start_time = time.time() + end_time = start_time + duration + while time.time() < end_time: latency_start = time.time() try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(1) - if s.connect_ex(('localhost', port)) == 0: - metrics["Connection Latency"] = (time.time() - latency_start)*1000 + if s.connect_ex(('localhost', port_to_monitor)) == 0: + metrics["Connection Latency"] = (time.time() - latency_start) * 1000 connections_established += 1 metrics["Port Status Text"] = "open" - except Exception as e: + except Exception: metrics["Port Status Text"] = "closed" - metrics["status"] = 0 - metrics["msg"] = f"Error: {str(e)}" - break # Update Port Status - if metrics["Port Status Text"] == "open": - metrics["Port Status"] = 1 - else: - metrics["Port Status"] = 0 + metrics["Port Status"] = 1 if metrics["Port Status Text"] == "open" else 0 - processes = set() + # Processes info + num_cores = psutil.cpu_count() + processes = set() for conn in psutil.net_connections(kind='inet'): - if conn.laddr.port == port and conn.status == 'LISTEN': + if conn.laddr.port == port_to_monitor and conn.status == 'LISTEN': try: process = psutil.Process(conn.pid) processes.add(conn.pid) - metrics["Cpu Usage"] += process.cpu_percent(interval=0.1) / num_cores + metrics["Cpu Usage"] += process.cpu_percent(interval=0.1) / num_cores metrics["Memory Usage"] += process.memory_info().rss / (1024 * 1024) except Exception: pass metrics["Processes Count"] = len(processes) - metrics["Active Connections"] = sum( - 1 for conn in psutil.net_connections(kind='inet') if conn.laddr.port == port + 1 for conn in psutil.net_connections(kind='inet') if conn.laddr.port == port_to_monitor ) final_io = psutil.net_io_counters() - metrics["Throughput Sent"] = final_io.bytes_sent - initial_io.bytes_sent metrics["Throughput Received"] = final_io.bytes_recv - initial_io.bytes_recv - metrics["Connection Rate"] = connections_established / duration if metrics["Port Status Text"] == "closed": - metrics["status"] = 0 metrics["msg"] = "Port is closed" + else: + metrics["msg"] = "Port is open" - return metrics - -def main(): - parser = argparse.ArgumentParser(description="Monitor a port and gather metrics.") - parser.add_argument("--port", type=int, required=True, help="The port number to monitor") - - args = parser.parse_args() - port_to_monitor = args.port - - monitor_duration = 5 - - metrics = monitor_port_metrics(port_to_monitor, monitor_duration) + plugin_rs.update(metrics) - print(json.dumps(metrics, indent=4)) +except Exception as e: + plugin_rs['status'] = 0 + plugin_rs['msg'] = str(e) -if __name__ == "__main__": - main() +print(json.dumps(plugin_rs, indent=4)) diff --git a/port/psutil_built.zip b/port/psutil_built.zip new file mode 100644 index 00000000..8c34f674 Binary files /dev/null and b/port/psutil_built.zip differ diff --git a/port/readme.md b/port/readme.md index 5a5fb384..f33dd191 100644 --- a/port/readme.md +++ b/port/readme.md @@ -3,11 +3,6 @@ ## Prerequisites - Download and install the latest version of the [Site24x7 Linux agent/Site24x7 Windows agent](https://www.site24x7.com/app/client#/admin/inventory/add-monitor) in the server where you plan to run the plugin. -- Install the required Python module psutil by running the following command: - -```bash -pip install psutil -``` ### Plugin Installation @@ -23,8 +18,12 @@ cd port/ ```bash wget https://raw.githubusercontent.com/site24x7/plugins/master/port/port.py && sed -i "1s|^.*|#! $(which python3)|" port.py wget https://raw.githubusercontent.com/site24x7/plugins/master/port/port.cfg +wget https://raw.githubusercontent.com/site24x7/plugins/master/port/psutil_built.zip -O psutil_built.zip +``` +### Extract the ZIP File in the directory +```bash +unzip psutil_built.zip ``` - - Execute the below command with appropriate arguments to check for the valid json output: ```bash