pkg/exporter/probe/procfd/procfd.go (99 lines of code) (raw):
package procfd
import (
"context"
"fmt"
"os"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/alibaba/kubeskoop/pkg/exporter/probe"
log "k8s.io/klog/v2"
"github.com/alibaba/kubeskoop/pkg/exporter/nettop"
)
const (
probeName = "fd"
)
var (
OpenFD = "openfd"
OpenSocket = "opensocket"
)
func init() {
probe.MustRegisterMetricsProbe(probeName, fdProbeCreator)
}
func fdProbeCreator() (probe.MetricsProbe, error) {
p := &ProcFD{}
opts := probe.BatchMetricsOpts{
Namespace: probe.MetricsNamespace,
Subsystem: probeName,
VariableLabels: probe.StandardMetricsLabels,
SingleMetricsOpts: []probe.SingleMetricsOpts{
{Name: OpenFD, Help: "The total number of open file descriptors for the process", ValueType: prometheus.GaugeValue},
{Name: OpenSocket, Help: "The total number of open sockets for the process", ValueType: prometheus.GaugeValue},
},
}
metrics := probe.NewBatchMetrics(opts, p.collectOnce)
return probe.NewMetricsProbe(probeName, p, metrics), nil
}
type ProcFD struct {
}
func (s *ProcFD) Start(_ context.Context) error {
return nil
}
func (s *ProcFD) Stop(_ context.Context) error {
return nil
}
func (s *ProcFD) collectOnce(emit probe.Emit) error {
ets := nettop.GetAllEntity()
if len(ets) == 0 {
log.Warningf("procfd: no entity found")
return nil
}
for _, entity := range ets {
collectProcessFd(entity, emit)
emit(OpenFD, probe.BuildStandardMetricsLabelValues(entity), 0)
}
return nil
}
func collectProcessFd(entity *nettop.Entity, emit probe.Emit) {
var (
fdCount int
sockCount int
)
for _, idx := range entity.GetPids() {
procfds, err := getProcessFdStat(idx)
if err != nil {
log.Warningf("failed open proc fd for pod %s: %v", entity, err)
return
}
for fd := range procfds {
fdCount++
if strings.HasPrefix(fd, "socket:") {
sockCount++
}
}
}
labels := probe.BuildStandardMetricsLabelValues(entity)
emit(OpenFD, labels, float64(fdCount))
emit(OpenSocket, labels, float64(sockCount))
}
func getProcessFdStat(pid int) (map[string]struct{}, error) {
fdpath := fmt.Sprintf("/proc/%d/fd", pid)
d, err := os.Open(fdpath)
if err != nil {
return nil, err
}
defer d.Close()
names, err := d.Readdirnames(-1)
if err != nil {
return nil, fmt.Errorf("could not read %s: %w", fdpath, err)
}
fds := map[string]struct{}{}
for _, name := range names {
fdlink := fmt.Sprintf("%s/%s", fdpath, name)
info, err := os.Readlink(fdlink)
if os.IsNotExist(err) {
continue
}
fds[info] = struct{}{}
}
return fds, nil
}