Skip to content

Commit 410fb18

Browse files
committed
inotify_instance: add commandline arguments for command and user parsing
on systems with many short lived processes, the cardinality of the rendered metrics can become quite bad. this change adds commandline arguments to the script to support filtering of processes based on the process command or user. the script logic remains backwards compatible as it continues to emit metrics for all accessible process information if not filters are provided. Signed-off-by: Gordon Bleux <[email protected]>
1 parent b36a2ea commit 410fb18

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+512
-113
lines changed

Diff for: .gitignore

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
__pypackages__/
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
htmlcov/
12+
cover/
13+
target/
14+
eggs/
15+
.eggs/
16+
lib/
17+
lib64/
18+
parts/
19+
sdist/
20+
var/
21+
wheels/
22+
share/python-wheels/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
MANIFEST
27+
*.manifest
28+
*.spec
29+
.tox/
30+
.nox/
31+
.coverage
32+
.coverage.*
33+
.cache
34+
nosetests.xml
35+
coverage.xml
36+
*.cover
37+
*.py,cover
38+
.hypothesis/
39+
.pytest_cache/
40+
.pybuilder/
41+
.env
42+
.venv
43+

Diff for: inotify-instances

-113
This file was deleted.

Diff for: inotify_instances.py

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Expose Linux inotify(7) instance resource consumption.
5+
6+
Operational properties:
7+
8+
- This script may be invoked as an unprivileged user; in this case, metrics
9+
will only be exposed for processes owned by that unprivileged user.
10+
11+
- No metrics will be exposed for processes that do not hold any inotify fds.
12+
13+
Requires Python 3.5 or later.
14+
"""
15+
16+
import argparse
17+
import collections
18+
import os
19+
import re
20+
import sys
21+
22+
from prometheus_client import CollectorRegistry, Gauge, generate_latest
23+
24+
__doc__ = "Exponse Linux Kernel inode information as Prometheus metrics."
25+
_NotifyTarget = "anon_inode:inotify"
26+
_Proc = "/proc"
27+
_CommFile = "cmdline"
28+
_StatsFile = "status"
29+
_DescriptorDir = "fd"
30+
31+
32+
class Error(Exception):
33+
pass
34+
35+
36+
class _PIDGoneError(Error):
37+
pass
38+
39+
40+
_Process = collections.namedtuple(
41+
"Process", ["pid", "uid", "command", "inotify_instances"]
42+
)
43+
44+
45+
def _read_bytes(name):
46+
with open(name, mode="rb") as f:
47+
return f.read()
48+
49+
50+
def _pids(proc=_Proc):
51+
for n in os.listdir(proc):
52+
if not n.isdigit():
53+
continue
54+
yield int(n)
55+
56+
57+
def _pid_uid(status):
58+
try:
59+
s = os.stat(status)
60+
except FileNotFoundError:
61+
raise _PIDGoneError()
62+
return s.st_uid
63+
64+
65+
def _pid_command(cmdline):
66+
# Avoid GNU ps(1) for it truncates comm.
67+
# https://bugs.launchpad.net/ubuntu/+source/procps/+bug/295876/comments/3
68+
try:
69+
cmdline = _read_bytes(cmdline)
70+
except FileNotFoundError:
71+
raise _PIDGoneError()
72+
73+
if not len(cmdline):
74+
return "<zombie>"
75+
76+
try:
77+
prog = cmdline[0 : cmdline.index(0x00)]
78+
except ValueError:
79+
prog = cmdline
80+
return os.path.basename(prog).decode(encoding="ascii", errors="surrogateescape")
81+
82+
83+
def _pid_inotify_instances(descriptors):
84+
instances = 0
85+
try:
86+
for fd in os.listdir(descriptors):
87+
try:
88+
target = os.readlink(os.path.join(descriptors, fd))
89+
except FileNotFoundError:
90+
continue
91+
if target == _NotifyTarget:
92+
instances += 1
93+
except FileNotFoundError:
94+
raise _PIDGoneError()
95+
return instances
96+
97+
98+
def _get_processes(proc=_Proc):
99+
for p in _pids(proc):
100+
try:
101+
pid = str(p)
102+
status = os.path.join(proc, pid, _StatsFile)
103+
cmdline = os.path.join(proc, pid, _CommFile)
104+
descriptors = os.path.join(proc, pid, _DescriptorDir)
105+
yield _Process(
106+
p,
107+
_pid_uid(status),
108+
_pid_command(cmdline),
109+
_pid_inotify_instances(descriptors),
110+
)
111+
except (PermissionError, _PIDGoneError):
112+
continue
113+
114+
115+
def _generate_metrics(command=None, user=-1, proc=_Proc):
116+
registry = CollectorRegistry()
117+
namespace = "inotify"
118+
119+
g = Gauge(
120+
"instances",
121+
"Total number of inotify instances held open by a process.",
122+
["pid", "uid", "command"],
123+
namespace=namespace,
124+
registry=registry,
125+
)
126+
127+
for proc in _get_processes(proc):
128+
if proc.inotify_instances <= 0:
129+
continue
130+
elif user >= 0 and user != proc.uid:
131+
continue
132+
elif command and not command.match(proc.command):
133+
continue
134+
else:
135+
g.labels(proc.pid, proc.uid, proc.command).set(proc.inotify_instances)
136+
137+
return generate_latest(registry).decode()
138+
139+
140+
def main(argv=[__name__]):
141+
parser = argparse.ArgumentParser(
142+
prog=os.path.basename(argv[0]),
143+
exit_on_error=False,
144+
description=__doc__,
145+
)
146+
parser.add_argument(
147+
"-c",
148+
"--command",
149+
default=".+",
150+
type=re.compile,
151+
dest="command",
152+
metavar="REGEX",
153+
help="Filter metrics based on the process command",
154+
)
155+
parser.add_argument(
156+
"-u",
157+
"--user",
158+
default=-1,
159+
type=int,
160+
dest="user",
161+
metavar="UID",
162+
help="Filter metrics based on the process user",
163+
)
164+
165+
try:
166+
args = parser.parse_args(argv[1:])
167+
except argparse.ArgumentError as err:
168+
print(err, file=sys.stderr)
169+
return 1
170+
171+
print(_generate_metrics(args.command, args.user, _Proc), end="")
172+
return 0
173+
174+
175+
if __name__ == "__main__":
176+
sys.exit(main(sys.argv))

Diff for: mock/fixtures/inotify_instances/1/cmdline

11 Bytes
Binary file not shown.

Diff for: mock/fixtures/inotify_instances/1/fd/0

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1/fd/1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1/fd/12

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
anon_inode:inotify

Diff for: mock/fixtures/inotify_instances/1/fd/123

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
anon_inode:inotify

Diff for: mock/fixtures/inotify_instances/1/fd/324

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
anon_inode:inotify

Diff for: mock/fixtures/inotify_instances/1/fd/5

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
anon_inode:inotify

Diff for: mock/fixtures/inotify_instances/1/fd/82

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
anon_inode:inotify

Diff for: mock/fixtures/inotify_instances/1/fd/90

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
anon_inode:inotify

Diff for: mock/fixtures/inotify_instances/1/status

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Uid: 0 0 0 0
2+
Gid: 0 0 0 0

Diff for: mock/fixtures/inotify_instances/1016/cmdline

41 Bytes
Binary file not shown.

Diff for: mock/fixtures/inotify_instances/1016/fd/0

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1016/fd/1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1016/status

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Uid: 0 0 0 0
2+
Gid: 0 0 0 0

Diff for: mock/fixtures/inotify_instances/1017/cmdline

140 Bytes
Binary file not shown.

Diff for: mock/fixtures/inotify_instances/1017/fd/0

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1017/fd/1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1017/status

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Uid: 0 0 0 0
2+
Gid: 0 0 0 0

Diff for: mock/fixtures/inotify_instances/1024/cmdline

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/usr/local/bin/rsyslogd

Diff for: mock/fixtures/inotify_instances/1024/fd/0

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1024/fd/1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1024/status

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Uid: 0 0 0 0
2+
Gid: 0 0 0 0

Diff for: mock/fixtures/inotify_instances/1025/cmdline

53 Bytes
Binary file not shown.

Diff for: mock/fixtures/inotify_instances/1025/fd/0

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

Diff for: mock/fixtures/inotify_instances/1025/fd/1

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/dev/null

0 commit comments

Comments
 (0)