flexmetric 0.1.4__tar.gz → 0.3.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 (27) hide show
  1. flexmetric-0.3.0/PKG-INFO +243 -0
  2. flexmetric-0.3.0/README.md +214 -0
  3. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/metric_process/database_processing.py +32 -19
  4. flexmetric-0.3.0/flexmetric/metric_process/expiring_queue.py +56 -0
  5. flexmetric-0.3.0/flexmetric/metric_process/process_commands.py +111 -0
  6. flexmetric-0.3.0/flexmetric/metric_process/prometheus_agent.py +248 -0
  7. flexmetric-0.3.0/flexmetric/metric_process/views.py +38 -0
  8. flexmetric-0.3.0/flexmetric.egg-info/PKG-INFO +243 -0
  9. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric.egg-info/SOURCES.txt +4 -1
  10. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric.egg-info/requires.txt +1 -0
  11. {flexmetric-0.1.4 → flexmetric-0.3.0}/setup.py +4 -3
  12. flexmetric-0.1.4/PKG-INFO +0 -152
  13. flexmetric-0.1.4/README.md +0 -124
  14. flexmetric-0.1.4/flexmetric/metric_process/prometheus_agent.py +0 -143
  15. flexmetric-0.1.4/flexmetric.egg-info/PKG-INFO +0 -152
  16. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/__init__.py +0 -0
  17. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/config/__init__.py +0 -0
  18. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/config/configuration.py +0 -0
  19. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/file_recognition/__init__.py +0 -0
  20. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/file_recognition/exec_file.py +0 -0
  21. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/logging_module/__init__.py +0 -0
  22. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/logging_module/logger.py +0 -0
  23. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric/metric_process/__init__.py +0 -0
  24. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric.egg-info/dependency_links.txt +0 -0
  25. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric.egg-info/entry_points.txt +0 -0
  26. {flexmetric-0.1.4 → flexmetric-0.3.0}/flexmetric.egg-info/top_level.txt +0 -0
  27. {flexmetric-0.1.4 → flexmetric-0.3.0}/setup.cfg +0 -0
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: flexmetric
3
+ Version: 0.3.0
4
+ Summary: A secure 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 securely expose system metrics, database query results, Python function outputs, and externally submitted metrics—via an optional Flask API with HTTPS support—as Prometheus-compatible metrics, all with minimal setup and maximum customization.
33
+
34
+ ---
35
+
36
+ ## Features
37
+
38
+ - Run shell commands and expose the results as Prometheus metrics.
39
+ ➔ **Harmful commands (e.g., file deletion, system shutdown) are blocked for safety.**
40
+ - Execute SQL queries (e.g., SQLite) and monitor database statistics.
41
+ ➔ **Potentially dangerous queries (e.g., `DROP`, `DELETE`, `TRUNCATE`) are not allowed.**
42
+ - Automatically discover and expose Python function outputs as metrics.
43
+ - Expose an optional **Flask API** (`/update_metric`) to receive external metrics dynamically.
44
+ - Modular and easy to extend—add your own custom integrations.
45
+ - Built-in Prometheus HTTP server (`/metrics`) with configurable port.
46
+ - **Supports HTTPS** to securely expose both metrics and API endpoints.
47
+ - **Input sanitization** is performed to ensure only safe commands and queries are executed.
48
+
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ Install from PyPI:
55
+
56
+ ```bash
57
+ pip install flexmetric
58
+ ```
59
+ ## Usage
60
+
61
+ Run FlexMetric from the command line:
62
+
63
+ ```bash
64
+ flexmetric --commands --commands-config commands.yaml --port 8000
65
+ ```
66
+
67
+ ## Available Modes
68
+
69
+ FlexMetric supports multiple modes that can be used individually or combined to expose metrics:
70
+
71
+ | Mode | Description | Required Configuration File(s) |
72
+ |-----------------|------------------------------------------------------------------------|------------------------------------------|
73
+ | `--commands` | Runs system commands and exports outputs as Prometheus metrics. | `commands.yaml` |
74
+ | `--database` | Executes SQL queries on databases and exports results. | `database.yaml` and `queries.yaml` |
75
+ | `--functions` | Discovers and runs user-defined Python functions and exports outputs. | `executable_functions.txt` |
76
+ | `--expose-api` | Exposes a Flask API (`/update_metric`) to receive external metrics. | *No configuration file required* |
77
+ ### Example of Using Multiple Modes Together
78
+
79
+ ```bash
80
+ flexmetric --commands --commands-config commands.yaml --database --database-config database.yaml --queries-config queries.yaml
81
+ ```
82
+
83
+ ## Configuration File Examples
84
+
85
+ Below are example configurations for each supported mode.
86
+
87
+ ## Using the Flask API in FlexMetric
88
+
89
+ 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.
90
+
91
+ ### Start FlexMetric with Flask API
92
+
93
+ ```bash
94
+ flexmetric --expose-api --port <port> --host <host>
95
+ ```
96
+
97
+ ## Example: Running FlexMetric with Flask API
98
+
99
+ To run FlexMetric with both Prometheus metrics and the Flask API enabled:
100
+
101
+ ```bash
102
+ flexmetric --expose-api --port 5000 --host 0.0.0.0
103
+ ```
104
+
105
+ Prometheus metrics exposed at:
106
+ http://localhost:5000/metrics
107
+
108
+ Flask API exposed at:
109
+ http://localhost:5000/update_metric
110
+
111
+ ### Submitting a Metric to the Flask API
112
+ ```bash
113
+ curl -X POST http://localhost:5000/update_metric \
114
+ -H "Content-Type: application/json" \
115
+ -d '{
116
+ "result": [
117
+ { "label": "cpu_usage", "value": 42.5 }
118
+ ],
119
+ "labels": ["cpu"]
120
+ }'
121
+
122
+ ```
123
+
124
+ ### Using flex metrics in secure mode
125
+
126
+ ```bash
127
+ flexmetric --port 5000 --host 0.0.0.0 --enable-https --ssl-cert=cert.pem --ssl-key=key.pem
128
+ ```
129
+ Prometheus metrics exposed at:
130
+ https://localhost:5000/metrics
131
+
132
+ Flask API exposed at:
133
+ https://localhost:5000/update_metric
134
+
135
+ ### Submitting a Metric to the Flask API
136
+ ```bash
137
+ curl -k -X POST https://localhost:5000/update_metric \
138
+ -H "Content-Type: application/json" \
139
+ -d '{
140
+ "result": [
141
+ { "label": "cpu_usage", "value": 42.5 }
142
+ ],
143
+ "labels": ["cpu"]
144
+ }'
145
+
146
+ ```
147
+
148
+ ### commands.yaml
149
+
150
+ ```yaml
151
+ commands:
152
+ - name: disk_usage
153
+ command: df -h
154
+ label: path
155
+ timeout_seconds: 60
156
+ ```
157
+ ```yaml
158
+ databases:
159
+ - name: mydb
160
+ db_type: sqlite
161
+ db_connection: /path/to/my.db
162
+ ````
163
+ ```yaml
164
+ queries:
165
+ - name: user_count
166
+ db_type: sqlite
167
+ db_name: mydb
168
+ query: "SELECT COUNT(*) FROM users;"
169
+ label: table
170
+ label_value: users
171
+ ```
172
+ executable_functions.txt
173
+ ```
174
+ function_name_1
175
+ function_name_2
176
+ ```
177
+
178
+ ## Python Function Output Format
179
+
180
+ When using the `--functions` mode, each Python function you define is expected to return a dictionary in the following format:
181
+
182
+ ```python
183
+ {
184
+ 'result': [
185
+ {'label': <label_or_labels>, 'value': <numeric_value>}
186
+ ],
187
+ 'labels': [<label_name_1>]
188
+ }
189
+ ```
190
+
191
+ ### Explanation:
192
+
193
+ | Key | Description |
194
+ |--------|---------------------------------------------------------------------------|
195
+ | `result` | A list of dictionaries, each containing a `label` and a corresponding numeric `value`. |
196
+ | `labels` | A list of label names (used as Prometheus labels). |
197
+
198
+
199
+ ## Command-Line Options
200
+
201
+ The following command-line options are available when running FlexMetric:
202
+
203
+ | Option | Description | Default |
204
+ |---------------------|----------------------------------------------------------|----------------------------|
205
+ | `--port` | Port for the Prometheus metrics server (`/metrics`) | `8000` |
206
+ | `--commands` | Enable commands mode | |
207
+ | `--commands-config` | Path to commands YAML file | `commands.yaml` |
208
+ | `--database` | Enable database mode | |
209
+ | `--database-config` | Path to database YAML file | `database.yaml` |
210
+ | `--queries-config` | Path to queries YAML file | `queries.yaml` |
211
+ | `--functions` | Enable Python functions mode | |
212
+ | `--functions-file` | Path to functions file | `executable_functions.txt` |
213
+ | `--expose-api` | Enable Flask API mode to receive external metrics | |
214
+ | `--flask-port` | Port for the Flask API (`/update_metric`) | `5000` |
215
+ | `--flask-host` | Hostname for the Flask API | `0.0.0.0` |
216
+ | `--enable-https` | Enable HTTPS for the Flask API | |
217
+ | `--ssl-cert` | Path to SSL certificate file (`cert.pem`) | |
218
+ | `--ssl-key` | Path to SSL private key file (`key.pem`) | |
219
+
220
+ ### Example Command:
221
+
222
+ ```bash
223
+ flexmetric --commands --commands-config commands.yaml --port 8000
224
+ ```
225
+ ## Example Prometheus Output
226
+
227
+ Once FlexMetric is running, the `/metrics` endpoint will expose metrics in the Prometheus format.
228
+
229
+ Example output:
230
+ ```bash
231
+ disk_usage_gauge{path="/"} 45.0
232
+ ```
233
+
234
+ Each metric includes labels and numeric values that Prometheus can scrape and visualize.
235
+
236
+ ---
237
+
238
+ ## Future Enhancements
239
+
240
+ The following features are planned or under consideration to improve FlexMetric:
241
+
242
+ - Support for additional databases such as PostgreSQL and MySQL.
243
+ - Enhanced support for more complex scripts and richer label extraction.
@@ -0,0 +1,214 @@
1
+ # FlexMetric
2
+
3
+ FlexMetric is a lightweight, flexible, and extensible Prometheus exporter that allows you to securely expose system metrics, database query results, Python function outputs, and externally submitted metrics—via an optional Flask API with HTTPS support—as Prometheus-compatible metrics, all with minimal setup and maximum customization.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - Run shell commands and expose the results as Prometheus metrics.
10
+ ➔ **Harmful commands (e.g., file deletion, system shutdown) are blocked for safety.**
11
+ - Execute SQL queries (e.g., SQLite) and monitor database statistics.
12
+ ➔ **Potentially dangerous queries (e.g., `DROP`, `DELETE`, `TRUNCATE`) are not allowed.**
13
+ - Automatically discover and expose Python function outputs as metrics.
14
+ - Expose an optional **Flask API** (`/update_metric`) to receive external metrics dynamically.
15
+ - Modular and easy to extend—add your own custom integrations.
16
+ - Built-in Prometheus HTTP server (`/metrics`) with configurable port.
17
+ - **Supports HTTPS** to securely expose both metrics and API endpoints.
18
+ - **Input sanitization** is performed to ensure only safe commands and queries are executed.
19
+
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ Install from PyPI:
26
+
27
+ ```bash
28
+ pip install flexmetric
29
+ ```
30
+ ## Usage
31
+
32
+ Run FlexMetric from the command line:
33
+
34
+ ```bash
35
+ flexmetric --commands --commands-config commands.yaml --port 8000
36
+ ```
37
+
38
+ ## Available Modes
39
+
40
+ FlexMetric supports multiple modes that can be used individually or combined to expose metrics:
41
+
42
+ | Mode | Description | Required Configuration File(s) |
43
+ |-----------------|------------------------------------------------------------------------|------------------------------------------|
44
+ | `--commands` | Runs system commands and exports outputs as Prometheus metrics. | `commands.yaml` |
45
+ | `--database` | Executes SQL queries on databases and exports results. | `database.yaml` and `queries.yaml` |
46
+ | `--functions` | Discovers and runs user-defined Python functions and exports outputs. | `executable_functions.txt` |
47
+ | `--expose-api` | Exposes a Flask API (`/update_metric`) to receive external metrics. | *No configuration file required* |
48
+ ### Example of Using Multiple Modes Together
49
+
50
+ ```bash
51
+ flexmetric --commands --commands-config commands.yaml --database --database-config database.yaml --queries-config queries.yaml
52
+ ```
53
+
54
+ ## Configuration File Examples
55
+
56
+ Below are example configurations for each supported mode.
57
+
58
+ ## Using the Flask API in FlexMetric
59
+
60
+ 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.
61
+
62
+ ### Start FlexMetric with Flask API
63
+
64
+ ```bash
65
+ flexmetric --expose-api --port <port> --host <host>
66
+ ```
67
+
68
+ ## Example: Running FlexMetric with Flask API
69
+
70
+ To run FlexMetric with both Prometheus metrics and the Flask API enabled:
71
+
72
+ ```bash
73
+ flexmetric --expose-api --port 5000 --host 0.0.0.0
74
+ ```
75
+
76
+ Prometheus metrics exposed at:
77
+ http://localhost:5000/metrics
78
+
79
+ Flask API exposed at:
80
+ http://localhost:5000/update_metric
81
+
82
+ ### Submitting a Metric to the Flask API
83
+ ```bash
84
+ curl -X POST http://localhost:5000/update_metric \
85
+ -H "Content-Type: application/json" \
86
+ -d '{
87
+ "result": [
88
+ { "label": "cpu_usage", "value": 42.5 }
89
+ ],
90
+ "labels": ["cpu"]
91
+ }'
92
+
93
+ ```
94
+
95
+ ### Using flex metrics in secure mode
96
+
97
+ ```bash
98
+ flexmetric --port 5000 --host 0.0.0.0 --enable-https --ssl-cert=cert.pem --ssl-key=key.pem
99
+ ```
100
+ Prometheus metrics exposed at:
101
+ https://localhost:5000/metrics
102
+
103
+ Flask API exposed at:
104
+ https://localhost:5000/update_metric
105
+
106
+ ### Submitting a Metric to the Flask API
107
+ ```bash
108
+ curl -k -X POST https://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
+ | `--expose-api` | Enable Flask API mode to receive external metrics | |
185
+ | `--flask-port` | Port for the Flask API (`/update_metric`) | `5000` |
186
+ | `--flask-host` | Hostname for the Flask API | `0.0.0.0` |
187
+ | `--enable-https` | Enable HTTPS for the Flask API | |
188
+ | `--ssl-cert` | Path to SSL certificate file (`cert.pem`) | |
189
+ | `--ssl-key` | Path to SSL private key file (`key.pem`) | |
190
+
191
+ ### Example Command:
192
+
193
+ ```bash
194
+ flexmetric --commands --commands-config commands.yaml --port 8000
195
+ ```
196
+ ## Example Prometheus Output
197
+
198
+ Once FlexMetric is running, the `/metrics` endpoint will expose metrics in the Prometheus format.
199
+
200
+ Example output:
201
+ ```bash
202
+ disk_usage_gauge{path="/"} 45.0
203
+ ```
204
+
205
+ Each metric includes labels and numeric values that Prometheus can scrape and visualize.
206
+
207
+ ---
208
+
209
+ ## Future Enhancements
210
+
211
+ The following features are planned or under consideration to improve FlexMetric:
212
+
213
+ - Support for additional databases such as PostgreSQL and MySQL.
214
+ - 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)