btrfs-progs: docs: mkfs, implications of DUP on devices
[platform/upstream/btrfs-progs.git] / show-blocks
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2007 Oracle.  All rights reserved.
4 #
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.
13
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.
18 #
19 import sys, os, signal, time, commands, tempfile, random
20
21 # numpy seems to override random() with something else.  Instantiate our
22 # own here
23 randgen = random.Random()
24 randgen.seed(50)
25
26 from optparse import OptionParser
27 from matplotlib import rcParams
28 from matplotlib.font_manager import fontManager, FontProperties
29 import numpy
30
31 rcParams['numerix'] = 'numpy'
32 rcParams['backend'] = 'Agg'
33 rcParams['interactive'] = 'False'
34 from pylab import *
35
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.
40     
41   Register this function like this:
42     
43   scatter(xdata, ydata)
44   af = AnnoteFinder(xdata, ydata, annotes)
45   connect('button_press_event', af)
46   """
47
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 = []
55     
56   def clear(self):
57     for k in self.drawnAnnotations.keys():
58         self.drawnAnnotations[k].set_visible(False)
59
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)
70     
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()
84
85 def loaddata(fh,delimiter=None, converters=None):
86
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
109
110 def run_debug_tree(device):
111     p = os.popen('btrfs-debug-tree -e ' + device)
112     data = loaddata(p)
113     return data
114
115 def shapeit(X):
116     lines = len(X) / 4
117     X.shape = (lines, 4)
118
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()
123
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)
129
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
134 #
135 meta_cmap = get_cmap("gist_ncar")
136 data_done = False
137
138 def plotone(a, xvals, yvals, owner, root, lines, labels):
139     global data_done
140     add_label = False
141
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]
160
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
167
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
187         
188     if not options.zoom:
189         return (0, 0)
190
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)
198
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")
209
210 (options,args) = parser.parse_args()
211
212 if not options.device and not options.input_file:
213     parser.print_help()
214     sys.exit(1)
215
216 zoommin, zoommax = parse_zoom()
217 total_data = 0
218 total_metadata = 0
219
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)
225
226 # try to drop out the least common data points by creating
227 # a historgram 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
237
238 f = figure(figsize=(8,6))
239
240 # Throughput goes at the botoom
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]
257
258     if owner <= 255:
259         owner = 0
260     else:
261         owner = 1
262
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
277
278 if xvals:
279     plotone(a, xvals, yvals, last_owner, last_root, lines, labels)
280
281 # make sure the final second goes on the x axes
282 ticks = []
283 a.set_xticks(ticks)
284 ticks = a.get_yticks()
285
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
302
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')
308
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') )
314
315 if total_data == 0:
316     percent_meta = 100
317 else:
318     percent_meta = (float(total_metadata) / float(total_data)) * 100
319
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()
325