flexmetric 0.3.2__py3-none-any.whl → 0.4.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.
@@ -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()
@@ -17,19 +17,35 @@ def add_update_metric_route():
17
17
  try:
18
18
  data = request.get_json()
19
19
 
20
- if not data or "result" not in data or not isinstance(data["result"], list):
21
- return jsonify({"status": "no"}), 400
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
22
23
 
23
- for item in data["result"]:
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:
24
33
  if "label" not in item or "value" not in item:
25
- return jsonify({"status": "no"}), 400
34
+ return jsonify({"status": "invalid result item"}), 400
35
+ if not isinstance(item["label"], list):
36
+ return jsonify({"status": "label must be list"}), 400
26
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
27
42
  metric_queue.put(data)
28
43
  print(metric_queue)
44
+
29
45
  return jsonify({"status": "success"}), 200
30
46
 
31
- except Exception:
32
- return jsonify({"status": "no"}), 500
47
+ except Exception as e:
48
+ return jsonify({"status": "error", "message": str(e)}), 500
33
49
 
34
50
 
35
51
  def run_flask(host, port):
@@ -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
 
@@ -6,13 +6,13 @@ flexmetric/file_recognition/exec_file.py,sha256=9wBbnqPConxtLhqWTgigEr8VQG-fa9K_
6
6
  flexmetric/logging_module/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  flexmetric/logging_module/logger.py,sha256=hXj9m2Q_KxJVI5YRHRoK6PXV5tO6NmvuVjq2Ipx_1tE,447
8
8
  flexmetric/metric_process/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- flexmetric/metric_process/database_processing.py,sha256=W06P7ybno_9gFtndUtEY6vI2gMQvtYn1Z14w8h4FYwU,2668
9
+ flexmetric/metric_process/database_processing.py,sha256=hbVbzIdO21NF0F3nILJ4d1x8fkks9P8s-wtn8qZ91qw,2739
10
10
  flexmetric/metric_process/expiring_queue.py,sha256=1oC0MjloitPiRo7yDgVarz81ETEQavKI_W-GFUvp5_Y,1920
11
- flexmetric/metric_process/process_commands.py,sha256=QAsR5uXt6rku_lTrBCfslerpqW0hMLSffzUFmxPC-fw,3556
12
- flexmetric/metric_process/prometheus_agent.py,sha256=hjPmNhJiB-844CgIcaw5A-VLmJqyMwQ_j1e_GI-UPSo,7422
13
- flexmetric/metric_process/views.py,sha256=05UbGBrAkEn_aJq7qBb_1yphCfnXiTqmXFLznMMpkN8,1194
14
- flexmetric-0.3.2.dist-info/METADATA,sha256=0imUzfRyM5YKgh-KWA9rtaiZ3ogZyScZLUAQFKH6dH0,8691
15
- flexmetric-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- flexmetric-0.3.2.dist-info/entry_points.txt,sha256=urVePn5EWr3JqNvkYP7OsB_h2_Bqvv-Wq1MJRBexm8A,79
17
- flexmetric-0.3.2.dist-info/top_level.txt,sha256=zBlrNwKhXUNhgu9RRZnXxYwYnmE-eocRe6wKSmQROA4,11
18
- flexmetric-0.3.2.dist-info/RECORD,,
11
+ flexmetric/metric_process/process_commands.py,sha256=clGMQhLNcuJUO1gElpAS9Dyk0KU5w41DIguczjo7ceA,4089
12
+ flexmetric/metric_process/prometheus_agent.py,sha256=rZ9pQrqA3J_-sJtl48qkFIHIUH01iert05u4SmGN4Yc,7714
13
+ flexmetric/metric_process/views.py,sha256=vdL7PyZ8KRvEn6UM4HUtUZdCufzTL_JkkMEy0J2-4Ng,1939
14
+ flexmetric-0.4.0.dist-info/METADATA,sha256=RQTETb4livHASNzKCDg6N1JOrrzl8skM1rrv2p9vk4g,10498
15
+ flexmetric-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ flexmetric-0.4.0.dist-info/entry_points.txt,sha256=urVePn5EWr3JqNvkYP7OsB_h2_Bqvv-Wq1MJRBexm8A,79
17
+ flexmetric-0.4.0.dist-info/top_level.txt,sha256=zBlrNwKhXUNhgu9RRZnXxYwYnmE-eocRe6wKSmQROA4,11
18
+ flexmetric-0.4.0.dist-info/RECORD,,