Update godeps
This commit is contained in:
parent
86dbf979cb
commit
f7011d22f8
108 changed files with 7093 additions and 4947 deletions
4
vendor/github.com/ncabatoff/process-exporter/.gitignore
generated
vendored
Normal file
4
vendor/github.com/ncabatoff/process-exporter/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.*.sw?
|
||||
process-exporter
|
||||
.tarballs
|
||||
process-exporter-*.tar.gz
|
||||
35
vendor/github.com/ncabatoff/process-exporter/.promu.yml
generated
vendored
Normal file
35
vendor/github.com/ncabatoff/process-exporter/.promu.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
repository:
|
||||
path: github.com/ncabatoff/process-exporter
|
||||
build:
|
||||
binaries:
|
||||
- name: process-exporter
|
||||
path: ./cmd/process-exporter
|
||||
flags: -a -tags netgo
|
||||
tarball:
|
||||
files:
|
||||
- LICENSE
|
||||
crossbuild:
|
||||
platforms:
|
||||
- linux/amd64
|
||||
- linux/386
|
||||
- darwin/amd64
|
||||
- darwin/386
|
||||
- freebsd/amd64
|
||||
- freebsd/386
|
||||
- openbsd/amd64
|
||||
- openbsd/386
|
||||
- netbsd/amd64
|
||||
- netbsd/386
|
||||
- dragonfly/amd64
|
||||
- linux/arm
|
||||
- linux/arm64
|
||||
- freebsd/arm
|
||||
# Temporarily deactivated as golang.org/x/sys does not have syscalls
|
||||
# implemented for that os/platform combination.
|
||||
#- openbsd/arm
|
||||
#- linux/mips64
|
||||
#- linux/mips64le
|
||||
- netbsd/arm
|
||||
- linux/ppc64
|
||||
- linux/ppc64le
|
||||
|
||||
17
vendor/github.com/ncabatoff/process-exporter/Dockerfile
generated
vendored
Normal file
17
vendor/github.com/ncabatoff/process-exporter/Dockerfile
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Start from a Debian image with the latest version of Go installed
|
||||
# and a workspace (GOPATH) configured at /go.
|
||||
FROM golang
|
||||
|
||||
# Copy the local package files to the container's workspace.
|
||||
ADD . /go/src/github.com/ncabatoff/process-exporter
|
||||
|
||||
# Build the process-exporter command inside the container.
|
||||
RUN make -C /go/src/github.com/ncabatoff/process-exporter
|
||||
|
||||
USER root
|
||||
|
||||
# Run the process-exporter command by default when the container starts.
|
||||
ENTRYPOINT ["/go/src/github.com/ncabatoff/process-exporter/process-exporter"]
|
||||
|
||||
# Document that the service listens on port 9256.
|
||||
EXPOSE 9256
|
||||
21
vendor/github.com/ncabatoff/process-exporter/LICENSE
generated
vendored
Normal file
21
vendor/github.com/ncabatoff/process-exporter/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 ncabatoff
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
71
vendor/github.com/ncabatoff/process-exporter/Makefile
generated
vendored
Normal file
71
vendor/github.com/ncabatoff/process-exporter/Makefile
generated
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Copyright 2015 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.
|
||||
|
||||
GO := GO15VENDOREXPERIMENT=1 go
|
||||
FIRST_GOPATH := $(firstword $(subst :, ,$(GOPATH)))
|
||||
PROMU := $(FIRST_GOPATH)/bin/promu
|
||||
pkgs = $(shell $(GO) list ./... | grep -v /vendor/)
|
||||
|
||||
PREFIX ?= $(shell pwd)
|
||||
BIN_DIR ?= $(shell pwd)
|
||||
DOCKER_IMAGE_NAME ?= process-exporter
|
||||
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
|
||||
|
||||
ifdef DEBUG
|
||||
bindata_flags = -debug
|
||||
endif
|
||||
|
||||
|
||||
all: format build test
|
||||
|
||||
style:
|
||||
@echo ">> checking code style"
|
||||
@! gofmt -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^'
|
||||
|
||||
test:
|
||||
@echo ">> running short tests"
|
||||
@$(GO) test -short $(pkgs)
|
||||
|
||||
format:
|
||||
@echo ">> formatting code"
|
||||
@$(GO) fmt $(pkgs)
|
||||
|
||||
vet:
|
||||
@echo ">> vetting code"
|
||||
@$(GO) vet $(pkgs)
|
||||
|
||||
build: promu
|
||||
@echo ">> building binaries"
|
||||
@$(PROMU) build --prefix $(PREFIX)
|
||||
|
||||
tarball: promu
|
||||
@echo ">> building release tarball"
|
||||
@$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
|
||||
|
||||
crossbuild: promu
|
||||
@echo ">> cross-building"
|
||||
@$(PROMU) crossbuild
|
||||
@$(PROMU) crossbuild tarballs
|
||||
|
||||
docker:
|
||||
@echo ">> building docker image"
|
||||
@docker build -t "$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" .
|
||||
|
||||
promu:
|
||||
@echo ">> fetching promu"
|
||||
@GOOS=$(shell uname -s | tr A-Z a-z) \
|
||||
GOARCH=$(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m))) \
|
||||
$(GO) get -u github.com/prometheus/promu
|
||||
|
||||
|
||||
.PHONY: all style format build test vet tarball crossbuild docker promu
|
||||
160
vendor/github.com/ncabatoff/process-exporter/README.md
generated
vendored
Normal file
160
vendor/github.com/ncabatoff/process-exporter/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# process-exporter
|
||||
Prometheus exporter that mines /proc to report on selected processes.
|
||||
|
||||
The premise for this exporter is that sometimes you have apps that are
|
||||
impractical to instrument directly, either because you don't control the code
|
||||
or they're written in a language that isn't easy to instrument with Prometheus.
|
||||
A fair bit of information can be gleaned from /proc, especially for
|
||||
long-running programs.
|
||||
|
||||
For most systems it won't be beneficial to create metrics for every process by
|
||||
name: there are just too many of them and most don't do enough to merit it.
|
||||
Various command-line options are provided to control how processes are grouped
|
||||
and the groups are named. Run "process-exporter -man" to see a help page
|
||||
giving details.
|
||||
|
||||
Metrics available currently include CPU usage, bytes written and read, and
|
||||
number of processes in each group.
|
||||
|
||||
Bytes read and written come from /proc/[pid]/io in recent enough kernels.
|
||||
These correspond to the fields `read_bytes` and `write_bytes` respectively.
|
||||
These IO stats come with plenty of caveats, see either the Linux kernel
|
||||
documentation or man 5 proc.
|
||||
|
||||
CPU usage comes from /proc/[pid]/stat fields utime (user time) and stime (system
|
||||
time.) It has been translated into fractional seconds of CPU consumed. Since
|
||||
it is a counter, using rate() will tell you how many fractional cores were running
|
||||
code from this process during the interval given.
|
||||
|
||||
An example Grafana dashboard to view the metrics is available at https://grafana.net/dashboards/249
|
||||
|
||||
## Instrumentation cost
|
||||
|
||||
process-exporter will consume CPU in proportion to the number of processes in
|
||||
the system and the rate at which new ones are created. The most expensive
|
||||
parts - applying regexps and executing templates - are only applied once per
|
||||
process seen. If you have mostly long-running processes process-exporter
|
||||
should be lightweight: each time a scrape occurs, parsing of /proc/$pid/stat
|
||||
and /proc/$pid/cmdline for every process being monitored and adding a few
|
||||
numbers.
|
||||
|
||||
## Config
|
||||
|
||||
To select and group the processes to monitor, either provide command-line
|
||||
arguments or use a YAML configuration file.
|
||||
|
||||
To avoid confusion with the cmdline YAML element, we'll refer to the
|
||||
null-delimited contents of `/proc/<pid>/cmdline` as the array `argv[]`.
|
||||
|
||||
Each item in `process_names` gives a recipe for identifying and naming
|
||||
processes. The optional `name` tag defines a template to use to name
|
||||
matching processes; if not specified, `name` defaults to `{{.ExeBase}}`.
|
||||
|
||||
Template variables available:
|
||||
- `{{.ExeBase}}` contains the basename of the executable
|
||||
- `{{.ExeFull}}` contains the fully qualified path of the executable
|
||||
- `{{.Matches}}` map contains all the matches resulting from applying cmdline regexps
|
||||
|
||||
Each item in `process_names` must contain one or more selectors (`comm`, `exe`
|
||||
or `cmdline`); if more than one selector is present, they must all match. Each
|
||||
selector is a list of strings to match against a process's `comm`, `argv[0]`,
|
||||
or in the case of `cmdline`, a regexp to apply to the command line.
|
||||
|
||||
For `comm` and `exe`, the list of strings is an OR, meaning any process
|
||||
matching any of the strings will be added to the item's group.
|
||||
|
||||
For `cmdline`, the list of regexes is an AND, meaning they all must match. Any
|
||||
capturing groups in a regexp must use the `?P<name>` option to assign a name to
|
||||
the capture, which is used to populate `.Matches`.
|
||||
|
||||
A process may only belong to one group: even if multiple items would match, the
|
||||
first one listed in the file wins.
|
||||
|
||||
Other performance tips: give an exe or comm clause in addition to any cmdline
|
||||
clause, so you avoid executing the regexp when the executable name doesn't
|
||||
match.
|
||||
|
||||
```
|
||||
|
||||
process_names:
|
||||
# comm is the second field of /proc/<pid>/stat minus parens.
|
||||
# It is the base executable name, truncated at 15 chars.
|
||||
# It cannot be modified by the program, unlike exe.
|
||||
- comm:
|
||||
- bash
|
||||
|
||||
# exe is argv[0]. If no slashes, only basename of argv[0] need match.
|
||||
# If exe contains slashes, argv[0] must match exactly.
|
||||
- exe:
|
||||
- postgres
|
||||
- /usr/local/bin/prometheus
|
||||
|
||||
# cmdline is a list of regexps applied to argv.
|
||||
# Each must match, and any captures are added to the .Matches map.
|
||||
- name: "{{.ExeFull}}:{{.Matches.Cfgfile}}"
|
||||
exe:
|
||||
- /usr/local/bin/process-exporter
|
||||
cmdline:
|
||||
- -config.path\\s+(?P<Cfgfile>\\S+)
|
||||
|
||||
|
||||
```
|
||||
|
||||
Here's the config I use on my home machine:
|
||||
|
||||
```
|
||||
|
||||
process_names:
|
||||
- comm:
|
||||
- chromium-browse
|
||||
- bash
|
||||
- prometheus
|
||||
- gvim
|
||||
- exe:
|
||||
- /sbin/upstart
|
||||
cmdline:
|
||||
- --user
|
||||
name: upstart:-user
|
||||
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
A docker image can be created with
|
||||
|
||||
```
|
||||
make docker
|
||||
```
|
||||
|
||||
Then run the docker, e.g.
|
||||
|
||||
```
|
||||
docker run --privileged --name pexporter -d -v /proc:/host/proc -p 127.0.0.1:9256:9256 process-exporter:master -procfs /host/proc -procnames chromium-browse,bash,prometheus,gvim,upstart:-user -namemapping "upstart,(-user)"
|
||||
```
|
||||
|
||||
This will expose metrics on http://localhost:9256/metrics. Leave off the
|
||||
`127.0.0.1:` to publish on all interfaces. Leave off the --priviliged and
|
||||
add the --user docker run argument if you only need to monitor processes
|
||||
belonging to a single user.
|
||||
|
||||
## History
|
||||
|
||||
An earlier version of this exporter had options to enable auto-discovery of
|
||||
which processes were consuming resources. This functionality has been removed.
|
||||
These options were based on a percentage of resource usage, e.g. if an
|
||||
untracked process consumed X% of CPU during a scrape, start tracking processes
|
||||
with that name. However during any given scrape it's likely that most
|
||||
processes are idle, so we could add a process that consumes minimal resources
|
||||
but which happened to be active during the interval preceding the current
|
||||
scrape. Over time this means that a great many processes wind up being
|
||||
scraped, which becomes unmanageable to visualize. This could be mitigated by
|
||||
looking at resource usage over longer intervals, but ultimately I didn't feel
|
||||
this feature was important enough to invest more time in at this point. It may
|
||||
re-appear at some point in the future, but no promises.
|
||||
|
||||
Another lost feature: the "other" group was used to count usage by non-tracked
|
||||
procs. This was useful to get an idea of what wasn't being monitored. But it
|
||||
comes at a high cost: if you know what processes you care about, you're wasting
|
||||
a lot of CPU to compute the usage of everything else that you don't care about.
|
||||
The new approach is to minimize resources expended on non-tracked processes and
|
||||
to require the user to whitelist the processes to track.
|
||||
1
vendor/github.com/ncabatoff/process-exporter/VERSION
generated
vendored
Normal file
1
vendor/github.com/ncabatoff/process-exporter/VERSION
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
0.1.0
|
||||
14
vendor/github.com/ncabatoff/process-exporter/common.go
generated
vendored
Normal file
14
vendor/github.com/ncabatoff/process-exporter/common.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package common
|
||||
|
||||
type (
|
||||
NameAndCmdline struct {
|
||||
Name string
|
||||
Cmdline []string
|
||||
}
|
||||
|
||||
MatchNamer interface {
|
||||
// MatchAndName returns false if the match failed, otherwise
|
||||
// true and the resulting name.
|
||||
MatchAndName(NameAndCmdline) (bool, string)
|
||||
}
|
||||
)
|
||||
166
vendor/github.com/ncabatoff/process-exporter/proc/grouper.go
generated
vendored
Normal file
166
vendor/github.com/ncabatoff/process-exporter/proc/grouper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
package proc
|
||||
|
||||
import (
|
||||
common "github.com/ncabatoff/process-exporter"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
Grouper struct {
|
||||
namer common.MatchNamer
|
||||
trackChildren bool
|
||||
// track how much was seen last time so we can report the delta
|
||||
GroupStats map[string]Counts
|
||||
tracker *Tracker
|
||||
}
|
||||
|
||||
GroupCountMap map[string]GroupCounts
|
||||
|
||||
GroupCounts struct {
|
||||
Counts
|
||||
Procs int
|
||||
Memresident uint64
|
||||
Memvirtual uint64
|
||||
OldestStartTime time.Time
|
||||
}
|
||||
)
|
||||
|
||||
func NewGrouper(trackChildren bool, namer common.MatchNamer) *Grouper {
|
||||
g := Grouper{
|
||||
trackChildren: trackChildren,
|
||||
namer: namer,
|
||||
GroupStats: make(map[string]Counts),
|
||||
tracker: NewTracker(),
|
||||
}
|
||||
return &g
|
||||
}
|
||||
|
||||
func (g *Grouper) checkAncestry(idinfo ProcIdInfo, newprocs map[ProcId]ProcIdInfo) string {
|
||||
ppid := idinfo.ParentPid
|
||||
pProcId := g.tracker.ProcIds[ppid]
|
||||
if pProcId.Pid < 1 {
|
||||
// Reached root of process tree without finding a tracked parent.
|
||||
g.tracker.Ignore(idinfo.ProcId)
|
||||
return ""
|
||||
}
|
||||
|
||||
// Is the parent already known to the tracker?
|
||||
if ptproc, ok := g.tracker.Tracked[pProcId]; ok {
|
||||
if ptproc != nil {
|
||||
// We've found a tracked parent.
|
||||
g.tracker.Track(ptproc.GroupName, idinfo)
|
||||
return ptproc.GroupName
|
||||
} else {
|
||||
// We've found an untracked parent.
|
||||
g.tracker.Ignore(idinfo.ProcId)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Is the parent another new process?
|
||||
if pinfoid, ok := newprocs[pProcId]; ok {
|
||||
if name := g.checkAncestry(pinfoid, newprocs); name != "" {
|
||||
// We've found a tracked parent, which implies this entire lineage should be tracked.
|
||||
g.tracker.Track(name, idinfo)
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
// Parent is dead, i.e. we never saw it, or there's no tracked proc in our ancestry.
|
||||
g.tracker.Ignore(idinfo.ProcId)
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
// Update tracks any new procs that should be according to policy, and updates
|
||||
// the metrics for already tracked procs. Permission errors are returned as a
|
||||
// count, and will not affect the error return value.
|
||||
func (g *Grouper) Update(iter ProcIter) (int, error) {
|
||||
newProcs, permErrs, err := g.tracker.Update(iter)
|
||||
if err != nil {
|
||||
return permErrs, err
|
||||
}
|
||||
|
||||
// Step 1: track any new proc that should be tracked based on its name and cmdline.
|
||||
untracked := make(map[ProcId]ProcIdInfo)
|
||||
for _, idinfo := range newProcs {
|
||||
wanted, gname := g.namer.MatchAndName(common.NameAndCmdline{idinfo.Name, idinfo.Cmdline})
|
||||
if !wanted {
|
||||
untracked[idinfo.ProcId] = idinfo
|
||||
continue
|
||||
}
|
||||
|
||||
g.tracker.Track(gname, idinfo)
|
||||
}
|
||||
|
||||
// Step 2: track any untracked new proc that should be tracked because its parent is tracked.
|
||||
if !g.trackChildren {
|
||||
return permErrs, nil
|
||||
}
|
||||
|
||||
for _, idinfo := range untracked {
|
||||
if _, ok := g.tracker.Tracked[idinfo.ProcId]; ok {
|
||||
// Already tracked or ignored
|
||||
continue
|
||||
}
|
||||
|
||||
g.checkAncestry(idinfo, untracked)
|
||||
}
|
||||
return permErrs, nil
|
||||
}
|
||||
|
||||
// groups returns the aggregate metrics for all groups tracked. This reflects
|
||||
// solely what's currently running.
|
||||
func (g *Grouper) groups() GroupCountMap {
|
||||
gcounts := make(GroupCountMap)
|
||||
|
||||
var zeroTime time.Time
|
||||
for _, tinfo := range g.tracker.Tracked {
|
||||
if tinfo == nil {
|
||||
continue
|
||||
}
|
||||
cur := gcounts[tinfo.GroupName]
|
||||
cur.Procs++
|
||||
_, counts, mem, start := tinfo.GetStats()
|
||||
cur.Memresident += mem.Resident
|
||||
cur.Memvirtual += mem.Virtual
|
||||
cur.Counts.Cpu += counts.Cpu
|
||||
cur.Counts.ReadBytes += counts.ReadBytes
|
||||
cur.Counts.WriteBytes += counts.WriteBytes
|
||||
if cur.OldestStartTime == zeroTime || start.Before(cur.OldestStartTime) {
|
||||
cur.OldestStartTime = start
|
||||
}
|
||||
gcounts[tinfo.GroupName] = cur
|
||||
}
|
||||
|
||||
return gcounts
|
||||
}
|
||||
|
||||
// Groups returns GroupCounts with Counts that never decrease in value from one
|
||||
// call to the next. Even if processes exit, their CPU and IO contributions up
|
||||
// to that point are included in the results. Even if no processes remain
|
||||
// in a group it will still be included in the results.
|
||||
func (g *Grouper) Groups() GroupCountMap {
|
||||
groups := g.groups()
|
||||
|
||||
// First add any accumulated counts to what was just observed,
|
||||
// and update the accumulators.
|
||||
for gname, group := range groups {
|
||||
if oldcounts, ok := g.GroupStats[gname]; ok {
|
||||
group.Counts.Cpu += oldcounts.Cpu
|
||||
group.Counts.ReadBytes += oldcounts.ReadBytes
|
||||
group.Counts.WriteBytes += oldcounts.WriteBytes
|
||||
}
|
||||
g.GroupStats[gname] = group.Counts
|
||||
groups[gname] = group
|
||||
}
|
||||
|
||||
// Now add any groups that were observed in the past but aren't running now.
|
||||
for gname, gcounts := range g.GroupStats {
|
||||
if _, ok := groups[gname]; !ok {
|
||||
groups[gname] = GroupCounts{Counts: gcounts}
|
||||
}
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
306
vendor/github.com/ncabatoff/process-exporter/proc/read.go
generated
vendored
Normal file
306
vendor/github.com/ncabatoff/process-exporter/proc/read.go
generated
vendored
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
package proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/prometheus/procfs"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newProcIdStatic(pid, ppid int, startTime uint64, name string, cmdline []string) ProcIdStatic {
|
||||
return ProcIdStatic{ProcId{pid, startTime}, ProcStatic{name, cmdline, ppid, time.Time{}}}
|
||||
}
|
||||
|
||||
type (
|
||||
// ProcId uniquely identifies a process.
|
||||
ProcId struct {
|
||||
// UNIX process id
|
||||
Pid int
|
||||
// The time the process started after system boot, the value is expressed
|
||||
// in clock ticks.
|
||||
StartTimeRel uint64
|
||||
}
|
||||
|
||||
// ProcStatic contains data read from /proc/pid/*
|
||||
ProcStatic struct {
|
||||
Name string
|
||||
Cmdline []string
|
||||
ParentPid int
|
||||
StartTime time.Time
|
||||
}
|
||||
|
||||
// ProcMetrics contains data read from /proc/pid/*
|
||||
ProcMetrics struct {
|
||||
CpuTime float64
|
||||
ReadBytes uint64
|
||||
WriteBytes uint64
|
||||
ResidentBytes uint64
|
||||
VirtualBytes uint64
|
||||
}
|
||||
|
||||
ProcIdStatic struct {
|
||||
ProcId
|
||||
ProcStatic
|
||||
}
|
||||
|
||||
ProcInfo struct {
|
||||
ProcStatic
|
||||
ProcMetrics
|
||||
}
|
||||
|
||||
ProcIdInfo struct {
|
||||
ProcId
|
||||
ProcStatic
|
||||
ProcMetrics
|
||||
}
|
||||
|
||||
// Proc wraps the details of the underlying procfs-reading library.
|
||||
Proc interface {
|
||||
// GetPid() returns the POSIX PID (process id). They may be reused over time.
|
||||
GetPid() int
|
||||
// GetProcId() returns (pid,starttime), which can be considered a unique process id.
|
||||
// It may fail if the caller doesn't have permission to read /proc/<pid>/stat, or if
|
||||
// the process has disapeared.
|
||||
GetProcId() (ProcId, error)
|
||||
// GetStatic() returns various details read from files under /proc/<pid>/. Technically
|
||||
// name may not be static, but we'll pretend it is.
|
||||
// It may fail if the caller doesn't have permission to read those files, or if
|
||||
// the process has disapeared.
|
||||
GetStatic() (ProcStatic, error)
|
||||
// GetMetrics() returns various metrics read from files under /proc/<pid>/.
|
||||
// It may fail if the caller doesn't have permission to read those files, or if
|
||||
// the process has disapeared.
|
||||
GetMetrics() (ProcMetrics, error)
|
||||
}
|
||||
|
||||
// proc is a wrapper for procfs.Proc that caches results of some reads and implements Proc.
|
||||
proc struct {
|
||||
procfs.Proc
|
||||
procid *ProcId
|
||||
stat *procfs.ProcStat
|
||||
cmdline []string
|
||||
io *procfs.ProcIO
|
||||
bootTime int64
|
||||
}
|
||||
|
||||
procs interface {
|
||||
get(int) Proc
|
||||
length() int
|
||||
}
|
||||
|
||||
procfsprocs struct {
|
||||
Procs []procfs.Proc
|
||||
bootTime int64
|
||||
}
|
||||
|
||||
// ProcIter is an iterator over a sequence of procs.
|
||||
ProcIter interface {
|
||||
// Next returns true if the iterator is not exhausted.
|
||||
Next() bool
|
||||
// Close releases any resources the iterator uses.
|
||||
Close() error
|
||||
// The iterator satisfies the Proc interface.
|
||||
Proc
|
||||
}
|
||||
|
||||
// procIterator implements the ProcIter interface using procfs.
|
||||
procIterator struct {
|
||||
// procs is the list of Proc we're iterating over.
|
||||
procs
|
||||
// idx is the current iteration, i.e. it's an index into procs.
|
||||
idx int
|
||||
// err is set with an error when Next() fails. It is not affected by failures accessing
|
||||
// the current iteration variable, e.g. with GetProcId.
|
||||
err error
|
||||
// Proc is the current iteration variable, or nil if Next() has never been called or the
|
||||
// iterator is exhausted.
|
||||
Proc
|
||||
}
|
||||
|
||||
procIdInfos []ProcIdInfo
|
||||
)
|
||||
|
||||
func procInfoIter(ps ...ProcIdInfo) ProcIter {
|
||||
return &procIterator{procs: procIdInfos(ps), idx: -1}
|
||||
}
|
||||
|
||||
func Info(p Proc) (ProcIdInfo, error) {
|
||||
id, err := p.GetProcId()
|
||||
if err != nil {
|
||||
return ProcIdInfo{}, err
|
||||
}
|
||||
static, err := p.GetStatic()
|
||||
if err != nil {
|
||||
return ProcIdInfo{}, err
|
||||
}
|
||||
metrics, err := p.GetMetrics()
|
||||
if err != nil {
|
||||
return ProcIdInfo{}, err
|
||||
}
|
||||
return ProcIdInfo{id, static, metrics}, nil
|
||||
}
|
||||
|
||||
func (p procIdInfos) get(i int) Proc {
|
||||
return &p[i]
|
||||
}
|
||||
|
||||
func (p procIdInfos) length() int {
|
||||
return len(p)
|
||||
}
|
||||
|
||||
func (p ProcIdInfo) GetPid() int {
|
||||
return p.ProcId.Pid
|
||||
}
|
||||
|
||||
func (p ProcIdInfo) GetProcId() (ProcId, error) {
|
||||
return p.ProcId, nil
|
||||
}
|
||||
|
||||
func (p ProcIdInfo) GetStatic() (ProcStatic, error) {
|
||||
return p.ProcStatic, nil
|
||||
}
|
||||
|
||||
func (p ProcIdInfo) GetMetrics() (ProcMetrics, error) {
|
||||
return p.ProcMetrics, nil
|
||||
}
|
||||
|
||||
func (p procfsprocs) get(i int) Proc {
|
||||
return &proc{Proc: p.Procs[i], bootTime: p.bootTime}
|
||||
}
|
||||
|
||||
func (p procfsprocs) length() int {
|
||||
return len(p.Procs)
|
||||
}
|
||||
|
||||
func (p *proc) GetPid() int {
|
||||
return p.Proc.PID
|
||||
}
|
||||
|
||||
func (p *proc) GetStat() (procfs.ProcStat, error) {
|
||||
if p.stat == nil {
|
||||
stat, err := p.Proc.NewStat()
|
||||
if err != nil {
|
||||
return procfs.ProcStat{}, err
|
||||
}
|
||||
p.stat = &stat
|
||||
}
|
||||
|
||||
return *p.stat, nil
|
||||
}
|
||||
|
||||
func (p *proc) GetProcId() (ProcId, error) {
|
||||
if p.procid == nil {
|
||||
stat, err := p.GetStat()
|
||||
if err != nil {
|
||||
return ProcId{}, err
|
||||
}
|
||||
p.procid = &ProcId{Pid: p.GetPid(), StartTimeRel: stat.Starttime}
|
||||
}
|
||||
|
||||
return *p.procid, nil
|
||||
}
|
||||
|
||||
func (p *proc) GetCmdLine() ([]string, error) {
|
||||
if p.cmdline == nil {
|
||||
cmdline, err := p.Proc.CmdLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.cmdline = cmdline
|
||||
}
|
||||
return p.cmdline, nil
|
||||
}
|
||||
|
||||
func (p *proc) GetIo() (procfs.ProcIO, error) {
|
||||
if p.io == nil {
|
||||
io, err := p.Proc.NewIO()
|
||||
if err != nil {
|
||||
return procfs.ProcIO{}, err
|
||||
}
|
||||
p.io = &io
|
||||
}
|
||||
return *p.io, nil
|
||||
}
|
||||
|
||||
func (p proc) GetStatic() (ProcStatic, error) {
|
||||
cmdline, err := p.GetCmdLine()
|
||||
if err != nil {
|
||||
return ProcStatic{}, err
|
||||
}
|
||||
stat, err := p.GetStat()
|
||||
if err != nil {
|
||||
return ProcStatic{}, err
|
||||
}
|
||||
startTime := time.Unix(p.bootTime, 0)
|
||||
startTime = startTime.Add(time.Second / userHZ * time.Duration(stat.Starttime))
|
||||
return ProcStatic{
|
||||
Name: stat.Comm,
|
||||
Cmdline: cmdline,
|
||||
ParentPid: stat.PPID,
|
||||
StartTime: startTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p proc) GetMetrics() (ProcMetrics, error) {
|
||||
io, err := p.GetIo()
|
||||
if err != nil {
|
||||
return ProcMetrics{}, err
|
||||
}
|
||||
stat, err := p.GetStat()
|
||||
if err != nil {
|
||||
return ProcMetrics{}, err
|
||||
}
|
||||
return ProcMetrics{
|
||||
CpuTime: stat.CPUTime(),
|
||||
ReadBytes: io.ReadBytes,
|
||||
WriteBytes: io.WriteBytes,
|
||||
ResidentBytes: uint64(stat.ResidentMemory()),
|
||||
VirtualBytes: uint64(stat.VirtualMemory()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type FS struct {
|
||||
procfs.FS
|
||||
BootTime int64
|
||||
}
|
||||
|
||||
// See https://github.com/prometheus/procfs/blob/master/proc_stat.go for details on userHZ.
|
||||
const userHZ = 100
|
||||
|
||||
// NewFS returns a new FS mounted under the given mountPoint. It will error
|
||||
// if the mount point can't be read.
|
||||
func NewFS(mountPoint string) (*FS, error) {
|
||||
fs, err := procfs.NewFS(mountPoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stat, err := fs.NewStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FS{fs, stat.BootTime}, nil
|
||||
}
|
||||
|
||||
func (fs *FS) AllProcs() ProcIter {
|
||||
procs, err := fs.FS.AllProcs()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading procs: %v", err)
|
||||
}
|
||||
return &procIterator{procs: procfsprocs{procs, fs.BootTime}, err: err, idx: -1}
|
||||
}
|
||||
|
||||
func (pi *procIterator) Next() bool {
|
||||
pi.idx++
|
||||
if pi.idx < pi.procs.length() {
|
||||
pi.Proc = pi.procs.get(pi.idx)
|
||||
} else {
|
||||
pi.Proc = nil
|
||||
}
|
||||
return pi.idx < pi.procs.length()
|
||||
}
|
||||
|
||||
func (pi *procIterator) Close() error {
|
||||
pi.Next()
|
||||
pi.procs = nil
|
||||
pi.Proc = nil
|
||||
return pi.err
|
||||
}
|
||||
160
vendor/github.com/ncabatoff/process-exporter/proc/tracker.go
generated
vendored
Normal file
160
vendor/github.com/ncabatoff/process-exporter/proc/tracker.go
generated
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
package proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
Counts struct {
|
||||
Cpu float64
|
||||
ReadBytes uint64
|
||||
WriteBytes uint64
|
||||
}
|
||||
|
||||
Memory struct {
|
||||
Resident uint64
|
||||
Virtual uint64
|
||||
}
|
||||
|
||||
// Tracker tracks processes and records metrics.
|
||||
Tracker struct {
|
||||
// Tracked holds the processes are being monitored. Processes
|
||||
// may be blacklisted such that they no longer get tracked by
|
||||
// setting their value in the Tracked map to nil.
|
||||
Tracked map[ProcId]*TrackedProc
|
||||
// ProcIds is a map from pid to ProcId. This is a convenience
|
||||
// to allow finding the Tracked entry of a parent process.
|
||||
ProcIds map[int]ProcId
|
||||
}
|
||||
|
||||
// TrackedProc accumulates metrics for a process, as well as
|
||||
// remembering an optional GroupName tag associated with it.
|
||||
TrackedProc struct {
|
||||
// lastUpdate is used internally during the update cycle to find which procs have exited
|
||||
lastUpdate time.Time
|
||||
// info is the most recently obtained info for this proc
|
||||
info ProcInfo
|
||||
// accum is the total CPU and IO accrued since we started tracking this proc
|
||||
accum Counts
|
||||
// lastaccum is the CPU and IO accrued in the last Update()
|
||||
lastaccum Counts
|
||||
// GroupName is an optional tag for this proc.
|
||||
GroupName string
|
||||
}
|
||||
)
|
||||
|
||||
func (tp *TrackedProc) GetName() string {
|
||||
return tp.info.Name
|
||||
}
|
||||
|
||||
func (tp *TrackedProc) GetCmdLine() []string {
|
||||
return tp.info.Cmdline
|
||||
}
|
||||
|
||||
func (tp *TrackedProc) GetStats() (aggregate, latest Counts, mem Memory, start time.Time) {
|
||||
return tp.accum, tp.lastaccum, Memory{Resident: tp.info.ResidentBytes, Virtual: tp.info.VirtualBytes}, tp.info.StartTime
|
||||
}
|
||||
|
||||
func NewTracker() *Tracker {
|
||||
return &Tracker{Tracked: make(map[ProcId]*TrackedProc), ProcIds: make(map[int]ProcId)}
|
||||
}
|
||||
|
||||
func (t *Tracker) Track(groupName string, idinfo ProcIdInfo) {
|
||||
info := ProcInfo{idinfo.ProcStatic, idinfo.ProcMetrics}
|
||||
t.Tracked[idinfo.ProcId] = &TrackedProc{GroupName: groupName, info: info}
|
||||
}
|
||||
|
||||
func (t *Tracker) Ignore(id ProcId) {
|
||||
t.Tracked[id] = nil
|
||||
}
|
||||
|
||||
// Scan procs and update metrics for those which are tracked. Processes that have gone
|
||||
// away get removed from the Tracked map. New processes are returned, along with the count
|
||||
// of permission errors.
|
||||
func (t *Tracker) Update(procs ProcIter) ([]ProcIdInfo, int, error) {
|
||||
now := time.Now()
|
||||
var newProcs []ProcIdInfo
|
||||
var permissionErrors int
|
||||
|
||||
for procs.Next() {
|
||||
procId, err := procs.GetProcId()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
last, known := t.Tracked[procId]
|
||||
|
||||
// Are we ignoring this proc?
|
||||
if known && last == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO if just the io file is unreadable, should we still return the other metrics?
|
||||
metrics, err := procs.GetMetrics()
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
permissionErrors++
|
||||
t.Ignore(procId)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if known {
|
||||
var newaccum, lastaccum Counts
|
||||
dcpu := metrics.CpuTime - last.info.CpuTime
|
||||
drbytes := metrics.ReadBytes - last.info.ReadBytes
|
||||
dwbytes := metrics.WriteBytes - last.info.WriteBytes
|
||||
|
||||
lastaccum = Counts{Cpu: dcpu, ReadBytes: drbytes, WriteBytes: dwbytes}
|
||||
newaccum = Counts{
|
||||
Cpu: last.accum.Cpu + lastaccum.Cpu,
|
||||
ReadBytes: last.accum.ReadBytes + lastaccum.ReadBytes,
|
||||
WriteBytes: last.accum.WriteBytes + lastaccum.WriteBytes,
|
||||
}
|
||||
|
||||
last.info.ProcMetrics = metrics
|
||||
last.lastUpdate = now
|
||||
last.accum = newaccum
|
||||
last.lastaccum = lastaccum
|
||||
} else {
|
||||
static, err := procs.GetStatic()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
newProcs = append(newProcs, ProcIdInfo{procId, static, metrics})
|
||||
|
||||
// Is this a new process with the same pid as one we already know?
|
||||
if oldProcId, ok := t.ProcIds[procId.Pid]; ok {
|
||||
// Delete it from known, otherwise the cleanup below will remove the
|
||||
// ProcIds entry we're about to create
|
||||
delete(t.Tracked, oldProcId)
|
||||
}
|
||||
t.ProcIds[procId.Pid] = procId
|
||||
}
|
||||
|
||||
}
|
||||
err := procs.Close()
|
||||
if err != nil {
|
||||
return nil, permissionErrors, fmt.Errorf("Error reading procs: %v", err)
|
||||
}
|
||||
|
||||
// Rather than allocating a new map each time to detect procs that have
|
||||
// disappeared, we bump the last update time on those that are still
|
||||
// present. Then as a second pass we traverse the map looking for
|
||||
// stale procs and removing them.
|
||||
for procId, pinfo := range t.Tracked {
|
||||
if pinfo == nil {
|
||||
// TODO is this a bug? we're not tracking the proc so we don't see it go away so ProcIds
|
||||
// and Tracked are leaking?
|
||||
continue
|
||||
}
|
||||
if pinfo.lastUpdate != now {
|
||||
delete(t.Tracked, procId)
|
||||
delete(t.ProcIds, procId.Pid)
|
||||
}
|
||||
}
|
||||
|
||||
return newProcs, permissionErrors, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue