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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ collector/fixtures/sys/
collector/fixtures/udev/

/vendor
.DS_Store
FIELDS.md
sf-node-exporter
examples/*
34 changes: 34 additions & 0 deletions Dockerfile.k8s
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
ARG ARCH="amd64"
ARG OS="linux"

FROM golang:1.25-bookworm AS builder
ARG ARCH
ARG OS
WORKDIR /src
ENV GOPROXY=https://goproxy.cn,direct
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=${OS} GOARCH=${ARCH} go build -ldflags="-s -w" -o /bin/node_exporter .

FROM ubuntu:22.04
ARG ARCH
ARG OS

RUN apt-get update && apt-get install -y --no-install-recommends \
pciutils \
util-linux \
dmidecode \
iproute2 \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /bin/node_exporter /bin/node_exporter
COPY entrypoint.sh /bin/entrypoint.sh

RUN printf '#!/bin/sh\nexec /host/root/usr/bin/nvidia-smi "$$@"\n' > /usr/local/bin/nvidia-smi \
&& chmod +x /usr/local/bin/nvidia-smi \
&& printf '#!/bin/sh\nexec /host/root/usr/local/sbin/npu-smi "$$@"\n' > /usr/local/bin/npu-smi \
&& chmod +x /usr/local/bin/npu-smi

EXPOSE 9100
ENTRYPOINT ["/bin/entrypoint.sh"]
105 changes: 105 additions & 0 deletions collector/asset/cmdb/cpu.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package cmdb

import (
"sort"
"strconv"

"github.com/prometheus/node_exporter/collector/asset/cmdb/model"
"github.com/shirou/gopsutil/v3/cpu"
)

func CollectCPU() (*model.CPU, error) {
c := &model.CPU{}

infos, _ := cpu.Info()
logical, _ := cpu.Counts(true)
physical, _ := cpu.Counts(false)

if logical == 0 {
logical = len(infos)
}

type socket struct {
coreIDs map[string]struct{}
threads int
hasCore bool
example cpu.InfoStat
}
socks := map[string]*socket{}
var order []string
for _, ci := range infos {
pid := ci.PhysicalID
if pid == "" {
pid = "0"
}
s, ok := socks[pid]
if !ok {
s = &socket{coreIDs: map[string]struct{}{}, example: ci}
socks[pid] = s
order = append(order, pid)
}
s.threads++
if ci.CoreID != "" {
s.hasCore = true
s.coreIDs[ci.CoreID] = struct{}{}
}
}

// 退化场景:每个逻辑线程都被报告为独立插槽(部分虚拟机给每个 vCPU
// 分配独立 physical id)。此时拓扑无意义,折叠为单路。
if len(socks) > 1 && len(socks) == len(infos) {
var ex cpu.InfoStat
if len(infos) > 0 {
ex = infos[0]
}
socks = map[string]*socket{"0": {coreIDs: map[string]struct{}{}, example: ex}}
order = []string{"0"}
}

sort.Slice(order, func(i, j int) bool {
a, _ := strconv.Atoi(order[i])
b, _ := strconv.Atoi(order[j])
return a < b
})

totalCores := 0
c.Devices = make([]model.CPUDevice, 0, len(order))
for _, pid := range order {
s := socks[pid]
cores := 0
if s.hasCore {
cores = len(s.coreIDs)
}
totalCores += cores
c.Devices = append(c.Devices, model.CPUDevice{
ModelName: s.example.ModelName,
VendorID: s.example.VendorID,
Cores: cores,
Threads: s.threads,
Mhz: s.example.Mhz,
CacheKB: int(s.example.CacheSize),
})
}

if totalCores > 0 {
// Topology recovered from core_id: per-socket devices are already
// built above. No machine-level aggregates are stored.
} else {
// 无法从 cpuinfo 获取 core_id 拓扑(虚拟机/容器常见):
// 报告单路,核数取物理核数,缺失时退化为逻辑核数。
cores := physical
if cores == 0 {
cores = logical
}
dev := model.CPUDevice{Cores: cores, Threads: logical}
if len(infos) > 0 {
dev.ModelName = infos[0].ModelName
dev.VendorID = infos[0].VendorID
dev.Mhz = infos[0].Mhz
dev.CacheKB = int(infos[0].CacheSize)
}
c.Devices = append(c.Devices[:0], dev)
}

return c, nil
}
84 changes: 84 additions & 0 deletions collector/asset/cmdb/disk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package cmdb

import (
"encoding/json"
"strconv"
"strings"

"github.com/prometheus/node_exporter/collector/asset/cmdb/model"
)

type lsblkDevice struct {
Name string `json:"name"`
Model string `json:"model"`
Vendor string `json:"vendor"`
Serial string `json:"serial"`
Size flexUint `json:"size"`
Type string `json:"type"`
Children []lsblkDevice `json:"children,omitempty"`
}

type flexUint uint64

func (f *flexUint) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), `"`)
if s == "" || s == "null" {
return nil
}
v, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return err
}
*f = flexUint(v)
return nil
}

type lsblkOutput struct {
BlockDevices []lsblkDevice `json:"blockdevices"`
}

func CollectDisk() (*model.Disk, error) {
d := &model.Disk{Devices: []model.DiskDevice{}}

collectLSBLK(d)

return d, nil
}

func collectLSBLK(d *model.Disk) {
if !commandExists("lsblk") {
return
}

out, err := runCmd("lsblk", "-b", "-J",
"-o", "NAME,MODEL,VENDOR,SERIAL,SIZE,TYPE")
if err != nil {
return
}

var parsed lsblkOutput
if err := json.Unmarshal([]byte(out), &parsed); err != nil {
return
}

for _, dev := range parsed.BlockDevices {
walkLSBLK(dev, d)
}
}

func walkLSBLK(dev lsblkDevice, d *model.Disk) {
if uint64(dev.Size) == 0 {
return
}
if dev.Type != "disk" {
return
}
d.Devices = append(d.Devices, model.DiskDevice{
Name: dev.Name,
Type: dev.Type,
Model: dev.Model,
Vendor: dev.Vendor,
Serial: dev.Serial,
SizeBytes: uint64(dev.Size),
})
}
Loading