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 QMPError 29from qemu.qmp.legacy import QEMUMonitorProtocol 30 31 32def perm(arr): 33 s = 'w' if 'write' in arr else '_' 34 s += 'r' if 'consistent-read' in arr else '_' 35 s += 'u' if 'write-unchanged' in arr else '_' 36 s += 's' if 'resize' in arr else '_' 37 return s 38 39 40def render_block_graph(qmp, filename, format='png'): 41 ''' 42 Render graph in text (dot) representation into "@filename" and 43 representation in @format into "@filename.@format" 44 ''' 45 46 bds_nodes = qmp.command('query-named-block-nodes') 47 bds_nodes = {n['node-name']: n for n in bds_nodes} 48 49 job_nodes = qmp.command('query-block-jobs') 50 job_nodes = {n['device']: n for n in job_nodes} 51 52 block_graph = qmp.command('x-debug-query-block-graph') 53 54 graph = Digraph(comment='Block Nodes Graph') 55 graph.format = format 56 graph.node('permission symbols:\l' 57 ' w - Write\l' 58 ' r - consistent-Read\l' 59 ' u - write - Unchanged\l' 60 ' g - Graph-mod\l' 61 ' s - reSize\l' 62 'edge label scheme:\l' 63 ' <child type>\l' 64 ' <perm>\l' 65 ' <shared_perm>\l', shape='none') 66 67 for n in block_graph['nodes']: 68 if n['type'] == 'block-driver': 69 info = bds_nodes[n['name']] 70 label = n['name'] + ' [' + info['drv'] + ']' 71 if info['drv'] == 'file': 72 label += '\n' + os.path.basename(info['file']) 73 shape = 'ellipse' 74 elif n['type'] == 'block-job': 75 info = job_nodes[n['name']] 76 label = info['type'] + ' job (' + n['name'] + ')' 77 shape = 'box' 78 else: 79 assert n['type'] == 'block-backend' 80 label = n['name'] if n['name'] else 'unnamed blk' 81 shape = 'box' 82 83 graph.node(str(n['id']), label, shape=shape) 84 85 for e in block_graph['edges']: 86 label = '%s\l%s\l%s\l' % (e['name'], perm(e['perm']), 87 perm(e['shared-perm'])) 88 graph.edge(str(e['parent']), str(e['child']), label=label) 89 90 graph.render(filename) 91 92 93class LibvirtGuest(): 94 def __init__(self, name): 95 self.name = name 96 97 def command(self, cmd): 98 # only supports qmp commands without parameters 99 m = {'execute': cmd} 100 ar = ['virsh', 'qemu-monitor-command', self.name, json.dumps(m)] 101 102 reply = json.loads(subprocess.check_output(ar)) 103 104 if 'error' in reply: 105 raise QMPError(reply) 106 107 return reply['return'] 108 109 110if __name__ == '__main__': 111 obj = sys.argv[1] 112 out = sys.argv[2] 113 114 if os.path.exists(obj): 115 # assume unix socket 116 qmp = QEMUMonitorProtocol(obj) 117 qmp.connect() 118 else: 119 # assume libvirt guest name 120 qmp = LibvirtGuest(obj) 121 122 render_block_graph(qmp, out) 123