flexmetric 0.3.3__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.3 → flexmetric-0.4.0}/PKG-INFO +21 -19
  2. {flexmetric-0.3.3 → flexmetric-0.4.0}/README.md +20 -18
  3. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/metric_process/database_processing.py +16 -14
  4. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/metric_process/process_commands.py +23 -7
  5. {flexmetric-0.3.3 → 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.3 → flexmetric-0.4.0}/flexmetric.egg-info/PKG-INFO +21 -19
  8. {flexmetric-0.3.3 → flexmetric-0.4.0}/setup.py +1 -1
  9. flexmetric-0.3.3/flexmetric/metric_process/views.py +0 -38
  10. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/__init__.py +0 -0
  11. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/config/__init__.py +0 -0
  12. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/config/configuration.py +0 -0
  13. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/file_recognition/__init__.py +0 -0
  14. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/file_recognition/exec_file.py +0 -0
  15. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/logging_module/__init__.py +0 -0
  16. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/logging_module/logger.py +0 -0
  17. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/metric_process/__init__.py +0 -0
  18. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric/metric_process/expiring_queue.py +0 -0
  19. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric.egg-info/SOURCES.txt +0 -0
  20. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric.egg-info/dependency_links.txt +0 -0
  21. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric.egg-info/entry_points.txt +0 -0
  22. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric.egg-info/requires.txt +0 -0
  23. {flexmetric-0.3.3 → flexmetric-0.4.0}/flexmetric.egg-info/top_level.txt +0 -0
  24. {flexmetric-0.3.3 → 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.3
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,8 +155,9 @@ 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
  ```
@@ -169,15 +170,15 @@ Filesystem Size Used Avail Use% Mounted on
169
170
  ```
170
171
  ## Fields description
171
172
 
172
-
173
- | Field | Description |
174
- |------------------|-----------------------------------------------------------------------------|
175
- | `name` | The **name** of the metric (`disk_usage`). This is the metric name seen in Prometheus. |
176
- | `command` | The **shell command** to execute (`df -h` in this case). |
177
- | `label` | The **label name** to attach to the metric (`path`). |
178
- | `label_column` | The **column index** from the command's output to use as the label. Here `-1` means the **last column** (typically the mount path in `df -h`). |
179
- | `value_column` | The **column index** from the command's output to extract the **numeric value**. Here `4` refers to the fifth column, which is usually the **Use%** in `df -h`. |
180
- | `timeout_seconds`| How long (in seconds) to wait for the command before timing out. Here it's set to `60` seconds. |
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 commands 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. |
181
182
 
182
183
  ## Database mode
183
184
 
@@ -188,13 +189,13 @@ databases:
188
189
  db_connection: /path/to/my.db
189
190
  ````
190
191
  ```yaml
191
- queries:
192
+ commands:
192
193
  - name: user_count
193
- db_type: sqlite
194
- db_name: mydb
194
+ database: userdb
195
195
  query: "SELECT COUNT(*) FROM users;"
196
- label: table
197
- label_value: users
196
+ main_label: database_user_count
197
+ labels: ["database_name", "table_name"]
198
+ label_values: ["userdb", "users"]
198
199
  ```
199
200
  ## Functions mode
200
201
 
@@ -211,9 +212,10 @@ When using the `--functions` mode, each Python function you define is expected t
211
212
  ```python
212
213
  {
213
214
  'result': [
214
- {'label': <label_or_labels>, 'value': <numeric_value>}
215
+ { 'label': [label_value1, label_value2, ...], 'value': numeric_value }
215
216
  ],
216
- 'labels': [<label_name_1>]
217
+ 'labels': [label_name1, label_name2, ...],
218
+ 'main_label': 'your_main_metric_name'
217
219
  }
218
220
  ```
219
221
 
@@ -122,8 +122,9 @@ 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
  ```
@@ -136,15 +137,15 @@ Filesystem Size Used Avail Use% Mounted on
136
137
  ```
137
138
  ## Fields description
138
139
 
139
-
140
- | Field | Description |
141
- |------------------|-----------------------------------------------------------------------------|
142
- | `name` | The **name** of the metric (`disk_usage`). This is the metric name seen in Prometheus. |
143
- | `command` | The **shell command** to execute (`df -h` in this case). |
144
- | `label` | The **label name** to attach to the metric (`path`). |
145
- | `label_column` | The **column index** from the command's output to use as the label. Here `-1` means the **last column** (typically the mount path in `df -h`). |
146
- | `value_column` | The **column index** from the command's output to extract the **numeric value**. Here `4` refers to the fifth column, which is usually the **Use%** in `df -h`. |
147
- | `timeout_seconds`| How long (in seconds) to wait for the command before timing out. Here it's set to `60` seconds. |
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 commands 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. |
148
149
 
149
150
  ## Database mode
150
151
 
@@ -155,13 +156,13 @@ databases:
155
156
  db_connection: /path/to/my.db
156
157
  ````
157
158
  ```yaml
158
- queries:
159
+ commands:
159
160
  - name: user_count
160
- db_type: sqlite
161
- db_name: mydb
161
+ database: userdb
162
162
  query: "SELECT COUNT(*) FROM users;"
163
- label: table
164
- label_value: users
163
+ main_label: database_user_count
164
+ labels: ["database_name", "table_name"]
165
+ label_values: ["userdb", "users"]
165
166
  ```
166
167
  ## Functions mode
167
168
 
@@ -178,9 +179,10 @@ When using the `--functions` mode, each Python function you define is expected t
178
179
  ```python
179
180
  {
180
181
  'result': [
181
- {'label': <label_or_labels>, 'value': <numeric_value>}
182
+ { 'label': [label_value1, label_value2, ...], 'value': numeric_value }
182
183
  ],
183
- 'labels': [<label_name_1>]
184
+ 'labels': [label_name1, label_name2, ...],
185
+ 'main_label': 'your_main_metric_name'
184
186
  }
185
187
  ```
186
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.3
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,8 +155,9 @@ 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
  ```
@@ -169,15 +170,15 @@ Filesystem Size Used Avail Use% Mounted on
169
170
  ```
170
171
  ## Fields description
171
172
 
172
-
173
- | Field | Description |
174
- |------------------|-----------------------------------------------------------------------------|
175
- | `name` | The **name** of the metric (`disk_usage`). This is the metric name seen in Prometheus. |
176
- | `command` | The **shell command** to execute (`df -h` in this case). |
177
- | `label` | The **label name** to attach to the metric (`path`). |
178
- | `label_column` | The **column index** from the command's output to use as the label. Here `-1` means the **last column** (typically the mount path in `df -h`). |
179
- | `value_column` | The **column index** from the command's output to extract the **numeric value**. Here `4` refers to the fifth column, which is usually the **Use%** in `df -h`. |
180
- | `timeout_seconds`| How long (in seconds) to wait for the command before timing out. Here it's set to `60` seconds. |
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 commands 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. |
181
182
 
182
183
  ## Database mode
183
184
 
@@ -188,13 +189,13 @@ databases:
188
189
  db_connection: /path/to/my.db
189
190
  ````
190
191
  ```yaml
191
- queries:
192
+ commands:
192
193
  - name: user_count
193
- db_type: sqlite
194
- db_name: mydb
194
+ database: userdb
195
195
  query: "SELECT COUNT(*) FROM users;"
196
- label: table
197
- label_value: users
196
+ main_label: database_user_count
197
+ labels: ["database_name", "table_name"]
198
+ label_values: ["userdb", "users"]
198
199
  ```
199
200
  ## Functions mode
200
201
 
@@ -211,9 +212,10 @@ When using the `--functions` mode, each Python function you define is expected t
211
212
  ```python
212
213
  {
213
214
  'result': [
214
- {'label': <label_or_labels>, 'value': <numeric_value>}
215
+ { 'label': [label_value1, label_value2, ...], 'value': numeric_value }
215
216
  ],
216
- 'labels': [<label_name_1>]
217
+ 'labels': [label_name1, label_name2, ...],
218
+ 'main_label': 'your_main_metric_name'
217
219
  }
218
220
  ```
219
221
 
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="flexmetric",
5
- version="0.3.3",
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