feat: Enhance config flexibility and setup

Introduced command-line flags for improved configuration management, including the creation of a default config file, and specified listening address override capabilities. This update facilitates easier and more flexible setup and deployment processes for the PostgreSQL connection exporter. The `.gitignore` and documentation were updated to align with these changes, reflecting the new command-line options and the removal of the `config.dist.yaml` in favor of creating a default configuration through the tool itself.

- Command-line flags `--create-config`, `--config`, `--host`, and `--port` added to streamline customization.
- Configuration now supports naming database hosts for clearer metrics identification.
- Documentation revised to guide through the new configuration and setup process.
- Changelog introduced to track project evolution.

These enhancements aim to make the exporter more adaptable to various deployment environments and to simplify the initial setup for users.
This commit is contained in:
Kumi 2024-05-16 06:33:21 +02:00
parent a8eb80aad2
commit 776c98fb47
Signed by: kumi
GPG key ID: ECBCC9082395383F
7 changed files with 135 additions and 23 deletions

3
.gitignore vendored
View file

@ -2,4 +2,5 @@
config.yaml
__pycache__
*.pyc
venv
venv/
dist/

16
CHANGELOG.md Normal file
View file

@ -0,0 +1,16 @@
# Changelog
## v0.0.2
### Added
- Add `--create-config` flag to create a default configuration file
- Add `--config` flag to specify a configuration file
- Add `--host` and `--port` flags to specify the address to listen on
- Make listening address configurable in the `config.yaml` file
## v0.0.1
### Added
- Initial release

View file

@ -17,9 +17,27 @@ You can install the exporter from PyPI. Within a virtual environment, run:
pip install postgres-connection-exporter
```
## Usage
## Configuration
The exporter is configured using a `config.yaml`. You can find an example in [config.dist.yaml](config.dist.yaml).
The exporter is configured using a `config.yaml`. You can create a default configuration file in the current working directory with:
```bash
postgres-connection-exporter --create-config
```
Now, edit the `config.yaml` file to match your PostgreSQL connection settings. Here is an example configuration:
```yaml
hosts:
host: localhost
port: 5432
user: postgres
password: postgres
```
The user must have the `pg_monitor` role to access the `pg_stat_activity` view.
## Usage
After you have created your `config.yaml`, you can start the exporter with:
@ -27,6 +45,18 @@ After you have created your `config.yaml`, you can start the exporter with:
postgres-connection-exporter
```
By default, the exporter listens on `localhost:8989`. You can change the address in the `config.yaml` file, or using the `--host` and `--port` flags:
```bash
postgres-connection-exporter --host 0.0.0.0 --port 9898
```
You can also specify a different configuration file with the `--config` flag:
```bash
postgres-connection-exporter --config /path/to/config.yaml
```
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

View file

@ -1,8 +0,0 @@
hosts: # List of database hosts
# Each host must have a user, password, host, and optionally port
- user: "user1"
password: "password1"
host: "host1"
port: "5432"
exporter:
port: 8989 # Port on which the exporter will listen

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "postgres-connection-exporter"
version = "0.0.1"
version = "0.0.2"
authors = [
{ name="Kumi Mitterer", email="postgres-connection-exporter@kumi.email" },
]

View file

@ -2,6 +2,10 @@ import yaml
from prometheus_client import start_http_server, Gauge
import psycopg2
import time
import argparse
import pathlib
import shutil
import sys
CONNECTIONS_PER_DB = Gauge(
"db_connections_per_database",
@ -56,6 +60,8 @@ def get_all_databases(db_config):
def get_db_connections(db_config):
try:
host_identifier = db_config.get("name") or db_config["host"]
# Connect to the PostgreSQL database
conn = psycopg2.connect(
dbname="postgres",
@ -76,7 +82,7 @@ def get_db_connections(db_config):
)
db_connections = cursor.fetchall()
for db, count in db_connections:
CONNECTIONS_PER_DB.labels(database=db, host=db_config["host"]).set(count)
CONNECTIONS_PER_DB.labels(database=db, host=host_identifier).set(count)
# Query to get the number of connections per user
cursor.execute(
@ -88,7 +94,7 @@ def get_db_connections(db_config):
)
user_connections = cursor.fetchall()
for user, count in user_connections:
CONNECTIONS_PER_USER.labels(user=user, host=db_config["host"]).set(count)
CONNECTIONS_PER_USER.labels(user=user, host=host_identifier).set(count)
# Query to get the number of connections by state
cursor.execute(
@ -100,7 +106,7 @@ def get_db_connections(db_config):
)
state_connections = cursor.fetchall()
for state, count in state_connections:
CONNECTIONS_BY_STATE.labels(state=state, host=db_config["host"]).set(count)
CONNECTIONS_BY_STATE.labels(state=state, host=host_identifier).set(count)
# Query to get the number of connections per source
cursor.execute(
@ -112,7 +118,9 @@ def get_db_connections(db_config):
)
source_connections = cursor.fetchall()
for source, count in source_connections:
CONNECTIONS_PER_SOURCE.labels(source=source, host=db_config["host"]).set(count)
CONNECTIONS_PER_SOURCE.labels(source=source, host=host_identifier).set(
count
)
cursor.close()
conn.close()
@ -121,11 +129,45 @@ def get_db_connections(db_config):
def main():
config = load_config()
parser = argparse.ArgumentParser(description="PostgreSQL connection exporter")
parser.add_argument(
"--config", "-c", help="Path to the configuration file", default="config.yaml"
)
parser.add_argument(
"--create", "-C", help="Create a new configuration file", action="store_true"
)
parser.add_argument(
"--port",
"-p",
help="Port for the exporter to listen on (default: 8989, or the port specified in the configuration file)",
type=int,
)
parser.add_argument(
"--host",
help="Host for the exporter to listen on (default: localhost, or the host specified in the configuration file)",
)
args = parser.parse_args()
if args.create:
config_file = pathlib.Path(args.config)
if config_file.exists():
print("Configuration file already exists.")
sys.exit(1)
template = pathlib.Path(__file__).parent / "config.dist.yaml"
try:
shutil.copy(template, config_file)
print(f"Configuration file created at {config_file}")
sys.exit(0)
except Exception as e:
print(f"Error creating configuration file: {e}")
sys.exit(1)
config = load_config(args.config)
if not ("hosts" in config and config["hosts"]):
print("No database hosts specified in the configuration file.")
exit(1)
sys.exit(1)
databases_to_query = []
@ -135,6 +177,7 @@ def main():
exit(1)
db_config = {
"name": host.get("name"),
"user": host["user"],
"password": host["password"],
"host": host["host"],
@ -148,13 +191,27 @@ def main():
exit(1)
exporter_port = (
config["exporter"]["port"]
if "exporter" in config and "port" in config["exporter"]
else 8989
args.port
if args.port
else (
config["exporter"]["port"]
if "exporter" in config and "port" in config["exporter"]
else 8989
)
)
start_http_server(exporter_port)
print(f"Prometheus exporter started on port {exporter_port}")
exporter_host = (
args.host
if args.host
else (
config["exporter"]["host"]
if "exporter" in config and "host" in config["exporter"]
else "localhost"
)
)
start_http_server(exporter_port, exporter_host)
print(f"Prometheus exporter started on {exporter_host}:{exporter_port}")
while True:
for db in databases_to_query:

View file

@ -0,0 +1,16 @@
hosts: # List of database hosts
# Each host must have a user, password, host, and optionally port
# A name can be provided to identify the host in the metrics instead of the host address
- name: "myserver"
user: "user1"
password: "password1"
host: "host1"
port: "5432"
- user: "user2"
password: "password2"
host: "host2"
port: "5432"
# As no name is provided, the host address "host2" will be used to identify the host in the metrics
exporter:
host: "localhost" # Network address on which the exporter will listen
port: 8989 # Port on which the exporter will listen