Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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
2 changes: 2 additions & 0 deletions docs/LICENSE_OF_DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ following works:
- github.com/jackc/pgx [MIT License](https://github.com/jackc/pgx/blob/master/LICENSE)
- github.com/jackc/puddle [MIT License](https://github.com/jackc/puddle/blob/master/LICENSE)
- github.com/jaegertracing/jaeger [Apache License 2.0](https://github.com/jaegertracing/jaeger/blob/master/LICENSE)
- github.com/jaypipes/ghw [Apache License 2.0](https://github.com/jaypipes/ghw/blob/main/COPYING)
- github.com/jaypipes/pcidb [Apache License 2.0](https://github.com/jaypipes/pcidb/blob/main/LICENSE)
- github.com/jcmturner/aescts [Apache License 2.0](https://github.com/jcmturner/aescts/blob/master/LICENSE)
- github.com/jcmturner/dnsutils [Apache License 2.0](https://github.com/jcmturner/dnsutils/blob/master/LICENSE)
- github.com/jcmturner/gofork [BSD 3-Clause "New" or "Revised" License](https://github.com/jcmturner/gofork/blob/master/LICENSE)
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ require (
github.com/intel/powertelemetry v1.0.2
github.com/jackc/pgio v1.0.0
github.com/jackc/pgx/v5 v5.9.2
github.com/jaypipes/ghw v0.24.0
github.com/jedib0t/go-pretty/v6 v6.7.10
github.com/jeremywohl/flatten/v2 v2.0.0-20211013061545-07e4a09fb8e4
github.com/jmespath/go-jmespath v0.4.0
Expand Down Expand Up @@ -439,6 +440,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jaegertracing/jaeger v1.47.0 // indirect
github.com/jaypipes/pcidb v1.1.1 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
Expand Down Expand Up @@ -596,7 +598,7 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.2.2 // indirect
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect
Expand Down
7 changes: 6 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,10 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jaegertracing/jaeger v1.47.0 h1:XXxTMO+GxX930gxKWsg90rFr6RswkCRIW0AgWFnTYsg=
github.com/jaegertracing/jaeger v1.47.0/go.mod h1:mHU/OHFML51CijQql4+rLfgPOcIb9MhxOMn+RKQwrJc=
github.com/jaypipes/ghw v0.24.0 h1:6RBrJzvHvZ0t+hSvqPmOd5b21C4fMsyiyFzWljEj8Wg=
github.com/jaypipes/ghw v0.24.0/go.mod h1:Qk3UjdH8Xu/OiVyb/eDJqnDsUc+awHU75y23ErZU33s=
github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro=
github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
Expand Down Expand Up @@ -3460,8 +3464,9 @@ honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
howett.net/plist v1.0.2-0.20250314012144-ee69052608d9 h1:eeH1AIcPvSc0Z25ThsYF+Xoqbn0CI/YnXVYoTLFdGQw=
howett.net/plist v1.0.2-0.20250314012144-ee69052608d9/go.mod h1:fyFX5Hj5tP1Mpk8obqA9MZgXT416Q5711SDT7dQLTLk=
k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ=
Expand Down
43 changes: 43 additions & 0 deletions plugins/inputs/system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ plugin ordering. See [CONFIGURATION.md][CONFIGURATION.md] for more details.
## uptime - system uptime
## legacy_uptime - legacy layout of system uptime; see README for details
## os - operating system release and uname information
## hardware - DMI/SMBIOS hardware information
Comment thread
bilkoua marked this conversation as resolved.
Outdated
# include = ["load", "users", "legacy_cpus", "legacy_uptime"]

## How long to cache the result of the "os" group between gathers.
## Set higher to reduce the number of os-release/uname reads, lower to
## surface distro upgrades and kexec'd kernels faster. Set to zero to
## re-read the data on every gather.
# os_cache_ttl = "8h"

## How long to cache the result of the "hardware" group between gathers.
## DMI/SMBIOS data is effectively static for the life of the machine.
## Set to zero to re-read on every gather.
# hardware_cache_ttl = "8h"
Comment thread
bilkoua marked this conversation as resolved.
Outdated
```

> [!NOTE]
Expand Down Expand Up @@ -68,6 +74,14 @@ FreeBSD/OpenBSD/Solaris) the `platform`, `platform_family`, `platform_version`
and `kernel_version` fields may be empty. Results are cached between gathers,
see `os_cache_ttl` above.

The `hardware` group exposes BIOS, baseboard, chassis and product information
from DMI/SMBIOS. On Linux the data is read from `/sys/class/dmi/id/` and does
not require root access for most fields; serial numbers and asset tags are
generally restricted by the kernel. On Windows the data is read via WMI.
macOS, BSD and Solaris are not supported. Fields that cannot be determined
are omitted from the metric. Results are cached between gathers, see
`hardware_cache_ttl` above.

## Metrics

The `include` option controls which measurements and fields are gathered.
Expand Down Expand Up @@ -108,6 +122,27 @@ may be empty on platforms where gopsutil cannot determine them.
| `platform_version` | string | Platform / distribution version (e.g. `26.04`) |
| `kernel_version` | string | Kernel release as returned by `uname -r` (e.g. `7.0.0-7-generic`) |

### `system_hardware`

Emitted only when `hardware` is included. Fields are reported as strings; any
field that the operating system does not expose is omitted.

| Field | Type | Description |
|----------------------------|--------|--------------------------------------------------------------------------------------|
| `bios_vendor` | string | BIOS vendor (e.g. `Dell Inc.`) |
| `bios_version` | string | BIOS version (e.g. `2.18.0`) |
| `bios_date` | string | BIOS release date (e.g. `04/12/2024`) |
| `board_vendor` | string | Baseboard / motherboard vendor |
| `board_product` | string | Baseboard product name (e.g. `0X3D66`) |
| `board_version` | string | Baseboard version |
| `chassis_vendor` | string | Chassis vendor |
| `chassis_type` | string | Chassis type code as defined by SMBIOS DSP0134 (e.g. `3`, `10`) |
| `chassis_type_description` | string | Human-readable chassis type description (e.g. `Desktop`, `Notebook`) |
| `chassis_version` | string | Chassis version |
| `product_vendor` | string | System product vendor (e.g. `Dell Inc.`) |
| `product_name` | string | System product name (e.g. `PowerEdge R750`) |
| `product_family` | string | System product family |

## Example Output

### Default configuration
Expand Down Expand Up @@ -137,3 +172,11 @@ With `include = ["os"]`, a separate `system_os` measurement is emitted:
```text
system_os,host=worker-01 os="linux",arch="x86_64",platform="ubuntu",platform_family="debian",platform_version="26.04",kernel_version="7.0.0-7-generic" 1748000000000000000
```

### Hardware information

With `include = ["hardware"]`, a separate `system_hardware` measurement is emitted:

```text
system_hardware,host=worker-01 bios_vendor="Dell Inc.",bios_version="2.18.0",bios_date="04/12/2024",board_vendor="Dell Inc.",board_product="0X3D66",chassis_vendor="Dell Inc.",chassis_type="23",chassis_type_description="Rack mount chassis",product_vendor="Dell Inc.",product_name="PowerEdge R750" 1748000000000000000
```
5 changes: 5 additions & 0 deletions plugins/inputs/system/hardware_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !linux && !windows

package system

const hardwareSupported = false
5 changes: 5 additions & 0 deletions plugins/inputs/system/hardware_supported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build linux || windows

package system

const hardwareSupported = true
6 changes: 6 additions & 0 deletions plugins/inputs/system/sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@
## uptime - system uptime
## legacy_uptime - legacy layout of system uptime; see README for details
## os - operating system release and uname information
## hardware - DMI/SMBIOS hardware information
# include = ["load", "users", "legacy_cpus", "legacy_uptime"]

## How long to cache the result of the "os" group between gathers.
## Set higher to reduce the number of os-release/uname reads, lower to
## surface distro upgrades and kexec'd kernels faster. Set to zero to
## re-read the data on every gather.
# os_cache_ttl = "8h"

## How long to cache the result of the "hardware" group between gathers.
## DMI/SMBIOS data is effectively static for the life of the machine.
## Set to zero to re-read on every gather.
# hardware_cache_ttl = "8h"
98 changes: 91 additions & 7 deletions plugins/inputs/system/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"time"

"github.com/jaypipes/ghw"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/load"
Expand All @@ -25,12 +26,15 @@ import (
var sampleConfig string

type System struct {
Include []string `toml:"include"`
OSCacheTTL config.Duration `toml:"os_cache_ttl"`
Log telegraf.Logger `toml:"-"`
Include []string `toml:"include"`
OSCacheTTL config.Duration `toml:"os_cache_ttl"`
HardwareCacheTTL config.Duration `toml:"hardware_cache_ttl"`
Log telegraf.Logger `toml:"-"`

osCache map[string]interface{}
osCachedAt time.Time
osCache map[string]interface{}
osCachedAt time.Time
hardwareCache map[string]interface{}
hardwareCachedAt time.Time
}

func (*System) SampleConfig() string {
Expand All @@ -51,7 +55,7 @@ func (s *System) Init() error {
continue
}
switch incl {
case "load", "users", "cpus", "uptime", "os":
case "load", "users", "cpus", "uptime", "os", "hardware":
case "legacy_cpus":
if userSupplied {
config.PrintOptionValueDeprecationNotice(
Expand Down Expand Up @@ -93,6 +97,10 @@ func (s *System) Init() error {
return errors.New(`"uptime" and "legacy_uptime" are mutually exclusive`)
}

if enabled["hardware"] && !hardwareSupported {
s.Log.Warn("'hardware' is not supported on this platform, ignoring")
}
Comment thread
bilkoua marked this conversation as resolved.
Outdated

return nil
}

Expand All @@ -115,6 +123,19 @@ func (s *System) Gather(acc telegraf.Accumulator) error {
if len(s.osCache) > 0 {
acc.AddFields("system_os", s.osCache, nil, now)
}
case "hardware":
if time.Since(s.hardwareCachedAt) > time.Duration(s.HardwareCacheTTL) {
hwCache, err := gatherHardware()
if err != nil {
acc.AddError(err)
} else {
s.hardwareCache = hwCache
s.hardwareCachedAt = now
}
}
if len(s.hardwareCache) > 0 {
acc.AddFields("system_hardware", s.hardwareCache, nil, now)
}
case "load":
loadavg, err := load.Avg()
if err != nil {
Expand Down Expand Up @@ -214,6 +235,68 @@ func gatherOS() (map[string]interface{}, error) {
}, nil
}

// gatherHardware reads BIOS, baseboard, chassis and product DMI/SMBIOS
// information. Fields that cannot be read are omitted.
func gatherHardware() (map[string]interface{}, error) {
// Disable ghw warnings; honor GHW_CHROOT and other GHW_* env variables.
ctx := ghw.WithDisableWarnings()(ghw.ContextFromEnv())
Comment thread
bilkoua marked this conversation as resolved.
Outdated

fields := make(map[string]interface{})
Comment thread
bilkoua marked this conversation as resolved.
Outdated

bios, err := ghw.BIOS(ctx)
if err != nil && !strings.Contains(err.Error(), "not implemented") {
return nil, fmt.Errorf("reading BIOS information: %w", err)
}
if bios != nil {
addNonEmpty(fields, "bios_vendor", bios.Vendor)
addNonEmpty(fields, "bios_version", bios.Version)
addNonEmpty(fields, "bios_date", bios.Date)
Comment thread
bilkoua marked this conversation as resolved.
Outdated
}

bb, err := ghw.Baseboard(ctx)
if err != nil && !strings.Contains(err.Error(), "not implemented") {
return nil, fmt.Errorf("reading baseboard information: %w", err)
}
if bb != nil {
addNonEmpty(fields, "board_vendor", bb.Vendor)
addNonEmpty(fields, "board_product", bb.Product)
addNonEmpty(fields, "board_version", bb.Version)
}

ch, err := ghw.Chassis(ctx)
if err != nil && !strings.Contains(err.Error(), "not implemented") {
return nil, fmt.Errorf("reading chassis information: %w", err)
}
if ch != nil {
addNonEmpty(fields, "chassis_vendor", ch.Vendor)
addNonEmpty(fields, "chassis_type", ch.Type)
addNonEmpty(fields, "chassis_type_description", ch.TypeDescription)
addNonEmpty(fields, "chassis_version", ch.Version)
}

prod, err := ghw.Product(ctx)
if err != nil && !strings.Contains(err.Error(), "not implemented") {
return nil, fmt.Errorf("reading product information: %w", err)
}
if prod != nil {
addNonEmpty(fields, "product_vendor", prod.Vendor)
addNonEmpty(fields, "product_name", prod.Name)
addNonEmpty(fields, "product_family", prod.Family)
}

return fields, nil
}

// addNonEmpty adds value to fields under key, dropping empty strings and the
// ghw "unknown" sentinel.
func addNonEmpty(fields map[string]interface{}, key, value string) {
value = strings.TrimSpace(value)
if value == "" || value == "unknown" {
return
}
fields[key] = value
}

func findUniqueUsers(userStats []host.UserStat) int {
uniqueUsers := make(map[string]bool)
for _, userstat := range userStats {
Expand Down Expand Up @@ -250,7 +333,8 @@ func formatUptime(uptime uint64) string {
func init() {
inputs.Add("system", func() telegraf.Input {
return &System{
OSCacheTTL: config.Duration(8 * time.Hour),
OSCacheTTL: config.Duration(8 * time.Hour),
HardwareCacheTTL: config.Duration(8 * time.Hour),
}
})
}
Loading
Loading