flexmetric 0.1.4__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,15 +2,20 @@ import yaml
2
2
  import sqlite3
3
3
  import re
4
4
 
5
+
5
6
  def read_yaml_file(file_path):
6
- with open(file_path, 'r') as f:
7
+ with open(file_path, "r") as f:
7
8
  return yaml.safe_load(f)
8
9
 
10
+
9
11
  def get_database_config(databases, db_name):
10
12
  for db in databases:
11
- if db['name'] == db_name:
13
+ if db["name"] == db_name:
12
14
  return db
13
- raise ValueError(f"[ERROR] Database config for '{db_name}' not found in database.yaml.")
15
+ raise ValueError(
16
+ f"[ERROR] Database config for '{db_name}' not found in database.yaml."
17
+ )
18
+
14
19
 
15
20
  def execute_sqlite_query(db_path, query):
16
21
  try:
@@ -24,11 +29,13 @@ def execute_sqlite_query(db_path, query):
24
29
  print(f"[ERROR] SQLite query failed on {db_path}: {e}")
25
30
  return None
26
31
 
32
+
27
33
  def is_safe_query(query):
28
34
  # Remove leading spaces and brackets
29
35
  cleaned_query = query.strip().lower()
30
36
  # Match only queries that start with "select"
31
- return re.match(r'^\(*\s*select', cleaned_query) is not None
37
+ return re.match(r"^\(*\s*select", cleaned_query) is not None
38
+
32
39
 
33
40
  def process_database_queries(queries_file, databases_file):
34
41
  # Get queries from queries file
@@ -36,24 +43,26 @@ def process_database_queries(queries_file, databases_file):
36
43
  # Get database from database file
37
44
  databases_config = read_yaml_file(databases_file)
38
45
 
39
- commands = queries_config.get('commands', [])
40
- databases = databases_config.get('databases', [])
46
+ commands = queries_config.get("commands", [])
47
+ databases = databases_config.get("databases", [])
41
48
 
42
49
  all_results = []
43
50
 
44
51
  for cmd in commands:
45
52
  try:
46
- db_conf = get_database_config(databases, cmd['database'])
53
+ db_conf = get_database_config(databases, cmd["database"])
47
54
 
48
- if db_conf['db_type'] != 'sqlite':
49
- print(f"[WARN] Unsupported database type: {db_conf['db_type']} in command {cmd['name']}")
55
+ if db_conf["db_type"] != "sqlite":
56
+ print(
57
+ f"[WARN] Unsupported database type: {db_conf['db_type']} in command {cmd['name']}"
58
+ )
50
59
  continue
51
60
 
52
- db_path = db_conf['db_connection']
53
- query = cmd['query']
54
- label = cmd['label']
55
- label_value = cmd['label_value']
56
- # check if query is safe
61
+ db_path = db_conf["db_connection"]
62
+ query = cmd["query"]
63
+ label = cmd["label"]
64
+ label_value = cmd["label_value"]
65
+ # check if query is safe
57
66
  if is_safe_query(query):
58
67
  value = execute_sqlite_query(db_path, query)
59
68
  else:
@@ -62,14 +71,18 @@ def process_database_queries(queries_file, databases_file):
62
71
 
63
72
  if value is not None:
64
73
  result = {
65
- 'result': [{'label': label_value, 'value': value}],
66
- 'labels': [label]
74
+ "result": [{"label": label_value, "value": value}],
75
+ "labels": [label],
67
76
  }
68
77
  all_results.append(result)
69
78
  else:
70
- print(f"[INFO] No result for command '{cmd['name']}' on database '{cmd['database']}'")
79
+ print(
80
+ f"[INFO] No result for command '{cmd['name']}' on database '{cmd['database']}'"
81
+ )
71
82
 
72
83
  except Exception as e:
73
- print(f"[ERROR] Processing command '{cmd.get('name', 'unknown')}' failed: {e}")
84
+ print(
85
+ f"[ERROR] Processing command '{cmd.get('name', 'unknown')}' failed: {e}"
86
+ )
74
87
 
75
- return all_results
88
+ return all_results
@@ -0,0 +1,56 @@
1
+ from collections import deque
2
+ import threading
3
+ import time
4
+ import sched
5
+
6
+ class ExpiringMetricQueue:
7
+ def __init__(self, expiry_seconds=60, cleanup_interval=5):
8
+ self.queue = deque()
9
+ self.expiry_seconds = expiry_seconds
10
+ self.cleanup_interval = cleanup_interval
11
+ self.lock = threading.Lock()
12
+ self.scheduler = sched.scheduler(time.time, time.sleep)
13
+ self._start_cleanup()
14
+
15
+ def put(self, metric):
16
+ metric['timestamp'] = time.time()
17
+ with self.lock:
18
+ self.queue.append(metric)
19
+
20
+ def pop_all(self):
21
+ with self.lock:
22
+ items = list(self.queue)
23
+ self.queue.clear()
24
+ cleaned_items = []
25
+ for item in items:
26
+ cleaned_item = {k: v for k, v in item.items() if k != 'timestamp'}
27
+ cleaned_items.append(cleaned_item)
28
+
29
+ return cleaned_items
30
+
31
+ def _start_cleanup_thread(self):
32
+ thread = threading.Thread(target=self._cleanup, daemon=True)
33
+ thread.start()
34
+
35
+ def _start_cleanup(self):
36
+ def scheduled_cleanup():
37
+ self._cleanup()
38
+ # Schedule next run
39
+ self.scheduler.enter(self.cleanup_interval, 1, scheduled_cleanup)
40
+
41
+ # Schedule first run immediately
42
+ self.scheduler.enter(0, 1, scheduled_cleanup)
43
+ threading.Thread(target=self.scheduler.run, daemon=True).start()
44
+
45
+ def _cleanup(self):
46
+ current_time = time.time()
47
+ with self.lock:
48
+ original_len = len(self.queue)
49
+ self.queue = deque(
50
+ [item for item in self.queue if current_time - item['timestamp'] <= self.expiry_seconds]
51
+ )
52
+ cleaned = original_len - len(self.queue)
53
+ if cleaned > 0:
54
+ print(f"[MetricQueue] Cleaned {cleaned} expired metrics")
55
+
56
+ metric_queue = ExpiringMetricQueue(expiry_seconds=60, cleanup_interval=60)
@@ -0,0 +1,111 @@
1
+ import subprocess
2
+ import yaml
3
+ import re
4
+ from flexmetric.logging_module.logger import get_logger
5
+
6
+ logger = get_logger(__name__)
7
+
8
+ logger.info("prometheus is running")
9
+
10
+ def read_commands_from_yaml(file_path):
11
+ with open(file_path, 'r') as f:
12
+ config = yaml.safe_load(f)
13
+ return config.get('commands', [])
14
+
15
+ def execute_command_with_timeout(command, timeout):
16
+ try:
17
+ result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
18
+ if result.returncode != 0:
19
+ logger.info(f"Exception in running the command {command}")
20
+ return ''
21
+ return result.stdout.strip()
22
+ except subprocess.TimeoutExpired:
23
+ return ''
24
+ except Exception as ex:
25
+ logger.error(f"Exception : {ex}")
26
+ return ''
27
+
28
+ def parse_command_output(raw_output, label_column, value_column, fixed_label_value):
29
+ result_list = []
30
+ lines = raw_output.strip().splitlines()
31
+
32
+ for line in lines:
33
+ parts = line.strip().split()
34
+ if not parts:
35
+ continue
36
+
37
+ if label_column == 'fixed':
38
+ label = fixed_label_value or 'label'
39
+ else:
40
+ try:
41
+ label = parts[label_column]
42
+ except IndexError:
43
+ label = 'unknown'
44
+
45
+ try:
46
+ raw_value = parts[value_column]
47
+ cleaned_value = re.sub(r'[^\d\.\-]', '', raw_value)
48
+ value = float(cleaned_value) if cleaned_value else 1
49
+ except (IndexError, ValueError):
50
+ value = 1
51
+
52
+ result_list.append({'label': label, 'value': value})
53
+
54
+ return result_list
55
+
56
+ def process_single_command(cmd_info):
57
+ command = cmd_info['command']
58
+ label_name = cmd_info['label']
59
+ timeout = cmd_info.get('timeout_seconds', 30)
60
+ label_column = cmd_info.get('label_column', -1)
61
+ value_column = cmd_info.get('value_column', 0)
62
+ fixed_label_value = cmd_info.get('label_value')
63
+
64
+ raw_output = execute_command_with_timeout(command, timeout)
65
+ if raw_output == '':
66
+ logger.warning(f"No results for command {command}")
67
+ return None
68
+
69
+ result_list = parse_command_output(raw_output, label_column, value_column, fixed_label_value)
70
+
71
+ return {
72
+ 'result': result_list,
73
+ 'labels': [label_name]
74
+ }
75
+
76
+ def is_command_safe(command):
77
+ blacklist = ['rm', 'reboot', 'shutdown', 'halt', 'poweroff', 'mkfs', 'dd']
78
+ for dangerous_cmd in blacklist:
79
+ if dangerous_cmd in command.split():
80
+ return False
81
+ return True
82
+
83
+ def process_commands(config_file):
84
+ commands = read_commands_from_yaml(config_file)
85
+ all_results = []
86
+
87
+ for cmd_info in commands:
88
+ command = cmd_info.get('command', '')
89
+ if not command:
90
+ logger.error("Command is missing in the configuration.")
91
+ continue
92
+
93
+ if not is_command_safe(command):
94
+ logger.warning(f"Command '{command}' is not allowed and will not be executed.")
95
+ continue
96
+
97
+ try:
98
+ formatted_result = process_single_command(cmd_info)
99
+ if formatted_result:
100
+ all_results.append(formatted_result)
101
+ except KeyError as e:
102
+ logger.error(f"Missing key in command configuration: {e}. Command: {cmd_info}")
103
+ except Exception as e:
104
+ logger.error(f"An error occurred while processing command '{command}': {e}")
105
+
106
+ return all_results
107
+
108
+ # # Example usage:
109
+ # if __name__ == "__main__":
110
+ # results = process_commands('/Users/nlingadh/code/custom_prometheus_agent/src/commands.yaml')
111
+ # print(results)
@@ -1,57 +1,137 @@
1
1
  from prometheus_client import Gauge, start_http_server
2
- import psutil
2
+ import psutil
3
3
  import time
4
4
  import sys
5
5
  import os
6
-
7
- from flexmetric.config.configuration import CA_PATH , CERT_PATH, KEY_PATH
6
+ from flexmetric.config.configuration import CA_PATH, CERT_PATH, KEY_PATH
8
7
  from flexmetric.logging_module.logger import get_logger
9
8
  from flexmetric.file_recognition.exec_file import execute_functions
10
9
  from flexmetric.metric_process.process_commands import process_commands
11
10
  from flexmetric.metric_process.database_processing import process_database_queries
11
+ from flexmetric.metric_process.expiring_queue import metric_queue
12
12
  import argparse
13
13
  import os
14
+ from flexmetric.metric_process.views import (
15
+ run_flask,
16
+ add_update_metric_route,
17
+ secure_flask_run,
18
+ )
19
+ import sched
20
+ import threading
21
+
22
+ scheduler = sched.scheduler(time.time, time.sleep)
23
+
24
+
14
25
  def arguments():
15
26
  parser = argparse.ArgumentParser(
16
- description='FlexMetric: A flexible Prometheus exporter for commands, databases, scripts, and Python functions.'
27
+ description="FlexMetric: A flexible Prometheus exporter for commands, databases, scripts, and Python functions."
17
28
  )
18
29
 
19
30
  # Input type flags
20
- parser.add_argument('--database', action='store_true', help='Process database.yaml and queries.yaml to extract metrics from databases.')
21
- parser.add_argument('--commands', action='store_true', help='Process commands.yaml to extract metrics from system commands.')
22
- parser.add_argument('--functions', action='store_true', help='Process Python functions from the provided path to extract metrics.')
23
-
31
+ parser.add_argument(
32
+ "--database",
33
+ action="store_true",
34
+ help="Process database.yaml and queries.yaml to extract metrics from databases.",
35
+ )
36
+ parser.add_argument(
37
+ "--commands",
38
+ action="store_true",
39
+ help="Process commands.yaml to extract metrics from system commands.",
40
+ )
41
+ parser.add_argument(
42
+ "--functions",
43
+ action="store_true",
44
+ help="Process Python functions from the provided path to extract metrics.",
45
+ )
46
+ parser.add_argument(
47
+ "--expose-api",
48
+ action="store_true",
49
+ help="Expose Flask API to receive external metric updates.",
50
+ )
24
51
 
25
52
  # Config file paths
26
- parser.add_argument('--database-config', type=str, default=None, help='Path to the database configuration YAML file.')
27
- parser.add_argument('--queries-config', type=str, default=None, help='Path to the database queries YAML file.')
28
- parser.add_argument('--commands-config', type=str, default=None, help='Path to the commands configuration YAML file.')
29
- parser.add_argument('--functions-dir', type=str, default=None, help='Path to the python files dir.')
30
- parser.add_argument('--functions-file', type=str, default=None, help='Path to the file containing which function to execute')
31
- parser.add_argument('--port', type=int, default=8000, help='port on which exportor runs')
53
+ parser.add_argument(
54
+ "--database-config",
55
+ type=str,
56
+ default=None,
57
+ help="Path to the database configuration YAML file.",
58
+ )
59
+ parser.add_argument(
60
+ "--queries-config",
61
+ type=str,
62
+ default=None,
63
+ help="Path to the database queries YAML file.",
64
+ )
65
+ parser.add_argument(
66
+ "--commands-config",
67
+ type=str,
68
+ default=None,
69
+ help="Path to the commands configuration YAML file.",
70
+ )
71
+ parser.add_argument(
72
+ "--functions-dir", type=str, default=None, help="Path to the python files dir."
73
+ )
74
+ parser.add_argument(
75
+ "--functions-file",
76
+ type=str,
77
+ default=None,
78
+ help="Path to the file containing which function to execute",
79
+ )
80
+ parser.add_argument(
81
+ "--port", type=int, default=8000, help="port on which exportor runs"
82
+ )
83
+ parser.add_argument(
84
+ "--host",
85
+ type=str,
86
+ default="0.0.0.0",
87
+ help="The hostname or IP address on which to run the Flask server (default: 0.0.0.0)",
88
+ )
89
+ parser.add_argument(
90
+ "--enable-https",
91
+ action="store_true",
92
+ help="Enable HTTPS for Flask API using SSL certificates.",
93
+ )
94
+ parser.add_argument(
95
+ "--ssl-cert",
96
+ type=str,
97
+ help="Path to the SSL certificate file (cert.pem) for HTTPS.",
98
+ )
99
+ parser.add_argument(
100
+ "--ssl-key",
101
+ type=str,
102
+ help="Path to the SSL private key file (key.pem) for HTTPS.",
103
+ )
32
104
 
33
105
  return parser.parse_args()
34
106
 
107
+
35
108
  logger = get_logger(__name__)
36
109
 
37
- logger.info("prometheus is running")
110
+ logger.info("prometheus is running")
111
+
38
112
 
39
113
  def convert_to_data_type(value):
40
- if isinstance(value, str) and '%' in value:
41
- return float(value.strip('%'))
42
- elif isinstance(value, str) and ('GB' in value or 'MB' in value):
43
- return float(value.split()[0].replace(',', ''))
114
+ if isinstance(value, str) and "%" in value:
115
+ return float(value.strip("%"))
116
+ elif isinstance(value, str) and ("GB" in value or "MB" in value):
117
+ return float(value.split()[0].replace(",", ""))
44
118
  return value
45
- gauges = []
119
+
120
+
121
+ gauges = {}
122
+
46
123
 
47
124
  def validate_required_files(mode_name, required_files):
48
125
  missing = [desc for desc, path in required_files.items() if path == None]
49
126
  if missing:
50
- print(f"Missing {', '.join(missing)} for '{mode_name}' mode. Skipping...")
127
+ logger.error(
128
+ f"Missing {', '.join(missing)} for '{mode_name}' mode. Skipping..."
129
+ )
51
130
  return False
52
131
 
53
132
  return True
54
133
 
134
+
55
135
  def validate_all_modes(args):
56
136
  """
57
137
  Validates all selected modes and their required files.
@@ -65,17 +145,25 @@ def validate_all_modes(args):
65
145
  has_valid_mode = False
66
146
 
67
147
  mode_validations = [
68
- (args.database, 'database', {
69
- 'database-config': args.database_config,
70
- 'queries-config': args.queries_config
71
- }),
72
- (args.commands, 'commands', {
73
- 'commands-config': args.commands_config
74
- }),
75
- (args.functions, 'functions', {
76
- 'functions-dir': args.functions_dir,
77
- 'functions-file':args.functions_file
78
- })
148
+ (
149
+ args.database,
150
+ "database",
151
+ {
152
+ "database-config": args.database_config,
153
+ "queries-config": args.queries_config,
154
+ },
155
+ ),
156
+ (args.commands, "commands", {"commands-config": args.commands_config}),
157
+ (
158
+ args.functions,
159
+ "functions",
160
+ {
161
+ "functions-dir": args.functions_dir,
162
+ "functions-file": args.functions_file,
163
+ },
164
+ ),
165
+ (args.expose_api, "expose-api", {}),
166
+ (args.enable_https,"enable-https",{"ssl-cert":args.ssl_cert,"ssl-key" :args.ssl_key})
79
167
  ]
80
168
 
81
169
  for is_enabled, mode_name, files in mode_validations:
@@ -86,58 +174,75 @@ def validate_all_modes(args):
86
174
  return has_valid_mode
87
175
 
88
176
 
89
- def measure(init_flag,args):
177
+ def measure(args):
90
178
  exec_result = []
179
+ queue_items = metric_queue.pop_all()
180
+ if len(queue_items) != 0:
181
+ exec_result.extend(queue_items)
91
182
  if args.database:
92
183
  db_results = process_database_queries(args.queries_config, args.database_config)
93
184
  exec_result.extend(db_results)
94
185
  if args.functions:
95
- function_results = execute_functions(args.functions_dir,args.functions_file)
186
+ function_results = execute_functions(args.functions_dir, args.functions_file)
96
187
  exec_result.extend(function_results)
97
188
  if args.commands:
98
189
  cmd_results = process_commands(args.commands_config)
99
190
  if cmd_results != None:
100
191
  exec_result.extend(cmd_results)
101
- # exec_result = process_commands('commands.yaml')
102
- # print(exec_result)
103
192
  global gauges
104
- count = 0
105
193
  for data in exec_result:
106
- results= data['result']
107
- labels = data['labels']
108
- gauge_name = '_'.join(labels).lower() + "_gauge"
109
- print(labels)
110
- if init_flag:
194
+ results = data["result"]
195
+ labels = data["labels"]
196
+ gauge_name = "_".join(labels).lower() + "_gauge"
197
+ # print(labels)
198
+ if gauge_name not in gauges:
111
199
  gauge = Gauge(gauge_name, f"{gauge_name} for different metrics", labels)
112
- gauges.append(gauge)
200
+ gauges[gauge_name] = gauge
113
201
  else:
114
- gauge = gauges[count]
115
- count += 1
202
+ gauge = gauges[gauge_name]
116
203
  for result in results:
117
- print(result,isinstance(result['label'],list))
118
- if isinstance(result['label'],str):
204
+ if isinstance(result["label"], str):
119
205
  try:
120
- gauge.labels(result['label']).set(convert_to_data_type(result['value']))
206
+ gauge.labels(result["label"]).set(
207
+ convert_to_data_type(result["value"])
208
+ )
121
209
  except Exception as ex:
122
210
  logger.error("Cannot pass string")
123
- elif isinstance(result['label'],list):
124
- label_dict = dict(zip(labels, result['label']))
125
- gauge.labels(**label_dict).set(convert_to_data_type(result['value']))
211
+ elif isinstance(result["label"], list):
212
+ label_dict = dict(zip(labels, result["label"]))
213
+ gauge.labels(**label_dict).set(convert_to_data_type(result["value"]))
214
+
215
+
216
+ def scheduled_measure(args):
217
+ measure(args)
218
+ scheduler.enter(5, 1, scheduled_measure, (args,))
219
+
220
+
221
+ def start_scheduler(args):
222
+ scheduler.enter(0, 1, scheduled_measure, (args,))
223
+ scheduler.run()
126
224
 
127
225
 
128
226
  def main():
129
227
  args = arguments()
130
- print("Validating configuration...")
228
+ logger.info("Validating configuration...")
131
229
  if not validate_all_modes(args):
132
- print("No valid modes with proper configuration found. Exiting.")
230
+ logger.error("No valid modes with proper configuration found. Exiting.")
133
231
  exit(1)
134
232
 
135
- print(f"Starting Prometheus metrics server on port {args.port}...")
136
- print("Starting server")
137
- start_http_server(args.port)
138
- flag = True
139
- while True:
140
- measure(flag,args)
141
- flag = False
142
- time.sleep(5)
143
- main()
233
+ logger.info(f"Starting Prometheus metrics server on port {args.port}...")
234
+ logger.info("Starting server")
235
+ # start_http_server(args.port)
236
+
237
+ scheduler_thread = threading.Thread(
238
+ target=start_scheduler, args=(args,), daemon=True
239
+ )
240
+ scheduler_thread.start()
241
+ if args.expose_api:
242
+ add_update_metric_route()
243
+ if args.enable_https:
244
+ secure_flask_run(args)
245
+ else:
246
+ run_flask(args.host, args.port)
247
+
248
+ main()
@@ -0,0 +1,38 @@
1
+ from flask import Flask, request, jsonify, Response
2
+ from flexmetric.metric_process.expiring_queue import metric_queue
3
+ import argparse
4
+ from prometheus_client import generate_latest, REGISTRY, CONTENT_TYPE_LATEST
5
+
6
+ app = Flask(__name__)
7
+
8
+
9
+ @app.route('/metrics')
10
+ def metrics():
11
+ return Response(generate_latest(REGISTRY), mimetype=CONTENT_TYPE_LATEST)
12
+
13
+
14
+ def add_update_metric_route():
15
+ @app.route("/update_metric", methods=["POST"])
16
+ def update_metric():
17
+ try:
18
+ data = request.get_json()
19
+
20
+ if not data or "result" not in data or not isinstance(data["result"], list):
21
+ return jsonify({"status": "no"}), 400
22
+
23
+ for item in data["result"]:
24
+ if "label" not in item or "value" not in item:
25
+ return jsonify({"status": "no"}), 400
26
+
27
+ metric_queue.put(data)
28
+ print(metric_queue)
29
+ return jsonify({"status": "success"}), 200
30
+
31
+ except Exception:
32
+ return jsonify({"status": "no"}), 500
33
+
34
+
35
+ def run_flask(host, port):
36
+ app.run(host=host, port=port)
37
+ def secure_flask_run(args):
38
+ app.run(host=args.host, port=args.port, ssl_context=(args.ssl_cert, args.ssl_key))
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: flexmetric
3
+ Version: 0.3.0
4
+ Summary: A secure flexible Prometheus exporter for commands, databases, functions, and scripts.
5
+ Home-page: https://github.com/nikhillingadhal1999/custom_prometheus_agent
6
+ Author: Nikhil Lingadhal
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: prometheus_client
14
+ Requires-Dist: PyYAML
15
+ Requires-Dist: psutil
16
+ Requires-Dist: setuptools
17
+ Requires-Dist: wheel
18
+ Requires-Dist: twine
19
+ Requires-Dist: flask
20
+ Dynamic: author
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: license
26
+ Dynamic: requires-dist
27
+ Dynamic: requires-python
28
+ Dynamic: summary
29
+
30
+ # FlexMetric
31
+
32
+ FlexMetric is a lightweight, flexible, and extensible Prometheus exporter that allows you to securely expose system metrics, database query results, Python function outputs, and externally submitted metrics—via an optional Flask API with HTTPS support—as Prometheus-compatible metrics, all with minimal setup and maximum customization.
33
+
34
+ ---
35
+
36
+ ## Features
37
+
38
+ - Run shell commands and expose the results as Prometheus metrics.
39
+ ➔ **Harmful commands (e.g., file deletion, system shutdown) are blocked for safety.**
40
+ - Execute SQL queries (e.g., SQLite) and monitor database statistics.
41
+ ➔ **Potentially dangerous queries (e.g., `DROP`, `DELETE`, `TRUNCATE`) are not allowed.**
42
+ - Automatically discover and expose Python function outputs as metrics.
43
+ - Expose an optional **Flask API** (`/update_metric`) to receive external metrics dynamically.
44
+ - Modular and easy to extend—add your own custom integrations.
45
+ - Built-in Prometheus HTTP server (`/metrics`) with configurable port.
46
+ - **Supports HTTPS** to securely expose both metrics and API endpoints.
47
+ - **Input sanitization** is performed to ensure only safe commands and queries are executed.
48
+
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ Install from PyPI:
55
+
56
+ ```bash
57
+ pip install flexmetric
58
+ ```
59
+ ## Usage
60
+
61
+ Run FlexMetric from the command line:
62
+
63
+ ```bash
64
+ flexmetric --commands --commands-config commands.yaml --port 8000
65
+ ```
66
+
67
+ ## Available Modes
68
+
69
+ FlexMetric supports multiple modes that can be used individually or combined to expose metrics:
70
+
71
+ | Mode | Description | Required Configuration File(s) |
72
+ |-----------------|------------------------------------------------------------------------|------------------------------------------|
73
+ | `--commands` | Runs system commands and exports outputs as Prometheus metrics. | `commands.yaml` |
74
+ | `--database` | Executes SQL queries on databases and exports results. | `database.yaml` and `queries.yaml` |
75
+ | `--functions` | Discovers and runs user-defined Python functions and exports outputs. | `executable_functions.txt` |
76
+ | `--expose-api` | Exposes a Flask API (`/update_metric`) to receive external metrics. | *No configuration file required* |
77
+ ### Example of Using Multiple Modes Together
78
+
79
+ ```bash
80
+ flexmetric --commands --commands-config commands.yaml --database --database-config database.yaml --queries-config queries.yaml
81
+ ```
82
+
83
+ ## Configuration File Examples
84
+
85
+ Below are example configurations for each supported mode.
86
+
87
+ ## Using the Flask API in FlexMetric
88
+
89
+ To use the Flask API for submitting external metrics, you need to start the agent with the `--expose-api` flag along with the Flask host and port.
90
+
91
+ ### Start FlexMetric with Flask API
92
+
93
+ ```bash
94
+ flexmetric --expose-api --port <port> --host <host>
95
+ ```
96
+
97
+ ## Example: Running FlexMetric with Flask API
98
+
99
+ To run FlexMetric with both Prometheus metrics and the Flask API enabled:
100
+
101
+ ```bash
102
+ flexmetric --expose-api --port 5000 --host 0.0.0.0
103
+ ```
104
+
105
+ Prometheus metrics exposed at:
106
+ http://localhost:5000/metrics
107
+
108
+ Flask API exposed at:
109
+ http://localhost:5000/update_metric
110
+
111
+ ### Submitting a Metric to the Flask API
112
+ ```bash
113
+ curl -X POST http://localhost:5000/update_metric \
114
+ -H "Content-Type: application/json" \
115
+ -d '{
116
+ "result": [
117
+ { "label": "cpu_usage", "value": 42.5 }
118
+ ],
119
+ "labels": ["cpu"]
120
+ }'
121
+
122
+ ```
123
+
124
+ ### Using flex metrics in secure mode
125
+
126
+ ```bash
127
+ flexmetric --port 5000 --host 0.0.0.0 --enable-https --ssl-cert=cert.pem --ssl-key=key.pem
128
+ ```
129
+ Prometheus metrics exposed at:
130
+ https://localhost:5000/metrics
131
+
132
+ Flask API exposed at:
133
+ https://localhost:5000/update_metric
134
+
135
+ ### Submitting a Metric to the Flask API
136
+ ```bash
137
+ curl -k -X POST https://localhost:5000/update_metric \
138
+ -H "Content-Type: application/json" \
139
+ -d '{
140
+ "result": [
141
+ { "label": "cpu_usage", "value": 42.5 }
142
+ ],
143
+ "labels": ["cpu"]
144
+ }'
145
+
146
+ ```
147
+
148
+ ### commands.yaml
149
+
150
+ ```yaml
151
+ commands:
152
+ - name: disk_usage
153
+ command: df -h
154
+ label: path
155
+ timeout_seconds: 60
156
+ ```
157
+ ```yaml
158
+ databases:
159
+ - name: mydb
160
+ db_type: sqlite
161
+ db_connection: /path/to/my.db
162
+ ````
163
+ ```yaml
164
+ queries:
165
+ - name: user_count
166
+ db_type: sqlite
167
+ db_name: mydb
168
+ query: "SELECT COUNT(*) FROM users;"
169
+ label: table
170
+ label_value: users
171
+ ```
172
+ executable_functions.txt
173
+ ```
174
+ function_name_1
175
+ function_name_2
176
+ ```
177
+
178
+ ## Python Function Output Format
179
+
180
+ When using the `--functions` mode, each Python function you define is expected to return a dictionary in the following format:
181
+
182
+ ```python
183
+ {
184
+ 'result': [
185
+ {'label': <label_or_labels>, 'value': <numeric_value>}
186
+ ],
187
+ 'labels': [<label_name_1>]
188
+ }
189
+ ```
190
+
191
+ ### Explanation:
192
+
193
+ | Key | Description |
194
+ |--------|---------------------------------------------------------------------------|
195
+ | `result` | A list of dictionaries, each containing a `label` and a corresponding numeric `value`. |
196
+ | `labels` | A list of label names (used as Prometheus labels). |
197
+
198
+
199
+ ## Command-Line Options
200
+
201
+ The following command-line options are available when running FlexMetric:
202
+
203
+ | Option | Description | Default |
204
+ |---------------------|----------------------------------------------------------|----------------------------|
205
+ | `--port` | Port for the Prometheus metrics server (`/metrics`) | `8000` |
206
+ | `--commands` | Enable commands mode | |
207
+ | `--commands-config` | Path to commands YAML file | `commands.yaml` |
208
+ | `--database` | Enable database mode | |
209
+ | `--database-config` | Path to database YAML file | `database.yaml` |
210
+ | `--queries-config` | Path to queries YAML file | `queries.yaml` |
211
+ | `--functions` | Enable Python functions mode | |
212
+ | `--functions-file` | Path to functions file | `executable_functions.txt` |
213
+ | `--expose-api` | Enable Flask API mode to receive external metrics | |
214
+ | `--flask-port` | Port for the Flask API (`/update_metric`) | `5000` |
215
+ | `--flask-host` | Hostname for the Flask API | `0.0.0.0` |
216
+ | `--enable-https` | Enable HTTPS for the Flask API | |
217
+ | `--ssl-cert` | Path to SSL certificate file (`cert.pem`) | |
218
+ | `--ssl-key` | Path to SSL private key file (`key.pem`) | |
219
+
220
+ ### Example Command:
221
+
222
+ ```bash
223
+ flexmetric --commands --commands-config commands.yaml --port 8000
224
+ ```
225
+ ## Example Prometheus Output
226
+
227
+ Once FlexMetric is running, the `/metrics` endpoint will expose metrics in the Prometheus format.
228
+
229
+ Example output:
230
+ ```bash
231
+ disk_usage_gauge{path="/"} 45.0
232
+ ```
233
+
234
+ Each metric includes labels and numeric values that Prometheus can scrape and visualize.
235
+
236
+ ---
237
+
238
+ ## Future Enhancements
239
+
240
+ The following features are planned or under consideration to improve FlexMetric:
241
+
242
+ - Support for additional databases such as PostgreSQL and MySQL.
243
+ - Enhanced support for more complex scripts and richer label extraction.
@@ -0,0 +1,18 @@
1
+ flexmetric/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ flexmetric/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ flexmetric/config/configuration.py,sha256=rlcpvH-Pe8U1suI_hmkZ9Ky4csneoShZGpqPuQNSDSo,129
4
+ flexmetric/file_recognition/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ flexmetric/file_recognition/exec_file.py,sha256=9wBbnqPConxtLhqWTgigEr8VQG-fa9K_U9UOLjqF8Xw,4273
6
+ flexmetric/logging_module/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ flexmetric/logging_module/logger.py,sha256=hXj9m2Q_KxJVI5YRHRoK6PXV5tO6NmvuVjq2Ipx_1tE,447
8
+ flexmetric/metric_process/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ flexmetric/metric_process/database_processing.py,sha256=W06P7ybno_9gFtndUtEY6vI2gMQvtYn1Z14w8h4FYwU,2668
10
+ flexmetric/metric_process/expiring_queue.py,sha256=1oC0MjloitPiRo7yDgVarz81ETEQavKI_W-GFUvp5_Y,1920
11
+ flexmetric/metric_process/process_commands.py,sha256=QAsR5uXt6rku_lTrBCfslerpqW0hMLSffzUFmxPC-fw,3556
12
+ flexmetric/metric_process/prometheus_agent.py,sha256=hjPmNhJiB-844CgIcaw5A-VLmJqyMwQ_j1e_GI-UPSo,7422
13
+ flexmetric/metric_process/views.py,sha256=05UbGBrAkEn_aJq7qBb_1yphCfnXiTqmXFLznMMpkN8,1194
14
+ flexmetric-0.3.0.dist-info/METADATA,sha256=aPtTf_ONN8rVkBpuRGscJZXv9sFF4DbSskEI09bAR-s,8430
15
+ flexmetric-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ flexmetric-0.3.0.dist-info/entry_points.txt,sha256=urVePn5EWr3JqNvkYP7OsB_h2_Bqvv-Wq1MJRBexm8A,79
17
+ flexmetric-0.3.0.dist-info/top_level.txt,sha256=zBlrNwKhXUNhgu9RRZnXxYwYnmE-eocRe6wKSmQROA4,11
18
+ flexmetric-0.3.0.dist-info/RECORD,,
@@ -1,152 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: flexmetric
3
- Version: 0.1.4
4
- Summary: A flexible Prometheus exporter for commands, databases, functions, and scripts.
5
- Home-page: https://github.com/nikhillingadhal1999/custom_prometheus_agent
6
- Author: Nikhil Lingadhal
7
- License: MIT
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- Requires-Dist: prometheus_client
14
- Requires-Dist: PyYAML
15
- Requires-Dist: psutil
16
- Requires-Dist: setuptools
17
- Requires-Dist: wheel
18
- Requires-Dist: twine
19
- Dynamic: author
20
- Dynamic: classifier
21
- Dynamic: description
22
- Dynamic: description-content-type
23
- Dynamic: home-page
24
- Dynamic: license
25
- Dynamic: requires-dist
26
- Dynamic: requires-python
27
- Dynamic: summary
28
-
29
- # FlexMetric
30
-
31
- FlexMetric is a lightweight, flexible, and extensible Prometheus exporter that allows you to expose system metrics, database query results, script outputs, and Python function outputs as Prometheus-compatible metrics with minimal setup and maximum customization.
32
-
33
- ---
34
-
35
- ## Features
36
-
37
- - Run shell commands and expose the results as Prometheus metrics.
38
- - Execute SQL queries (e.g., SQLite) and monitor database statistics.
39
- - Automatically discover and expose Python function outputs.
40
- - Run custom shell scripts and monitor their outputs.
41
- - Modular and easy to extend—add your own integrations.
42
- - Built-in Prometheus HTTP server (`/metrics`) with configurable port.
43
-
44
- ---
45
-
46
- ## Installation
47
-
48
- Install from PyPI:
49
-
50
- ```bash
51
- pip install flexmetric
52
- ```
53
- ## Usage
54
-
55
- Run FlexMetric from the command line:
56
-
57
- ```bash
58
- flexmetric --commands --commands-config commands.yaml --port 8000
59
- ```
60
-
61
- ## Available Modes
62
-
63
- FlexMetric supports multiple modes that can be used individually or combined to expose metrics:
64
-
65
- | Mode | Description | Required Configuration File(s) |
66
- |---------------|--------------------------------------------------|------------------------------------------|
67
- | `--commands` | Runs system commands and exports outputs as Prometheus metrics. | `commands.yaml` |
68
- | `--database` | Executes SQL queries on databases and exports results. | `database.yaml` and `queries.yaml` |
69
- | `--functions` | Discovers and runs user-defined Python functions and exports outputs. | `executable_functions.txt` |
70
- | `--scripts` | Executes custom shell scripts and exports outputs. | `scripts.yaml` |
71
-
72
- ### Example of Using Multiple Modes Together
73
-
74
- ```bash
75
- flexmetric --commands --commands-config commands.yaml --database --database-config database.yaml --queries-config queries.yaml
76
- ```
77
-
78
- ## Configuration File Examples
79
-
80
- Below are example configurations for each supported mode.
81
-
82
- ### commands.yaml
83
-
84
- ```yaml
85
- commands:
86
- - name: disk_usage
87
- command: df -h
88
- label: path
89
- timeout_seconds: 60
90
- ```
91
- ```yaml
92
- databases:
93
- - name: mydb
94
- db_type: sqlite
95
- db_connection: /path/to/my.db
96
- ````
97
- ```yaml
98
- queries:
99
- - name: user_count
100
- db_type: sqlite
101
- db_name: mydb
102
- query: "SELECT COUNT(*) FROM users;"
103
- label: table
104
- label_value: users
105
- ```
106
- executable_functions.txt
107
- ```
108
- function_name_1
109
- function_name_2
110
- ```
111
-
112
- ## Command-Line Options
113
-
114
- The following command-line options are available when running FlexMetric:
115
-
116
- | Option | Description | Default |
117
- |---------------------|----------------------------------------------------------|----------------------------|
118
- | `--port` | Port for the Prometheus metrics server | `8000` |
119
- | `--commands` | Enable commands mode | |
120
- | `--commands-config` | Path to commands YAML file | `commands.yaml` |
121
- | `--database` | Enable database mode | |
122
- | `--database-config` | Path to database YAML file | `database.yaml` |
123
- | `--queries-config` | Path to queries YAML file | `queries.yaml` |
124
- | `--functions` | Enable Python functions mode | |
125
- | `--functions-file` | Path to functions file | `executable_functions.txt` |
126
- | `--scripts` | Enable shell scripts mode | |
127
- | `--scripts-config` | Path to scripts YAML file | `scripts.yaml` |
128
-
129
- ### Example Command:
130
-
131
- ```bash
132
- flexmetric --commands --commands-config commands.yaml --port 8000
133
- ```
134
- ## Example Prometheus Output
135
-
136
- Once FlexMetric is running, the `/metrics` endpoint will expose metrics in the Prometheus format.
137
-
138
- Example output:
139
- ```bash
140
- disk_usage_gauge{path="/"} 45.0
141
- ```
142
-
143
- Each metric includes labels and numeric values that Prometheus can scrape and visualize.
144
-
145
- ---
146
-
147
- ## Future Enhancements
148
-
149
- The following features are planned or under consideration to improve FlexMetric:
150
-
151
- - Support for additional databases such as PostgreSQL and MySQL.
152
- - Enhanced support for more complex scripts and richer label extraction.
@@ -1,15 +0,0 @@
1
- flexmetric/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- flexmetric/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- flexmetric/config/configuration.py,sha256=rlcpvH-Pe8U1suI_hmkZ9Ky4csneoShZGpqPuQNSDSo,129
4
- flexmetric/file_recognition/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- flexmetric/file_recognition/exec_file.py,sha256=9wBbnqPConxtLhqWTgigEr8VQG-fa9K_U9UOLjqF8Xw,4273
6
- flexmetric/logging_module/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- flexmetric/logging_module/logger.py,sha256=hXj9m2Q_KxJVI5YRHRoK6PXV5tO6NmvuVjq2Ipx_1tE,447
8
- flexmetric/metric_process/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- flexmetric/metric_process/database_processing.py,sha256=OAJ0FbvjQLSqUh85KenuEQ5YG7ghWQYT-pzqogCOafk,2542
10
- flexmetric/metric_process/prometheus_agent.py,sha256=pZ0Czv1ZQvyH0JhuNac5rjP1PC0ixcXM3HsQ0J-WxaY,5402
11
- flexmetric-0.1.4.dist-info/METADATA,sha256=adZgMG3ZOx9mlJwhCy3GHdih68q4CJODNb0MxKCSt8Y,5373
12
- flexmetric-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- flexmetric-0.1.4.dist-info/entry_points.txt,sha256=urVePn5EWr3JqNvkYP7OsB_h2_Bqvv-Wq1MJRBexm8A,79
14
- flexmetric-0.1.4.dist-info/top_level.txt,sha256=zBlrNwKhXUNhgu9RRZnXxYwYnmE-eocRe6wKSmQROA4,11
15
- flexmetric-0.1.4.dist-info/RECORD,,