Add debug-tree -e to print all allocated extents, and show-blocks to graph them
[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
20 from optparse import OptionParser
21 from matplotlib import rcParams
22 from matplotlib.font_manager import fontManager, FontProperties
23 import numpy
24
25 rcParams['numerix'] = 'numpy'
26 rcParams['backend'] = 'Agg'
27 rcParams['interactive'] = 'False'
28 from pylab import *
29
30 class AnnoteFinder:
31   """
32   callback for matplotlib to display an annotation when points are clicked on.  The
33   point which is closest to the click and within xtol and ytol is identified.
34     
35   Register this function like this:
36     
37   scatter(xdata, ydata)
38   af = AnnoteFinder(xdata, ydata, annotes)
39   connect('button_press_event', af)
40   """
41
42   def __init__(self, axis=None):
43     if axis is None:
44       self.axis = gca()
45     else:
46       self.axis= axis
47     self.drawnAnnotations = {}
48     self.links = []
49     
50   def clear(self):
51     for k in self.drawnAnnotations.keys():
52         self.drawnAnnotations[k].set_visible(False)
53
54   def __call__(self, event):
55     if event.inaxes:
56       if event.button != 1:
57         self.clear()
58         draw()
59         return
60       clickX = event.xdata
61       clickY = event.ydata
62       if (self.axis is None) or (self.axis==event.inaxes):
63         self.drawAnnote(event.inaxes, clickX, clickY)
64     
65   def drawAnnote(self, axis, x, y):
66     """
67     Draw the annotation on the plot
68     """
69     if self.drawnAnnotations.has_key((x,y)):
70       markers = self.drawnAnnotations[(x,y)]
71       markers.set_visible(not markers.get_visible())
72       draw()
73     else:
74       t = axis.text(x,y, "(%3.2f, %3.2f)"%(x,y), bbox=dict(facecolor='red',
75                     alpha=0.8))
76       self.drawnAnnotations[(x,y)] = t
77       draw()
78
79 def loaddata(fh,delimiter=None, converters=None):
80
81     def iter(fh, delimiter, converters):
82         global total_data
83         global total_metadata
84         for i,line in enumerate(fh):
85             line = line.split(' ')
86             start = float(line[0])
87             len = float(line[1])
88             owner = float(line[10])
89             if owner <= 255:
90                 total_metadata += int(len)
91             else:
92                 total_data += int(len)
93             if start < zoommin or (zoommax != 0 and start > zoommax):
94                 continue
95             yield start
96             yield len
97             yield owner
98     X = numpy.fromiter(iter(fh, delimiter, converters), dtype=float)
99     return X
100
101 def run_debug_tree(device):
102     p = os.popen('debug-tree -e ' + device)
103     data = loaddata(p)
104     return data
105
106 def shapeit(X):
107     lines = len(X) / 3
108     X.shape = (lines, 3)
109
110 def line_picker(line, mouseevent):
111     if mouseevent.xdata is None: return False, dict()
112     print "%d %d\n", mouseevent.xdata, mouseevent.ydata
113     return False, dict()
114
115 def xycalc(byte):
116     byte = byte / bytes_per_cell
117     yval = floor(byte / num_cells)
118     xval = byte % num_cells
119     return (xval, yval + 1)
120
121 def plotone(a, xvals, yvals, owner):
122     global data_lines
123     global meta_lines
124
125     if owner:
126         if options.meta_only:
127             return
128         color = "blue"
129         label = "Data"
130     else:
131         if options.data_only:
132             return
133         color = "green"
134         label = "Metadata"
135
136     lines = a.plot(xvals, yvals, 's', color=color, mfc=color, mec=color,
137            markersize=.23, label=label)
138     if owner and not data_lines:
139         data_lines = lines
140     elif not owner and not meta_lines:
141         meta_lines = lines
142
143
144 def parse_zoom():
145     def parse_num(s):
146         mult = 1
147         c = s.lower()[-1]
148         if c == 't':
149             mult = 1024 * 1024 * 1024 * 1024
150         elif c == 'g':
151             mult = 1024 * 1024 * 1024
152         elif c == 'm':
153             mult = 1024 * 1024
154         elif c == 'k':
155             mult = 1024
156         else:
157             c = None
158         if c:
159             num = int(s[:-1]) * mult
160         else:
161             num = int(s)
162         return num
163         
164     if not options.zoom:
165         return (0, 0)
166
167     vals = options.zoom.split(':')
168     if len(vals) != 2:
169         sys.stderr.write("warning: unable to parse zoom %s\n" % options.zoom)
170         return (0, 0)
171     zoommin = parse_num(vals[0])
172     zoommax = parse_num(vals[1])
173     return (zoommin, zoommax)
174
175 usage = "usage: %prog [options]"
176 parser = OptionParser(usage=usage)
177 parser.add_option("-d", "--device", help="Btrfs device", default="")
178 parser.add_option("-i", "--input-file", help="debug-tree data", default="")
179 parser.add_option("-o", "--output", help="Output file", default="blocks.png")
180 parser.add_option("-z", "--zoom", help="Zoom", default=None)
181 parser.add_option("", "--data-only", help="Only print data blocks",
182                   default=False, action="store_true")
183 parser.add_option("", "--meta-only", help="Only print metadata blocks",
184                   default=False, action="store_true")
185
186 (options,args) = parser.parse_args()
187
188 if not options.device and not options.input_file:
189     parser.print_help()
190     sys.exit(1)
191
192 zoommin, zoommax = parse_zoom()
193 total_data = 0
194 total_metadata = 0
195 data_lines = []
196 meta_lines = []
197
198 if options.device:
199     data = run_debug_tree(options.device)
200 elif options.input_file:
201     data = loaddata(file(options.input_file))
202 shapeit(data)
203
204 # try to drop out the least common data points by creating
205 # a historgram of the sectors seen.
206 sectors = data[:,0]
207 sizes = data[:,1]
208 datalen = len(data)
209 sectormax = numpy.max(sectors)
210 sectormin = 0
211 num_cells = 800
212 total_cells = num_cells * num_cells
213 byte_range = sectormax - sectormin
214 bytes_per_cell = byte_range / total_cells
215
216 f = figure(figsize=(8,6))
217
218 # Throughput goes at the botoom
219 a = subplot(1, 1, 1)
220 datai = 0
221 xvals = []
222 yvals = []
223 last = 0
224 while datai < datalen:
225     row = data[datai]
226     datai += 1
227     byte = row[0]
228     size = row[1]
229     owner = row[2]
230
231     if owner <= 255:
232         owner = 0
233     else:
234         owner = 1
235
236     if len(xvals) and owner != last:
237         plotone(a, xvals, yvals, last)
238         xvals = []
239         yvals = []
240     cell = 0
241     while cell < size:
242         xy = xycalc(byte)
243         byte += bytes_per_cell
244         cell += bytes_per_cell
245         if xy:
246             xvals.append(xy[0])
247             yvals.append(xy[1])
248     last = owner
249
250 if xvals:
251     plotone(a, xvals, yvals, last)
252
253 # make sure the final second goes on the x axes
254 ticks = []
255 a.set_xticks(ticks)
256 ticks = a.get_yticks()
257
258 first_tick = ticks[1] * bytes_per_cell * num_cells
259 if first_tick > 1024 * 1024 * 1024 * 1024:
260     scale = 1024 * 1024 * 1024 * 1024;
261     scalestr = "TB"
262 elif first_tick > 1024 * 1024 * 1024:
263     scale = 1024 * 1024 * 1024;
264     scalestr = "GB"
265 elif first_tick > 1024 * 1024:
266     scale = 1024 * 1024;
267     scalestr = "MB"
268 elif first_tick > 1024:
269     scale = 1024;
270     scalestr = "KB"
271 else:
272     scalestr = "Bytes"
273     scale = 1
274
275 ylabels = [ str(int((x * bytes_per_cell * num_cells) / scale)) for x in ticks ]
276 a.set_yticklabels(ylabels)
277 a.set_ylabel('Disk offset (%s)' % scalestr)
278 a.set_xlim(0, num_cells)
279 a.set_title('Blocks')
280
281 lines = []
282 labels = []
283 if data_lines:
284     lines += data_lines
285     labels += ["Data"]
286 if meta_lines:
287     lines += meta_lines
288     labels += ["Metadata"]
289
290 a.legend(lines, labels, loc=(.9, 1.02), shadow=True, pad=0.5, numpoints=1,
291               handletextsep = 0.005,
292               labelsep = 0.01,
293               markerscale=10,
294               prop=FontProperties(size='x-small') )
295
296 if total_data == 0:
297     percent_meta = 100
298 else:
299     percent_meta = (float(total_metadata) / float(total_data)) * 100
300
301 print "Total metadata bytes %d data %d ratio %.3f" % (total_metadata,
302                                                     total_data, percent_meta)
303 print "saving graph to %s" % options.output
304 savefig(options.output, orientation='landscape')
305 show()
306