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.
- flexmetric-0.2.0/PKG-INFO +212 -0
- flexmetric-0.2.0/README.md +183 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/metric_process/database_processing.py +32 -19
- flexmetric-0.2.0/flexmetric/metric_process/expiring_queue.py +56 -0
- flexmetric-0.2.0/flexmetric/metric_process/process_commands.py +111 -0
- flexmetric-0.2.0/flexmetric/metric_process/prometheus_agent.py +233 -0
- flexmetric-0.2.0/flexmetric/metric_process/views.py +29 -0
- flexmetric-0.2.0/flexmetric.egg-info/PKG-INFO +212 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/SOURCES.txt +3 -1
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/requires.txt +1 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/setup.py +4 -3
- flexmetric-0.1.3/PKG-INFO +0 -152
- flexmetric-0.1.3/README.md +0 -124
- flexmetric-0.1.3/flexmetric/metric_process/process_commands.py +0 -73
- flexmetric-0.1.3/flexmetric/metric_process/prometheus_agent.py +0 -141
- flexmetric-0.1.3/flexmetric.egg-info/PKG-INFO +0 -152
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/__init__.py +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/config/__init__.py +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/config/configuration.py +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/file_recognition/__init__.py +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/file_recognition/exec_file.py +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/logging_module/__init__.py +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/logging_module/logger.py +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric/metric_process/__init__.py +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/dependency_links.txt +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/entry_points.txt +0 -0
- {flexmetric-0.1.3 → flexmetric-0.2.0}/flexmetric.egg-info/top_level.txt +0 -0
- {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,
|
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[
|
13
|
+
if db["name"] == db_name:
|
12
14
|
return db
|
13
|
-
raise ValueError(
|
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
|
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(
|
40
|
-
databases = databases_config.get(
|
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[
|
53
|
+
db_conf = get_database_config(databases, cmd["database"])
|
47
54
|
|
48
|
-
if db_conf[
|
49
|
-
print(
|
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[
|
53
|
-
query = cmd[
|
54
|
-
label = cmd[
|
55
|
-
label_value = cmd[
|
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
|
-
|
66
|
-
|
74
|
+
"result": [{"label": label_value, "value": value}],
|
75
|
+
"labels": [label],
|
67
76
|
}
|
68
77
|
all_results.append(result)
|
69
78
|
else:
|
70
|
-
print(
|
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(
|
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)
|