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