Btrfs image tool
[btrfs-progs-unstable.git] / show-blocks
blob8db4c0b14f50395bae5b2401f012d6b3540ae62a
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
20 from optparse import OptionParser
21 from matplotlib import rcParams
22 from matplotlib.font_manager import fontManager, FontProperties
23 import numpy
25 rcParams['numerix'] = 'numpy'
26 rcParams['backend'] = 'Agg'
27 rcParams['interactive'] = 'False'
28 from pylab import *
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.
35 Register this function like this:
37 scatter(xdata, ydata)
38 af = AnnoteFinder(xdata, ydata, annotes)
39 connect('button_press_event', af)
40 """
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 = []
50 def clear(self):
51 for k in self.drawnAnnotations.keys():
52 self.drawnAnnotations[k].set_visible(False)
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)
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()
79 def loaddata(fh,delimiter=None, converters=None):
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
101 def run_debug_tree(device):
102 p = os.popen('debug-tree -e ' + device)
103 data = loaddata(p)
104 return data
106 def shapeit(X):
107 lines = len(X) / 3
108 X.shape = (lines, 3)
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()
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)
121 def plotone(a, xvals, yvals, owner):
122 global data_lines
123 global meta_lines
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"
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
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
164 if not options.zoom:
165 return (0, 0)
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)
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")
186 (options,args) = parser.parse_args()
188 if not options.device and not options.input_file:
189 parser.print_help()
190 sys.exit(1)
192 zoommin, zoommax = parse_zoom()
193 total_data = 0
194 total_metadata = 0
195 data_lines = []
196 meta_lines = []
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)
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
216 f = figure(figsize=(8,6))
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]
231 if owner <= 255:
232 owner = 0
233 else:
234 owner = 1
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
250 if xvals:
251 plotone(a, xvals, yvals, last)
253 # make sure the final second goes on the x axes
254 ticks = []
255 a.set_xticks(ticks)
256 ticks = a.get_yticks()
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
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')
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"]
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') )
296 if total_data == 0:
297 percent_meta = 100
298 else:
299 percent_meta = (float(total_metadata) / float(total_data)) * 100
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()