Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ The variables exposed to a NUT client by the NUT system are the lifeblood of a d

* See the [NUT documentation](https://networkupstools.org/docs/user-manual.chunked/apcs01.html) for a list of all possible variables
* Variables are set as prometheus metrics with the `ups` name added as a lable. Example: `ups.load` is set as `network_ups_tools_ups_load 100`
* The exporter SHOULD be called with the ups to scrape set in the query string. Example: `https://127.0.0.1:9199/ups_metrics?ups=foo`
* If the exporter scrapes NUT and detects more than one UPS, it is an error condition that will fail the scrape. In this case, use a variant of the scrape config example below for your environment
* Default configs usually permit reading variables without authentication. If you have disabled this, see the Usage below to set credentials
* This exporter will always export the device.* metrics as labels, except for uptime, with a constant value of 1
* Setting the `nut.vars_enable` parameter to an empty string will cause all numeric variables to be exported
Expand Down Expand Up @@ -87,7 +85,7 @@ Therefore, these are all enabled in the default value for `--nut.statuses`.
The exporter allows for per-scrape overrides of command line parameters by passing query string parameters. This enables a single nut_exporter to scrape multiple NUT servers

The following query string parameters can be passed to the `/ups_metrics` path:
* `ups` - Required if more than one UPS is present in NUT)
* `ups` - Export specific UPS only instead of all
* `server` - Overrides the command line parameter `--nut.server`
* `username` - Overrides the command line parameter `--nut.username`
* `password` - Overrides the environment variable NUT_EXPORTER_PASSWORD. It is **strongly** recommended to avoid passing credentials over http unless the exporter is configured with TLS
Expand All @@ -96,46 +94,46 @@ The following query string parameters can be passed to the `/ups_metrics` path:
See the example scrape configurations below for how to utilize this capability

### Example Scrape Configurations
Note that this exporter will scrape only one UPS per scrape invocation. If there are multiple UPS devices visible to NUT, you MUST ensure that you set up different scrape configs for each UPS device. Here is an example configuration for such a use case:
By default all UPSes will be exported.

```
- job_name: nut
metrics_path: /ups_metrics
static_configs:
- targets: ['myserver:9199']
```

Specifying the `ups` parameter limits the output to that one UPS.

```
- job_name: nut-primary
metrics_path: /ups_metrics
static_configs:
- targets: ['myserver:9199']
labels:
ups: "primary"
params:
ups: [ "primary" ]
- job_name: nut-secondary
metrics_path: /ups_metrics
static_configs:
- targets: ['myserver:9199']
labels:
ups: "secondary"
params:
ups: [ "secondary" ]
```

You can also configure a single exporter to scrape several NUT servers like so:
You can also configure a single exporter to scrape several NUT servers. The server is added as a metric label.

```
- job_name: nut-primary
metrics_path: /ups_metrics
static_configs:
- targets: ['exporterserver:9199']
labels:
ups: "primary"
params:
ups: [ "primary" ]
server: [ "nutserver1" ]
- job_name: nut-secondary
metrics_path: /ups_metrics
static_configs:
- targets: ['exporterserver:9199']
labels:
ups: "secondary"
params:
ups: [ "secondary" ]
server: [ "nutserver2" ]
```

Expand Down
32 changes: 12 additions & 20 deletions collectors/nut_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type NutCollectorOpts struct {
func NewNutCollector(opts NutCollectorOpts) (*NutCollector, error) {
deviceDesc := prometheus.NewDesc(prometheus.BuildFQName(opts.Namespace, "", "device_info"),
"UPS Device information",
deviceLabels, nil,
append([]string{"server", "name"}, deviceLabels...), nil,
)

return &NutCollector{
Expand All @@ -52,20 +52,20 @@ func (c *NutCollector) Collect(ch chan<- prometheus.Metric) {
if err != nil {
log.Error(err)
} else {
upsList := []nut.UPS{}
var upsList []nut.UPS
if c.opts.Ups != "" {
ups, err := nut.NewUPS(c.opts.Ups, &client)
var ups nut.UPS
ups, err = nut.NewUPS(c.opts.Ups, &client)
if err == nil {
log.Debugf("Instantiated UPS named `%s`", c.opts.Ups)
upsList = append(upsList, ups)
upsList = []nut.UPS{ups}
} else {
log.Errorf("Failure instantiating the UPS named `%s`: %v", c.opts.Ups, err)
}
} else {
tmp, err := client.GetUPSList()
upsList, err = client.GetUPSList()
if err == nil {
log.Debugf("Obtained list of UPS devices")
upsList = tmp
} else {
log.Errorf("Failure getting the list of UPS devices: %v", err)
}
Expand All @@ -76,17 +76,7 @@ func (c *NutCollector) Collect(ch chan<- prometheus.Metric) {
prometheus.NewDesc(prometheus.BuildFQName(c.opts.Namespace, "", "error"),
"Failure gathering UPS variables", nil, nil),
err)
}

if len(upsList) > 1 {
log.Errorf("Multiple UPS devices were found by NUT for this scrap. For this configuration, you MUST scrape this exporter with a query string parameter indicating which UPS to scrape. Valid values of ups are:")
for _, ups := range upsList {
log.Errorf(" %s", ups.Name)
}
ch <- prometheus.NewInvalidMetric(
prometheus.NewDesc(prometheus.BuildFQName(c.opts.Namespace, "", "error"),
"Multiple UPS devices were found fron NUT. Please add a ups=<name> query string", nil, nil),
err)
return
}

for _, ups := range upsList {
Expand All @@ -95,6 +85,8 @@ func (c *NutCollector) Collect(ch chan<- prometheus.Metric) {
device[label] = ""
}

constLabels := prometheus.Labels{"server": c.opts.Server, "name": ups.Name}

log.Debugf("UPS info:")
log.Debugf(" Name: %v", ups.Name)
log.Debugf(" Description: %v", ups.Description)
Expand Down Expand Up @@ -133,7 +125,7 @@ func (c *NutCollector) Collect(ch chan<- prometheus.Metric) {
setStatuses := make(map[string]bool)
varDesc := prometheus.NewDesc(prometheus.BuildFQName(c.opts.Namespace, "", strings.Replace(variable.Name, ".", "_", -1)),
fmt.Sprintf("%s (%s)", variable.Description, variable.Name),
[]string{"flag"}, nil,
[]string{"flag"}, constLabels,
)

for _, statusFlag := range strings.Split(variable.Value.(string), " ") {
Expand Down Expand Up @@ -189,7 +181,7 @@ func (c *NutCollector) Collect(ch chan<- prometheus.Metric) {

varDesc := prometheus.NewDesc(prometheus.BuildFQName(c.opts.Namespace, "", strings.Replace(variable.Name, ".", "_", -1)),
fmt.Sprintf("%s (%s)", variable.Description, variable.Name),
nil, nil,
nil, constLabels,
)

ch <- prometheus.MustNewConstMetric(varDesc, prometheus.GaugeValue, value)
Expand All @@ -198,7 +190,7 @@ func (c *NutCollector) Collect(ch chan<- prometheus.Metric) {
}
}

deviceValues := []string{}
deviceValues := []string{c.opts.Server, ups.Name}
for _, label := range deviceLabels {
deviceValues = append(deviceValues, device[label])
}
Expand Down