volume_visualizer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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>