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