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.
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
23 randgen
= random
.Random()
26 from optparse
import OptionParser
27 from matplotlib
import rcParams
28 from matplotlib
.font_manager
import fontManager
, FontProperties
31 rcParams
['numerix'] = 'numpy'
32 rcParams
['backend'] = 'Agg'
33 rcParams
['interactive'] = 'False'
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:
44 af = AnnoteFinder(xdata, ydata, annotes)
45 connect('button_press_event', af)
48 def __init__(self
, axis
=None):
53 self
.drawnAnnotations
= {}
57 for k
in self
.drawnAnnotations
.keys():
58 self
.drawnAnnotations
[k
].set_visible(False)
60 def __call__(self
, event
):
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
):
73 Draw the annotation on the plot
75 if self
.drawnAnnotations
.has_key((x
,y
)):
76 markers
= self
.drawnAnnotations
[(x
,y
)]
77 markers
.set_visible(not markers
.get_visible())
80 t
= axis
.text(x
,y
, "(%3.2f, %3.2f)"%(x
,y
), bbox
=dict(facecolor
='red',
82 self
.drawnAnnotations
[(x
,y
)] = t
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
):
91 for i
,line
in enumerate(fh
):
92 line
= line
.split(' ')
93 start
= float(line
[0])
95 owner
= float(line
[10])
98 total_metadata
+= int(len)
100 total_data
+= int(len)
101 if start
< zoommin
or (zoommax
!= 0 and start
> zoommax
):
107 X
= numpy
.fromiter(iter(fh
, delimiter
, converters
), dtype
=float)
110 def run_debug_tree(device
):
111 p
= os
.popen('btrfs-debug-tree -e ' + device
)
119 def line_picker(line
, mouseevent
):
120 if mouseevent
.xdata
is None: return False, dict()
121 print "%d %d\n", mouseevent
.xdata
, mouseevent
.ydata
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
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")
138 def plotone(a
, xvals
, yvals
, owner
, root
, lines
, labels
):
143 if options
.meta_only
:
151 if options
.data_only
:
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
)
159 color
, label
= root_colors
[root
]
161 plotlines
= a
.plot(xvals
, yvals
, 's', color
=color
, mfc
=color
, mec
=color
,
162 markersize
=.23, label
=label
)
166 print "add label %s" % label
173 mult
= 1024 * 1024 * 1024 * 1024
175 mult
= 1024 * 1024 * 1024
183 num
= int(s
[:-1]) * mult
191 vals
= options
.zoom
.split(':')
193 sys
.stderr
.write("warning: unable to parse zoom %s\n" % options
.zoom
)
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
:
216 zoommin
, zoommax
= parse_zoom()
221 data
= run_debug_tree(options
.device
)
222 elif options
.input_file
:
223 data
= loaddata(file(options
.input_file
))
226 # try to drop out the least common data points by creating
227 # a histogram of the sectors seen.
231 sectormax
= numpy
.max(sectors
)
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
242 subplots_adjust(right
=0.7)
250 while datai
< datalen
:
263 if len(xvals
) and (owner
!= last_owner
or last_root
!= root
):
264 plotone(a
, xvals
, yvals
, last_owner
, last_root
, lines
, labels
)
270 byte
+= bytes_per_cell
271 cell
+= bytes_per_cell
279 plotone(a
, xvals
, yvals
, last_owner
, last_root
, lines
, labels
)
281 # make sure the final second goes on the x axes
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;
290 elif first_tick
> 1024 * 1024 * 1024:
291 scale
= 1024 * 1024 * 1024;
293 elif first_tick
> 1024 * 1024:
296 elif first_tick
> 1024:
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,
313 prop
=FontProperties(size
='x-small') )
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')