The PDU Exporter is a lightweight custom Prometheus exporter designed to collect metrics from Power Distribution Unit (PDU) devices that expose raw status data via the /status.cgi endpoint over HTTP, transforming this data into Prometheus-compatible metrics.
- Connects directly to the PDU via raw TCP (port 80)
- Sends a manual HTTP GET request
- Supports basic authentication
- Exposes metrics such as current, voltage, power, energy, temperature, humidity, and sensor existence
- Dockerized for easy deployment
| Metric Name | Description | Labels |
|---|---|---|
current |
Current in Amperes | address |
voltage |
Voltage in Volts | address |
power |
Power in Watts | address |
power_factor |
Power factor (0.0 to 1.0) | address |
energy |
Energy in kilowatt-hours | address |
temperature |
Temperature in Celsius | address, channel |
humidity |
Humidity in percent | address, channel |
sensor_exists |
Sensor existence (1.0 or 0.0) | type |
Method: POST, PUT
Description: Reloads the configuration without restarting the process.
POST /-/reload
PUT /-/reload
You can also reload configuration by sending the SIGHUP signal to the pdu_exporter process ID using the command below:
kill -s SIGHUP $(pidof pdu_exporter)Method: GET
Query Parameters:
target: IP address or hostname of the PDU.
GET /pdu?target=192.168.1.1
Method: GET
Description: Returns a list of rack names extracted from each PDU address block.
Query Parameters:
target: IP address or hostname of the PDU.
GET /api/rack-names?target=192.168.1.1
{
"rack_names": {
"rack_1": "# 1 Rack A",
"rack_2": "# 2 Rack B",
...
"rack_32": "# 32 Rack AF"
}
}
scrape_configs:
- job_name: 'pdu'
metrics_path: /pdu
static_configs:
- targets:
- 192.168.1.1
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 127.0.0.1:9117 # Address of the PDU Exporter container or host
basic_auth: # Sets the `Authorization` header on every request
username: user # Configured username
password: pass # Configured password# How long until a scrape request times out.
scrape_configs:
scrape_timeout: 5s
# Usernames and hashed passwords that have full access to the web server via basic authentication.
# If empty, no basic authentication is required. Passwords are hashed with bcrypt.
basic_auth_users:
user: $2a$12$eId/v3HxJFjZWCkeTV/.VeUg5Qie66aPumgGlzELcy2ndCxpo5fV6
rlggyp: $2a$12$fKG1d9B5d7JDa78s7XBHDu/YD.46VK.t3W8BnujOwErrNCvRB9vsSrefresh_rate: 30 seconds
appenders:
rolling_file:
kind: rolling_file
path: "/etc/pdu_exporter/logs/app.log"
policy:
kind: compound
trigger:
kind: size
limit: 1 mb
roller:
kind: fixed_window
pattern: "/etc/pdu_exporter/logs/app-{}.log"
base: 1
count: 7
encoder:
pattern: "{d(%Y-%m-%d %H:%M:%S)} [{l}] {t} - {m}{n}"
root:
level: info
appenders:
- rolling_fileBelow is a sample docker-compose.yaml:
services:
pdu_exporter:
image: rlggyp/pdu_exporter:latest
container_name: pdu_exporter
user: 1000:1000
pull_policy: always
environment:
- CONFIG_FILE=/etc/pdu_exporter/configs/pdu_exporter.yaml
- LOG_CONFIG_FILE=/etc/pdu_exporter/configs/log4rs.yaml
ports:
- 9117:9117
volumes:
- ./configs:/etc/pdu_exporter/configs
- ./logs:/etc/pdu_exporter/logs
restart: unless-stoppedDirectory structure example:
.
├── configs
│ ├── pdu_exporter.yaml
│ └── log4rs.yaml
├── logs
└── docker-compose.yaml
- Place your
pdu_exporter.yamlandlog4rs.yamlin theconfigsdirectory. - Logs will be written to the
logsdirectory.
You have two options to run the PDU Exporter:
-
Build the image:
docker build -t pdu_exporter:latest . -
Edit your
docker-compose.yamland change theimageline to use your local image:image: pdu_exporter:latest
-
Start the service:
docker compose up -d
No need to build anything. Just use the provided docker-compose.yaml (with image: rlggyp/pdu_exporter:latest) and run:
docker compose up -d- Returns 400 Bad Request if
targetis missing. - Returns 401 Unauthorized if authentication fails.
- Returns 404 Not Found if the TCP connection to the PDU fails.
- Returns 408 Request Timeout if the connection to
targettimes out. - Returns 422 Unprocessable Entity if the response structure is invalid.
- Returns 500 Internal Server Error for I/O or parsing errors.
- Assumes the PDU
/status.cgiresponse contains exactly 2016 elements. - Metrics parsing is tightly coupled with this structure.
- Only supports plain TCP and HTTP (no TLS, no SNMP).
This project is licensed under the MIT License. See the LICENSE file for details.