flexmetric 0.1.3__tar.gz → 0.2.0__tar.gz

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.
Files changed (28) hide show
  1. flexmetric-0.2.0/PKG-INFO +212 -0
  2. flexmetric-0.2.0/README.md +183 -0
  3. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/metric_process/database_processing.py +32 -19
  4. flexmetric-0.2.0/flexmetric/metric_process/expiring_queue.py +56 -0
  5. flexmetric-0.2.0/flexmetric/metric_process/process_commands.py +111 -0
  6. flexmetric-0.2.0/flexmetric/metric_process/prometheus_agent.py +233 -0
  7. flexmetric-0.2.0/flexmetric/metric_process/views.py +29 -0
  8. flexmetric-0.2.0/flexmetric.egg-info/PKG-INFO +212 -0
  9. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/SOURCES.txt +3 -1
  10. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/requires.txt +1 -0
  11. {flexmetric-0.1.3 → flexmetric-0.2.0}/setup.py +4 -3
  12. flexmetric-0.1.3/PKG-INFO +0 -152
  13. flexmetric-0.1.3/README.md +0 -124
  14. flexmetric-0.1.3/flexmetric/metric_process/process_commands.py +0 -73
  15. flexmetric-0.1.3/flexmetric/metric_process/prometheus_agent.py +0 -141
  16. flexmetric-0.1.3/flexmetric.egg-info/PKG-INFO +0 -152
  17. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/__init__.py +0 -0
  18. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/config/__init__.py +0 -0
  19. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/config/configuration.py +0 -0
  20. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/file_recognition/__init__.py +0 -0
  21. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/file_recognition/exec_file.py +0 -0
  22. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/logging_module/__init__.py +0 -0
  23. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/logging_module/logger.py +0 -0
  24. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/metric_process/__init__.py +0 -0
  25. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/dependency_links.txt +0 -0
  26. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/entry_points.txt +0 -0
  27. {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/top_level.txt +0 -0
  28. {flexmetric-0.1.3 → flexmetric-0.2.0}/setup.cfg +0 -0
@@ -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,183 @@
1
+ # FlexMetric
2
+
3
+ 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.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - Run shell commands and expose the results as Prometheus metrics.
10
+ - Execute SQL queries (e.g., SQLite) and monitor database statistics.
11
+ - Automatically discover and expose Python function outputs as metrics.
12
+ - Expose an optional **Flask API** (`/update_metric`) to receive external metrics dynamically.
13
+ - Modular and easy to extend—add your own custom integrations.
14
+ - Built-in Prometheus HTTP server (`/metrics`) with configurable port.
15
+
16
+ ---
17
+
18
+ ## Installation
19
+
20
+ Install from PyPI:
21
+
22
+ ```bash
23
+ pip install flexmetric
24
+ ```
25
+ ## Usage
26
+
27
+ Run FlexMetric from the command line:
28
+
29
+ ```bash
30
+ flexmetric --commands --commands-config commands.yaml --port 8000
31
+ ```
32
+
33
+ ## Available Modes
34
+
35
+ FlexMetric supports multiple modes that can be used individually or combined to expose metrics:
36
+
37
+ | Mode | Description | Required Configuration File(s) |
38
+ |-----------------|------------------------------------------------------------------------|------------------------------------------|
39
+ | `--commands` | Runs system commands and exports outputs as Prometheus metrics. | `commands.yaml` |
40
+ | `--database` | Executes SQL queries on databases and exports results. | `database.yaml` and `queries.yaml` |
41
+ | `--functions` | Discovers and runs user-defined Python functions and exports outputs. | `executable_functions.txt` |
42
+ | `--expose-api` | Exposes a Flask API (`/update_metric`) to receive external metrics. | *No configuration file required* |
43
+ ### Example of Using Multiple Modes Together
44
+
45
+ ```bash
46
+ flexmetric --commands --commands-config commands.yaml --database --database-config database.yaml --queries-config queries.yaml
47
+ ```
48
+
49
+ ## Configuration File Examples
50
+
51
+ Below are example configurations for each supported mode.
52
+
53
+ ## Using the Flask API in FlexMetric
54
+
55
+ 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.
56
+
57
+ ### Start FlexMetric with Flask API
58
+
59
+ ```bash
60
+ flexmetric --expose-api --flask-port <port> --flask-host <host> --metrics-port <metrics-port>
61
+ ```
62
+
63
+ ## Example: Running FlexMetric with Flask API
64
+
65
+ To run FlexMetric with both Prometheus metrics and the Flask API enabled:
66
+
67
+ ```bash
68
+ flexmetric --expose-api --flask-port 5000 --flask-host 0.0.0.0 --port 8000
69
+ ```
70
+
71
+ Prometheus metrics exposed at:
72
+ http://localhost:8000/metrics
73
+
74
+ Flask API exposed at:
75
+ http://localhost:5000/update_metric
76
+
77
+ ### Submitting a Metric to the Flask API
78
+ ```bash
79
+ curl -X POST http://localhost:5000/update_metric \
80
+ -H "Content-Type: application/json" \
81
+ -d '{
82
+ "result": [
83
+ { "label": "cpu_usage", "value": 42.5 }
84
+ ],
85
+ "labels": ["cpu"]
86
+ }'
87
+
88
+ ```
89
+
90
+ ### commands.yaml
91
+
92
+ ```yaml
93
+ commands:
94
+ - name: disk_usage
95
+ command: df -h
96
+ label: path
97
+ timeout_seconds: 60
98
+ ```
99
+ ```yaml
100
+ databases:
101
+ - name: mydb
102
+ db_type: sqlite
103
+ db_connection: /path/to/my.db
104
+ ````
105
+ ```yaml
106
+ queries:
107
+ - name: user_count
108
+ db_type: sqlite
109
+ db_name: mydb
110
+ query: "SELECT COUNT(*) FROM users;"
111
+ label: table
112
+ label_value: users
113
+ ```
114
+ executable_functions.txt
115
+ ```
116
+ function_name_1
117
+ function_name_2
118
+ ```
119
+
120
+ ## Python Function Output Format
121
+
122
+ When using the `--functions` mode, each Python function you define is expected to return a dictionary in the following format:
123
+
124
+ ```python
125
+ {
126
+ 'result': [
127
+ {'label': <label_or_labels>, 'value': <numeric_value>}
128
+ ],
129
+ 'labels': [<label_name_1>]
130
+ }
131
+ ```
132
+
133
+ ### Explanation:
134
+
135
+ | Key | Description |
136
+ |--------|---------------------------------------------------------------------------|
137
+ | `result` | A list of dictionaries, each containing a `label` and a corresponding numeric `value`. |
138
+ | `labels` | A list of label names (used as Prometheus labels). |
139
+
140
+
141
+ ## Command-Line Options
142
+
143
+ The following command-line options are available when running FlexMetric:
144
+
145
+ | Option | Description | Default |
146
+ |---------------------|----------------------------------------------------------|----------------------------|
147
+ | `--port` | Port for the Prometheus metrics server (`/metrics`) | `8000` |
148
+ | `--commands` | Enable commands mode | |
149
+ | `--commands-config` | Path to commands YAML file | `commands.yaml` |
150
+ | `--database` | Enable database mode | |
151
+ | `--database-config` | Path to database YAML file | `database.yaml` |
152
+ | `--queries-config` | Path to queries YAML file | `queries.yaml` |
153
+ | `--functions` | Enable Python functions mode | |
154
+ | `--functions-file` | Path to functions file | `executable_functions.txt` |
155
+ | `--scripts` | Enable shell scripts mode | |
156
+ | `--scripts-config` | Path to scripts YAML file | `scripts.yaml` |
157
+ | `--expose-api` | Enable Flask API mode to receive external metrics | |
158
+ | `--flask-port` | Port for the Flask API (`/update_metric`) | `5000` |
159
+ | `--flask-host` | Hostname for the Flask API | `0.0.0.0` |
160
+ ### Example Command:
161
+
162
+ ```bash
163
+ flexmetric --commands --commands-config commands.yaml --port 8000
164
+ ```
165
+ ## Example Prometheus Output
166
+
167
+ Once FlexMetric is running, the `/metrics` endpoint will expose metrics in the Prometheus format.
168
+
169
+ Example output:
170
+ ```bash
171
+ disk_usage_gauge{path="/"} 45.0
172
+ ```
173
+
174
+ Each metric includes labels and numeric values that Prometheus can scrape and visualize.
175
+
176
+ ---
177
+
178
+ ## Future Enhancements
179
+
180
+ The following features are planned or under consideration to improve FlexMetric:
181
+
182
+ - Support for additional databases such as PostgreSQL and MySQL.
183
+ - Enhanced support for more complex scripts and richer label extraction.
@@ -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)