fn: add docker-containerd to prometheus processes (#863)

*) switch to /proc/<pid>/cmdline due to /proc/<pid>/status
process name truncation.
*) Optional override with FN_PROCESS_COLLECTOR_LIST
This commit is contained in:
Tolga Ceylan
2018-03-15 10:24:27 -07:00
committed by GitHub
parent a7743a03ea
commit 1b6f8cb24a

View File

@@ -17,6 +17,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"unicode"
"github.com/fnproject/fn/api/agent" "github.com/fnproject/fn/api/agent"
"github.com/fnproject/fn/api/agent/hybrid" "github.com/fnproject/fn/api/agent/hybrid"
@@ -63,6 +64,8 @@ const (
EnvCertKey = "FN_NODE_CERT_KEY" EnvCertKey = "FN_NODE_CERT_KEY"
EnvCertAuth = "FN_NODE_CERT_AUTHORITY" EnvCertAuth = "FN_NODE_CERT_AUTHORITY"
EnvProcessCollectorList = "FN_PROCESS_COLLECTOR_LIST"
// Defaults // Defaults
DefaultLogLevel = "info" DefaultLogLevel = "info"
DefaultLogDest = "stderr" DefaultLogDest = "stderr"
@@ -530,10 +533,17 @@ func WithPrometheus() ServerOption {
return func(ctx context.Context, s *Server) error { return func(ctx context.Context, s *Server) error {
reg := promclient.NewRegistry() reg := promclient.NewRegistry()
reg.MustRegister(promclient.NewProcessCollector(os.Getpid(), "fn"), reg.MustRegister(promclient.NewProcessCollector(os.Getpid(), "fn"),
promclient.NewProcessCollectorPIDFn(dockerPid(), "dockerd"),
promclient.NewGoCollector(), promclient.NewGoCollector(),
) )
for _, exeName := range getMonitoredCmdNames() {
san := promSanitizeMetricName(exeName)
err := reg.Register(promclient.NewProcessCollectorPIDFn(getPidCmd(exeName), san))
if err != nil {
panic(err)
}
}
exporter, err := prometheus.NewExporter(prometheus.Options{ exporter, err := prometheus.NewExporter(prometheus.Options{
Namespace: "fn", Namespace: "fn",
Registry: reg, Registry: reg,
@@ -590,8 +600,35 @@ func WithZipkin(zipkinURL string) ServerOption {
} }
} }
// prometheus only allows [a-zA-Z0-9:_] in metrics names.
func promSanitizeMetricName(name string) string {
res := make([]rune, 0, len(name))
for _, rVal := range name {
if unicode.IsDigit(rVal) || unicode.IsLetter(rVal) || rVal == ':' {
res = append(res, rVal)
} else {
res = append(res, '_')
}
}
return string(res)
}
// determine sidecar-monitored cmd names. But by default
// we track dockerd + containerd
func getMonitoredCmdNames() []string {
// override? empty variable to disable trackers
val, ok := os.LookupEnv(EnvProcessCollectorList)
if ok {
return strings.Fields(val)
}
// by default, we monitor dockerd and containerd
return []string{"dockerd", "docker-containerd"}
}
// TODO plumbing considerations, we've put the S pipe next to the chandalier... // TODO plumbing considerations, we've put the S pipe next to the chandalier...
func dockerPid() func() (int, error) { func getPidCmd(cmdName string) func() (int, error) {
// prometheus' process collector only works on linux anyway. let them do the // prometheus' process collector only works on linux anyway. let them do the
// process detection, if we return an error here we just get 0 metrics and it // process detection, if we return an error here we just get 0 metrics and it
// does not log / blow up (that's fine!) it's also likely we hit permissions // does not log / blow up (that's fine!) it's also likely we hit permissions
@@ -602,50 +639,62 @@ func dockerPid() func() (int, error) {
return func() (int, error) { return func() (int, error) {
if pid != 0 { if pid != 0 {
// make sure it's docker pid. // make sure it's our pid.
if isDockerPid("/proc/" + strconv.Itoa(pid) + "/status") { if isPidMatchCmd(cmdName, pid) {
return pid, nil return pid, nil
} }
pid = 0 // reset to go search pid = 0 // reset to go search
} }
err := filepath.Walk("/proc", func(path string, info os.FileInfo, err error) error { if pids, err := getPidList(); err == nil {
if err != nil || pid != 0 { for _, test := range pids {
// we get permission errors digging around in here, ignore them and press on if isPidMatchCmd(cmdName, test) {
return nil pid = test
} return pid, nil
// /proc/<pid>/status
if strings.Count(path, "/") == 3 && strings.Contains(path, "/status") {
if isDockerPid(path) {
// extract pid from path
pid, _ = strconv.Atoi(path[6:strings.LastIndex(path, "/")])
return io.EOF // end the search
} }
} }
// keep searching
return nil
})
if err == io.EOF { // used as sentinel
err = nil
} }
return pid, err
return pid, io.EOF
} }
} }
func isDockerPid(path string) bool { func isPidMatchCmd(cmdName string, pid int) bool {
// first line of status file is: "Name: <name>" fs, err := os.Open("/proc/" + strconv.Itoa(pid) + "/cmdline")
f, err := os.Open(path)
if err != nil { if err != nil {
return false return false
} }
defer f.Close() defer fs.Close()
// scan first line only rd := bufio.NewReader(fs)
scanner := bufio.NewScanner(f) tok, err := rd.ReadSlice(0)
scanner.Scan() if err != nil || len(tok) < len(cmdName) {
return strings.HasSuffix(scanner.Text(), "dockerd") return false
}
return filepath.Base(string(tok[:len(tok)-1])) == cmdName
}
func getPidList() ([]int, error) {
var pids []int
dir, err := os.Open("/proc")
if err != nil {
return pids, nil
}
defer dir.Close()
files, err := dir.Readdirnames(0)
if err != nil {
return pids, nil
}
pids = make([]int, 0, len(files))
for _, tok := range files {
if conv, err := strconv.ParseUint(tok, 10, 64); err == nil {
pids = append(pids, int(conv))
}
}
return pids, nil
} }
func setMachineID() { func setMachineID() {