volume_visualizer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 593e47fe627c73fb820a9f2ca2fada9ac8047ff9
4
+ data.tar.gz: 96a938b038dfad171bab6a99fc2e5d236ee8392c
5
+ SHA512:
6
+ metadata.gz: eca862e193efb061d4ab844e279d00b45265ec0993f548d566dac4c2b72ee9106d8bc5f788b537b017e3d5c76b720de7645a665bb9fa093de07129b0255fb4ec
7
+ data.tar.gz: 93735cd10cf0b1eadb9d9af2fd66d77d95d6d13ff49249f1b1f66aa4dca30b31e44ab12a77c669de475c8d3ef9ff80825a756d1cfd7d9c23159369bf73527519
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in volume_visualizer.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,184 @@
1
+ # Volviz - The Volume Visualizer
2
+
3
+ Commandline tool for visualizing data distribution on your storage volumes.
4
+
5
+ It can be difficult to create a mental picture of where your bytes are,
6
+ especially when you have a lot of them. Thin provisioning, copy-on-write,
7
+ deduplication and compression can help you pack more bytes into your raw
8
+ storage, but can also make it even harder to understand what's really
9
+ consuming your storage.
10
+
11
+ This becomes really important when you're trying to cherrypick the most
12
+ critical data from a 9TB filesystem that will still fit on a 640GB USB
13
+ disk. It also would have been useful a year ago when I was trying to
14
+ keep track of 100TB+ on a nearline backup system.
15
+
16
+ It also particularly helps when you're using a tool like
17
+ [zfs-auto-snapshot](https://github.com/zfsonlinux/zfs-auto-snapshot)
18
+ and you're wondering if deleting any of your old snapshots would
19
+ gain you back a significant amount of space, or if they're mostly
20
+ just referencing the same data that's in the live filesystem.
21
+
22
+ Volviz tries to help.
23
+
24
+ If you've ever used [WinDirStat](https://windirstat.info/) or
25
+ [Baobab](http://www.marzocca.net/linux/baobab/) (Gnome's Disk Usage
26
+ Analyzer), you're familiar with the basic idea. Those tools, however,
27
+ map the size of *files* in a hierarchy of *directories*.
28
+
29
+ Volviz maps the size of *filesystems*, *snapshots*, and *volumes*. It
30
+ shows the actual space used on disk, not just the reported size.
31
+
32
+ ## Screenshot
33
+
34
+ ![Screenshot](https://raw.githubusercontent.com/rleemorlang/volume_visualizer/master/example/screenshot_scaled.jpg)
35
+
36
+ ## Status
37
+
38
+ Barely alpha. This was hacked together in a day. The HTML/JavaScript/CSS
39
+ is the sort of mess you expect when cutting and pasting from a bunch of
40
+ different examples.
41
+
42
+ But safe! It's completely read-only in its default mode. If you ask it
43
+ to, it'll write a JSON file and an HTML file where you tell it to.
44
+
45
+ ## Compatibility
46
+
47
+ **Only ZFS is currently supported**. It shouldn't be too hard to refactor
48
+ and add adapters for LVM and others.
49
+
50
+ Only Linux has been tested.
51
+
52
+ ## Acknowledgements
53
+
54
+ Volviz gets all its visualization magic from [D3.js](http://d3js.org). D3.js
55
+ is awesome.
56
+
57
+ ## Installation
58
+
59
+ Installation should be simple with RubyGems. If you don't already have it,
60
+ your package manager can install everything you need.
61
+
62
+ sudo apt-get install rubygems || sudo yum install rubygems
63
+
64
+ Once that's done, you can install the latest release:
65
+
66
+ sudo gem install volume_visualizer
67
+
68
+ ## Usage
69
+
70
+ Volviz's default mode will query the first volume it finds, discover where
71
+ all the bytes are, launch an HTTP server and display a URL on the console.
72
+ Navigate to this URL to see the visualization.
73
+
74
+ **Note:** Volviz needs root privileges to get volume information, and will
75
+ run sudo if necessary. If you don't want to trust Volviz with root, there
76
+ are other ways to run it. Keep reading.
77
+
78
+ ### Default (Automatic) Mode
79
+
80
+ In Automatic Mode, Volviz scans the first volume it finds and starts a web
81
+ server to show you the output. Volviz will output a URL on the console.
82
+
83
+ > vv
84
+
85
+ This is a shortcut for the `instaweb` command.
86
+
87
+ > vv instaweb
88
+
89
+ You can specify the volume you want to visualize.
90
+
91
+ > vv --volume backup2
92
+
93
+ ### Untrusted (Parse) Mode
94
+
95
+ Volviz only needs root when querying your volumes. If you don't like that,
96
+ you can ask Volviz to parse a text file. First, ask volviz for the correct
97
+ command.
98
+
99
+ > vv show-command
100
+
101
+ You can figure it out yourself easily enough, but you can specify a
102
+ volume if you like.
103
+
104
+ > vv show-command --volume backup2
105
+
106
+ Once you're satisfied that the command Volviz suggests is safe, you can run
107
+ it as root yourself and pipe the output into Volviz.
108
+
109
+ > sudo zfs list -t all -H -r \
110
+ -o name,available,used,referenced,usedbysnapshots,usedbydataset,\
111
+ usedbychildren,compressratio,type \
112
+ tank | vv --parse-file -
113
+
114
+ Or:
115
+
116
+ > sudo zfs list -t all -H -r \
117
+ -o name,available,used,referenced,usedbysnapshots,usedbydataset,\
118
+ usedbychildren,compressratio,type \
119
+ tank > /tmp/zfsdata
120
+ > vv --parse-file /tmp/zfsdata
121
+
122
+ ### Static Mode
123
+
124
+ Volviz's built-in web server is meant for convenience only. You might instead
125
+ want to generate static output that can be served by a webserver of your
126
+ choice.
127
+
128
+ > mkdir /var/www/volviz
129
+ > vv generate --output-path /var/www/volviz
130
+
131
+ You can use a non-default name for the datafile if you like:
132
+
133
+ > vv generate --output-path /var/www/volviz --data-name backup2
134
+
135
+ You can load alternate datafiles in the Volviz UI by appending
136
+ `?data=backup2` to the URL.
137
+
138
+ This might make particular sense if you want to update your data regularly
139
+ from cron.
140
+
141
+ You can combine untrusted and static mode if you like.
142
+
143
+ > vv generate --output-path /var/www/volviz --parse-file /tmp/zfsdata
144
+
145
+ Maybe you have a bunch of hosts and you don't want to install Volviz on
146
+ each one. Generate data to parse and ship it to a central host that has
147
+ Volviz installed, and then use Static Mode to generate the output.
148
+ Assuming you've put all your volume dumps in `/var/lib/volviz/`, here's
149
+ some Bash that might be a good starting point.
150
+
151
+ OUTDIR=/var/www/volviz
152
+ DIR=$OUTDIR/directory.html
153
+ echo "<h1>Volviz</h1><ul>" > $DIR
154
+ for DATAFILE in /var/lib/volviz/*; do
155
+ NAME=`basename $DATAFILE`
156
+ vv generate --output-path $OUTDIR --data-name $NAME --parse-file $DATAFILE
157
+ echo "<li><a href="index.html?data=$NAME">$NAME</a></li>" >> $DIR
158
+ done
159
+ echo "</ul>" > $DIR
160
+
161
+ echo Processing complete. The following URL might work.
162
+ echo http://`hostname`/volviz/directory.html
163
+
164
+ ### Help!
165
+
166
+ > vv help
167
+
168
+ You probably figured that out on your own.
169
+
170
+ ## Notes
171
+
172
+ Volviz ignores any volume, filesystem, or snapshot with a reported size of
173
+ 0 bytes.
174
+
175
+ ## Contributing
176
+
177
+ I'd be thrilled to get your pull request. Boilerplate follows.
178
+
179
+ 1. Fork it ( http://github.com/rleemorlang/volume_visualizer/fork )
180
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
181
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
182
+ 4. Push to the branch (`git push origin my-new-feature`)
183
+ 5. Create new Pull Request
184
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,320 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Volviz - The Volume Visualizer</title>
5
+ <meta charset="utf-8">
6
+ <style>
7
+
8
+ path {
9
+ stroke: #fff;
10
+ fill-rule: evenodd;
11
+ opacity: 0.8;
12
+ }
13
+
14
+ path:hover {
15
+ fill: #f00;
16
+ opacity: 1;
17
+ }
18
+
19
+ svg {
20
+ margin-left: auto;
21
+ margin-right: auto;
22
+ display: block;
23
+ }
24
+
25
+
26
+ .tooltip, .tooltip-pending-removal {
27
+ position: absolute;
28
+ background-color: rgba(255,255,255,1);
29
+ padding: 10px;
30
+ border: 1px solid #ddd;
31
+ z-index: 10000;
32
+ display: inline-block;
33
+ font-family: Arial;
34
+ font-size: 13px;
35
+ border-radius: 10px;
36
+
37
+ pointer-events: none;
38
+
39
+ -webkit-touch-callout: none;
40
+ -webkit-user-select: none;
41
+ user-select: none;
42
+ }
43
+ .tooltip {
44
+
45
+ transition: opacity 500ms linear;
46
+ -webkit-transition: opacity 500ms linear;
47
+
48
+ transition-delay: 500ms;
49
+ -webkit-transition-delay: 500ms;
50
+
51
+ -moz-box-shadow: 4px 4px 8px rgba(0,0,0,.5);
52
+ -webkit-box-shadow: 4px 4px 8px rgba(0,0,0,.5);
53
+ box-shadow: 4px 4px 8px rgba(0,0,0,.5);
54
+
55
+ }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <center id="header">
60
+ <h1>Volume Visualizer</h1>
61
+ <h2>Data for volume "<span id="volname"></span>"</h2>
62
+ </center>
63
+ <script src="http://d3js.org/d3.v3.min.js"></script>
64
+ <script>
65
+
66
+ function urlObject(options) {
67
+ var url_search_arr,
68
+ option_key,
69
+ i,
70
+ urlObj,
71
+ get_param,
72
+ key,
73
+ val,
74
+ url_query,
75
+ url_get_params = {},
76
+ a = document.createElement('a'),
77
+ default_options = {
78
+ 'url': window.location.href,
79
+ 'unescape': true,
80
+ 'convert_num': true
81
+ };
82
+
83
+ if (typeof options !== "object") {
84
+ options = default_options;
85
+ } else {
86
+ for (option_key in default_options) {
87
+ if (default_options.hasOwnProperty(option_key)) {
88
+ if (options[option_key] === undefined) {
89
+ options[option_key] = default_options[option_key];
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ a.href = options.url;
96
+ url_query = a.search.substring(1);
97
+ url_search_arr = url_query.split('&');
98
+
99
+ if (url_search_arr[0].length > 1) {
100
+ for (i = 0; i < url_search_arr.length; i += 1) {
101
+ get_param = url_search_arr[i].split("=");
102
+
103
+ if (options.unescape) {
104
+ key = decodeURI(get_param[0]);
105
+ val = decodeURI(get_param[1]);
106
+ } else {
107
+ key = get_param[0];
108
+ val = get_param[1];
109
+ }
110
+
111
+ if (options.convert_num) {
112
+ if (val.match(/^\d+$/)) {
113
+ val = parseInt(val, 10);
114
+ } else if (val.match(/^\d+\.\d+$/)) {
115
+ val = parseFloat(val);
116
+ }
117
+ }
118
+
119
+ if (url_get_params[key] === undefined) {
120
+ url_get_params[key] = val;
121
+ } else if (typeof url_get_params[key] === "string") {
122
+ url_get_params[key] = [url_get_params[key], val];
123
+ } else {
124
+ url_get_params[key].push(val);
125
+ }
126
+
127
+ get_param = [];
128
+ }
129
+ }
130
+
131
+ urlObj = {
132
+ protocol: a.protocol,
133
+ hostname: a.hostname,
134
+ host: a.host,
135
+ port: a.port,
136
+ hash: a.hash.substr(1),
137
+ pathname: a.pathname,
138
+ search: a.search,
139
+ parameters: url_get_params
140
+ };
141
+
142
+ return urlObj;
143
+ }
144
+ /*****
145
+ * A no frills tooltip implementation.
146
+ *****/
147
+
148
+ (function() {
149
+
150
+ var tooltip = window.tooltip = {}
151
+
152
+ tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
153
+
154
+ var container = d3.select('body').selectAll('.tooltip').data([1])
155
+
156
+ container.enter().append('div').attr('class', 'tooltip ' + (classes ? classes : 'xy-tooltip'))
157
+
158
+ container.html(content)
159
+
160
+ gravity = gravity || 'n'
161
+ dist = dist || 20
162
+
163
+ var body = document.getElementsByTagName('body')[0]
164
+
165
+ var height = parseInt(container[0][0].offsetHeight)
166
+ , width = parseInt(container[0][0].offsetWidth)
167
+ , windowWidth = window.innerWidth
168
+ , windowHeight = window.innerHeight
169
+ , scrollTop = body.scrollTop
170
+ , scrollLeft = body.scrollLeft
171
+ , left = 0
172
+ , top = 0
173
+
174
+
175
+ switch (gravity) {
176
+ case 'e':
177
+ left = pos[0] - width - dist
178
+ top = pos[1] - (height / 2)
179
+ if (left < scrollLeft) left = pos[0] + dist
180
+ if (top < scrollTop) top = scrollTop + 5
181
+ if (top + height > scrollTop + windowHeight) top = scrollTop - height - 5
182
+ break
183
+ case 'w':
184
+ left = pos[0] + dist
185
+ top = pos[1] - (height / 2)
186
+ if (left + width > windowWidth) left = pos[0] - width - dist
187
+ if (top < scrollTop) top = scrollTop + 5
188
+ if (top + height > scrollTop + windowHeight) top = scrollTop - height - 5
189
+ break
190
+ case 's':
191
+ left = pos[0] - (width / 2)
192
+ top = pos[1] + dist
193
+ if (left < scrollLeft) left = scrollLeft + 5
194
+ if (left + width > windowWidth) left = windowWidth - width - 5
195
+ if (top + height > scrollTop + windowHeight) top = pos[1] - height - dist
196
+ break
197
+ case 'n':
198
+ left = pos[0] - (width / 2)
199
+ top = pos[1] - height - dist
200
+ if (left < scrollLeft) left = scrollLeft + 5
201
+ if (left + width > windowWidth) left = windowWidth - width - 5
202
+ if (scrollTop > top) top = pos[1] + 20
203
+ break
204
+ }
205
+
206
+
207
+ container.style('left', left+'px')
208
+ container.style('top', top+'px')
209
+
210
+ return container
211
+ }
212
+
213
+ tooltip.cleanup = function() {
214
+ // Find the tooltips, mark them for removal by this class (so other tooltip functions won't find it)
215
+ var tooltips = d3.selectAll('.tooltip').attr('class','tooltip-pending-removal').transition().duration(250).style('opacity',0).remove()
216
+
217
+ }
218
+ })();
219
+
220
+ var width = 960,
221
+ height = 960,
222
+ radius = Math.min(width, height) / 2 * 0.9;
223
+
224
+ var x = d3.scale.linear()
225
+ .range([0, 2 * Math.PI]);
226
+
227
+ var y = d3.scale.sqrt()
228
+ .range([0, radius]);
229
+
230
+
231
+ var svg = d3.select("body").append("svg")
232
+ .attr("width", width)
233
+ .attr("height", height)
234
+ .append("g")
235
+ .attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
236
+
237
+ var partition = d3.layout.partition()
238
+ .value(function(d) { return d.size; });
239
+
240
+ var arc = d3.svg.arc()
241
+ .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x)))})
242
+ .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)))})
243
+ .innerRadius(function(d) { return Math.max(0, y(d.y)) + 0.5;})
244
+ .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)) - 0.5; });
245
+
246
+ d3.select(self.frameElement).style("height", height + "px");
247
+
248
+ // Interpolate the scales!
249
+ function arcTween(d) {
250
+ var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
251
+ yd = d3.interpolate(y.domain(), [d.y, 1]),
252
+ yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
253
+ return function(d, i) {
254
+ return i
255
+ ? function(t) { return arc(d); }
256
+ : function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
257
+ };
258
+ }
259
+
260
+ colors = {
261
+ "container": "#000099",
262
+ "free": "#cccccc",
263
+ "filesystem": "#0066cc",
264
+ "snapshot": "#3399ff",
265
+ "volume": "#ff6666"
266
+ }
267
+
268
+ var color = d3.scale.category20c();
269
+
270
+ var colors = {
271
+ "filesystem": d3.scale.ordinal().range([
272
+ "#5096ff", "#4c8ef2", "#4887e5", "#4480d9", "#4078cc",
273
+ "#3c70bf", "#3869b2", "#3461a6", "#305a99", "#2c528c"
274
+ ]),
275
+ "container": d3.scale.ordinal().range([
276
+ "#e5e5e5", "#ebebeb", "#e3e3e3", "#dbdbdb"
277
+ ]),
278
+ "snapshot": d3.scale.ordinal().range([
279
+ "#ff5f25", "#f25a23", "#e55521", "#d9511f"
280
+ ]),
281
+ "volume": d3.scale.ordinal().range([
282
+ "#3bf0ff"
283
+ ]),
284
+ "root": function() { return "#ffffff"; },
285
+ "free": function() { return "#fdfdfd"; },
286
+ }
287
+
288
+ var color = function(node) {
289
+ return colors[node.type](node.name);
290
+ };
291
+
292
+ var data = urlObject().parameters.data || "volviz";
293
+ d3.json(data + ".json", function(error, root) {
294
+ document.getElementById("volname").innerHTML = root.name;
295
+
296
+ var g = svg.selectAll("g")
297
+ .data(partition.nodes(root))
298
+ .enter().append("g");
299
+
300
+ var path = g.append("path")
301
+ .attr("d", arc)
302
+ .style("fill", function(d) { return color(d); })
303
+ .on("click", click)
304
+ .on("mouseover", function(d) {
305
+ if (d.type != "root") {
306
+ tooltip.show([d3.event.clientX, d3.event.clientY],
307
+ '<div>'+d.name+'</div><div>'+d.size_human+'</div>')
308
+ };
309
+ });
310
+
311
+ function click(d) {
312
+ path.transition()
313
+ .duration(750)
314
+ .attrTween("d", arcTween(d));
315
+ }
316
+ });
317
+
318
+ </script>
319
+ </body>
320
+ </html>