Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ netstat | Exposes network statistics from `/proc/net/netstat`. This is the same
nfs | Exposes NFS client statistics from `/proc/net/rpc/nfs`. This is the same information as `nfsstat -c`. | Linux
nfsd | Exposes NFS kernel server statistics from `/proc/net/rpc/nfsd`. This is the same information as `nfsstat -s`. | Linux
nvme | Exposes NVMe info from `/sys/class/nvme/` | Linux
os | Expose OS release info from `/etc/os-release` or `/usr/lib/os-release` | _any_
powersupplyclass | Exposes Power Supply statistics from `/sys/class/power_supply` | Linux
pressure | Exposes pressure stall statistics from `/proc/pressure/`. | Linux (kernel 4.20+ and/or [CONFIG\_PSI](https://www.kernel.org/doc/html/latest/accounting/psi.html))
rapl | Exposes various statistics from `/sys/class/powercap`. | Linux
Expand Down
7 changes: 7 additions & 0 deletions collector/fixtures/e2e-64k-page-output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2442,6 +2442,12 @@ node_nfsd_server_threads 8
# HELP node_nvme_info Non-numeric data from /sys/class/nvme/<device>, value is always 1.
# TYPE node_nvme_info gauge
node_nvme_info{device="nvme0",firmware_revision="1B2QEXP7",model="Samsung SSD 970 PRO 512GB",serial="S680HF8N190894I",state="live"} 1
# HELP node_os_info A metric with a constant '1' value labeled by build_id, id, id_like, image_id, image_version, name, pretty_name, variant, variant_id, version, version_codename, version_id.
# TYPE node_os_info gauge
node_os_info{build_id="",id="ubuntu",id_like="debian",image_id="",image_version="",name="Ubuntu",pretty_name="Ubuntu 20.04.2 LTS",variant="",variant_id="",version="20.04.2 LTS (Focal Fossa)",version_codename="focal",version_id="20.04"} 1
# HELP node_os_version Metric containing the major.minor part of the OS version.
# TYPE node_os_version gauge
node_os_version{id="ubuntu",id_like="debian",name="Ubuntu"} 20.04
# HELP node_power_supply_capacity capacity value of /sys/class/power_supply/<power_supply>.
# TYPE node_power_supply_capacity gauge
node_power_supply_capacity{power_supply="BAT0"} 81
Expand Down Expand Up @@ -2590,6 +2596,7 @@ node_scrape_collector_success{collector="netstat"} 1
node_scrape_collector_success{collector="nfs"} 1
node_scrape_collector_success{collector="nfsd"} 1
node_scrape_collector_success{collector="nvme"} 1
node_scrape_collector_success{collector="os"} 1
node_scrape_collector_success{collector="powersupplyclass"} 1
node_scrape_collector_success{collector="pressure"} 1
node_scrape_collector_success{collector="processes"} 1
Expand Down
7 changes: 7 additions & 0 deletions collector/fixtures/e2e-output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2640,6 +2640,12 @@ node_nfsd_server_threads 8
# HELP node_nvme_info Non-numeric data from /sys/class/nvme/<device>, value is always 1.
# TYPE node_nvme_info gauge
node_nvme_info{device="nvme0",firmware_revision="1B2QEXP7",model="Samsung SSD 970 PRO 512GB",serial="S680HF8N190894I",state="live"} 1
# HELP node_os_info A metric with a constant '1' value labeled by build_id, id, id_like, image_id, image_version, name, pretty_name, variant, variant_id, version, version_codename, version_id.
# TYPE node_os_info gauge
node_os_info{build_id="",id="ubuntu",id_like="debian",image_id="",image_version="",name="Ubuntu",pretty_name="Ubuntu 20.04.2 LTS",variant="",variant_id="",version="20.04.2 LTS (Focal Fossa)",version_codename="focal",version_id="20.04"} 1
# HELP node_os_version Metric containing the major.minor part of the OS version.
# TYPE node_os_version gauge
node_os_version{id="ubuntu",id_like="debian",name="Ubuntu"} 20.04
# HELP node_power_supply_capacity capacity value of /sys/class/power_supply/<power_supply>.
# TYPE node_power_supply_capacity gauge
node_power_supply_capacity{power_supply="BAT0"} 81
Expand Down Expand Up @@ -2791,6 +2797,7 @@ node_scrape_collector_success{collector="netstat"} 1
node_scrape_collector_success{collector="nfs"} 1
node_scrape_collector_success{collector="nfsd"} 1
node_scrape_collector_success{collector="nvme"} 1
node_scrape_collector_success{collector="os"} 1
node_scrape_collector_success{collector="powersupplyclass"} 1
node_scrape_collector_success{collector="pressure"} 1
node_scrape_collector_success{collector="processes"} 1
Expand Down
12 changes: 12 additions & 0 deletions collector/fixtures/usr/lib/os-release
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
178 changes: 178 additions & 0 deletions collector/os_release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
"errors"
"io"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
envparse "github.com/hashicorp/go-envparse"
"github.com/prometheus/client_golang/prometheus"
)

const (
etcOSRelease = "/etc/os-release"
usrLibOSRelease = "/usr/lib/os-release"
)

var (
versionRegex = regexp.MustCompile(`^[0-9]+\.?[0-9]*`)
)

type osRelease struct {
Name string
ID string
IDLike string
PrettyName string
Variant string
VariantID string
Version string
VersionID string
VersionCodename string
BuildID string
ImageID string
ImageVersion string
}

type osReleaseCollector struct {
infoDesc *prometheus.Desc
logger log.Logger
os *osRelease
osFilename string // file name of cached release information
osMtime time.Time // mtime of cached release file
osMutex sync.Mutex
osReleaseFilenames []string // all os-release file names to check
version float64
versionDesc *prometheus.Desc
}

func init() {
registerCollector("os", defaultEnabled, NewOSCollector)
}

// NewOSCollector returns a new Collector exposing os-release information.
func NewOSCollector(logger log.Logger) (Collector, error) {
return &osReleaseCollector{
logger: logger,
infoDesc: prometheus.NewDesc(
prometheus.BuildFQName(namespace, "os", "info"),
"A metric with a constant '1' value labeled by build_id, id, id_like, image_id, image_version, "+
"name, pretty_name, variant, variant_id, version, version_codename, version_id.",
[]string{"build_id", "id", "id_like", "image_id", "image_version", "name", "pretty_name",
"variant", "variant_id", "version", "version_codename", "version_id"}, nil,
),
osReleaseFilenames: []string{etcOSRelease, usrLibOSRelease},
versionDesc: prometheus.NewDesc(
prometheus.BuildFQName(namespace, "os", "version"),
"Metric containing the major.minor part of the OS version.",
[]string{"id", "id_like", "name"}, nil,
),
}, nil
}

func parseOSRelease(r io.Reader) (*osRelease, error) {
env, err := envparse.Parse(r)
return &osRelease{
Name: env["NAME"],
ID: env["ID"],
IDLike: env["ID_LIKE"],
PrettyName: env["PRETTY_NAME"],
Variant: env["VARIANT"],
VariantID: env["VARIANT_ID"],
Version: env["VERSION"],
VersionID: env["VERSION_ID"],
VersionCodename: env["VERSION_CODENAME"],
BuildID: env["BUILD_ID"],
ImageID: env["IMAGE_ID"],
ImageVersion: env["IMAGE_VERSION"],
}, err
}

func (c *osReleaseCollector) UpdateStruct(path string) error {
releaseFile, err := os.Open(path)
if err != nil {
return err
}
defer releaseFile.Close()

stat, err := releaseFile.Stat()
if err != nil {
return err
}

t := stat.ModTime()
if path == c.osFilename && t == c.osMtime {
// osReleaseCollector struct is already up-to-date.
return nil
}

// Acquire a lock to update the osReleaseCollector struct.
c.osMutex.Lock()
defer c.osMutex.Unlock()

level.Debug(c.logger).Log("msg", "file modification time has changed",
"file", path, "old_value", c.osMtime, "new_value", t)
c.osFilename = path
c.osMtime = t

c.os, err = parseOSRelease(releaseFile)
if err != nil {
return err
}

majorMinor := versionRegex.FindString(c.os.VersionID)
if majorMinor != "" {
c.version, err = strconv.ParseFloat(majorMinor, 64)
if err != nil {
return err
}
} else {
c.version = 0
}
return nil
}

func (c *osReleaseCollector) Update(ch chan<- prometheus.Metric) error {
for i, path := range c.osReleaseFilenames {
err := c.UpdateStruct(*rootfsPath + path)
if err == nil {
break
}
if errors.Is(err, os.ErrNotExist) {
if i >= (len(c.osReleaseFilenames) - 1) {
level.Debug(c.logger).Log("msg", "no os-release file found", "files", strings.Join(c.osReleaseFilenames, ","))
return ErrNoData
}
continue
}
return err
}

ch <- prometheus.MustNewConstMetric(c.infoDesc, prometheus.GaugeValue, 1.0,
c.os.BuildID, c.os.ID, c.os.IDLike, c.os.ImageID, c.os.ImageVersion, c.os.Name, c.os.PrettyName,
c.os.Variant, c.os.VariantID, c.os.Version, c.os.VersionCodename, c.os.VersionID)
if c.version > 0 {
ch <- prometheus.MustNewConstMetric(c.versionDesc, prometheus.GaugeValue, c.version,
c.os.ID, c.os.IDLike, c.os.Name)
}
return nil
}
105 changes: 105 additions & 0 deletions collector/os_release_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
"os"
"reflect"
"strings"
"testing"

"github.com/go-kit/log"
)

const debianBullseye string = `PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
`

func TestParseOSRelease(t *testing.T) {
want := &osRelease{
Name: "Ubuntu",
ID: "ubuntu",
IDLike: "debian",
PrettyName: "Ubuntu 20.04.2 LTS",
Version: "20.04.2 LTS (Focal Fossa)",
VersionID: "20.04",
VersionCodename: "focal",
}

osReleaseFile, err := os.Open("fixtures" + usrLibOSRelease)
if err != nil {
t.Fatal(err)
}
got, err := parseOSRelease(osReleaseFile)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("should have %+v osRelease: got %+v", want, got)
}

want = &osRelease{
Name: "Debian GNU/Linux",
ID: "debian",
PrettyName: "Debian GNU/Linux 11 (bullseye)",
Version: "11 (bullseye)",
VersionID: "11",
VersionCodename: "bullseye",
}
got, err = parseOSRelease(strings.NewReader(debianBullseye))
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("should have %+v osRelease: got %+v", want, got)
}
}

func TestUpdateStruct(t *testing.T) {
wantedOS := &osRelease{
Name: "Ubuntu",
ID: "ubuntu",
IDLike: "debian",
PrettyName: "Ubuntu 20.04.2 LTS",
Version: "20.04.2 LTS (Focal Fossa)",
VersionID: "20.04",
VersionCodename: "focal",
}
wantedVersion := 20.04

collector, err := NewOSCollector(log.NewNopLogger())
if err != nil {
t.Fatal(err)
}
c := collector.(*osReleaseCollector)

err = c.UpdateStruct("fixtures" + usrLibOSRelease)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(wantedOS, c.os) {
t.Fatalf("should have %+v osRelease: got %+v", wantedOS, c.os)
}
if wantedVersion != c.version {
t.Errorf("Expected '%v' but got '%v'", wantedVersion, c.version)
}
}
1 change: 1 addition & 0 deletions end-to-end-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ then
fi

./node_exporter \
--path.rootfs="collector/fixtures" \
--path.procfs="collector/fixtures/proc" \
--path.sysfs="collector/fixtures/sys" \
$(for c in ${enabled_collectors}; do echo --collector.${c} ; done) \
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/ema/qdisc v0.0.0-20200603082823-62d0308e3e00
github.com/go-kit/log v0.1.0
github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968
github.com/hashicorp/go-envparse v0.0.0-20200406174449-d9cfd743a15e
github.com/hodgesds/perf-utils v0.2.5
github.com/illumos/go-kstat v0.0.0-20210513183136-173c9b0a9973
github.com/jsimonetti/rtnetlink v0.0.0-20210713125558-2bfdf1dbdbd6
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/go-envparse v0.0.0-20200406174449-d9cfd743a15e h1:v1d9+AJMP6i4p8BSKNU0InuvmIAdZjQLNN19V86AG4Q=
github.com/hashicorp/go-envparse v0.0.0-20200406174449-d9cfd743a15e/go.mod h1:/NlxCzN2D4C4L2uDE6ux/h6jM+n98VFQM14nnCIfHJU=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hodgesds/perf-utils v0.2.5 h1:X992/V3OaNJRM8Ivcram8Hhxz4JhWiKI0T8iGCJwk2k=
Expand Down