Btrfs progs v4.17.1
[btrfs-progs-unstable/devel.git] / show-blocks
blob3876b2acfa74781b1591e25321a960a999befa06
1 #!/usr/bin/env python
3 # Copyright (C) 2007 Oracle. All rights reserved.
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public
7 # License v2 as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public
15 # License along with this program; if not, write to the
16 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 # Boston, MA 021110-1307, USA.
19 import sys, os, signal, time, commands, tempfile, random
21 # numpy seems to override random() with something else. Instantiate our
22 # own here
23 randgen = random.Random()
24 randgen.seed(50)
26 from optparse import OptionParser
27 from matplotlib import rcParams
28 from matplotlib.font_manager import fontManager, FontProperties
29 import numpy
31 rcParams['numerix'] = 'numpy'
32 rcParams['backend'] = 'Agg'
33 rcParams['interactive'] = 'False'
34 from pylab import *
36 class AnnoteFinder:
37 """
38 callback for matplotlib to display an annotation when points are clicked on. The
39 point which is closest to the click and within xtol and ytol is identified.
41 Register this function like this:
43 scatter(xdata, ydata)
44 af = AnnoteFinder(xdata, ydata, annotes)
45 connect('button_press_event', af)
46 """
48 def __init__(self, axis=None):
49 if axis is None:
50 self.axis = gca()
51 else:
52 self.axis= axis
53 self.drawnAnnotations = {}
54 self.links = []
56 def clear(self):
57 for k in self.drawnAnnotations.keys():
58 self.drawnAnnotations[k].set_visible(False)
60 def __call__(self, event):
61 if event.inaxes:
62 if event.button != 1:
63 self.clear()
64 draw()
65 return
66 clickX = event.xdata
67 clickY = event.ydata
68 if (self.axis is None) or (self.axis==event.inaxes):
69 self.drawAnnote(event.inaxes, clickX, clickY)
71 def drawAnnote(self, axis, x, y):
72 """
73 Draw the annotation on the plot
74 """
75 if self.drawnAnnotations.has_key((x,y)):
76 markers = self.drawnAnnotations[(x,y)]
77 markers.set_visible(not markers.get_visible())
78 draw()
79 else:
80 t = axis.text(x,y, "(%3.2f, %3.2f)"%(x,y), bbox=dict(facecolor='red',
81 alpha=0.8))
82 self.drawnAnnotations[(x,y)] = t
83 draw()
85 def loaddata(fh,delimiter=None, converters=None):
87 #14413824 8192 extent back ref root 5 gen 10 owner 282 num_refs 1
88 def iter(fh, delimiter, converters):
89 global total_data
90 global total_metadata
91 for i,line in enumerate(fh):
92 line = line.split(' ')
93 start = float(line[0])
94 len = float(line[1])
95 owner = float(line[10])
96 root = float(line[6])
97 if owner <= 255:
98 total_metadata += int(len)
99 else:
100 total_data += int(len)
101 if start < zoommin or (zoommax != 0 and start > zoommax):
102 continue
103 yield start
104 yield len
105 yield owner
106 yield root
107 X = numpy.fromiter(iter(fh, delimiter, converters), dtype=float)
108 return X
110 def run_debug_tree(device):
111 p = os.popen('btrfs inspect-internal dump-tree -e ' + device)
112 data = loaddata(p)
113 return data
115 def shapeit(X):
116 lines = len(X) / 4
117 X.shape = (lines, 4)
119 def line_picker(line, mouseevent):
120 if mouseevent.xdata is None: return False, dict()
121 print "%d %d\n", mouseevent.xdata, mouseevent.ydata
122 return False, dict()
124 def xycalc(byte):
125 byte = byte / bytes_per_cell
126 yval = floor(byte / num_cells)
127 xval = byte % num_cells
128 return (xval, yval + 1)
130 # record the color used for each root the first time we find it
131 root_colors = {}
132 # there are lots of good colormaps to choose from
133 # http://www.scipy.org/Cookbook/Matplotlib/Show_colormaps
135 meta_cmap = get_cmap("gist_ncar")
136 data_done = False
138 def plotone(a, xvals, yvals, owner, root, lines, labels):
139 global data_done
140 add_label = False
142 if owner:
143 if options.meta_only:
144 return
145 color = "blue"
146 label = "Data"
147 if not data_done:
148 add_label = True
149 data_done = True
150 else:
151 if options.data_only:
152 return
153 if root not in root_colors:
154 color = meta_cmap(randgen.random())
155 label = "Meta %d" % int(root)
156 root_colors[root] = (color, label)
157 add_label = True
158 else:
159 color, label = root_colors[root]
161 plotlines = a.plot(xvals, yvals, 's', color=color, mfc=color, mec=color,
162 markersize=.23, label=label)
163 if add_label:
164 lines += plotlines
165 labels.append(label)
166 print "add label %s" % label
168 def parse_zoom():
169 def parse_num(s):
170 mult = 1
171 c = s.lower()[-1]
172 if c == 't':
173 mult = 1024 * 1024 * 1024 * 1024
174 elif c == 'g':
175 mult = 1024 * 1024 * 1024
176 elif c == 'm':
177 mult = 1024 * 1024
178 elif c == 'k':
179 mult = 1024
180 else:
181 c = None
182 if c:
183 num = int(s[:-1]) * mult
184 else:
185 num = int(s)
186 return num
188 if not options.zoom:
189 return (0, 0)
191 vals = options.zoom.split(':')
192 if len(vals) != 2:
193 sys.stderr.write("warning: unable to parse zoom %s\n" % options.zoom)
194 return (0, 0)
195 zoommin = parse_num(vals[0])
196 zoommax = parse_num(vals[1])
197 return (zoommin, zoommax)
199 usage = "usage: %prog [options]"
200 parser = OptionParser(usage=usage)
201 parser.add_option("-d", "--device", help="Btrfs device", default="")
202 parser.add_option("-i", "--input-file", help="debug-tree data", default="")
203 parser.add_option("-o", "--output", help="Output file", default="blocks.png")
204 parser.add_option("-z", "--zoom", help="Zoom", default=None)
205 parser.add_option("", "--data-only", help="Only print data blocks",
206 default=False, action="store_true")
207 parser.add_option("", "--meta-only", help="Only print metadata blocks",
208 default=False, action="store_true")
210 (options,args) = parser.parse_args()
212 if not options.device and not options.input_file:
213 parser.print_help()
214 sys.exit(1)
216 zoommin, zoommax = parse_zoom()
217 total_data = 0
218 total_metadata = 0
220 if options.device:
221 data = run_debug_tree(options.device)
222 elif options.input_file:
223 data = loaddata(file(options.input_file))
224 shapeit(data)
226 # try to drop out the least common data points by creating
227 # a histogram of the sectors seen.
228 sectors = data[:,0]
229 sizes = data[:,1]
230 datalen = len(data)
231 sectormax = numpy.max(sectors)
232 sectormin = 0
233 num_cells = 800
234 total_cells = num_cells * num_cells
235 byte_range = sectormax - sectormin
236 bytes_per_cell = byte_range / total_cells
238 f = figure(figsize=(8,6))
240 # Throughput goes at the bottom
241 a = subplot(1, 1, 1)
242 subplots_adjust(right=0.7)
243 datai = 0
244 xvals = []
245 yvals = []
246 last_owner = 0
247 last_root = 0
248 lines = []
249 labels = []
250 while datai < datalen:
251 row = data[datai]
252 datai += 1
253 byte = row[0]
254 size = row[1]
255 owner = row[2]
256 root = row[3]
258 if owner <= 255:
259 owner = 0
260 else:
261 owner = 1
263 if len(xvals) and (owner != last_owner or last_root != root):
264 plotone(a, xvals, yvals, last_owner, last_root, lines, labels)
265 xvals = []
266 yvals = []
267 cell = 0
268 while cell < size:
269 xy = xycalc(byte)
270 byte += bytes_per_cell
271 cell += bytes_per_cell
272 if xy:
273 xvals.append(xy[0])
274 yvals.append(xy[1])
275 last_owner = owner
276 last_root = root
278 if xvals:
279 plotone(a, xvals, yvals, last_owner, last_root, lines, labels)
281 # make sure the final second goes on the x axes
282 ticks = []
283 a.set_xticks(ticks)
284 ticks = a.get_yticks()
286 first_tick = ticks[1] * bytes_per_cell * num_cells
287 if first_tick > 1024 * 1024 * 1024 * 1024:
288 scale = 1024 * 1024 * 1024 * 1024;
289 scalestr = "TB"
290 elif first_tick > 1024 * 1024 * 1024:
291 scale = 1024 * 1024 * 1024;
292 scalestr = "GB"
293 elif first_tick > 1024 * 1024:
294 scale = 1024 * 1024;
295 scalestr = "MB"
296 elif first_tick > 1024:
297 scale = 1024;
298 scalestr = "KB"
299 else:
300 scalestr = "Bytes"
301 scale = 1
303 ylabels = [ str(int((x * bytes_per_cell * num_cells) / scale)) for x in ticks ]
304 a.set_yticklabels(ylabels)
305 a.set_ylabel('Disk offset (%s)' % scalestr)
306 a.set_xlim(0, num_cells)
307 a.set_title('Blocks')
309 a.legend(lines, labels, loc=(1.05, 0.8), shadow=True, pad=0.1, numpoints=1,
310 handletextsep = 0.005,
311 labelsep = 0.01,
312 markerscale=10,
313 prop=FontProperties(size='x-small') )
315 if total_data == 0:
316 percent_meta = 100
317 else:
318 percent_meta = (float(total_metadata) / float(total_data)) * 100
320 print "Total metadata bytes %d data %d ratio %.3f" % (total_metadata,
321 total_data, percent_meta)
322 print "saving graph to %s" % options.output
323 savefig(options.output, orientation='landscape')
324 show()