Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / libvirt / kvm_net @ 2385a6f5

Historique | Voir | Annoter | Télécharger (7,22 ko)

1 3267bbd0 Lars Kruse
#!/usr/bin/python3
2 a9059703 Lars Kruse
"""
3
4
=head1 NAME
5
6
kvm_net - Munin plugin to show the network I/O per VM
7
8
9 e0df6aa7 Lars Kruse
=head1 APPLICABLE SYSTEMS
10
11
Virtualization server with VMs based on KVM may be able to track the network
12
traffic of their VMs, if the KVM processes are started in a specific way.
13
14
Probably proxmox-based virtualization hosts fit into this category.
15
16
You can easily check if your KVM processes are started in the expected way, by
17
running the following command:
18
19
  ps -ef | grep "netdev.*ifname="
20
21
The plugin can be used, if the above command outputs one line for every
22
currently running VM.
23
24
In all other cases you need to use other munin plugins instead, e.g. "libvirt".
25
26
27 a9059703 Lars Kruse
=head1 CONFIGURATION
28
29
parsed environment variables:
30
31
  * vmsuffix: part of vm name to be removed
32
33
34
=head1 AUTHOR
35
36
Copyright (C) 2012 - Igor Borodikhin
37
Copyright (C) 2018 - Lars Kruse <devel@sumpfralle.de>
38
39
40
=head1 LICENSE
41
42
GPLv3
43
44
45
=head1 MAGIC MARKERS
46
47
 #%# capabilities=autoconf
48
 #%# family=contrib
49
50
=cut
51
"""
52 82242a42 Rodolphe Qui?deville
53 aa8f1a59 Lars Kruse
import os
54
import re
55 82242a42 Rodolphe Qui?deville
from subprocess import Popen, PIPE
56 aa8f1a59 Lars Kruse
import sys
57 82242a42 Rodolphe Qui?deville
58 f6080f5f Lars Kruse
59 b6c6a02e Lars Kruse
VM_NAME_REGEX = re.compile("^.*\x00-{arg_name}\x00(.+)\x00.*$")
60 e0df6aa7 Lars Kruse
KVM_INTERFACE_NAME_REGEX = re.compile("(?:^|,)ifname=([^,]+)(?:,|$)")
61 b6c6a02e Lars Kruse
62
63 82242a42 Rodolphe Qui?deville
def config(vm_names):
64 7f98e21f Lars Kruse
    """ Print the plugin's config
65
66 82242a42 Rodolphe Qui?deville
    @param vm_names : a list of "cleaned" vms' name
67 7f98e21f Lars Kruse
    """
68 e0b74c69 Lars Kruse
    print("graph_title KVM Network I/O")
69
    print("graph_vlabel Bytes rx(-)/tx(+) per second")
70
    print("graph_category virtualization")
71
    print("graph_args --base 1024")
72
    print("graph_info This graph shows the network I/O of the virtual "
73
          "machines. It is only usable for VMs that were started in a very "
74
          "specific way. If you see no values in the diagrams, then you "
75
          "should check, if the command \"ps -ef | grep 'netdev.*ifname='\" "
76
          "returns one line of output for every running VM. If there is no "
77
          "output, then you need to change the setup of your VMs or you need "
78
          "to use a different munin plugin for monitoring the network traffic "
79
          "(e.g. 'libvirt').")
80
    print()
81 82242a42 Rodolphe Qui?deville
    for vm in vm_names:
82 67cc769c Lars Kruse
        print("%s_in.label %s" % (vm, vm))
83
        print("%s_in.type COUNTER" % vm)
84
        print("%s_in.min 0" % vm)
85 00f8fce4 Bianco Veigel
        print("%s_in.graph no" % vm)
86 67cc769c Lars Kruse
        print("%s_out.negative %s_in" % (vm, vm))
87
        print("%s_out.label %s" % (vm, vm))
88
        print("%s_out.type COUNTER" % vm)
89
        print("%s_out.min 0" % vm)
90 82242a42 Rodolphe Qui?deville
91 f6080f5f Lars Kruse
92 82242a42 Rodolphe Qui?deville
def clean_vm_name(vm_name):
93 7f98e21f Lars Kruse
    """ Replace all special chars
94
95 82242a42 Rodolphe Qui?deville
    @param vm_name : a vm's name
96
    @return cleaned vm's name
97 7f98e21f Lars Kruse
    """
98 82242a42 Rodolphe Qui?deville
    # suffix part defined in conf
99 7f98e21f Lars Kruse
    suffix = os.getenv("vmsuffix")
100 82242a42 Rodolphe Qui?deville
    if suffix:
101 7f98e21f Lars Kruse
        vm_name = re.sub(suffix, "", vm_name)
102 225a9156 Bianco Veigel
    # proxmox uses kvm with -name parameter
103 113008b0 Bianco Veigel
    parts = vm_name.split('\x00')
104
    if (parts[0].endswith('kvm')):
105
        try:
106
            return parts[parts.index('-name')+1]
107
        except ValueError:
108
            pass
109 82242a42 Rodolphe Qui?deville
    return re.sub(r"[^a-zA-Z0-9_]", "_", vm_name)
110 f6080f5f Lars Kruse
111
112 82242a42 Rodolphe Qui?deville
def fetch(vms):
113 7f98e21f Lars Kruse
    """ Fetch values for a list of pids
114
115 8713eb37 Lars Kruse
    @param dictionary {kvm_pid: cleaned vm name}
116 7f98e21f Lars Kruse
    """
117 e0df6aa7 Lars Kruse
    for pid, vm_data in vms.items():
118
        vm_interface_names = get_vm_network_interface_names(pid)
119
        sum_incoming = 0
120
        sum_outgoing = 0
121
        interface_found = False
122
        with open("/proc/net/dev", "r") as net_file:
123
            for line in net_file.readlines():
124
                tokens = line.split()
125
                current_interface_name = tokens[0].rstrip(":").strip()
126
                if current_interface_name in vm_interface_names:
127
                    sum_incoming += int(tokens[1])
128
                    sum_outgoing += int(tokens[9])
129
                    interface_found = True
130
            if not interface_found:
131
                # we want to distinguish "no traffic" from "not found"
132
                sum_incoming = "U"
133
                sum_outgoing = "U"
134
            print("%s_in.value %s" % (vm_data, sum_incoming))
135
            print("%s_out.value %s" % (vm_data, sum_outgoing))
136
137
138
def get_vm_network_interface_names(pid):
139
    """ return the MAC addresses configured for network interfacs of a PID """
140
    result = set()
141
    for netdev_description in _get_kvm_process_arguments(pid, "netdev"):
142
        match = KVM_INTERFACE_NAME_REGEX.search(netdev_description)
143
        if match:
144
            result.add(match.groups()[0])
145
    return result
146 82242a42 Rodolphe Qui?deville
147 f6080f5f Lars Kruse
148 82242a42 Rodolphe Qui?deville
def detect_kvm():
149 7f98e21f Lars Kruse
    """ Check if kvm is installed """
150 89e4dd64 Lars Kruse
    kvm = Popen(["which", "kvm"], stdout=PIPE)
151 82242a42 Rodolphe Qui?deville
    kvm.communicate()
152 89e4dd64 Lars Kruse
    return kvm.returncode == 0
153 82242a42 Rodolphe Qui?deville
154 f6080f5f Lars Kruse
155 82242a42 Rodolphe Qui?deville
def find_vm_names(pids):
156 7f98e21f Lars Kruse
    """Find and clean vm names from pids
157
158 8713eb37 Lars Kruse
    @return a dictionary of {pids : cleaned vm name}
159 7f98e21f Lars Kruse
    """
160 82242a42 Rodolphe Qui?deville
    result = {}
161
    for pid in pids:
162 b6c6a02e Lars Kruse
        name = None
163
        name_arg_values = _get_kvm_process_arguments(pid, "name")
164
        if name_arg_values:
165
            name_arg_value = name_arg_values[0]
166
            if "," in name_arg_value:
167
                # the modern parameter format may look like this:
168
                #    guest=foo,debug-threads=on
169
                for index, token in enumerate(name_arg_value.split(",")):
170
                    if (index == 0) and ("=" not in token):
171
                        # the first item may the plain name
172
                        name = value
173
                    elif "=" in token:
174
                        key, value = token.split("=", 1)
175
                        if key == "guest":
176
                            name = value
177
                    else:
178
                        # unknown format (no "mapping")
179
                        pass
180
            else:
181
                name = name_arg_value
182
        if name is None:
183
            print("Failed to parse VM name from commandline of process: {}"
184
                  .format(name_arg_values), file=sys.stderr)
185
        else:
186
            result[pid] = clean_vm_name(name)
187 82242a42 Rodolphe Qui?deville
    return result
188 f6080f5f Lars Kruse
189
190 b6c6a02e Lars Kruse
def _get_kvm_process_arguments(pid, arg_name):
191
    """ parse all value with the given name from the process identified by PID
192
193
    The result is a list of tokens, that follow this argument name. The result
194
    is empty in case of problems.
195
    """
196
    # the "cmdline" (e.g. /proc/self/cmdline) is a null-separated token list
197
    try:
198
        with open("/proc/%s/cmdline" % pid, "r") as cmdline_file:
199
            cmdline = cmdline_file.read()
200
    except IOError:
201
        # the process seems to have died meanwhile
202
        return []
203
    is_value = False
204
    result = []
205
    for arg_token in cmdline.split("\0"):
206
        if is_value:
207
            # the previous token was our argument name
208
            result.append(arg_token)
209
            is_value = False
210
        elif arg_token == "-{}".format(arg_name):
211
            # this is our argument name - we want to store the next value
212
            is_value = True
213
        else:
214
            # any other irrelevant value
215
            pass
216
    return result
217
218
219 82242a42 Rodolphe Qui?deville
def list_pids():
220 7f98e21f Lars Kruse
    """ Find the pid of kvm processes
221
222 82242a42 Rodolphe Qui?deville
    @return a list of pids from running kvm
223 7f98e21f Lars Kruse
    """
224 89e4dd64 Lars Kruse
    pid = Popen(["pidof", "qemu-kvm", "qemu-system-x86_64", "kvm"], stdout=PIPE)
225 3267bbd0 Lars Kruse
    return pid.communicate()[0].decode().split()
226 82242a42 Rodolphe Qui?deville
227 6bc704a1 Igor Borodikhin
228 82242a42 Rodolphe Qui?deville
if __name__ == "__main__":
229 54330cc3 Lars Kruse
    action = sys.argv[1] if len(sys.argv) > 1 else None
230
    if action == "autoconf":
231
        if detect_kvm():
232
            print("yes")
233 82242a42 Rodolphe Qui?deville
        else:
234 54330cc3 Lars Kruse
            print("no")
235
    elif action == "config":
236
        vm_data = find_vm_names(list_pids())
237
        config(vm_data.values())
238 82242a42 Rodolphe Qui?deville
    else:
239 54330cc3 Lars Kruse
        vm_data = find_vm_names(list_pids())
240
        fetch(vm_data)