derailed_benchmarks 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +4 -4
- data/README.md +52 -18
- data/bin/derailed +2 -1
- data/derailed_benchmarks.gemspec +1 -1
- data/lib/derailed_benchmarks.rb +1 -4
- data/lib/derailed_benchmarks/require_tree.rb +19 -1
- data/lib/derailed_benchmarks/tasks.rb +7 -2
- data/lib/derailed_benchmarks/version.rb +1 -1
- data/test/derailed_benchmarks/require_tree_test.rb +42 -3
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f00460799a696f7a1779ea6bbb4a6d0bbb4e0612
|
4
|
+
data.tar.gz: 96d81116a29b5211015581af27c3b69ede4fd125
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8d8d3c5a56696af7327a6ef4fbb927c04956a38dfced8d07ee229209eeb68beb00c047dbc317e0c6cff7a2685170305b65895e8a3d3ddb1c53ee93e4cf1a221
|
7
|
+
data.tar.gz: fdd8db66a4a0f2937b32d31a1ec3dca308c76d80ef337e6a0e817666bf0e127849083e0b594974324627a09dd86fec93e4c0443f89051018a2d02e6047a0e3ca
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# A Log of Changes!
|
2
2
|
|
3
|
+
## [1.0.1] - 2015-20-06
|
4
|
+
|
5
|
+
- `bundle:mem` and similar tasks now keep track of duplicate requires and display them along side of memory requirements. This makes it easier to identify where components are used by multiple libraries
|
6
|
+
- Add rake to gemspec which gets rid of `Unresolved specs during Gem::Specification.reset:` warning
|
7
|
+
- Outputs of memory are now done in [mebibytes](https://en.wikipedia.org/wiki/Mebibyte), a more accurate unit for the value we're measuring (hint: it's what you think MB is).
|
3
8
|
|
4
9
|
## [1.0.0] - 2015-15-05
|
5
10
|
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
derailed_benchmarks (1.0.
|
4
|
+
derailed_benchmarks (1.0.1)
|
5
5
|
benchmark-ips (~> 2)
|
6
6
|
get_process_mem (~> 0)
|
7
7
|
memory_profiler (~> 0)
|
8
8
|
rack (~> 1)
|
9
|
-
rake (~> 10
|
9
|
+
rake (~> 10)
|
10
10
|
|
11
11
|
GEM
|
12
12
|
remote: https://rubygems.org/
|
@@ -40,7 +40,7 @@ GEM
|
|
40
40
|
multi_json (~> 1.0)
|
41
41
|
arel (3.0.3)
|
42
42
|
bcrypt (3.1.10)
|
43
|
-
benchmark-ips (2.
|
43
|
+
benchmark-ips (2.2.0)
|
44
44
|
builder (3.0.4)
|
45
45
|
capybara (2.4.4)
|
46
46
|
mime-types (>= 1.16)
|
@@ -64,7 +64,7 @@ GEM
|
|
64
64
|
mail (2.5.4)
|
65
65
|
mime-types (~> 1.16)
|
66
66
|
treetop (~> 1.4.8)
|
67
|
-
memory_profiler (0.9.
|
67
|
+
memory_profiler (0.9.1)
|
68
68
|
mime-types (1.25.1)
|
69
69
|
mini_portile (0.6.2)
|
70
70
|
multi_json (1.11.0)
|
data/README.md
CHANGED
@@ -40,7 +40,7 @@ You must be using Ruby 2.1+ to install these libraries. If you're on an older ve
|
|
40
40
|
|
41
41
|
## Use
|
42
42
|
|
43
|
-
There are two ways to use benchmark an app. Derailed can either try to boot your web app and run requests against it while benchmarking, or it can
|
43
|
+
There are two ways to use benchmark an app. Derailed can either try to boot your web app and run requests against it while benchmarking, or it can statically give you more information about the dependencies that are in your Gemfile. Booting your app will always be more accurate, but if you cannot get your app to run in production locally, you'll still find the static information useful.
|
44
44
|
|
45
45
|
## Static Benchmarking
|
46
46
|
|
@@ -62,16 +62,21 @@ This will load each of your gems in your Gemfile and see how much memory they co
|
|
62
62
|
|
63
63
|
```
|
64
64
|
$ derailed bundle:mem
|
65
|
-
TOP: 54.1836
|
66
|
-
mail: 18.9688
|
67
|
-
mime/types: 17.4453
|
68
|
-
mail/field: 0.4023
|
69
|
-
mail/message: 0.3906
|
70
|
-
action_view/view_paths: 0.4453
|
71
|
-
action_view/base: 0.4336
|
65
|
+
TOP: 54.1836 MiB
|
66
|
+
mail: 18.9688 MiB
|
67
|
+
mime/types: 17.4453 MiB
|
68
|
+
mail/field: 0.4023 MiB
|
69
|
+
mail/message: 0.3906 MiB
|
70
|
+
action_view/view_paths: 0.4453 MiB
|
71
|
+
action_view/base: 0.4336 MiB
|
72
72
|
```
|
73
73
|
|
74
|
-
|
74
|
+
_Aside: A "MiB", which is the [IEEE] and [IEC] symbol for Mebibyte, is 2<sup>20</sup> bytes / 1024 Kibibytes (which are in turn 1024 bytes)._
|
75
|
+
|
76
|
+
[IEEE]: http://en.wikipedia.org/wiki/IEEE_1541-2002
|
77
|
+
[IEC]: http://en.wikipedia.org/wiki/IEC_80000-13
|
78
|
+
|
79
|
+
Here we can see that `mail` uses 18MiB, with the majority coming from `mime/types`. You can use this information to prune out large dependencies you don't need. Also if you see a large memory use by a gem that you do need, please open up an issue with that library to let them know (be sure to include reproduction instructions). Hopefully as a community we can identify memory hotspots and reduce their impact. Before we can fix performance problems, we need to know where those problems exist.
|
75
80
|
|
76
81
|
By default this task will only return results from the `:default` and `"production"` groups. If you want a different group you can run with.
|
77
82
|
|
@@ -83,6 +88,35 @@ You can use `CUT_OFF=0.3` to only show files that have above a certain memory us
|
|
83
88
|
|
84
89
|
Note: This method won't include files in your own app, only items in your Gemfile. For that you'll need to use `derailed exec mem`. See below for more info.
|
85
90
|
|
91
|
+
The same file may be required by several libraries, since Ruby only requires files once, the cost is only associated with the first library to require a file. To make this more visible duplicate entries will list all the parents they belong to. For example both `mail` and `fog` require `mime/types. So it may show up something like this in your app:
|
92
|
+
|
93
|
+
```
|
94
|
+
$ derailed bundle:mem
|
95
|
+
TOP: 54.1836 MiB
|
96
|
+
mail: 18.9688 MiB
|
97
|
+
mime/types: 17.4453 MiB (Also required by: fog/storage)
|
98
|
+
mail/field: 0.4023 MiB
|
99
|
+
mail/message: 0.3906 MiB
|
100
|
+
```
|
101
|
+
|
102
|
+
That way you'll know that simply removing the top level library (mail) would not result in a memory reduction. The output is trucated after the first two entries:
|
103
|
+
|
104
|
+
|
105
|
+
```
|
106
|
+
fog/core: 0.9844 MiB (Also required by: fog/xml, fog/json, and 48 others)
|
107
|
+
fog/rackspace: 0.957 MiB
|
108
|
+
fog/joyent: 0.7227 MiB
|
109
|
+
fog/joyent/compute: 0.7227 MiB
|
110
|
+
```
|
111
|
+
|
112
|
+
If you want to see everything that requires `fog/core` you can run `CUT_OFF=0 bundle exec derailed bundle:mem` to get the full output that you can then grep through manually.
|
113
|
+
|
114
|
+
Update: While `mime/types` looks horible in these examples, it's been fixed. You can add this to the top of your gemfile for free memory:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
gem 'mime-types', '~> 2.4.3', require: 'mime/types/columnar'
|
118
|
+
```
|
119
|
+
|
86
120
|
### Objects created at Require time
|
87
121
|
|
88
122
|
To get more info about the objects, using [memory_profiler](https://github.com/SamSaffron/memory_profiler), created when your dependencies are required you can run:
|
@@ -105,7 +139,7 @@ allocated memory by gem
|
|
105
139
|
8103432 json-1.8.2
|
106
140
|
```
|
107
141
|
|
108
|
-
Once you identify a gem that creates a large
|
142
|
+
Once you identify a gem that creates a large amount of memory using `$ derailed bundle:mem` you can pull that gem into it's own Gemfile and run `$ derailed bundle:objects` to get detailed information about it. This information can be used by contributors and library authors to identify and eliminate object creation hotspots.
|
109
143
|
|
110
144
|
|
111
145
|
By default this task will only return results from the `:default` and `"production"` groups. If you want a different group you can run with.
|
@@ -202,7 +236,7 @@ PID: 78675
|
|
202
236
|
183.62109375
|
203
237
|
```
|
204
238
|
|
205
|
-
Here we can see that while the memory use is increasing, it levels off around 183
|
239
|
+
Here we can see that while the memory use is increasing, it levels off around 183 MiB. You'll want to run this task using ever increasing values of `TEST_COUNT=` for example
|
206
240
|
|
207
241
|
```
|
208
242
|
$ TEST_COUNT=5000 derailed exec perf:ram_over_time
|
@@ -246,13 +280,13 @@ This task does essentially the same thing, however it hits your app with one req
|
|
246
280
|
```
|
247
281
|
$ derailed exec perf:mem
|
248
282
|
|
249
|
-
TOP: 54.1836
|
250
|
-
mail: 18.9688
|
251
|
-
mime/types: 17.4453
|
252
|
-
mail/field: 0.4023
|
253
|
-
mail/message: 0.3906
|
254
|
-
action_view/view_paths: 0.4453
|
255
|
-
action_view/base: 0.4336
|
283
|
+
TOP: 54.1836 MiB
|
284
|
+
mail: 18.9688 MiB
|
285
|
+
mime/types: 17.4453 MiB
|
286
|
+
mail/field: 0.4023 MiB
|
287
|
+
mail/message: 0.3906 MiB
|
288
|
+
action_view/view_paths: 0.4453 MiB
|
289
|
+
action_view/base: 0.4336 MiB
|
256
290
|
```
|
257
291
|
|
258
292
|
You can use `CUT_OFF=0.3` to only show files that have above a certain memory useage, this can be used to help eliminate noise.
|
data/bin/derailed
CHANGED
@@ -13,7 +13,7 @@ $: << lib
|
|
13
13
|
|
14
14
|
|
15
15
|
require File.join(lib, 'derailed_benchmarks.rb')
|
16
|
-
|
16
|
+
|
17
17
|
Bundler.setup
|
18
18
|
|
19
19
|
require 'thor'
|
@@ -22,6 +22,7 @@ class DerailedBenchmarkCLI < Thor
|
|
22
22
|
|
23
23
|
desc "exec", "executes given derailed benchmark"
|
24
24
|
def exec(task = nil)
|
25
|
+
setup_bundler!
|
25
26
|
require 'derailed_benchmarks'
|
26
27
|
require 'rake'
|
27
28
|
Rake::TaskManager.record_task_metadata = true
|
data/derailed_benchmarks.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.add_dependency "get_process_mem", "~> 0"
|
23
23
|
gem.add_dependency "benchmark-ips", "~> 2"
|
24
24
|
gem.add_dependency "rack", "~> 1"
|
25
|
-
gem.add_dependency "rake", "~> 10
|
25
|
+
gem.add_dependency "rake", "~> 10"
|
26
26
|
|
27
27
|
gem.add_development_dependency "capybara", "~> 2"
|
28
28
|
gem.add_development_dependency "rails", "~> 3"
|
data/lib/derailed_benchmarks.rb
CHANGED
@@ -2,8 +2,11 @@
|
|
2
2
|
# RequireTree.new('get_process_mem')
|
3
3
|
module DerailedBenchmarks
|
4
4
|
class RequireTree
|
5
|
+
REQUIRED_BY = {}
|
6
|
+
|
5
7
|
attr_reader :name
|
6
8
|
attr_accessor :cost
|
9
|
+
attr_accessor :parent
|
7
10
|
|
8
11
|
def initialize(name)
|
9
12
|
@name = name
|
@@ -12,6 +15,8 @@ module DerailedBenchmarks
|
|
12
15
|
|
13
16
|
def <<(tree)
|
14
17
|
@children[tree.name.to_s] = tree
|
18
|
+
tree.parent = self
|
19
|
+
(REQUIRED_BY[tree.name.to_s] ||= []) << self.name
|
15
20
|
end
|
16
21
|
|
17
22
|
def [](name)
|
@@ -32,10 +37,23 @@ module DerailedBenchmarks
|
|
32
37
|
children.sort { |c1, c2| c2.cost <=> c1.cost }
|
33
38
|
end
|
34
39
|
|
40
|
+
def to_string
|
41
|
+
str = "#{name}: #{cost.round(4)} MiB"
|
42
|
+
if parent && REQUIRED_BY[self.name.to_s]
|
43
|
+
names = REQUIRED_BY[self.name.to_s].uniq - [parent.name.to_s]
|
44
|
+
if names.any?
|
45
|
+
str << " (Also required by: #{ names.first(2).join(", ") }"
|
46
|
+
str << ", and #{names.count - 2} others" if names.count > 3
|
47
|
+
str << ")"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
str
|
51
|
+
end
|
52
|
+
|
35
53
|
# Recursively prints all child nodes
|
36
54
|
def print_sorted_children(level = 0, out = STDOUT)
|
37
55
|
return if cost < ENV['CUT_OFF'].to_f
|
38
|
-
out.puts " " * level +
|
56
|
+
out.puts " " * level + self.to_string
|
39
57
|
level += 1
|
40
58
|
sorted_children.each do |child|
|
41
59
|
child.print_sorted_children(level, out)
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
namespace :perf do
|
3
2
|
task :rails_load do
|
4
3
|
ENV["RAILS_ENV"] ||= "production"
|
@@ -67,6 +66,9 @@ namespace :perf do
|
|
67
66
|
PATH_TO_HIT = ENV["PATH_TO_HIT"] || ENV['ENDPOINT'] || "/"
|
68
67
|
puts "Endpoint: #{ PATH_TO_HIT.inspect }"
|
69
68
|
|
69
|
+
require 'rack/test'
|
70
|
+
require 'rack/file'
|
71
|
+
|
70
72
|
DERAILED_APP = DerailedBenchmarks.add_auth(DERAILED_APP)
|
71
73
|
if server = ENV["USE_SERVER"]
|
72
74
|
@port = (3000..3900).to_a.sample
|
@@ -130,7 +132,7 @@ namespace :perf do
|
|
130
132
|
task :mem => [:kernel_require_patch, :setup] do
|
131
133
|
puts "## Impact of `require <file>` on RAM"
|
132
134
|
puts
|
133
|
-
puts "Showing all `require <file>` calls that consume #{ENV['CUT_OFF']}
|
135
|
+
puts "Showing all `require <file>` calls that consume #{ENV['CUT_OFF']} MiB or more of RSS"
|
134
136
|
puts "Configure with `CUT_OFF=0` for all entries or `CUT_OFF=5` for few entries"
|
135
137
|
|
136
138
|
puts "Note: Files only count against RAM on their first load."
|
@@ -145,6 +147,7 @@ namespace :perf do
|
|
145
147
|
|
146
148
|
desc "outputs ram usage over time"
|
147
149
|
task :ram_over_time => [:setup] do
|
150
|
+
require 'get_process_mem'
|
148
151
|
puts "PID: #{Process.pid}"
|
149
152
|
ram = GetProcessMem.new
|
150
153
|
@keep_going = true
|
@@ -177,6 +180,8 @@ namespace :perf do
|
|
177
180
|
|
178
181
|
desc "iterations per second"
|
179
182
|
task :ips => [:setup] do
|
183
|
+
require 'benchmark/ips'
|
184
|
+
|
180
185
|
Benchmark.ips do |x|
|
181
186
|
x.report("ips") { call_app }
|
182
187
|
end
|
@@ -6,6 +6,10 @@ class RequireTree < ActiveSupport::TestCase
|
|
6
6
|
DerailedBenchmarks::RequireTree.new(name)
|
7
7
|
end
|
8
8
|
|
9
|
+
def teardown
|
10
|
+
DerailedBenchmarks::RequireTree.const_set("REQUIRED_BY", {})
|
11
|
+
end
|
12
|
+
|
9
13
|
test "default_cost" do
|
10
14
|
parent = tree("parent")
|
11
15
|
assert_equal 0, parent.cost
|
@@ -43,9 +47,44 @@ class RequireTree < ActiveSupport::TestCase
|
|
43
47
|
assert_equal expected, parent.sorted_children
|
44
48
|
|
45
49
|
expected = <<-OUT
|
46
|
-
parent: #{ parent.cost.round(4) }
|
47
|
-
large: #{ large.cost.round(4) }
|
48
|
-
small: #{ small.cost.round(4) }
|
50
|
+
parent: #{ parent.cost.round(4) } MiB
|
51
|
+
large: #{ large.cost.round(4) } MiB
|
52
|
+
small: #{ small.cost.round(4) } MiB
|
53
|
+
OUT
|
54
|
+
capture = StringIO.new
|
55
|
+
|
56
|
+
parent.print_sorted_children(0, capture)
|
57
|
+
|
58
|
+
assert_equal expected, capture.string
|
59
|
+
end
|
60
|
+
|
61
|
+
test "attributes duplicate children" do
|
62
|
+
parent = tree("parent")
|
63
|
+
parent.cost = rand(5..10)
|
64
|
+
small = tree("small")
|
65
|
+
small.cost = rand(10..100)
|
66
|
+
|
67
|
+
large = tree("large")
|
68
|
+
large.cost = small.cost + 1
|
69
|
+
|
70
|
+
dup = tree("large")
|
71
|
+
dup.cost = 0.4
|
72
|
+
small << dup
|
73
|
+
|
74
|
+
parent << small
|
75
|
+
parent << large
|
76
|
+
|
77
|
+
expected = [large, small]
|
78
|
+
assert_equal expected, parent.sorted_children
|
79
|
+
|
80
|
+
expected = [dup]
|
81
|
+
assert_equal expected, small.sorted_children
|
82
|
+
|
83
|
+
expected = <<-OUT
|
84
|
+
parent: #{ parent.cost.round(4) } MiB
|
85
|
+
large: #{ large.cost.round(4) } MiB (Also required by: small)
|
86
|
+
small: #{ small.cost.round(4) } MiB
|
87
|
+
large: #{ dup.cost.round(4) } MiB (Also required by: parent)
|
49
88
|
OUT
|
50
89
|
capture = StringIO.new
|
51
90
|
parent.print_sorted_children(0, capture)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: derailed_benchmarks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Schneeman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: memory_profiler
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '10
|
75
|
+
version: '10'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '10
|
82
|
+
version: '10'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: capybara
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|