Skip to content

Commit c5f4190

Browse files
carlpettbeorn7
authored andcommitted
Implement process collector for Windows (#596)
* Implement process collector for Windows Signed-off-by: Calle Pettersson <[email protected]>
1 parent 1335ef4 commit c5f4190

File tree

6 files changed

+250
-43
lines changed

6 files changed

+250
-43
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ require (
1010
github.com/prometheus/common v0.4.1
1111
github.com/prometheus/procfs v0.0.2
1212
github.com/stretchr/testify v1.3.0 // indirect
13+
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5
1314
)

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
5858
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
5959
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
6060
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
61+
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5 h1:mzjBh+S5frKOsOBobWIMAbXavqjmgO17k/2puhcFR94=
6162
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
6263
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
6364
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

prometheus/process_collector.go

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ package prometheus
1616
import (
1717
"errors"
1818
"os"
19-
20-
"github.com/prometheus/procfs"
2119
)
2220

2321
type processCollector struct {
@@ -126,7 +124,7 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
126124
}
127125

128126
// Set up process metric collection if supported by the runtime.
129-
if _, err := procfs.NewDefaultFS(); err == nil {
127+
if canCollectProcess() {
130128
c.collectFn = c.processCollect
131129
} else {
132130
c.collectFn = func(ch chan<- Metric) {
@@ -153,46 +151,6 @@ func (c *processCollector) Collect(ch chan<- Metric) {
153151
c.collectFn(ch)
154152
}
155153

156-
func (c *processCollector) processCollect(ch chan<- Metric) {
157-
pid, err := c.pidFn()
158-
if err != nil {
159-
c.reportError(ch, nil, err)
160-
return
161-
}
162-
163-
p, err := procfs.NewProc(pid)
164-
if err != nil {
165-
c.reportError(ch, nil, err)
166-
return
167-
}
168-
169-
if stat, err := p.Stat(); err == nil {
170-
ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime())
171-
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory()))
172-
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory()))
173-
if startTime, err := stat.StartTime(); err == nil {
174-
ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
175-
} else {
176-
c.reportError(ch, c.startTime, err)
177-
}
178-
} else {
179-
c.reportError(ch, nil, err)
180-
}
181-
182-
if fds, err := p.FileDescriptorsLen(); err == nil {
183-
ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds))
184-
} else {
185-
c.reportError(ch, c.openFDs, err)
186-
}
187-
188-
if limits, err := p.Limits(); err == nil {
189-
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
190-
ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace))
191-
} else {
192-
c.reportError(ch, nil, err)
193-
}
194-
}
195-
196154
func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) {
197155
if !c.reportErrors {
198156
return
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2019 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
// +build !windows
15+
16+
package prometheus
17+
18+
import (
19+
"github.com/prometheus/procfs"
20+
)
21+
22+
func canCollectProcess() bool {
23+
_, err := procfs.NewDefaultFS()
24+
return err == nil
25+
}
26+
27+
func (c *processCollector) processCollect(ch chan<- Metric) {
28+
pid, err := c.pidFn()
29+
if err != nil {
30+
c.reportError(ch, nil, err)
31+
return
32+
}
33+
34+
p, err := procfs.NewProc(pid)
35+
if err != nil {
36+
c.reportError(ch, nil, err)
37+
return
38+
}
39+
40+
if stat, err := p.Stat(); err == nil {
41+
ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime())
42+
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory()))
43+
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory()))
44+
if startTime, err := stat.StartTime(); err == nil {
45+
ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
46+
} else {
47+
c.reportError(ch, c.startTime, err)
48+
}
49+
} else {
50+
c.reportError(ch, nil, err)
51+
}
52+
53+
if fds, err := p.FileDescriptorsLen(); err == nil {
54+
ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds))
55+
} else {
56+
c.reportError(ch, c.openFDs, err)
57+
}
58+
59+
if limits, err := p.Limits(); err == nil {
60+
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
61+
ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace))
62+
} else {
63+
c.reportError(ch, nil, err)
64+
}
65+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2019 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package prometheus
15+
16+
import (
17+
"syscall"
18+
"unsafe"
19+
20+
"golang.org/x/sys/windows"
21+
)
22+
23+
func canCollectProcess() bool {
24+
return true
25+
}
26+
27+
var (
28+
modpsapi = syscall.NewLazyDLL("psapi.dll")
29+
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
30+
31+
procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
32+
procGetProcessHandleCount = modkernel32.NewProc("GetProcessHandleCount")
33+
)
34+
35+
type processMemoryCounters struct {
36+
// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-_process_memory_counters_ex
37+
_ uint32
38+
PageFaultCount uint32
39+
PeakWorkingSetSize uint64
40+
WorkingSetSize uint64
41+
QuotaPeakPagedPoolUsage uint64
42+
QuotaPagedPoolUsage uint64
43+
QuotaPeakNonPagedPoolUsage uint64
44+
QuotaNonPagedPoolUsage uint64
45+
PagefileUsage uint64
46+
PeakPagefileUsage uint64
47+
PrivateUsage uint64
48+
}
49+
50+
func getProcessMemoryInfo(handle windows.Handle) (processMemoryCounters, error) {
51+
mem := processMemoryCounters{}
52+
r1, _, err := procGetProcessMemoryInfo.Call(
53+
uintptr(handle),
54+
uintptr(unsafe.Pointer(&mem)),
55+
uintptr(unsafe.Sizeof(mem)),
56+
)
57+
if r1 != 1 {
58+
return mem, err
59+
} else {
60+
return mem, nil
61+
}
62+
}
63+
64+
func getProcessHandleCount(handle windows.Handle) (uint32, error) {
65+
var count uint32
66+
r1, _, err := procGetProcessHandleCount.Call(
67+
uintptr(handle),
68+
uintptr(unsafe.Pointer(&count)),
69+
)
70+
if r1 != 1 {
71+
return 0, err
72+
} else {
73+
return count, nil
74+
}
75+
}
76+
77+
func (c *processCollector) processCollect(ch chan<- Metric) {
78+
h, err := windows.GetCurrentProcess()
79+
if err != nil {
80+
c.reportError(ch, nil, err)
81+
return
82+
}
83+
84+
var startTime, exitTime, kernelTime, userTime windows.Filetime
85+
err = windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime)
86+
if err != nil {
87+
c.reportError(ch, nil, err)
88+
return
89+
}
90+
ch <- MustNewConstMetric(c.startTime, GaugeValue, float64(startTime.Nanoseconds()/1e9))
91+
ch <- MustNewConstMetric(c.cpuTotal, CounterValue, fileTimeToSeconds(kernelTime)+fileTimeToSeconds(userTime))
92+
93+
mem, err := getProcessMemoryInfo(h)
94+
if err != nil {
95+
c.reportError(ch, nil, err)
96+
return
97+
}
98+
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(mem.PrivateUsage))
99+
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(mem.WorkingSetSize))
100+
101+
handles, err := getProcessHandleCount(h)
102+
if err != nil {
103+
c.reportError(ch, nil, err)
104+
return
105+
}
106+
ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(handles))
107+
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(16*1024*1024)) // Windows has a hard-coded max limit, not per-process.
108+
}
109+
110+
func fileTimeToSeconds(ft windows.Filetime) float64 {
111+
return float64(uint64(ft.HighDateTime)<<32+uint64(ft.LowDateTime)) / 1e7
112+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2019 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package prometheus
15+
16+
import (
17+
"bytes"
18+
"os"
19+
"regexp"
20+
"testing"
21+
22+
"github.com/prometheus/common/expfmt"
23+
)
24+
25+
func TestWindowsProcessCollector(t *testing.T) {
26+
registry := NewRegistry()
27+
if err := registry.Register(NewProcessCollector(ProcessCollectorOpts{})); err != nil {
28+
t.Fatal(err)
29+
}
30+
if err := registry.Register(NewProcessCollector(ProcessCollectorOpts{
31+
PidFn: func() (int, error) { return os.Getpid(), nil },
32+
Namespace: "foobar",
33+
ReportErrors: true, // No errors expected, just to see if none are reported.
34+
})); err != nil {
35+
t.Fatal(err)
36+
}
37+
38+
mfs, err := registry.Gather()
39+
if err != nil {
40+
t.Fatal(err)
41+
}
42+
43+
var buf bytes.Buffer
44+
for _, mf := range mfs {
45+
if _, err := expfmt.MetricFamilyToText(&buf, mf); err != nil {
46+
t.Fatal(err)
47+
}
48+
}
49+
50+
for _, re := range []*regexp.Regexp{
51+
regexp.MustCompile("\nprocess_cpu_seconds_total [0-9]"),
52+
regexp.MustCompile("\nprocess_max_fds [1-9]"),
53+
regexp.MustCompile("\nprocess_open_fds [1-9]"),
54+
regexp.MustCompile("\nprocess_virtual_memory_max_bytes (-1|[1-9])"),
55+
regexp.MustCompile("\nprocess_virtual_memory_bytes [1-9]"),
56+
regexp.MustCompile("\nprocess_resident_memory_bytes [1-9]"),
57+
regexp.MustCompile("\nprocess_start_time_seconds [0-9.]{10,}"),
58+
regexp.MustCompile("\nfoobar_process_cpu_seconds_total [0-9]"),
59+
regexp.MustCompile("\nfoobar_process_max_fds [1-9]"),
60+
regexp.MustCompile("\nfoobar_process_open_fds [1-9]"),
61+
regexp.MustCompile("\nfoobar_process_virtual_memory_max_bytes (-1|[1-9])"),
62+
regexp.MustCompile("\nfoobar_process_virtual_memory_bytes [1-9]"),
63+
regexp.MustCompile("\nfoobar_process_resident_memory_bytes [1-9]"),
64+
regexp.MustCompile("\nfoobar_process_start_time_seconds [0-9.]{10,}"),
65+
} {
66+
if !re.Match(buf.Bytes()) {
67+
t.Errorf("want body to match %s\n%s", re, buf.String())
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)