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
#!/usr/bin/python3
2
"""
3

    
4
=head1 NAME
5

    
6
kvm_net - Munin plugin to show the network I/O per VM
7

    
8

    
9
=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
=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

    
53
import os
54
import re
55
from subprocess import Popen, PIPE
56
import sys
57

    
58

    
59
VM_NAME_REGEX = re.compile("^.*\x00-{arg_name}\x00(.+)\x00.*$")
60
KVM_INTERFACE_NAME_REGEX = re.compile("(?:^|,)ifname=([^,]+)(?:,|$)")
61

    
62

    
63
def config(vm_names):
64
    """ Print the plugin's config
65

    
66
    @param vm_names : a list of "cleaned" vms' name
67
    """
68
    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
    for vm in vm_names:
82
        print("%s_in.label %s" % (vm, vm))
83
        print("%s_in.type COUNTER" % vm)
84
        print("%s_in.min 0" % vm)
85
        print("%s_in.graph no" % vm)
86
        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

    
91

    
92
def clean_vm_name(vm_name):
93
    """ Replace all special chars
94

    
95
    @param vm_name : a vm's name
96
    @return cleaned vm's name
97
    """
98
    # suffix part defined in conf
99
    suffix = os.getenv("vmsuffix")
100
    if suffix:
101
        vm_name = re.sub(suffix, "", vm_name)
102
    # proxmox uses kvm with -name parameter
103
    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
    return re.sub(r"[^a-zA-Z0-9_]", "_", vm_name)
110

    
111

    
112
def fetch(vms):
113
    """ Fetch values for a list of pids
114

    
115
    @param dictionary {kvm_pid: cleaned vm name}
116
    """
117
    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

    
147

    
148
def detect_kvm():
149
    """ Check if kvm is installed """
150
    kvm = Popen(["which", "kvm"], stdout=PIPE)
151
    kvm.communicate()
152
    return kvm.returncode == 0
153

    
154

    
155
def find_vm_names(pids):
156
    """Find and clean vm names from pids
157

    
158
    @return a dictionary of {pids : cleaned vm name}
159
    """
160
    result = {}
161
    for pid in pids:
162
        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
    return result
188

    
189

    
190
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
def list_pids():
220
    """ Find the pid of kvm processes
221

    
222
    @return a list of pids from running kvm
223
    """
224
    pid = Popen(["pidof", "qemu-kvm", "qemu-system-x86_64", "kvm"], stdout=PIPE)
225
    return pid.communicate()[0].decode().split()
226

    
227

    
228
if __name__ == "__main__":
229
    action = sys.argv[1] if len(sys.argv) > 1 else None
230
    if action == "autoconf":
231
        if detect_kvm():
232
            print("yes")
233
        else:
234
            print("no")
235
    elif action == "config":
236
        vm_data = find_vm_names(list_pids())
237
        config(vm_data.values())
238
    else:
239
        vm_data = find_vm_names(list_pids())
240
        fetch(vm_data)