1#!/usr/bin/env python3 2# 3# Render Qemu Block Graph 4# 5# Copyright (c) 2018 Virtuozzo International GmbH. All rights reserved. 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <http://www.gnu.org/licenses/>. 19# 20 21import os 22import sys 23import subprocess 24import json 25from graphviz import Digraph 26 27sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python')) 28from qemu.qmp import ( 29 QEMUMonitorProtocol, 30 QMPResponseError, 31) 32 33 34def perm(arr): 35 s = 'w' if 'write' in arr else '_' 36 s += 'r' if 'consistent-read' in arr else '_' 37 s += 'u' if 'write-unchanged' in arr else '_' 38 s += 'g' if 'graph-mod' in arr else '_' 39 s += 's' if 'resize' in arr else '_' 40 return s 41 42 43def render_block_graph(qmp, filename, format='png'): 44 ''' 45 Render graph in text (dot) representation into "@filename" and 46 representation in @format into "@filename.@format" 47 ''' 48 49 bds_nodes = qmp.command('query-named-block-nodes') 50 bds_nodes = {n['node-name']: n for n in bds_nodes} 51 52 job_nodes = qmp.command('query-block-jobs') 53 job_nodes = {n['device']: n for n in job_nodes} 54 55 block_graph = qmp.command('x-debug-query-block-graph') 56 57 graph = Digraph(comment='Block Nodes Graph') 58 graph.format = format 59 graph.node('permission symbols:\l' 60 ' w - Write\l' 61 ' r - consistent-Read\l' 62 ' u - write - Unchanged\l' 63 ' g - Graph-mod\l' 64 ' s - reSize\l' 65 'edge label scheme:\l' 66 ' <child type>\l' 67 ' <perm>\l' 68 ' <shared_perm>\l', shape='none') 69 70 for n in block_graph['nodes']: 71 if n['type'] == 'block-driver': 72 info = bds_nodes[n['name']] 73 label = n['name'] + ' [' + info['drv'] + ']' 74 if info['drv'] == 'file': 75 label += '\n' + os.path.basename(info['file']) 76 shape = 'ellipse' 77 elif n['type'] == 'block-job': 78 info = job_nodes[n['name']] 79 label = info['type'] + ' job (' + n['name'] + ')' 80 shape = 'box' 81 else: 82 assert n['type'] == 'block-backend' 83 label = n['name'] if n['name'] else 'unnamed blk' 84 shape = 'box' 85 86 graph.node(str(n['id']), label, shape=shape) 87 88 for e in block_graph['edges']: 89 label = '%s\l%s\l%s\l' % (e['name'], perm(e['perm']), 90 perm(e['shared-perm'])) 91 graph.edge(str(e['parent']), str(e['child']), label=label) 92 93 graph.render(filename) 94 95 96class LibvirtGuest(): 97 def __init__(self, name): 98 self.name = name 99 100 def command(self, cmd): 101 # only supports qmp commands without parameters 102 m = {'execute': cmd} 103 ar = ['virsh', 'qemu-monitor-command', self.name, json.dumps(m)] 104 105 reply = json.loads(subprocess.check_output(ar)) 106 107 if 'error' in reply: 108 raise QMPResponseError(reply) 109 110 return reply['return'] 111 112 113if __name__ == '__main__': 114 obj = sys.argv[1] 115 out = sys.argv[2] 116 117 if os.path.exists(obj): 118 # assume unix socket 119 qmp = QEMUMonitorProtocol(obj) 120 qmp.connect() 121 else: 122 # assume libvirt guest name 123 qmp = LibvirtGuest(obj) 124 125 render_block_graph(qmp, out) 126