flexmetric 0.3.2__tar.gz → 0.4.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 (24) hide show
  1. {flexmetric-0.3.2 → flexmetric-0.4.0}/PKG-INFO +35 -10
  2. {flexmetric-0.3.2 → flexmetric-0.4.0}/README.md +34 -9
  3. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/metric_process/database_processing.py +16 -14
  4. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/metric_process/process_commands.py +23 -7
  5. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/metric_process/prometheus_agent.py +24 -13
  6. flexmetric-0.4.0/flexmetric/metric_process/views.py +54 -0
  7. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric.egg-info/PKG-INFO +35 -10
  8. {flexmetric-0.3.2 → flexmetric-0.4.0}/setup.py +1 -1
  9. flexmetric-0.3.2/flexmetric/metric_process/views.py +0 -38
  10. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/__init__.py +0 -0
  11. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/config/__init__.py +0 -0
  12. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/config/configuration.py +0 -0
  13. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/file_recognition/__init__.py +0 -0
  14. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/file_recognition/exec_file.py +0 -0
  15. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/logging_module/__init__.py +0 -0
  16. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/logging_module/logger.py +0 -0
  17. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/metric_process/__init__.py +0 -0
  18. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric/metric_process/expiring_queue.py +0 -0
  19. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric.egg-info/SOURCES.txt +0 -0
  20. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric.egg-info/dependency_links.txt +0 -0
  21. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric.egg-info/entry_points.txt +0 -0
  22. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric.egg-info/requires.txt +0 -0
  23. {flexmetric-0.3.2 → flexmetric-0.4.0}/flexmetric.egg-info/top_level.txt +0 -0
  24. {flexmetric-0.3.2 → flexmetric-0.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flexmetric
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: A secure flexible Prometheus exporter for commands, databases, functions, and scripts.
5
5
  Home-page: https://github.com/nikhillingadhal1999/flexmetric
6
6
  Author: Nikhil Lingadhal
@@ -155,11 +155,33 @@ curl -k -X POST https://localhost:5000/update_metric \
155
155
  commands:
156
156
  - name: disk_usage
157
157
  command: df -h
158
- label: path
159
- label_column: -1
158
+ main_label: disk_usage_filesystem_mount_point
159
+ labels: ["filesystem", "mounted"]
160
+ label_columns: [0, -1]
160
161
  value_column: 4
161
162
  timeout_seconds: 60
162
163
  ```
164
+ Example to select label_column and value_column
165
+
166
+ ```bash
167
+ Filesystem Size Used Avail Use% Mounted on
168
+ /dev/sda1 50G 20G 28G 42% /
169
+ /dev/sdb1 100G 10G 85G 10% /data
170
+ ```
171
+ ## Fields description
172
+
173
+ | Field | Description |
174
+ |-------------------|----------------------------------------------------------------------------------------------------------------------------|
175
+ | `name` | A **nickname** you give to this check. It's just for your reference to know what this command is doing (e.g., `"disk_usage"`). |
176
+ | `command` | The **actual shell command** to run (e.g., `"df -h"`). It fetches the data you want to monitor. |
177
+ | `main_label` | The **metric name** that will appear in Prometheus. This is what you will query to see the metric values. |
178
+ | `labels` | A list of **label names** used to describe different dimensions of the metric (e.g., `["filesystem", "mounted"]`). |
179
+ | `label_columns` | A list of **column indexes** from the command’s output to extract the label values (e.g., `[0, -1]` for first and last column). |
180
+ | `value_column` | The **column index** from the command's output to extract the **numeric value** (the actual metric value, e.g., disk usage). |
181
+ | `timeout_seconds` | Maximum time (in seconds) to wait for the command to complete. If it exceeds this time, the command is aborted. |
182
+
183
+ ## Database mode
184
+
163
185
  ```yaml
164
186
  databases:
165
187
  - name: mydb
@@ -167,14 +189,16 @@ databases:
167
189
  db_connection: /path/to/my.db
168
190
  ````
169
191
  ```yaml
170
- queries:
192
+ commands:
171
193
  - name: user_count
172
- db_type: sqlite
173
- db_name: mydb
194
+ database: userdb
174
195
  query: "SELECT COUNT(*) FROM users;"
175
- label: table
176
- label_value: users
196
+ main_label: database_user_count
197
+ labels: ["database_name", "table_name"]
198
+ label_values: ["userdb", "users"]
177
199
  ```
200
+ ## Functions mode
201
+
178
202
  executable_functions.txt
179
203
  ```
180
204
  function_name_1
@@ -188,9 +212,10 @@ When using the `--functions` mode, each Python function you define is expected t
188
212
  ```python
189
213
  {
190
214
  'result': [
191
- {'label': <label_or_labels>, 'value': <numeric_value>}
215
+ { 'label': [label_value1, label_value2, ...], 'value': numeric_value }
192
216
  ],
193
- 'labels': [<label_name_1>]
217
+ 'labels': [label_name1, label_name2, ...],
218
+ 'main_label': 'your_main_metric_name'
194
219
  }
195
220
  ```
196
221
 
@@ -122,11 +122,33 @@ curl -k -X POST https://localhost:5000/update_metric \
122
122
  commands:
123
123
  - name: disk_usage
124
124
  command: df -h
125
- label: path
126
- label_column: -1
125
+ main_label: disk_usage_filesystem_mount_point
126
+ labels: ["filesystem", "mounted"]
127
+ label_columns: [0, -1]
127
128
  value_column: 4
128
129
  timeout_seconds: 60
129
130
  ```
131
+ Example to select label_column and value_column
132
+
133
+ ```bash
134
+ Filesystem Size Used Avail Use% Mounted on
135
+ /dev/sda1 50G 20G 28G 42% /
136
+ /dev/sdb1 100G 10G 85G 10% /data
137
+ ```
138
+ ## Fields description
139
+
140
+ | Field | Description |
141
+ |-------------------|----------------------------------------------------------------------------------------------------------------------------|
142
+ | `name` | A **nickname** you give to this check. It's just for your reference to know what this command is doing (e.g., `"disk_usage"`). |
143
+ | `command` | The **actual shell command** to run (e.g., `"df -h"`). It fetches the data you want to monitor. |
144
+ | `main_label` | The **metric name** that will appear in Prometheus. This is what you will query to see the metric values. |
145
+ | `labels` | A list of **label names** used to describe different dimensions of the metric (e.g., `["filesystem", "mounted"]`). |
146
+ | `label_columns` | A list of **column indexes** from the command’s output to extract the label values (e.g., `[0, -1]` for first and last column). |
147
+ | `value_column` | The **column index** from the command's output to extract the **numeric value** (the actual metric value, e.g., disk usage). |
148
+ | `timeout_seconds` | Maximum time (in seconds) to wait for the command to complete. If it exceeds this time, the command is aborted. |
149
+
150
+ ## Database mode
151
+
130
152
  ```yaml
131
153
  databases:
132
154
  - name: mydb
@@ -134,14 +156,16 @@ databases:
134
156
  db_connection: /path/to/my.db
135
157
  ````
136
158
  ```yaml
137
- queries:
159
+ commands:
138
160
  - name: user_count
139
- db_type: sqlite
140
- db_name: mydb
161
+ database: userdb
141
162
  query: "SELECT COUNT(*) FROM users;"
142
- label: table
143
- label_value: users
163
+ main_label: database_user_count
164
+ labels: ["database_name", "table_name"]
165
+ label_values: ["userdb", "users"]
144
166
  ```
167
+ ## Functions mode
168
+
145
169
  executable_functions.txt
146
170
  ```
147
171
  function_name_1
@@ -155,9 +179,10 @@ When using the `--functions` mode, each Python function you define is expected t
155
179
  ```python
156
180
  {
157
181
  'result': [
158
- {'label': <label_or_labels>, 'value': <numeric_value>}
182
+ { 'label': [label_value1, label_value2, ...], 'value': numeric_value }
159
183
  ],
160
- 'labels': [<label_name_1>]
184
+ 'labels': [label_name1, label_name2, ...],
185
+ 'main_label': 'your_main_metric_name'
161
186
  }
162
187
  ```
163
188
 
@@ -60,8 +60,10 @@ def process_database_queries(queries_file, databases_file):
60
60
 
61
61
  db_path = db_conf["db_connection"]
62
62
  query = cmd["query"]
63
- label = cmd["label"]
64
- label_value = cmd["label_value"]
63
+ labels = cmd.get('labels', [])
64
+ label_values = cmd.get('label_values', [])
65
+ main_label = cmd.get('main_label', 'default_db_metric')
66
+
65
67
  # check if query is safe
66
68
  if is_safe_query(query):
67
69
  value = execute_sqlite_query(db_path, query)
@@ -69,20 +71,20 @@ def process_database_queries(queries_file, databases_file):
69
71
  print(f"[WARN] Unsupported query type: {query}")
70
72
  return None
71
73
 
72
- if value is not None:
73
- result = {
74
- "result": [{"label": label_value, "value": value}],
75
- "labels": [label],
76
- }
77
- all_results.append(result)
78
- else:
79
- print(
80
- f"[INFO] No result for command '{cmd['name']}' on database '{cmd['database']}'"
81
- )
82
-
74
+ if not isinstance(label_values, list):
75
+ label_values = [label_values]
76
+
77
+ result = {
78
+ 'result': [{
79
+ 'label': label_values,
80
+ 'value': value
81
+ }],
82
+ 'labels': labels,
83
+ 'main_label': main_label
84
+ }
85
+ all_results.append(result)
83
86
  except Exception as e:
84
87
  print(
85
88
  f"[ERROR] Processing command '{cmd.get('name', 'unknown')}' failed: {e}"
86
89
  )
87
-
88
90
  return all_results
@@ -55,24 +55,40 @@ def parse_command_output(raw_output, label_column, value_column, fixed_label_val
55
55
 
56
56
  def process_single_command(cmd_info):
57
57
  command = cmd_info['command']
58
- label_name = cmd_info['label']
59
58
  timeout = cmd_info.get('timeout_seconds', 30)
60
- label_column = cmd_info.get('label_column', -1)
59
+ labels = cmd_info.get('labels', [])
60
+ label_columns = cmd_info.get('label_columns', [])
61
61
  value_column = cmd_info.get('value_column', 0)
62
- fixed_label_value = cmd_info.get('label_value')
62
+ main_label = cmd_info.get('main_label', 'default_metric')
63
63
 
64
64
  raw_output = execute_command_with_timeout(command, timeout)
65
- if raw_output == '':
65
+ if not raw_output:
66
66
  logger.warning(f"No results for command {command}")
67
67
  return None
68
68
 
69
- result_list = parse_command_output(raw_output, label_column, value_column, fixed_label_value)
70
-
69
+ lines = raw_output.strip().split('\n')
70
+ if not lines:
71
+ logger.error(f"No valid lines returned from command: {command}")
72
+ return None
73
+ result_list = []
74
+ for line in lines:
75
+ parts = line.split()
76
+ try:
77
+ label_columns_value = []
78
+ for value in label_columns:
79
+ label_columns_value.append(parts[value])
80
+ result_list.append({ "label": label_columns_value, "value": parts[value_column] })
81
+ except Exception as e:
82
+ logger.error(f"Error parsing line: '{line}' → {e}")
83
+ continue
71
84
  return {
72
85
  'result': result_list,
73
- 'labels': [label_name]
86
+ 'labels': labels,
87
+ 'main_label': main_label
74
88
  }
75
89
 
90
+
91
+
76
92
  def is_command_safe(command):
77
93
  blacklist = ['rm', 'reboot', 'shutdown', 'halt', 'poweroff', 'mkfs', 'dd']
78
94
  for dangerous_cmd in blacklist:
@@ -190,27 +190,37 @@ def measure(args):
190
190
  if cmd_results != None:
191
191
  exec_result.extend(cmd_results)
192
192
  global gauges
193
+
193
194
  for data in exec_result:
194
195
  results = data["result"]
195
196
  labels = data["labels"]
196
- gauge_name = "_".join(labels).lower() + "_gauge"
197
- # print(labels)
197
+ main_label_value = data.get("main_label", "default_main")
198
+ gauge_name = main_label_value.lower() + "_gauge"
199
+
198
200
  if gauge_name not in gauges:
199
201
  gauge = Gauge(gauge_name, f"{gauge_name} for different metrics", labels)
200
202
  gauges[gauge_name] = gauge
201
203
  else:
202
204
  gauge = gauges[gauge_name]
205
+
203
206
  for result in results:
204
- if isinstance(result["label"], str):
205
- try:
206
- gauge.labels(result["label"]).set(
207
- convert_to_data_type(result["value"])
208
- )
209
- except Exception as ex:
210
- logger.error("Cannot pass string")
211
- elif isinstance(result["label"], list):
212
- label_dict = dict(zip(labels, result["label"]))
207
+ label_values = result["label"]
208
+
209
+ if not isinstance(label_values, list):
210
+ # Automatically wrap single label into list for consistency
211
+ label_values = [label_values]
212
+
213
+ if len(label_values) != len(labels):
214
+ logger.error(f"Label mismatch: expected {len(labels)} values but got {len(label_values)}")
215
+ continue
216
+
217
+ label_dict = dict(zip(labels, label_values))
218
+ # print(label_dict)
219
+
220
+ try:
213
221
  gauge.labels(**label_dict).set(convert_to_data_type(result["value"]))
222
+ except Exception as ex:
223
+ logger.error(f"Failed to set gauge for labels {label_dict}: {ex}")
214
224
 
215
225
 
216
226
  def scheduled_measure(args):
@@ -244,5 +254,6 @@ def main():
244
254
  secure_flask_run(args)
245
255
  else:
246
256
  run_flask(args.host, args.port)
247
-
248
- main()
257
+ # # args = arguments()
258
+ # # measure(args)
259
+ # main()
@@ -0,0 +1,54 @@
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
+ # Validate top-level structure
21
+ if not data or "result" not in data or "labels" not in data or "main_label" not in data:
22
+ return jsonify({"status": "invalid structure"}), 400
23
+
24
+ result = data["result"]
25
+ labels = data["labels"]
26
+ main_label = data["main_label"]
27
+
28
+ # Validate types
29
+ if not isinstance(result, list) or not isinstance(labels, list) or not isinstance(main_label, str):
30
+ return jsonify({"status": "invalid types"}), 400
31
+
32
+ for item in result:
33
+ if "label" not in item or "value" not in item:
34
+ return jsonify({"status": "invalid result item"}), 400
35
+ if not isinstance(item["label"], list):
36
+ return jsonify({"status": "label must be list"}), 400
37
+
38
+ if len(item["label"]) != len(labels):
39
+ return jsonify({"status": "label count mismatch"}), 400
40
+
41
+ # If everything is valid, queue the data
42
+ metric_queue.put(data)
43
+ print(metric_queue)
44
+
45
+ return jsonify({"status": "success"}), 200
46
+
47
+ except Exception as e:
48
+ return jsonify({"status": "error", "message": str(e)}), 500
49
+
50
+
51
+ def run_flask(host, port):
52
+ app.run(host=host, port=port)
53
+ def secure_flask_run(args):
54
+ app.run(host=args.host, port=args.port, ssl_context=(args.ssl_cert, args.ssl_key))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flexmetric
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: A secure flexible Prometheus exporter for commands, databases, functions, and scripts.
5
5
  Home-page: https://github.com/nikhillingadhal1999/flexmetric
6
6
  Author: Nikhil Lingadhal
@@ -155,11 +155,33 @@ curl -k -X POST https://localhost:5000/update_metric \
155
155
  commands:
156
156
  - name: disk_usage
157
157
  command: df -h
158
- label: path
159
- label_column: -1
158
+ main_label: disk_usage_filesystem_mount_point
159
+ labels: ["filesystem", "mounted"]
160
+ label_columns: [0, -1]
160
161
  value_column: 4
161
162
  timeout_seconds: 60
162
163
  ```
164
+ Example to select label_column and value_column
165
+
166
+ ```bash
167
+ Filesystem Size Used Avail Use% Mounted on
168
+ /dev/sda1 50G 20G 28G 42% /
169
+ /dev/sdb1 100G 10G 85G 10% /data
170
+ ```
171
+ ## Fields description
172
+
173
+ | Field | Description |
174
+ |-------------------|----------------------------------------------------------------------------------------------------------------------------|
175
+ | `name` | A **nickname** you give to this check. It's just for your reference to know what this command is doing (e.g., `"disk_usage"`). |
176
+ | `command` | The **actual shell command** to run (e.g., `"df -h"`). It fetches the data you want to monitor. |
177
+ | `main_label` | The **metric name** that will appear in Prometheus. This is what you will query to see the metric values. |
178
+ | `labels` | A list of **label names** used to describe different dimensions of the metric (e.g., `["filesystem", "mounted"]`). |
179
+ | `label_columns` | A list of **column indexes** from the command’s output to extract the label values (e.g., `[0, -1]` for first and last column). |
180
+ | `value_column` | The **column index** from the command's output to extract the **numeric value** (the actual metric value, e.g., disk usage). |
181
+ | `timeout_seconds` | Maximum time (in seconds) to wait for the command to complete. If it exceeds this time, the command is aborted. |
182
+
183
+ ## Database mode
184
+
163
185
  ```yaml
164
186
  databases:
165
187
  - name: mydb
@@ -167,14 +189,16 @@ databases:
167
189
  db_connection: /path/to/my.db
168
190
  ````
169
191
  ```yaml
170
- queries:
192
+ commands:
171
193
  - name: user_count
172
- db_type: sqlite
173
- db_name: mydb
194
+ database: userdb
174
195
  query: "SELECT COUNT(*) FROM users;"
175
- label: table
176
- label_value: users
196
+ main_label: database_user_count
197
+ labels: ["database_name", "table_name"]
198
+ label_values: ["userdb", "users"]
177
199
  ```
200
+ ## Functions mode
201
+
178
202
  executable_functions.txt
179
203
  ```
180
204
  function_name_1
@@ -188,9 +212,10 @@ When using the `--functions` mode, each Python function you define is expected t
188
212
  ```python
189
213
  {
190
214
  'result': [
191
- {'label': <label_or_labels>, 'value': <numeric_value>}
215
+ { 'label': [label_value1, label_value2, ...], 'value': numeric_value }
192
216
  ],
193
- 'labels': [<label_name_1>]
217
+ 'labels': [label_name1, label_name2, ...],
218
+ 'main_label': 'your_main_metric_name'
194
219
  }
195
220
  ```
196
221
 
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="flexmetric",
5
- version="0.3.2",
5
+ version="0.4.0",
6
6
  author="Nikhil Lingadhal",
7
7
  description="A secure flexible Prometheus exporter for commands, databases, functions, and scripts.",
8
8
  long_description=open("README.md").read(),
@@ -1,38 +0,0 @@
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))
File without changes