mirror of
				https://github.com/prometheus/node_exporter.git
				synced 2025-08-20 18:33:52 -07:00 
			
		
		
		
	
		
			
	
	
		
			142 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			142 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
								 | 
							
								#!/usr/bin/env python3
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								Expose Linux inotify(7) instance resource consumption.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Operational properties:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  - This script may be invoked as an unprivileged user; in this case, metrics
							 | 
						||
| 
								 | 
							
								    will only be exposed for processes owned by that unprivileged user.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  - No metrics will be exposed for processes that do not hold any inotify fds.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Requires Python 3.5 or later.
							 | 
						||
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import collections
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Error(Exception):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class _PIDGoneError(Error):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_Process = collections.namedtuple(
							 | 
						||
| 
								 | 
							
								    "Process", ["pid", "uid", "command", "inotify_instances"])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _read_bytes(name):
							 | 
						||
| 
								 | 
							
								    with open(name, mode='rb') as f:
							 | 
						||
| 
								 | 
							
								        return f.read()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _pids():
							 | 
						||
| 
								 | 
							
								    for n in os.listdir("/proc"):
							 | 
						||
| 
								 | 
							
								        if not n.isdigit():
							 | 
						||
| 
								 | 
							
								            continue
							 | 
						||
| 
								 | 
							
								        yield int(n)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _pid_uid(pid):
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        s = os.stat("/proc/{}".format(pid))
							 | 
						||
| 
								 | 
							
								    except FileNotFoundError:
							 | 
						||
| 
								 | 
							
								        raise _PIDGoneError()
							 | 
						||
| 
								 | 
							
								    return s.st_uid
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _pid_command(pid):
							 | 
						||
| 
								 | 
							
								    # Avoid GNU ps(1) for it truncates comm.
							 | 
						||
| 
								 | 
							
								    # https://bugs.launchpad.net/ubuntu/+source/procps/+bug/295876/comments/3
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        cmdline = _read_bytes("/proc/{}/cmdline".format(pid))
							 | 
						||
| 
								 | 
							
								    except FileNotFoundError:
							 | 
						||
| 
								 | 
							
								        raise _PIDGoneError()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if not len(cmdline):
							 | 
						||
| 
								 | 
							
								        return "<zombie>"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        prog = cmdline[0:cmdline.index(0x00)]
							 | 
						||
| 
								 | 
							
								    except ValueError:
							 | 
						||
| 
								 | 
							
								        prog = cmdline
							 | 
						||
| 
								 | 
							
								    return os.path.basename(prog).decode(encoding="ascii",
							 | 
						||
| 
								 | 
							
								                                         errors="surrogateescape")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _pid_inotify_instances(pid):
							 | 
						||
| 
								 | 
							
								    instances = 0
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        for fd in os.listdir("/proc/{}/fd".format(pid)):
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                target = os.readlink("/proc/{}/fd/{}".format(pid, fd))
							 | 
						||
| 
								 | 
							
								            except FileNotFoundError:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            if target == "anon_inode:inotify":
							 | 
						||
| 
								 | 
							
								                instances += 1
							 | 
						||
| 
								 | 
							
								    except FileNotFoundError:
							 | 
						||
| 
								 | 
							
								        raise _PIDGoneError()
							 | 
						||
| 
								 | 
							
								    return instances
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _get_processes():
							 | 
						||
| 
								 | 
							
								    for p in _pids():
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            yield _Process(p, _pid_uid(p), _pid_command(p),
							 | 
						||
| 
								 | 
							
								                           _pid_inotify_instances(p))
							 | 
						||
| 
								 | 
							
								        except (PermissionError, _PIDGoneError):
							 | 
						||
| 
								 | 
							
								            continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _get_processes_nontrivial():
							 | 
						||
| 
								 | 
							
								    return (p for p in _get_processes() if p.inotify_instances > 0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _format_gauge_metric(metric_name, metric_help, samples,
							 | 
						||
| 
								 | 
							
								                         value_func, tags_func=None, stream=sys.stdout):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _println(*args, **kwargs):
							 | 
						||
| 
								 | 
							
								        if "file" not in kwargs:
							 | 
						||
| 
								 | 
							
								            kwargs["file"] = stream
							 | 
						||
| 
								 | 
							
								        print(*args, **kwargs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _print(*args, **kwargs):
							 | 
						||
| 
								 | 
							
								        if "end" not in kwargs:
							 | 
						||
| 
								 | 
							
								            kwargs["end"] = ""
							 | 
						||
| 
								 | 
							
								        _println(*args, **kwargs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _println("# HELP {} {}".format(metric_name, metric_help))
							 | 
						||
| 
								 | 
							
								    _println("# TYPE {} gauge".format(metric_name))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for s in samples:
							 | 
						||
| 
								 | 
							
								        value = value_func(s)
							 | 
						||
| 
								 | 
							
								        tags = None
							 | 
						||
| 
								 | 
							
								        if tags_func:
							 | 
						||
| 
								 | 
							
								            tags = tags_func(s)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        _print(metric_name)
							 | 
						||
| 
								 | 
							
								        if tags:
							 | 
						||
| 
								 | 
							
								            _print("{")
							 | 
						||
| 
								 | 
							
								            _print(",".join(["{}=\"{}\"".format(k, v) for k, v in tags]))
							 | 
						||
| 
								 | 
							
								            _print("}")
							 | 
						||
| 
								 | 
							
								        _print(" ")
							 | 
						||
| 
								 | 
							
								        _println(value)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def main(args_unused=None):
							 | 
						||
| 
								 | 
							
								    _format_gauge_metric(
							 | 
						||
| 
								 | 
							
								        "inotify_instances",
							 | 
						||
| 
								 | 
							
								        "Total number of inotify instances held open by a process.",
							 | 
						||
| 
								 | 
							
								        _get_processes_nontrivial(),
							 | 
						||
| 
								 | 
							
								        lambda s: s.inotify_instances,
							 | 
						||
| 
								 | 
							
								        lambda s: [("pid", s.pid), ("uid", s.uid), ("command", s.command)])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if __name__ == "__main__":
							 | 
						||
| 
								 | 
							
								    sys.exit(main(sys.argv))
							 |