flexmetric 0.1.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.
flexmetric/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,125 @@
1
+ import os
2
+ import ast
3
+ import importlib.util
4
+ from typing import Dict, List, Any
5
+
6
+
7
+ def get_python_files(folder_path: str) -> List[str]:
8
+
9
+ return [ os.path.join(root,file) for root, _ , files in os.walk(folder_path) for file in files if file.endswith('.py') ]
10
+
11
+ def extract_functions_with_code(file_path: str) -> Dict[str, str]:
12
+ with open(file_path, "r") as file:
13
+ file_content = file.read() # Read file content once
14
+
15
+ tree = ast.parse(file_content)
16
+
17
+ functions = {}
18
+ for node in ast.walk(tree):
19
+ if isinstance(node, ast.FunctionDef):
20
+ function_name = node.name
21
+ function_code = ast.get_source_segment(file_content, node)
22
+ functions[function_name] = function_code
23
+ return functions
24
+
25
+ def analyze_dependencies(function_code: str) -> List[str]:
26
+ try:
27
+ tree = ast.parse(function_code)
28
+ except SyntaxError as e:
29
+ print(f"Syntax error in function code: {e}")
30
+ return []
31
+
32
+ dependencies = []
33
+ for node in ast.walk(tree):
34
+ if isinstance(node, ast.Call):
35
+ if isinstance(node.func, ast.Name):
36
+ dependencies.append(node.func.id)
37
+ elif isinstance(node.func, ast.Attribute):
38
+ dependencies.append(node.func.attr)
39
+
40
+ return dependencies
41
+
42
+ def find_function_dependencies(file_path: str) -> Dict[str, List[str]]:
43
+ functions_with_code = extract_functions_with_code(file_path)
44
+ function_dependencies = {}
45
+
46
+ for func_name, func_code in functions_with_code.items():
47
+ dependencies = analyze_dependencies(func_code)
48
+ function_dependencies[func_name] = dependencies
49
+
50
+ return function_dependencies
51
+
52
+ def topological_sort(dependency_graph: Dict[str, List[str]]) -> List[str]:
53
+ visited = set()
54
+ stack = []
55
+
56
+ def visit(node):
57
+ if node not in visited:
58
+ visited.add(node)
59
+ for neighbor in dependency_graph.get(node, []):
60
+ visit(neighbor)
61
+ stack.append(node)
62
+
63
+ for node in dependency_graph:
64
+ visit(node)
65
+
66
+ return stack[::-1]
67
+
68
+ def execute_function(file_path: str, function_name: str, attributes: Dict[str,Any]):
69
+ dependencies = find_function_dependencies(file_path)
70
+ sorted_functions = topological_sort(dependencies)
71
+
72
+ if function_name not in sorted_functions:
73
+ return
74
+
75
+ module_name = os.path.splitext(os.path.basename(file_path))[0]
76
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
77
+ module = importlib.util.module_from_spec(spec)
78
+ spec.loader.exec_module(module)
79
+
80
+ for func in sorted_functions:
81
+ if hasattr(module, func):
82
+ func_to_call = getattr(module, func)
83
+ if callable(func_to_call):
84
+ try:
85
+ args = attributes.get(func,())
86
+ if not isinstance(args,tuple):
87
+ args = (args,)
88
+ result = func_to_call(*args)
89
+ if func == function_name:
90
+ return result
91
+ except Exception as e:
92
+ print(f"Error executing {func}: {e}")
93
+
94
+ def main(folder_path: str, function_name: str, attributes: Dict[str,Any]):
95
+ python_files = get_python_files(folder_path)
96
+
97
+ for file_path in python_files:
98
+ return execute_function(file_path, function_name,attributes)
99
+
100
+
101
+ def read_function_file(custom_path):
102
+ with open(custom_path,'r') as file:
103
+ lines = file.readlines()
104
+ return lines
105
+
106
+ def get_env_dir():
107
+ current_path = os.getcwd()
108
+ return os.path.abspath(os.path.join(current_path,'env/bin/activate'))
109
+
110
+ def install_requirements(requirements_path):
111
+ env_dir = get_env_dir()
112
+ command = f"source {env_dir}; pip3 install -r {requirements_path}"
113
+ os.system(command)
114
+
115
+ def load_variable_from_file(file_path, variable_name="ARGS"):
116
+ spec = importlib.util.spec_from_file_location("module_name", file_path)
117
+ module = importlib.util.module_from_spec(spec)
118
+ spec.loader.exec_module(module)
119
+ return getattr(module, variable_name, None)
120
+
121
+ def execute_functions(folder_path,file_path):
122
+ functions = read_function_file(file_path)
123
+ args= { 'collect_disk_metrics' : ()}
124
+ result = [ main(folder_path, function_name.strip('\n'), args) for function_name in functions ]
125
+ return result
File without changes
@@ -0,0 +1,15 @@
1
+ import logging
2
+ import sys
3
+
4
+
5
+ def get_logger(name: str , level = logging.DEBUG) -> logging.Logger:
6
+ logger = logging.getLogger(__name__)
7
+ if not logger.hasHandlers():
8
+ logger.setLevel(level)
9
+ handler = logging.StreamHandler(sys.stdout)
10
+ handler.setFormatter(
11
+ logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
12
+ )
13
+ logger.addHandler(handler)
14
+ return logger
15
+
File without changes
@@ -0,0 +1,75 @@
1
+ import yaml
2
+ import sqlite3
3
+ import re
4
+
5
+ def read_yaml_file(file_path):
6
+ with open(file_path, 'r') as f:
7
+ return yaml.safe_load(f)
8
+
9
+ def get_database_config(databases, db_name):
10
+ for db in databases:
11
+ if db['name'] == db_name:
12
+ return db
13
+ raise ValueError(f"[ERROR] Database config for '{db_name}' not found in database.yaml.")
14
+
15
+ def execute_sqlite_query(db_path, query):
16
+ try:
17
+ conn = sqlite3.connect(db_path)
18
+ cursor = conn.cursor()
19
+ cursor.execute(query)
20
+ result = cursor.fetchone()
21
+ conn.close()
22
+ return float(result[0]) if result and result[0] is not None else None
23
+ except Exception as e:
24
+ print(f"[ERROR] SQLite query failed on {db_path}: {e}")
25
+ return None
26
+
27
+ def is_safe_query(query):
28
+ # Remove leading spaces and brackets
29
+ cleaned_query = query.strip().lower()
30
+ # Match only queries that start with "select"
31
+ return re.match(r'^\(*\s*select', cleaned_query) is not None
32
+
33
+ def process_database_queries(queries_file, databases_file):
34
+ # Get queries from queries file
35
+ queries_config = read_yaml_file(queries_file)
36
+ # Get database from database file
37
+ databases_config = read_yaml_file(databases_file)
38
+
39
+ commands = queries_config.get('commands', [])
40
+ databases = databases_config.get('databases', [])
41
+
42
+ all_results = []
43
+
44
+ for cmd in commands:
45
+ try:
46
+ db_conf = get_database_config(databases, cmd['database'])
47
+
48
+ if db_conf['db_type'] != 'sqlite':
49
+ print(f"[WARN] Unsupported database type: {db_conf['db_type']} in command {cmd['name']}")
50
+ continue
51
+
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
57
+ if is_safe_query(query):
58
+ value = execute_sqlite_query(db_path, query)
59
+ else:
60
+ print(f"[WARN] Unsupported query type: {query}")
61
+ return None
62
+
63
+ if value is not None:
64
+ result = {
65
+ 'result': [{'label': label_value, 'value': value}],
66
+ 'labels': [label]
67
+ }
68
+ all_results.append(result)
69
+ else:
70
+ print(f"[INFO] No result for command '{cmd['name']}' on database '{cmd['database']}'")
71
+
72
+ except Exception as e:
73
+ print(f"[ERROR] Processing command '{cmd.get('name', 'unknown')}' failed: {e}")
74
+
75
+ return all_results
@@ -0,0 +1,73 @@
1
+ import subprocess
2
+ import yaml
3
+ import re
4
+
5
+ # 1. Read YAML commands
6
+ def read_commands_from_yaml(file_path):
7
+ with open(file_path, 'r') as f:
8
+ config = yaml.safe_load(f)
9
+ return config.get('commands', [])
10
+
11
+ # 2. Execute command with timeout
12
+ def execute_command_with_timeout(command, timeout):
13
+ try:
14
+ result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=timeout)
15
+ if result.returncode != 0:
16
+ return ''
17
+ return result.stdout.strip()
18
+ except subprocess.TimeoutExpired:
19
+ return ''
20
+
21
+ def process_commands(config_file):
22
+ commands = read_commands_from_yaml(config_file)
23
+ all_results = []
24
+
25
+ for cmd_info in commands:
26
+ command = cmd_info['command']
27
+ label_name = cmd_info['label']
28
+ timeout = cmd_info.get('timeout_seconds', 30)
29
+ label_column = cmd_info.get('label_column', -1)
30
+ value_column = cmd_info.get('value_column', 0)
31
+ fixed_label_value = cmd_info.get('label_value')
32
+
33
+ raw_output = execute_command_with_timeout(command, timeout)
34
+ lines = raw_output.strip().splitlines()
35
+
36
+ result_list = []
37
+
38
+ for line in lines:
39
+ parts = line.strip().split()
40
+ if not parts:
41
+ continue
42
+ if label_column == 'fixed':
43
+ label = fixed_label_value or 'label'
44
+ else:
45
+ try:
46
+ label = parts[label_column]
47
+ except IndexError:
48
+ label = 'unknown'
49
+ try:
50
+ raw_value = parts[value_column]
51
+ cleaned_value = re.sub(r'[^\d\.\-]', '', raw_value)
52
+ value = float(cleaned_value) if cleaned_value else 1
53
+ except (IndexError, ValueError):
54
+ value = 1
55
+
56
+ result_list.append({
57
+ 'label': label,
58
+ 'value': value
59
+ })
60
+
61
+ formatted = {
62
+ 'result': result_list,
63
+ 'labels': [label_name]
64
+ }
65
+ all_results.append(formatted)
66
+
67
+ return all_results
68
+
69
+
70
+ # # Example usage:
71
+ # if __name__ == "__main__":
72
+ # results = process_commands('/Users/nlingadh/code/custom_prometheus_agent/src/commands.yaml')
73
+ # print(results)
@@ -0,0 +1,140 @@
1
+ from prometheus_client import Gauge, start_http_server
2
+ import psutil
3
+ import time
4
+ import sys
5
+ import os
6
+
7
+ from flexmetric.config.configuration import CA_PATH , CERT_PATH, KEY_PATH
8
+ from flexmetric.logging_module.logger import get_logger
9
+ from flexmetric.file_recognition.exec_file import execute_functions
10
+ from flexmetric.metric_process.process_commands import process_commands
11
+ from flexmetric.metric_process.database_processing import process_database_queries
12
+ import argparse
13
+ import os
14
+ def arguments():
15
+ parser = argparse.ArgumentParser(
16
+ description='FlexMetric: A flexible Prometheus exporter for commands, databases, scripts, and Python functions.'
17
+ )
18
+
19
+ # Input type flags
20
+ parser.add_argument('--database', action='store_true', help='Process database.yaml and queries.yaml to extract metrics from databases.')
21
+ parser.add_argument('--commands', action='store_true', help='Process commands.yaml to extract metrics from system commands.')
22
+ parser.add_argument('--functions', action='store_true', help='Process Python functions from the provided path to extract metrics.')
23
+
24
+
25
+ # Config file paths
26
+ parser.add_argument('--database-config', type=str, default=None, help='Path to the database configuration YAML file.')
27
+ parser.add_argument('--queries-config', type=str, default=None, help='Path to the database queries YAML file.')
28
+ parser.add_argument('--commands-config', type=str, default=None, help='Path to the commands configuration YAML file.')
29
+ parser.add_argument('--functions-dir', type=str, default=None, help='Path to the python files dir.')
30
+ parser.add_argument('--functions-file', type=str, default=None, help='Path to the file containing which function to execute')
31
+ parser.add_argument('--port', type=int, default=8000, help='port on which exportor runs')
32
+
33
+ return parser.parse_args()
34
+
35
+ logger = get_logger(__name__)
36
+
37
+ logger.info("prometheus is running")
38
+
39
+ def convert_to_data_type(value):
40
+ if isinstance(value, str) and '%' in value:
41
+ return float(value.strip('%'))
42
+ elif isinstance(value, str) and ('GB' in value or 'MB' in value):
43
+ return float(value.split()[0].replace(',', ''))
44
+ return value
45
+ gauges = []
46
+
47
+ def validate_required_files(mode_name, required_files):
48
+ missing = [desc for desc, path in required_files.items() if path == None]
49
+ if missing:
50
+ print(f"Missing {', '.join(missing)} for '{mode_name}' mode. Skipping...")
51
+ return False
52
+
53
+ return True
54
+
55
+ def validate_all_modes(args):
56
+ """
57
+ Validates all selected modes and their required files.
58
+
59
+ Args:
60
+ args: Parsed command-line arguments.
61
+
62
+ Returns:
63
+ bool: True if at least one valid mode is properly configured, False otherwise.
64
+ """
65
+ has_valid_mode = False
66
+
67
+ mode_validations = [
68
+ (args.database, 'database', {
69
+ 'database-config': args.database_config,
70
+ 'queries-config': args.queries_config
71
+ }),
72
+ (args.commands, 'commands', {
73
+ 'commands-config': args.commands_config
74
+ }),
75
+ (args.functions, 'functions', {
76
+ 'functions-dir': args.functions_dir,
77
+ 'functions-file':args.functions_file
78
+ })
79
+ ]
80
+
81
+ for is_enabled, mode_name, files in mode_validations:
82
+ if is_enabled:
83
+ if validate_required_files(mode_name, files):
84
+ has_valid_mode = True
85
+
86
+ return has_valid_mode
87
+
88
+
89
+ def measure(init_flag,args):
90
+ exec_result = []
91
+ if args.database:
92
+ db_results = process_database_queries(args.queries_config, args.database_config)
93
+ exec_result.extend(db_results)
94
+ if args.functions:
95
+ function_results = execute_functions(args.functions_dir,args.functions_file)
96
+ exec_result.extend(function_results)
97
+ if args.commands:
98
+ cmd_results = process_commands(args.commands_config)
99
+ exec_result.extend(cmd_results)
100
+ # exec_result = process_commands('commands.yaml')
101
+ # print(exec_result)
102
+ global gauges
103
+ count = 0
104
+ for data in exec_result:
105
+ results= data['result']
106
+ labels = data['labels']
107
+ gauge_name = '_'.join(labels).lower() + "_gauge"
108
+ print(labels)
109
+ if init_flag:
110
+ gauge = Gauge(gauge_name, f"{gauge_name} for different metrics", labels)
111
+ gauges.append(gauge)
112
+ else:
113
+ gauge = gauges[count]
114
+ count += 1
115
+ for result in results:
116
+ print(result,isinstance(result['label'],list))
117
+ if isinstance(result['label'],str):
118
+ try:
119
+ gauge.labels(result['label']).set(convert_to_data_type(result['value']))
120
+ except Exception as ex:
121
+ logger.error("Cannot pass string")
122
+ elif isinstance(result['label'],list):
123
+ label_dict = dict(zip(labels, result['label']))
124
+ gauge.labels(**label_dict).set(convert_to_data_type(result['value']))
125
+
126
+ if __name__ == "__main__":
127
+ args = arguments()
128
+ print("Validating configuration...")
129
+ if not validate_all_modes(args):
130
+ print("No valid modes with proper configuration found. Exiting.")
131
+ exit(1)
132
+
133
+ print(f"Starting Prometheus metrics server on port {args.port}...")
134
+ print("Starting server")
135
+ start_http_server(args.port)
136
+ flag = True
137
+ while True:
138
+ measure(flag,args)
139
+ flag = False
140
+ time.sleep(5)
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: flexmetric
3
+ Version: 0.1.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
+ Dynamic: author
20
+ Dynamic: classifier
21
+ Dynamic: description
22
+ Dynamic: description-content-type
23
+ Dynamic: home-page
24
+ Dynamic: license
25
+ Dynamic: requires-dist
26
+ Dynamic: requires-python
27
+ Dynamic: summary
28
+
29
+ # FlexMetric
30
+
31
+ FlexMetric is a lightweight, flexible, and extensible Prometheus exporter that allows you to expose system metrics, database query results, script outputs, and Python function outputs as Prometheus-compatible metrics with minimal setup and maximum customization.
32
+
33
+ ---
34
+
35
+ ## Features
36
+
37
+ - Run shell commands and expose the results as Prometheus metrics.
38
+ - Execute SQL queries (e.g., SQLite) and monitor database statistics.
39
+ - Automatically discover and expose Python function outputs.
40
+ - Run custom shell scripts and monitor their outputs.
41
+ - Modular and easy to extend—add your own integrations.
42
+ - Built-in Prometheus HTTP server (`/metrics`) with configurable port.
43
+
44
+ ---
45
+
46
+ ## Installation
47
+
48
+ Install from PyPI:
49
+
50
+ ```bash
51
+ pip install flexmetric
52
+
53
+ ## Usage
54
+
55
+ Run FlexMetric from the command line:
56
+
57
+ ```bash
58
+ flexmetric --commands --commands-config commands.yaml --port 8000
59
+ ```
60
+
61
+ ## Available Modes
62
+
63
+ FlexMetric supports multiple modes that can be used individually or combined to expose metrics:
64
+
65
+ | Mode | Description | Required Configuration File(s) |
66
+ |---------------|--------------------------------------------------|------------------------------------------|
67
+ | `--commands` | Runs system commands and exports outputs as Prometheus metrics. | `commands.yaml` |
68
+ | `--database` | Executes SQL queries on databases and exports results. | `database.yaml` and `queries.yaml` |
69
+ | `--functions` | Discovers and runs user-defined Python functions and exports outputs. | `executable_functions.txt` |
70
+ | `--scripts` | Executes custom shell scripts and exports outputs. | `scripts.yaml` |
71
+
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
+ ### commands.yaml
83
+
84
+ ```yaml
85
+ commands:
86
+ - name: disk_usage
87
+ command: df -h
88
+ label: path
89
+ timeout_seconds: 60
90
+ ```
91
+ ```yaml
92
+ databases:
93
+ - name: mydb
94
+ db_type: sqlite
95
+ db_connection: /path/to/my.db
96
+ ````
97
+ ```yaml
98
+ queries:
99
+ - name: user_count
100
+ db_type: sqlite
101
+ db_name: mydb
102
+ query: "SELECT COUNT(*) FROM users;"
103
+ label: table
104
+ label_value: users
105
+ ```
106
+ executable_functions.txt
107
+ ```
108
+ function_name_1
109
+ function_name_2
110
+ ```
111
+
112
+ ## Command-Line Options
113
+
114
+ The following command-line options are available when running FlexMetric:
115
+
116
+ | Option | Description | Default |
117
+ |---------------------|----------------------------------------------------------|----------------------------|
118
+ | `--port` | Port for the Prometheus metrics server | `8000` |
119
+ | `--commands` | Enable commands mode | |
120
+ | `--commands-config` | Path to commands YAML file | `commands.yaml` |
121
+ | `--database` | Enable database mode | |
122
+ | `--database-config` | Path to database YAML file | `database.yaml` |
123
+ | `--queries-config` | Path to queries YAML file | `queries.yaml` |
124
+ | `--functions` | Enable Python functions mode | |
125
+ | `--functions-file` | Path to functions file | `executable_functions.txt` |
126
+ | `--scripts` | Enable shell scripts mode | |
127
+ | `--scripts-config` | Path to scripts YAML file | `scripts.yaml` |
128
+
129
+ ### Example Command:
130
+
131
+ ```bash
132
+ flexmetric --commands --commands-config commands.yaml --port 8000
133
+
134
+ ## Example Prometheus Output
135
+
136
+ Once FlexMetric is running, the `/metrics` endpoint will expose metrics in the Prometheus format.
137
+
138
+ Example output:
139
+ ```bash
140
+ disk_usage_gauge{path="/"} 45.0
141
+ ```
142
+
143
+ Each metric includes labels and numeric values that Prometheus can scrape and visualize.
144
+
145
+ ---
146
+
147
+ ## Future Enhancements
148
+
149
+ The following features are planned or under consideration to improve FlexMetric:
150
+
151
+ - Support for additional databases such as PostgreSQL and MySQL.
152
+ - Enhanced support for more complex scripts and richer label extraction.
@@ -0,0 +1,14 @@
1
+ flexmetric/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ flexmetric/file_recognition/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ flexmetric/file_recognition/exec_file.py,sha256=9wBbnqPConxtLhqWTgigEr8VQG-fa9K_U9UOLjqF8Xw,4273
4
+ flexmetric/logging_module/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ flexmetric/logging_module/logger.py,sha256=hXj9m2Q_KxJVI5YRHRoK6PXV5tO6NmvuVjq2Ipx_1tE,447
6
+ flexmetric/metric_process/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ flexmetric/metric_process/database_processing.py,sha256=OAJ0FbvjQLSqUh85KenuEQ5YG7ghWQYT-pzqogCOafk,2542
8
+ flexmetric/metric_process/process_commands.py,sha256=cHtoyzP0IgZOwoS_BgFZU0VHuOKQO0UVvGOCwWuByNM,2236
9
+ flexmetric/metric_process/prometheus_agent.py,sha256=kb_egYolKh7Q6BufbvswuhPanBGzAjKMkUHPRE7nP4Y,5374
10
+ flexmetric-0.1.0.dist-info/METADATA,sha256=ozCoaEh4TUeWSO9jAQsQkFZ8BsO6JykmImw0rBuseuU,5367
11
+ flexmetric-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ flexmetric-0.1.0.dist-info/entry_points.txt,sha256=urVePn5EWr3JqNvkYP7OsB_h2_Bqvv-Wq1MJRBexm8A,79
13
+ flexmetric-0.1.0.dist-info/top_level.txt,sha256=zBlrNwKhXUNhgu9RRZnXxYwYnmE-eocRe6wKSmQROA4,11
14
+ flexmetric-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ flexmetric = flexmetric.metric_process.prometheus_agent:main
@@ -0,0 +1 @@
1
+ flexmetric