flexmetric 0.1.3__py3-none-any.whl → 0.2.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)
@@ -1,71 +1,109 @@
1
1
  import subprocess
2
2
  import yaml
3
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")
4
9
 
5
- # 1. Read YAML commands
6
10
  def read_commands_from_yaml(file_path):
7
11
  with open(file_path, 'r') as f:
8
12
  config = yaml.safe_load(f)
9
13
  return config.get('commands', [])
10
14
 
11
- # 2. Execute command with timeout
12
15
  def execute_command_with_timeout(command, timeout):
13
16
  try:
14
17
  result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
15
18
  if result.returncode != 0:
19
+ logger.info(f"Exception in running the command {command}")
16
20
  return ''
17
21
  return result.stdout.strip()
18
22
  except subprocess.TimeoutExpired:
19
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
20
82
 
21
83
  def process_commands(config_file):
22
84
  commands = read_commands_from_yaml(config_file)
23
85
  all_results = []
24
86
 
25
87
  for cmd_info in commands:
26
- command = cmd_info['command']
27
- label_name = cmd_info['label']
28
- timeout = cmd_info.get('timeout_seconds', 30)
29
- label_column = cmd_info.get('label_column', -1)
30
- value_column = cmd_info.get('value_column', 0)
31
- fixed_label_value = cmd_info.get('label_value')
32
-
33
- raw_output = execute_command_with_timeout(command, timeout)
34
- lines = raw_output.strip().splitlines()
35
-
36
- result_list = []
37
-
38
- for line in lines:
39
- parts = line.strip().split()
40
- if not parts:
41
- continue
42
- if label_column == 'fixed':
43
- label = fixed_label_value or 'label'
44
- else:
45
- try:
46
- label = parts[label_column]
47
- except IndexError:
48
- label = 'unknown'
49
- try:
50
- raw_value = parts[value_column]
51
- cleaned_value = re.sub(r'[^\d\.\-]', '', raw_value)
52
- value = float(cleaned_value) if cleaned_value else 1
53
- except (IndexError, ValueError):
54
- value = 1
55
-
56
- result_list.append({
57
- 'label': label,
58
- 'value': value
59
- })
60
-
61
- formatted = {
62
- 'result': result_list,
63
- 'labels': [label_name]
64
- }
65
- all_results.append(formatted)
88
+ command = cmd_info.get('command', '')
89
+ if not command:
90
+ logger.error("Command is missing in the configuration.")
91
+ continue
66
92
 
67
- return all_results
93
+ if not is_command_safe(command):
94
+ logger.warning(f"Command '{command}' is not allowed and will not be executed.")
95
+ continue
68
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
69
107
 
70
108
  # # Example usage:
71
109
  # if __name__ == "__main__":
@@ -1,48 +1,112 @@
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 run_flask
15
+ import sched
16
+ import threading
17
+
18
+ scheduler = sched.scheduler(time.time, time.sleep)
19
+
20
+
14
21
  def arguments():
15
22
  parser = argparse.ArgumentParser(
16
- description='FlexMetric: A flexible Prometheus exporter for commands, databases, scripts, and Python functions.'
23
+ description="FlexMetric: A flexible Prometheus exporter for commands, databases, scripts, and Python functions."
17
24
  )
18
25
 
19
26
  # 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
-
27
+ parser.add_argument(
28
+ "--database",
29
+ action="store_true",
30
+ help="Process database.yaml and queries.yaml to extract metrics from databases.",
31
+ )
32
+ parser.add_argument(
33
+ "--commands",
34
+ action="store_true",
35
+ help="Process commands.yaml to extract metrics from system commands.",
36
+ )
37
+ parser.add_argument(
38
+ "--functions",
39
+ action="store_true",
40
+ help="Process Python functions from the provided path to extract metrics.",
41
+ )
42
+ parser.add_argument(
43
+ "--expose-api",
44
+ action="store_true",
45
+ help="Expose Flask API to receive external metric updates.",
46
+ )
24
47
 
25
48
  # 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')
49
+ parser.add_argument(
50
+ "--database-config",
51
+ type=str,
52
+ default=None,
53
+ help="Path to the database configuration YAML file.",
54
+ )
55
+ parser.add_argument(
56
+ "--queries-config",
57
+ type=str,
58
+ default=None,
59
+ help="Path to the database queries YAML file.",
60
+ )
61
+ parser.add_argument(
62
+ "--commands-config",
63
+ type=str,
64
+ default=None,
65
+ help="Path to the commands configuration YAML file.",
66
+ )
67
+ parser.add_argument(
68
+ "--functions-dir", type=str, default=None, help="Path to the python files dir."
69
+ )
70
+ parser.add_argument(
71
+ "--functions-file",
72
+ type=str,
73
+ default=None,
74
+ help="Path to the file containing which function to execute",
75
+ )
76
+ parser.add_argument(
77
+ "--port", type=int, default=8000, help="port on which exportor runs"
78
+ )
79
+ parser.add_argument(
80
+ "--flask-host",
81
+ type=str,
82
+ default="0.0.0.0",
83
+ help="The hostname or IP address on which to run the Flask server (default: 0.0.0.0)",
84
+ )
85
+ parser.add_argument(
86
+ "--flask-port",
87
+ type=int,
88
+ default=5000,
89
+ help="The port number on which to run the Flask server (default: 5000)",
90
+ )
32
91
 
33
92
  return parser.parse_args()
34
93
 
94
+
35
95
  logger = get_logger(__name__)
36
96
 
37
- logger.info("prometheus is running")
97
+ logger.info("prometheus is running")
98
+
38
99
 
39
100
  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(',', ''))
101
+ if isinstance(value, str) and "%" in value:
102
+ return float(value.strip("%"))
103
+ elif isinstance(value, str) and ("GB" in value or "MB" in value):
104
+ return float(value.split()[0].replace(",", ""))
44
105
  return value
45
- gauges = []
106
+
107
+
108
+ gauges = {}
109
+
46
110
 
47
111
  def validate_required_files(mode_name, required_files):
48
112
  missing = [desc for desc, path in required_files.items() if path == None]
@@ -52,6 +116,7 @@ def validate_required_files(mode_name, required_files):
52
116
 
53
117
  return True
54
118
 
119
+
55
120
  def validate_all_modes(args):
56
121
  """
57
122
  Validates all selected modes and their required files.
@@ -65,17 +130,24 @@ def validate_all_modes(args):
65
130
  has_valid_mode = False
66
131
 
67
132
  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
- })
133
+ (
134
+ args.database,
135
+ "database",
136
+ {
137
+ "database-config": args.database_config,
138
+ "queries-config": args.queries_config,
139
+ },
140
+ ),
141
+ (args.commands, "commands", {"commands-config": args.commands_config}),
142
+ (
143
+ args.functions,
144
+ "functions",
145
+ {
146
+ "functions-dir": args.functions_dir,
147
+ "functions-file": args.functions_file,
148
+ },
149
+ ),
150
+ (args.expose_api, "expose-api", {})
79
151
  ]
80
152
 
81
153
  for is_enabled, mode_name, files in mode_validations:
@@ -86,42 +158,55 @@ def validate_all_modes(args):
86
158
  return has_valid_mode
87
159
 
88
160
 
89
- def measure(init_flag,args):
161
+ def measure(args):
90
162
  exec_result = []
163
+ queue_items = metric_queue.pop_all()
164
+ print("QUEUE_ITEMS : ", queue_items)
165
+ if len(queue_items) != 0:
166
+ exec_result.extend(queue_items)
91
167
  if args.database:
92
168
  db_results = process_database_queries(args.queries_config, args.database_config)
93
169
  exec_result.extend(db_results)
94
170
  if args.functions:
95
- function_results = execute_functions(args.functions_dir,args.functions_file)
171
+ function_results = execute_functions(args.functions_dir, args.functions_file)
96
172
  exec_result.extend(function_results)
97
173
  if args.commands:
98
174
  cmd_results = process_commands(args.commands_config)
99
- exec_result.extend(cmd_results)
100
- # exec_result = process_commands('commands.yaml')
101
- # print(exec_result)
175
+ if cmd_results != None:
176
+ exec_result.extend(cmd_results)
102
177
  global gauges
103
- count = 0
104
178
  for data in exec_result:
105
- results= data['result']
106
- labels = data['labels']
107
- gauge_name = '_'.join(labels).lower() + "_gauge"
108
- print(labels)
109
- if init_flag:
179
+ results = data["result"]
180
+ labels = data["labels"]
181
+ gauge_name = "_".join(labels).lower() + "_gauge"
182
+ # print(labels)
183
+ if gauge_name not in gauges:
110
184
  gauge = Gauge(gauge_name, f"{gauge_name} for different metrics", labels)
111
- gauges.append(gauge)
185
+ gauges[gauge_name] = gauge
112
186
  else:
113
- gauge = gauges[count]
114
- count += 1
187
+ gauge = gauges[gauge_name]
115
188
  for result in results:
116
- print(result,isinstance(result['label'],list))
117
- if isinstance(result['label'],str):
189
+ print(result, isinstance(result["label"], list))
190
+ if isinstance(result["label"], str):
118
191
  try:
119
- gauge.labels(result['label']).set(convert_to_data_type(result['value']))
192
+ gauge.labels(result["label"]).set(
193
+ convert_to_data_type(result["value"])
194
+ )
120
195
  except Exception as ex:
121
196
  logger.error("Cannot pass string")
122
- elif isinstance(result['label'],list):
123
- label_dict = dict(zip(labels, result['label']))
124
- gauge.labels(**label_dict).set(convert_to_data_type(result['value']))
197
+ elif isinstance(result["label"], list):
198
+ label_dict = dict(zip(labels, result["label"]))
199
+ gauge.labels(**label_dict).set(convert_to_data_type(result["value"]))
200
+
201
+
202
+ def scheduled_measure(args):
203
+ measure(args)
204
+ scheduler.enter(5, 1, scheduled_measure, (args,))
205
+
206
+
207
+ def start_scheduler(args):
208
+ scheduler.enter(0, 1, scheduled_measure, (args,))
209
+ scheduler.run()
125
210
 
126
211
 
127
212
  def main():
@@ -134,8 +219,15 @@ def main():
134
219
  print(f"Starting Prometheus metrics server on port {args.port}...")
135
220
  print("Starting server")
136
221
  start_http_server(args.port)
137
- flag = True
138
- while True:
139
- measure(flag,args)
140
- flag = False
141
- time.sleep(5)
222
+ scheduler_thread = threading.Thread(
223
+ target=start_scheduler, args=(args,), daemon=True
224
+ )
225
+ scheduler_thread.start()
226
+ if args.expose_api:
227
+ run_flask(args.flask_host, args.flask_port)
228
+ else:
229
+ while True:
230
+ time.sleep(2)
231
+
232
+
233
+ main()
@@ -0,0 +1,29 @@
1
+ from flask import Flask, request, jsonify
2
+ from flexmetric.metric_process.expiring_queue import metric_queue
3
+ import argparse
4
+
5
+ app = Flask(__name__)
6
+
7
+
8
+ @app.route("/update_metric", methods=["POST"])
9
+ def update_metric():
10
+ try:
11
+ data = request.get_json()
12
+
13
+ if not data or "result" not in data or not isinstance(data["result"], list):
14
+ return jsonify({"status": "no"}), 400
15
+
16
+ for item in data["result"]:
17
+ if "label" not in item or "value" not in item:
18
+ return jsonify({"status": "no"}), 400
19
+
20
+ metric_queue.put(data)
21
+ print(metric_queue)
22
+ return jsonify({"status": "success"}), 200
23
+
24
+ except Exception:
25
+ return jsonify({"status": "no"}), 500
26
+
27
+
28
+ def run_flask(host, port):
29
+ app.run(host=host, port=port)
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.4
2
+ Name: flexmetric
3
+ Version: 0.2.0
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
+ 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 expose system metrics, database query results, Python function outputs, and externally submitted metrics via an optional Flask API as Prometheus-compatible metrics—with minimal setup and maximum customization.
33
+
34
+ ---
35
+
36
+ ## Features
37
+
38
+ - Run shell commands and expose the results as Prometheus metrics.
39
+ - Execute SQL queries (e.g., SQLite) and monitor database statistics.
40
+ - Automatically discover and expose Python function outputs as metrics.
41
+ - Expose an optional **Flask API** (`/update_metric`) to receive external metrics dynamically.
42
+ - Modular and easy to extend—add your own custom integrations.
43
+ - Built-in Prometheus HTTP server (`/metrics`) with configurable port.
44
+
45
+ ---
46
+
47
+ ## Installation
48
+
49
+ Install from PyPI:
50
+
51
+ ```bash
52
+ pip install flexmetric
53
+ ```
54
+ ## Usage
55
+
56
+ Run FlexMetric from the command line:
57
+
58
+ ```bash
59
+ flexmetric --commands --commands-config commands.yaml --port 8000
60
+ ```
61
+
62
+ ## Available Modes
63
+
64
+ FlexMetric supports multiple modes that can be used individually or combined to expose metrics:
65
+
66
+ | Mode | Description | Required Configuration File(s) |
67
+ |-----------------|------------------------------------------------------------------------|------------------------------------------|
68
+ | `--commands` | Runs system commands and exports outputs as Prometheus metrics. | `commands.yaml` |
69
+ | `--database` | Executes SQL queries on databases and exports results. | `database.yaml` and `queries.yaml` |
70
+ | `--functions` | Discovers and runs user-defined Python functions and exports outputs. | `executable_functions.txt` |
71
+ | `--expose-api` | Exposes a Flask API (`/update_metric`) to receive external metrics. | *No configuration file required* |
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
+ ## Using the Flask API in FlexMetric
83
+
84
+ 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.
85
+
86
+ ### Start FlexMetric with Flask API
87
+
88
+ ```bash
89
+ flexmetric --expose-api --flask-port <port> --flask-host <host> --metrics-port <metrics-port>
90
+ ```
91
+
92
+ ## Example: Running FlexMetric with Flask API
93
+
94
+ To run FlexMetric with both Prometheus metrics and the Flask API enabled:
95
+
96
+ ```bash
97
+ flexmetric --expose-api --flask-port 5000 --flask-host 0.0.0.0 --port 8000
98
+ ```
99
+
100
+ Prometheus metrics exposed at:
101
+ http://localhost:8000/metrics
102
+
103
+ Flask API exposed at:
104
+ http://localhost:5000/update_metric
105
+
106
+ ### Submitting a Metric to the Flask API
107
+ ```bash
108
+ curl -X POST http://localhost:5000/update_metric \
109
+ -H "Content-Type: application/json" \
110
+ -d '{
111
+ "result": [
112
+ { "label": "cpu_usage", "value": 42.5 }
113
+ ],
114
+ "labels": ["cpu"]
115
+ }'
116
+
117
+ ```
118
+
119
+ ### commands.yaml
120
+
121
+ ```yaml
122
+ commands:
123
+ - name: disk_usage
124
+ command: df -h
125
+ label: path
126
+ timeout_seconds: 60
127
+ ```
128
+ ```yaml
129
+ databases:
130
+ - name: mydb
131
+ db_type: sqlite
132
+ db_connection: /path/to/my.db
133
+ ````
134
+ ```yaml
135
+ queries:
136
+ - name: user_count
137
+ db_type: sqlite
138
+ db_name: mydb
139
+ query: "SELECT COUNT(*) FROM users;"
140
+ label: table
141
+ label_value: users
142
+ ```
143
+ executable_functions.txt
144
+ ```
145
+ function_name_1
146
+ function_name_2
147
+ ```
148
+
149
+ ## Python Function Output Format
150
+
151
+ When using the `--functions` mode, each Python function you define is expected to return a dictionary in the following format:
152
+
153
+ ```python
154
+ {
155
+ 'result': [
156
+ {'label': <label_or_labels>, 'value': <numeric_value>}
157
+ ],
158
+ 'labels': [<label_name_1>]
159
+ }
160
+ ```
161
+
162
+ ### Explanation:
163
+
164
+ | Key | Description |
165
+ |--------|---------------------------------------------------------------------------|
166
+ | `result` | A list of dictionaries, each containing a `label` and a corresponding numeric `value`. |
167
+ | `labels` | A list of label names (used as Prometheus labels). |
168
+
169
+
170
+ ## Command-Line Options
171
+
172
+ The following command-line options are available when running FlexMetric:
173
+
174
+ | Option | Description | Default |
175
+ |---------------------|----------------------------------------------------------|----------------------------|
176
+ | `--port` | Port for the Prometheus metrics server (`/metrics`) | `8000` |
177
+ | `--commands` | Enable commands mode | |
178
+ | `--commands-config` | Path to commands YAML file | `commands.yaml` |
179
+ | `--database` | Enable database mode | |
180
+ | `--database-config` | Path to database YAML file | `database.yaml` |
181
+ | `--queries-config` | Path to queries YAML file | `queries.yaml` |
182
+ | `--functions` | Enable Python functions mode | |
183
+ | `--functions-file` | Path to functions file | `executable_functions.txt` |
184
+ | `--scripts` | Enable shell scripts mode | |
185
+ | `--scripts-config` | Path to scripts YAML file | `scripts.yaml` |
186
+ | `--expose-api` | Enable Flask API mode to receive external metrics | |
187
+ | `--flask-port` | Port for the Flask API (`/update_metric`) | `5000` |
188
+ | `--flask-host` | Hostname for the Flask API | `0.0.0.0` |
189
+ ### Example Command:
190
+
191
+ ```bash
192
+ flexmetric --commands --commands-config commands.yaml --port 8000
193
+ ```
194
+ ## Example Prometheus Output
195
+
196
+ Once FlexMetric is running, the `/metrics` endpoint will expose metrics in the Prometheus format.
197
+
198
+ Example output:
199
+ ```bash
200
+ disk_usage_gauge{path="/"} 45.0
201
+ ```
202
+
203
+ Each metric includes labels and numeric values that Prometheus can scrape and visualize.
204
+
205
+ ---
206
+
207
+ ## Future Enhancements
208
+
209
+ The following features are planned or under consideration to improve FlexMetric:
210
+
211
+ - Support for additional databases such as PostgreSQL and MySQL.
212
+ - 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=kUUrsUyW-G_be5ohDGH37Izr16bfHV2i1sZFDWzJGes,7027
13
+ flexmetric/metric_process/views.py,sha256=qIQ7twEwpir3aMV4GEZhIj7YWKx5Wix2fGiEjtk6KOs,788
14
+ flexmetric-0.2.0.dist-info/METADATA,sha256=DIZ7ZxmdN4BfP7qeNOmyxlewqgb_DzkHGsDUj9bNQbY,7480
15
+ flexmetric-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ flexmetric-0.2.0.dist-info/entry_points.txt,sha256=urVePn5EWr3JqNvkYP7OsB_h2_Bqvv-Wq1MJRBexm8A,79
17
+ flexmetric-0.2.0.dist-info/top_level.txt,sha256=zBlrNwKhXUNhgu9RRZnXxYwYnmE-eocRe6wKSmQROA4,11
18
+ flexmetric-0.2.0.dist-info/RECORD,,
@@ -1,152 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: flexmetric
3
- Version: 0.1.3
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,16 +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/process_commands.py,sha256=cHtoyzP0IgZOwoS_BgFZU0VHuOKQO0UVvGOCwWuByNM,2236
11
- flexmetric/metric_process/prometheus_agent.py,sha256=1C18_n4qsqkxim20raD2LAZJm1zDd9AEPpVmuxDC9MA,5359
12
- flexmetric-0.1.3.dist-info/METADATA,sha256=FAXGW1RX6ZaOQpbGnNMyIwi_x74UyHwIKmag1ijq7y4,5373
13
- flexmetric-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- flexmetric-0.1.3.dist-info/entry_points.txt,sha256=urVePn5EWr3JqNvkYP7OsB_h2_Bqvv-Wq1MJRBexm8A,79
15
- flexmetric-0.1.3.dist-info/top_level.txt,sha256=zBlrNwKhXUNhgu9RRZnXxYwYnmE-eocRe6wKSmQROA4,11
16
- flexmetric-0.1.3.dist-info/RECORD,,