Merge 1da6c8e34b
into f2080448e1
This commit is contained in:
commit
1197417267
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
|
@ -0,0 +1,322 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Munin plugin for monitoring counters in nftables. For more information on
|
||||
nftables see https://wiki.nftables.org/wiki-nftables/index.php/Counters
|
||||
|
||||
=head1 NAME
|
||||
|
||||
nft_counters - Munin Plugin for monitoring counters in nftables
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Plugin reads counters [1] from nftables and shows the associated values in bytes
|
||||
and packets. Which counters and/or values are to be shown can be configured
|
||||
(see L<below|"CONFIGURATION">).
|
||||
|
||||
=head1 REQUIREMENTS
|
||||
|
||||
Plugin runs on systems with nftables installed. It makes use of the excellent
|
||||
pymunin module, so that needs to be installed as well (see L<below|"ACKNOWLEDGEMENT">).
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
Since reading nftables needs root permissions, so does this plugin. That makes
|
||||
the 'user root' setting in the configuration file mandatory.
|
||||
|
||||
To further tune what should be graphed or not, you can adjust the configuration
|
||||
usually found in /etc/munin/plugin-conf.d/nft_counters:
|
||||
|
||||
[nft_counters]
|
||||
user root
|
||||
|
||||
env.counters_exclude counter_one,counter_two
|
||||
env.counters_include counter_this,counter_that
|
||||
env.count_only [bytes | packets]
|
||||
|
||||
=head2 env.counters_exclude
|
||||
|
||||
Exclude counters from graph. Comma separated list of counters, as known to
|
||||
nftables (see 'nft list counters'), to exclude from graphing.
|
||||
|
||||
=head2 env.counters_include
|
||||
|
||||
Include counters in graph, I<exclusively>. Comma separated list of counters,
|
||||
as known to nftables (see 'nft list counters'), to include in graphing.
|
||||
B<If this doesn't match any counter, nothing will be graphed.>
|
||||
|
||||
=head2 env.count_only
|
||||
|
||||
Show values only in bytes or packets. Default both counters are shown.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
=head2 {fieldname}.info
|
||||
|
||||
The {fieldname}.info should show the comment associated with the counter.
|
||||
However, the JSON output of 'nft' does not contain the comment attribute. So,
|
||||
for now, the plugin uses the counter name for {fieldname}.info. There is an
|
||||
open L<bug|https://bugzilla.netfilter.org/show_bug.cgi?id=1611> upstream.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Written and Blessed by (Holy) Penguinpee <L<devel@penguinpee.nl>>
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
GPLv3
|
||||
|
||||
=head1 ACKNOWLEDGEMENT
|
||||
|
||||
This plugin makes use of the excellent pymunin [2] module by Ali Onur Uyar,
|
||||
adapted to Python3 [3] and available on PyPI as PyMunin3 [4].
|
||||
|
||||
The implementation of nftables interaction is based on the very helpful
|
||||
examples [5] provided by Arturo Borrero Gonzalez.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over
|
||||
|
||||
=item [1] L<https://wiki.nftables.org/wiki-nftables/index.php/Counters>
|
||||
|
||||
=item [2] L<https://github.com/aouyar/PyMunin>
|
||||
|
||||
=item [3] L<https://github.com/penguinpee/PyMunin3>
|
||||
|
||||
=item [4] L<https://pypi.org/project/PyMunin3>
|
||||
|
||||
=item [5] L<https://github.com/aborrero/python-nftables-tutorial>
|
||||
|
||||
=back
|
||||
|
||||
=head1 MAGIC MARKERS
|
||||
|
||||
#%# family=auto
|
||||
#%# capabilities=autoconf
|
||||
|
||||
=cut
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from nftables import Nftables
|
||||
from nftables import json
|
||||
except Exception as err:
|
||||
print("Unable to load nftables module.")
|
||||
sys.exit(err)
|
||||
|
||||
try:
|
||||
from pymunin import MuninPlugin, MuninGraph, muninMain
|
||||
except Exception as err:
|
||||
print("Unable to load PyMunin module.")
|
||||
sys.exit(err)
|
||||
|
||||
|
||||
def _find_objects(ruleset, type):
|
||||
# isn't this pure python?
|
||||
return [o[type] for o in ruleset if type in o]
|
||||
|
||||
|
||||
def nft_cmd(nftlib, cmd):
|
||||
rc, output, error = nftlib.cmd(cmd)
|
||||
if rc != 0:
|
||||
# do proper error handling here, exceptions etc
|
||||
raise RuntimeError("Error running cmd 'nft {}'".format(cmd))
|
||||
|
||||
if len(output) == 0:
|
||||
# more error control
|
||||
raise RuntimeError("ERROR: no output from libnftables")
|
||||
|
||||
# transform the libnftables JSON output into generic python data structures
|
||||
ruleset = json.loads(output)["nftables"]
|
||||
|
||||
# validate we understand the libnftables JSON schema version.
|
||||
# if the schema bumps version, this program might require updates
|
||||
for metainfo in _find_objects(ruleset, "metainfo"):
|
||||
if metainfo["json_schema_version"] > 1:
|
||||
print("WARNING: we might not understand the JSON produced by libnftables")
|
||||
|
||||
return ruleset
|
||||
|
||||
|
||||
def getCounters():
|
||||
|
||||
nft = Nftables()
|
||||
nft.set_json_output(True)
|
||||
nft.set_handle_output(True)
|
||||
|
||||
ruleset = nft_cmd(nft, "list counters")
|
||||
counters = _find_objects(ruleset, "counter")
|
||||
|
||||
if len(counters) > 0:
|
||||
return counters
|
||||
else:
|
||||
raise ValueError("No counters in nftables")
|
||||
|
||||
|
||||
class MuninNftCountersPlugin(MuninPlugin):
|
||||
|
||||
"""
|
||||
Munin Plugin for nftables counters
|
||||
"""
|
||||
|
||||
plugin_name = "nft_counters"
|
||||
isMultigraph = True
|
||||
|
||||
def __init__(self, argv=(), env=None, debug=False):
|
||||
|
||||
"""
|
||||
|
||||
Initialize Munin Plugin
|
||||
|
||||
Parameters
|
||||
----------
|
||||
argv : TYPE, optional
|
||||
List of commandline arguments. The default is ().
|
||||
env : TYPE, optional
|
||||
Dictionary of environment variables. The default is None.
|
||||
debug : TYPE, optional
|
||||
Print debugging messages. The default is False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
MuninPlugin.__init__(self, argv, env, debug)
|
||||
|
||||
# Munin graph parameters
|
||||
graph_category = "network"
|
||||
|
||||
try:
|
||||
self.counters = getCounters()
|
||||
except ValueError:
|
||||
if self._argv[1] == "autoconf":
|
||||
return
|
||||
else:
|
||||
print(
|
||||
"# No counters in nftables. Try adding some first.",
|
||||
"# See 'munin-doc %s' for more information." % self.plugin_name,
|
||||
sep="\n",
|
||||
)
|
||||
raise
|
||||
except Exception:
|
||||
if self._argv[1] == "autoconf":
|
||||
return
|
||||
else:
|
||||
print(
|
||||
"# Plugin needs to be run as root since nftables can only be",
|
||||
"# run as root.",
|
||||
"#",
|
||||
"# Use the following setting in the configuration file",
|
||||
"# to enable root privileges:",
|
||||
"#",
|
||||
"# [%s]" % self.plugin_name,
|
||||
"# user root",
|
||||
sep="\n",
|
||||
)
|
||||
raise
|
||||
|
||||
count_only = self.envGet("count_only")
|
||||
|
||||
# Create the graphs
|
||||
if not (count_only == "bytes"):
|
||||
graph_packets = MuninGraph(
|
||||
"nftables counters (packets)",
|
||||
graph_category,
|
||||
vlabel="packets / second",
|
||||
args="--base 1000",
|
||||
)
|
||||
if not (count_only == "packets"):
|
||||
graph_bytes = MuninGraph(
|
||||
"nftables counters (bytes)",
|
||||
graph_category,
|
||||
vlabel="bytes / second",
|
||||
args="--base 1024",
|
||||
)
|
||||
|
||||
# Define filter to allow for tuning of counters graphed
|
||||
self.envRegisterFilter("counters")
|
||||
|
||||
# add counters as field to each graph (packets and bytes)
|
||||
for counter in self.counters:
|
||||
# JSON output does not contain "comment" attribute.
|
||||
# Until it does, use counter name as info
|
||||
try:
|
||||
field_info = counter["comment"]
|
||||
except Exception:
|
||||
field_info = counter["name"]
|
||||
|
||||
if self.envCheckFilter("counters", counter["name"]):
|
||||
if not (count_only == "bytes"):
|
||||
graph_packets.addField(
|
||||
counter["name"],
|
||||
counter["name"],
|
||||
min=0,
|
||||
type="DERIVE",
|
||||
info=field_info,
|
||||
)
|
||||
if not (count_only == "packets"):
|
||||
graph_bytes.addField(
|
||||
counter["name"],
|
||||
counter["name"],
|
||||
min=0,
|
||||
type="DERIVE",
|
||||
info=field_info,
|
||||
)
|
||||
|
||||
if not (count_only == "bytes"):
|
||||
self.appendGraph("nft_counters_packets", graph_packets)
|
||||
if not (count_only == "packets"):
|
||||
self.appendGraph("nft_counters_bytes", graph_bytes)
|
||||
|
||||
def retrieveVals(self):
|
||||
|
||||
"""
|
||||
Get values and add them to the graphs
|
||||
|
||||
Returns
|
||||
-------
|
||||
None.
|
||||
|
||||
"""
|
||||
|
||||
# add values for each field
|
||||
for counter in self.counters:
|
||||
if self.envCheckFilter("counters", counter["name"]):
|
||||
if self.hasGraph("nft_counters_packets"):
|
||||
self.setGraphVal(
|
||||
"nft_counters_packets", counter["name"], counter["packets"]
|
||||
)
|
||||
if self.hasGraph("nft_counters_bytes"):
|
||||
self.setGraphVal(
|
||||
"nft_counters_bytes", counter["name"], counter["bytes"]
|
||||
)
|
||||
|
||||
def autoconf(self):
|
||||
"""
|
||||
Implements Munin Plugin Auto-Configuration Option.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if plugin can be auto-configured.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
getCounters()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
sys.exit(muninMain(MuninNftCountersPlugin))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue