estackprof 0.1.0
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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +28 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rubocop.yml +30 -0
- data/.ruby-version +1 -0
- data/Gemfile +24 -0
- data/Guardfile +50 -0
- data/README.md +150 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/estackprof.gemspec +35 -0
- data/example/app.rb +26 -0
- data/exe/estackprof +6 -0
- data/flamegraph.html +153 -0
- data/lib/estackprof/cli.rb +74 -0
- data/lib/estackprof/flamegraph.rb +24 -0
- data/lib/estackprof/list.rb +30 -0
- data/lib/estackprof/middleware.rb +26 -0
- data/lib/estackprof/report.rb +73 -0
- data/lib/estackprof/top.rb +25 -0
- data/lib/estackprof/version.rb +5 -0
- data/lib/estackprof.rb +14 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2fb2e9ab5ae1157f4f88c4ddf9091aba225e97036725c1641598df88a9959c91
|
4
|
+
data.tar.gz: b9b994ef293a0f97df4a1a2c72395fd2d2765e7023ce4cf4e86e19898137bf12
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8e98f8ebe4b4051c418e6f75d7f93aab8a40b0d6a1cf9f02d24d00b0ae5909371a020c1c59d76a3de243cbe0f0b16c98d18627ac40d4977c6870cbeccc7fff63
|
7
|
+
data.tar.gz: 0e5de4a47cd8217c29b4fd8956796a14674bfab44992b7e58e9152e619d870bd20ef343cec94dc6e4865dbc1e2c534f11031b008791f416a49db712748d88b2d
|
@@ -0,0 +1,28 @@
|
|
1
|
+
version: 2.1
|
2
|
+
jobs:
|
3
|
+
build:
|
4
|
+
docker:
|
5
|
+
- image: ruby:3.0.1
|
6
|
+
steps:
|
7
|
+
- checkout
|
8
|
+
- restore_cache:
|
9
|
+
name: Restore bundle cache
|
10
|
+
keys:
|
11
|
+
- bundle-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "estackprof.gemspec" }}
|
12
|
+
- bundle-{{ arch }}-{{ checksum "Gemfile" }}
|
13
|
+
- bundle-{{ arch }}
|
14
|
+
- bundle
|
15
|
+
- run:
|
16
|
+
name: Bundle Install
|
17
|
+
command: bundle install
|
18
|
+
- save_cache:
|
19
|
+
name: Store bundle cache
|
20
|
+
key: bundle-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "estackprof.gemspec" }}
|
21
|
+
paths:
|
22
|
+
- vendor/bundle
|
23
|
+
- run:
|
24
|
+
name: Rubocop
|
25
|
+
command: bundle exec rubocop
|
26
|
+
- run:
|
27
|
+
name: RSpec
|
28
|
+
command: bundle exec rspec
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# The behavior of RuboCop can be controlled via the .rubocop.yml
|
2
|
+
# configuration file. It makes it possible to enable/disable
|
3
|
+
# certain cops (checks) and to alter their behavior if they accept
|
4
|
+
# any parameters. The file can be placed either in your home
|
5
|
+
# directory or in some project directory.
|
6
|
+
#
|
7
|
+
# RuboCop will start looking for the configuration file in the directory
|
8
|
+
# where the inspected file is and continue its way up to the root directory.
|
9
|
+
#
|
10
|
+
# See https://docs.rubocop.org/rubocop/configuration
|
11
|
+
require:
|
12
|
+
- rubocop-rspec
|
13
|
+
- rubocop-rake
|
14
|
+
|
15
|
+
AllCops:
|
16
|
+
NewCops: enable
|
17
|
+
TargetRubyVersion: 2.5
|
18
|
+
|
19
|
+
Style/Documentation:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Metrics/BlockLength:
|
23
|
+
Exclude:
|
24
|
+
- "spec/**/*.rb"
|
25
|
+
|
26
|
+
RSpec/MessageSpies:
|
27
|
+
EnforcedStyle: receive
|
28
|
+
|
29
|
+
Security/MarshalLoad:
|
30
|
+
Enabled: false
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.0.1
|
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in estackprof.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem 'rake', '~> 13.0'
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem 'guard-rubocop'
|
12
|
+
gem 'pry'
|
13
|
+
gem 'rubocop'
|
14
|
+
gem 'rubocop-rake'
|
15
|
+
gem 'rubocop-rspec'
|
16
|
+
gem 'sinatra'
|
17
|
+
gem 'webrick'
|
18
|
+
end
|
19
|
+
|
20
|
+
group :test do
|
21
|
+
gem 'aruba', '~> 2.0.0'
|
22
|
+
gem 'guard-rspec'
|
23
|
+
gem 'rspec', '~> 3.0'
|
24
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A sample Guardfile
|
4
|
+
# More info at https://github.com/guard/guard#readme
|
5
|
+
|
6
|
+
## Uncomment and set this to only include directories you want to watch
|
7
|
+
# directories %w(app lib config test spec features) \
|
8
|
+
# .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
9
|
+
|
10
|
+
## Note: if you are using the `directories` clause above and you are not
|
11
|
+
## watching the project directory ('.'), then you will want to move
|
12
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
13
|
+
#
|
14
|
+
# $ mkdir config
|
15
|
+
# $ mv Guardfile config/
|
16
|
+
# $ ln -s config/Guardfile .
|
17
|
+
#
|
18
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
19
|
+
|
20
|
+
# NOTE: The cmd option is now required due to the increasing number of ways
|
21
|
+
# rspec may be run, below are examples of the most common uses.
|
22
|
+
# * bundler: 'bundle exec rspec'
|
23
|
+
# * bundler binstubs: 'bin/rspec'
|
24
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
25
|
+
# installed the spring binstubs per the docs)
|
26
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
27
|
+
# * 'just' rspec: 'rspec'
|
28
|
+
|
29
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
30
|
+
require 'guard/rspec/dsl'
|
31
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
32
|
+
|
33
|
+
# Feel free to open issues for suggestions and improvements
|
34
|
+
|
35
|
+
# RSpec files
|
36
|
+
rspec = dsl.rspec
|
37
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
38
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
39
|
+
watch(rspec.spec_files)
|
40
|
+
|
41
|
+
# Ruby files
|
42
|
+
ruby = dsl.ruby
|
43
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
44
|
+
dsl.watch_spec_files_for(%r{^(exe/.+)\.rb$})
|
45
|
+
end
|
46
|
+
|
47
|
+
guard :rubocop, cli: %w[-a] do
|
48
|
+
watch(/.+\.rb$/)
|
49
|
+
watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
|
50
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
# Estackprof
|
2
|
+
|
3
|
+
Estackprof is a wrapper to make it easier to use [Stackprof](https://github.com/tmm1/stackprof) in your rack application.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'estackprof'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install estackprof
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Profiling
|
24
|
+
|
25
|
+
Add the following code to your rack application to enable the rack middleware.
|
26
|
+
`Estackprof::Middleware` supports the same options as `StackProf::Middleware`.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
require 'estackprof'
|
30
|
+
|
31
|
+
use Estackprof::Middleware
|
32
|
+
|
33
|
+
# ...your rack application
|
34
|
+
```
|
35
|
+
|
36
|
+
### Reporting
|
37
|
+
|
38
|
+
Use the CLI to report.
|
39
|
+
|
40
|
+
```sh
|
41
|
+
# Report the top 3 frames
|
42
|
+
$ estackprof top -l 3
|
43
|
+
==================================
|
44
|
+
Mode: cpu(1000)
|
45
|
+
Samples: 516 (0.00% miss rate)
|
46
|
+
GC: 77 (14.92%)
|
47
|
+
==================================
|
48
|
+
TOTAL (pct) SAMPLES (pct) FRAME
|
49
|
+
434 (84.1%) 434 (84.1%) Object#bubble_sort(example/app.rb:9)
|
50
|
+
45 (8.7%) 45 (8.7%) (sweeping)
|
51
|
+
31 (6.0%) 31 (6.0%) (marking)
|
52
|
+
|
53
|
+
# Report the top 3 cumlative frames
|
54
|
+
$ estackprof top -l 3 -c
|
55
|
+
==================================
|
56
|
+
Mode: cpu(1000)
|
57
|
+
Samples: 516 (0.00% miss rate)
|
58
|
+
GC: 77 (14.92%)
|
59
|
+
==================================
|
60
|
+
TOTAL (pct) SAMPLES (pct) FRAME
|
61
|
+
439 (85.1%) 0 (0.0%) Rack::MethodOverride#call(vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/method_override.rb:15)
|
62
|
+
439 (85.1%) 0 (0.0%) Sinatra::ExtendedRack#call(vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:215)
|
63
|
+
439 (85.1%) 0 (0.0%) Sinatra::Wrapper#call(vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1990)
|
64
|
+
|
65
|
+
# Report the top frames filtered by patten(file name).
|
66
|
+
$ estackprof top -p app.rb
|
67
|
+
==================================
|
68
|
+
Mode: cpu(1000)
|
69
|
+
Samples: 516 (0.00% miss rate)
|
70
|
+
GC: 77 (14.92%)
|
71
|
+
==================================
|
72
|
+
TOTAL (pct) SAMPLES (pct) FRAME
|
73
|
+
434 (84.1%) 434 (84.1%) Object#bubble_sort(example/app.rb:9)
|
74
|
+
438 (84.9%) 0 (0.0%) block in <main>(example/app.rb:23)
|
75
|
+
|
76
|
+
# Report the top frames filtered by patten(method name).
|
77
|
+
$ estackprof top -p bubble
|
78
|
+
==================================
|
79
|
+
Mode: cpu(1000)
|
80
|
+
Samples: 516 (0.00% miss rate)
|
81
|
+
GC: 77 (14.92%)
|
82
|
+
==================================
|
83
|
+
TOTAL (pct) SAMPLES (pct) FRAME
|
84
|
+
434 (84.1%) 434 (84.1%) Object#bubble_sort(example/app.rb:9)
|
85
|
+
|
86
|
+
# Report the list in the specified file.
|
87
|
+
$ estackprof list -f app.rb
|
88
|
+
| 1 | # frozen_string_literal: true
|
89
|
+
| 2 |
|
90
|
+
| 3 | require 'sinatra'
|
91
|
+
| 4 | require 'json'
|
92
|
+
| 5 | require 'estackprof'
|
93
|
+
| 6 |
|
94
|
+
| 7 | use Estackprof::Middleware
|
95
|
+
| 8 |
|
96
|
+
| 9 | def bubble_sort(array)
|
97
|
+
| 10 | ary = array.dup
|
98
|
+
| 11 | pos_max = ary.size - 1
|
99
|
+
| 12 |
|
100
|
+
434 (84.1%) | 13 | (0...pos_max).each do |n|
|
101
|
+
433 (83.9%) | 14 | (0...(pos_max - n)).each do |ix|
|
102
|
+
| 15 | iy = ix + 1
|
103
|
+
139 (26.9%) / 139 (26.9%) | 16 | ary[ix], ary[iy] = ary[iy], ary[ix] if ary[ix] > ary[iy]
|
104
|
+
294 (57.0%) / 294 (57.0%) | 17 | end
|
105
|
+
1 (0.2%) / 1 (0.2%) | 18 | end
|
106
|
+
| 19 |
|
107
|
+
| 20 | ary
|
108
|
+
| 21 | end
|
109
|
+
| 22 |
|
110
|
+
| 23 | get '/' do
|
111
|
+
| 24 | array = Array.new(1000) { rand(10_000) }
|
112
|
+
438 (84.9%) | 25 | bubble_sort(array).to_s
|
113
|
+
| 26 | end
|
114
|
+
|
115
|
+
# Report the list in the specified method.
|
116
|
+
$ estackprof list -m bubble
|
117
|
+
Object#bubble_sort (/estackprof/example/app.rb:9)
|
118
|
+
samples: 434 self (84.1%) / 434 total (84.1%)
|
119
|
+
callers:
|
120
|
+
867 ( 199.8%) Range#each
|
121
|
+
434 ( 100.0%) block in <main>
|
122
|
+
callees (0 total):
|
123
|
+
867 ( Inf%) Range#each
|
124
|
+
code:
|
125
|
+
| 9 | def bubble_sort(array)
|
126
|
+
| 10 | ary = array.dup
|
127
|
+
| 11 | pos_max = ary.size - 1
|
128
|
+
| 12 |
|
129
|
+
434 (84.1%) | 13 | (0...pos_max).each do |n|
|
130
|
+
433 (83.9%) | 14 | (0...(pos_max - n)).each do |ix|
|
131
|
+
| 15 | iy = ix + 1
|
132
|
+
139 (26.9%) / 139 (26.9%) | 16 | ary[ix], ary[iy] = ary[iy], ary[ix] if ary[ix] > ary[iy]
|
133
|
+
294 (57.0%) / 294 (57.0%) | 17 | end
|
134
|
+
1 (0.2%) / 1 (0.2%) | 18 | end
|
135
|
+
| 19 |
|
136
|
+
|
137
|
+
# Display the flamegraph
|
138
|
+
$ estackprof flamegraph
|
139
|
+
#=> Open flamegraph in your browser.
|
140
|
+
```
|
141
|
+
|
142
|
+
## Development
|
143
|
+
|
144
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
145
|
+
|
146
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
147
|
+
|
148
|
+
## Contributing
|
149
|
+
|
150
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/estackprof.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'estackprof'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/estackprof.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/estackprof/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'estackprof'
|
7
|
+
spec.version = Estackprof::VERSION
|
8
|
+
spec.authors = ['Yuhei Okazaki']
|
9
|
+
spec.email = ['okazaki@fusic.co.jp']
|
10
|
+
|
11
|
+
spec.summary = 'We want to make stackprof easier to use.'
|
12
|
+
spec.description = 'Estackprof is a wrapper to make it easier to use Stackprof in your rack application'
|
13
|
+
spec.homepage = 'https://github.com/fusic/estackprof'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
|
15
|
+
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
17
|
+
spec.metadata['source_code_uri'] = 'https://github.com/fusic/estackprof'
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
spec.bindir = 'exe'
|
25
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ['lib']
|
27
|
+
|
28
|
+
# Uncomment to register a new dependency of your gem
|
29
|
+
spec.add_dependency 'launchy'
|
30
|
+
spec.add_dependency 'stackprof', '~> 0.2'
|
31
|
+
spec.add_dependency 'thor', '~> 1.1.0'
|
32
|
+
|
33
|
+
# For more information and examples about making a new gem, checkout our
|
34
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
35
|
+
end
|
data/example/app.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sinatra'
|
4
|
+
require 'json'
|
5
|
+
require 'estackprof'
|
6
|
+
|
7
|
+
use Estackprof::Middleware
|
8
|
+
|
9
|
+
def bubble_sort(array)
|
10
|
+
ary = array.dup
|
11
|
+
pos_max = ary.size - 1
|
12
|
+
|
13
|
+
(0...pos_max).each do |n|
|
14
|
+
(0...(pos_max - n)).each do |ix|
|
15
|
+
iy = ix + 1
|
16
|
+
ary[ix], ary[iy] = ary[iy], ary[ix] if ary[ix] > ary[iy]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
ary
|
21
|
+
end
|
22
|
+
|
23
|
+
get '/' do
|
24
|
+
array = Array.new(1000) { rand(10_000) }
|
25
|
+
bubble_sort(array).to_s
|
26
|
+
end
|
data/exe/estackprof
ADDED
data/flamegraph.html
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
|
8
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
9
|
+
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.css">
|
10
|
+
|
11
|
+
<style>
|
12
|
+
|
13
|
+
/* Space out content a bit */
|
14
|
+
body {
|
15
|
+
padding-top: 20px;
|
16
|
+
padding-bottom: 20px;
|
17
|
+
}
|
18
|
+
|
19
|
+
/* Custom page header */
|
20
|
+
.header {
|
21
|
+
padding-bottom: 20px;
|
22
|
+
padding-right: 15px;
|
23
|
+
padding-left: 15px;
|
24
|
+
border-bottom: 1px solid #e5e5e5;
|
25
|
+
}
|
26
|
+
|
27
|
+
/* Make the masthead heading the same height as the navigation */
|
28
|
+
.header h3 {
|
29
|
+
margin-top: 0;
|
30
|
+
margin-bottom: 0;
|
31
|
+
line-height: 40px;
|
32
|
+
}
|
33
|
+
|
34
|
+
/* Customize container */
|
35
|
+
.container {
|
36
|
+
max-width: 990px;
|
37
|
+
}
|
38
|
+
|
39
|
+
address {
|
40
|
+
text-align: right;
|
41
|
+
}
|
42
|
+
</style>
|
43
|
+
|
44
|
+
<title>stackprof (mode: cpu)</title>
|
45
|
+
|
46
|
+
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
47
|
+
<!--[if lt IE 9]>
|
48
|
+
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
49
|
+
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
50
|
+
<![endif]-->
|
51
|
+
</head>
|
52
|
+
<body>
|
53
|
+
<div class="container">
|
54
|
+
<div class="header clearfix">
|
55
|
+
<nav>
|
56
|
+
<div class="pull-right">
|
57
|
+
<form class="form-inline" id="form">
|
58
|
+
<a class="btn" href="javascript: resetZoom();">Reset zoom</a>
|
59
|
+
<a class="btn" href="javascript: clear();">Clear</a>
|
60
|
+
<div class="form-group">
|
61
|
+
<input type="text" class="form-control" id="term">
|
62
|
+
</div>
|
63
|
+
<a class="btn btn-primary" href="javascript: search();">Search</a>
|
64
|
+
</form>
|
65
|
+
</div>
|
66
|
+
</nav>
|
67
|
+
<h3 class="text-muted">stackprof (mode: cpu)</h3>
|
68
|
+
</div>
|
69
|
+
<div id="chart">
|
70
|
+
</div>
|
71
|
+
<address>
|
72
|
+
powered by <a href="https://github.com/spiermar/d3-flame-graph">d3-flame-graph</a>
|
73
|
+
</address>
|
74
|
+
<hr>
|
75
|
+
<div id="details">
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
|
79
|
+
<!-- D3.js -->
|
80
|
+
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
|
81
|
+
|
82
|
+
<!-- d3-tip -->
|
83
|
+
<script type="text/javascript" src=https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js></script>
|
84
|
+
|
85
|
+
<!-- d3-flamegraph -->
|
86
|
+
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.min.js"></script>
|
87
|
+
|
88
|
+
<script type="text/javascript">
|
89
|
+
var flameGraph = d3.flamegraph()
|
90
|
+
.width(960)
|
91
|
+
.cellHeight(18)
|
92
|
+
.transitionDuration(750)
|
93
|
+
.minFrameSize(5)
|
94
|
+
.transitionEase(d3.easeCubic)
|
95
|
+
.sort(true)
|
96
|
+
//Example to sort in reverse order
|
97
|
+
//.sort(function(a,b){ return d3.descending(a.name, b.name);})
|
98
|
+
.title("")
|
99
|
+
.onClick(onClick)
|
100
|
+
.differential(false)
|
101
|
+
.selfValue(false);
|
102
|
+
|
103
|
+
|
104
|
+
// Example on how to use custom tooltips using d3-tip.
|
105
|
+
// var tip = d3.tip()
|
106
|
+
// .direction("s")
|
107
|
+
// .offset([8, 0])
|
108
|
+
// .attr('class', 'd3-flame-graph-tip')
|
109
|
+
// .html(function(d) { return "name: " + d.data.name + ", value: " + d.data.value; });
|
110
|
+
|
111
|
+
// flameGraph.tooltip(tip);
|
112
|
+
|
113
|
+
var details = document.getElementById("details");
|
114
|
+
flameGraph.setDetailsElement(details);
|
115
|
+
|
116
|
+
// Example on how to use custom labels
|
117
|
+
// var label = function(d) {
|
118
|
+
// return "name: " + d.name + ", value: " + d.value;
|
119
|
+
// }
|
120
|
+
// flameGraph.label(label);
|
121
|
+
|
122
|
+
// Example of how to set fixed chart height
|
123
|
+
// flameGraph.height(540);
|
124
|
+
|
125
|
+
d3.select("#chart")
|
126
|
+
.datum({"name":"<root>","value":278,"children":[{"name":"(garbage collection) : ","value":36,"children":[{"name":"(marking) : ","value":12,"children":[]},{"name":"(sweeping) : ","value":24,"children":[]}]},{"name":"WEBrick::GenericServer#start_thread : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/webrick-1.7.0/lib/webrick/server.rb","value":241,"children":[{"name":"WEBrick::HTTPServer#run : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/webrick-1.7.0/lib/webrick/httpserver.rb","value":241,"children":[{"name":"WEBrick::HTTPServer#service : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/webrick-1.7.0/lib/webrick/httpserver.rb","value":241,"children":[{"name":"Rack::Handler::WEBrick#service : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/handler/webrick.rb","value":241,"children":[{"name":"Sinatra::Base.call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::Base.synchronize : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::Base.call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::Wrapper#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::ExtendedRack#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::ShowExceptions#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/show_exceptions.rb","value":241,"children":[{"name":"Rack::MethodOverride#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/method_override.rb","value":241,"children":[{"name":"Rack::Head#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/head.rb","value":241,"children":[{"name":"Sinatra::CommonLogger#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Rack::CommonLogger#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Rack::CommonLogger#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/common_logger.rb","value":241,"children":[{"name":"Rack::Logger#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/logger.rb","value":241,"children":[{"name":"Rack::Protection::FrameOptions#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/frame_options.rb","value":241,"children":[{"name":"Rack::Protection::Base#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb","value":241,"children":[{"name":"Rack::Protection::Base#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/base.rb","value":241,"children":[{"name":"Rack::Protection::JsonCsrf#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/json_csrf.rb","value":241,"children":[{"name":"Rack::Protection::PathTraversal#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/path_traversal.rb","value":241,"children":[{"name":"Rack::Protection::XSSHeader#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-protection-2.1.0/lib/rack/protection/xss_header.rb","value":241,"children":[{"name":"StackProf::Middleware#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/stackprof-0.2.17/lib/stackprof/middleware.rb","value":241,"children":[{"name":"Sinatra::Base#call : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::Base#call! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::Base#invoke : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Kernel#catch : <cfunc>","value":241,"children":[{"name":"Sinatra::Base#invoke : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::Base#call! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::Base#dispatch! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":241,"children":[{"name":"Sinatra::Request#params : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":1,"children":[{"name":"Rack::Request#params : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/request.rb","value":1,"children":[{"name":"Rack::Request::Helpers#params : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/request.rb","value":1,"children":[{"name":"Rack::Request::Helpers#POST : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/request.rb","value":1,"children":[{"name":"Rack::Request::Helpers#form_data? : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/request.rb","value":1,"children":[{"name":"Rack::Request::Helpers#media_type : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/request.rb","value":1,"children":[{"name":"Kernel#require : <cfunc>","value":1,"children":[{"name":"<top (required)> : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/media_type.rb","value":1,"children":[{"name":"<module:Rack> : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/media_type.rb","value":1,"children":[{"name":"Class#inherited : <cfunc>","value":1,"children":[]}]}]}]}]}]}]}]}]}]},{"name":"Sinatra::Base#invoke : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":240,"children":[{"name":"Kernel#catch : <cfunc>","value":238,"children":[{"name":"Sinatra::Base#invoke : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Sinatra::Base#dispatch! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Sinatra::Base#route! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Array#each : <cfunc>","value":238,"children":[{"name":"Sinatra::Base#route! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Sinatra::Base#process_route : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Kernel#catch : <cfunc>","value":238,"children":[{"name":"Sinatra::Base#process_route : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Sinatra::Base#route! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Sinatra::Base#route_eval : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Sinatra::Base#route! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Sinatra::Base.compile! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":238,"children":[{"name":"Method#call : <cfunc>","value":238,"children":[{"name":"block in <main> : /Users/yokazaki/src/github.com/fusic/estackprof/example/app.rb","value":238,"children":[{"name":"Object#bubble_sort : /Users/yokazaki/src/github.com/fusic/estackprof/example/app.rb","value":237,"children":[{"name":"Range#each : <cfunc>","value":237,"children":[{"name":"Object#bubble_sort : /Users/yokazaki/src/github.com/fusic/estackprof/example/app.rb","value":237,"children":[{"name":"Range#each : <cfunc>","value":237,"children":[{"name":"Object#bubble_sort : /Users/yokazaki/src/github.com/fusic/estackprof/example/app.rb","value":237,"children":[]}]}]}]}]},{"name":"Array#inspect : <cfunc>","value":1,"children":[{"name":"Integer#to_s : <cfunc>","value":1,"children":[]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]},{"name":"Sinatra::Helpers#body : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":2,"children":[{"name":"Kernel#require : <cfunc>","value":1,"children":[{"name":"<top (required)> : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/file.rb","value":1,"children":[{"name":"Kernel#require_relative : <cfunc>","value":1,"children":[]}]}]},{"name":"Kernel#block_given? : <cfunc>","value":1,"children":[]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]},{"name":"<main> : example/app.rb","value":1,"children":[{"name":"block in <module:Sinatra> : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/main.rb","value":1,"children":[{"name":"Sinatra::Base.run! : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":1,"children":[{"name":"Sinatra::Base.start_server : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/sinatra-2.1.0/lib/sinatra/base.rb","value":1,"children":[{"name":"Rack::Handler::WEBrick.run : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/handler/webrick.rb","value":1,"children":[{"name":"WEBrick::GenericServer#start : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/webrick-1.7.0/lib/webrick/server.rb","value":1,"children":[{"name":"WEBrick::SimpleServer.start : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/webrick-1.7.0/lib/webrick/server.rb","value":1,"children":[{"name":"WEBrick::GenericServer#start : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/webrick-1.7.0/lib/webrick/server.rb","value":1,"children":[{"name":"Array#each : <cfunc>","value":1,"children":[{"name":"WEBrick::GenericServer#start : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/webrick-1.7.0/lib/webrick/server.rb","value":1,"children":[{"name":"WEBrick::GenericServer#start_thread : /Users/yokazaki/src/github.com/fusic/estackprof/vendor/bundle/ruby/3.0.0/gems/webrick-1.7.0/lib/webrick/server.rb","value":1,"children":[{"name":"Thread.start : <cfunc>","value":1,"children":[]}]}]}]}]}]}]}]}]}]}]}]}]})
|
127
|
+
.call(flameGraph);
|
128
|
+
|
129
|
+
document.getElementById("form").addEventListener("submit", function(event){
|
130
|
+
event.preventDefault();
|
131
|
+
search();
|
132
|
+
});
|
133
|
+
|
134
|
+
function search() {
|
135
|
+
var term = document.getElementById("term").value;
|
136
|
+
flameGraph.search(term);
|
137
|
+
}
|
138
|
+
|
139
|
+
function clear() {
|
140
|
+
document.getElementById('term').value = '';
|
141
|
+
flameGraph.clear();
|
142
|
+
}
|
143
|
+
|
144
|
+
function resetZoom() {
|
145
|
+
flameGraph.resetZoom();
|
146
|
+
}
|
147
|
+
|
148
|
+
function onClick(d) {
|
149
|
+
console.info("Clicked on " + d.data.name);
|
150
|
+
}
|
151
|
+
</script>
|
152
|
+
</body>
|
153
|
+
</html>
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'pry'
|
5
|
+
require 'estackprof'
|
6
|
+
|
7
|
+
module Estackprof
|
8
|
+
class CLI < Thor
|
9
|
+
class_option :debug, type: :boolean, aliases: '-d', desc: 'debug mode'
|
10
|
+
|
11
|
+
desc 'top [OPTIONS] [FILE...]', 'Report to top of methods'
|
12
|
+
option :limit, aliases: '-l', desc: 'Limit reports.', type: :numeric
|
13
|
+
option :pattern, aliases: '-p', desc: 'Filter reports by pattern match.'
|
14
|
+
option :cumlative, aliases: '-c', desc: 'Sort by cumulative count.', type: :boolean
|
15
|
+
def top(*files)
|
16
|
+
puts Estackprof.top(
|
17
|
+
files: files.empty? ? Dir.glob('./tmp/*.dump') : files,
|
18
|
+
options: options
|
19
|
+
)
|
20
|
+
exit
|
21
|
+
rescue StandardError => e
|
22
|
+
output_error_if_debug_mode(e)
|
23
|
+
exit(-1)
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'list [OPTIONS] [FILE...]', 'Display sample counts of each line'
|
27
|
+
option :file, aliases: '-f', desc: 'Filter by file name.'
|
28
|
+
option :method, aliases: '-m', desc: 'Filter by method name'
|
29
|
+
def list(*files)
|
30
|
+
puts Estackprof.list(
|
31
|
+
files: files.empty? ? Dir.glob('./tmp/*.dump') : files,
|
32
|
+
options: options
|
33
|
+
)
|
34
|
+
exit
|
35
|
+
rescue StandardError => e
|
36
|
+
output_error_if_debug_mode(e)
|
37
|
+
exit(-1)
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'flamegraph [OPTIONS] [FILE]', 'Generate and open flamegraph'
|
41
|
+
def flamegraph(*files)
|
42
|
+
puts Estackprof.flamegraph(
|
43
|
+
files: files.empty? ? Dir.glob('./tmp/*.dump') : files
|
44
|
+
)
|
45
|
+
exit
|
46
|
+
rescue StandardError => e
|
47
|
+
output_error_if_debug_mode(e)
|
48
|
+
exit(-1)
|
49
|
+
end
|
50
|
+
|
51
|
+
map %w[--version -v] => :version
|
52
|
+
desc 'version', 'version'
|
53
|
+
def version
|
54
|
+
puts Estackprof::VERSION
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def output_error_if_debug_mode(exception)
|
60
|
+
return unless options[:debug]
|
61
|
+
|
62
|
+
warn(exception.message)
|
63
|
+
warn(exception.backtrace)
|
64
|
+
end
|
65
|
+
|
66
|
+
class << self
|
67
|
+
private
|
68
|
+
|
69
|
+
def exit_on_failure?
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stackprof'
|
4
|
+
require 'launchy'
|
5
|
+
|
6
|
+
module Estackprof
|
7
|
+
def flamegraph(files:)
|
8
|
+
mkdir(tmp_path = './tmp')
|
9
|
+
html_path = File.expand_path("#{tmp_path}/flamegraph.html")
|
10
|
+
File.open(html_path, 'w') { |f| Report.create([files[0]]).print_d3_flamegraph(f) }
|
11
|
+
Launchy.open(html_path)
|
12
|
+
html_path
|
13
|
+
rescue StandardError
|
14
|
+
puts 'Dump files are missing or incorrect.'
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def mkdir(path)
|
20
|
+
FileUtils.mkdir_p(path) unless FileTest.exist?(path)
|
21
|
+
end
|
22
|
+
|
23
|
+
module_function :flamegraph, :mkdir
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stackprof'
|
4
|
+
|
5
|
+
module Estackprof
|
6
|
+
def list(files:, options:)
|
7
|
+
io = StringIO.new
|
8
|
+
|
9
|
+
print_by_options(Report.create(files), options, io)
|
10
|
+
|
11
|
+
io.rewind
|
12
|
+
io.read.to_s
|
13
|
+
rescue StandardError
|
14
|
+
puts 'Dump files are missing or incorrect.'
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def print_by_options(report, options, io)
|
20
|
+
if options[:method]
|
21
|
+
report.print_method(options[:method], io)
|
22
|
+
elsif options[:file]
|
23
|
+
report.print_file(options[:file], io)
|
24
|
+
else
|
25
|
+
report.print_files(false, nil, io)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module_function :list, :print_by_options
|
30
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stackprof'
|
4
|
+
|
5
|
+
module Estackprof
|
6
|
+
class Middleware < StackProf::Middleware
|
7
|
+
def initialize(app, options = {})
|
8
|
+
options[:enabled] = true if options[:enabled].nil?
|
9
|
+
options[:raw] = true if options[:raw].nil?
|
10
|
+
options[:save_every] ||= 10
|
11
|
+
|
12
|
+
super(app, **options)
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
%i[enabled mode interval raw path metadata].each do |sym|
|
17
|
+
define_method(sym) do
|
18
|
+
StackProf::Middleware.send(sym)
|
19
|
+
end
|
20
|
+
define_method("#{sym}=") do |value|
|
21
|
+
StackProf::Middleware.send("#{sym}=", value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stackprof'
|
4
|
+
|
5
|
+
module Estackprof
|
6
|
+
class Report < StackProf::Report
|
7
|
+
def self.create(files)
|
8
|
+
reports = files.map do |file|
|
9
|
+
new(Marshal.load(IO.binread(file)))
|
10
|
+
end
|
11
|
+
reports.inject(:+)
|
12
|
+
end
|
13
|
+
|
14
|
+
def print_text(limit:, pattern:, sort_by_total: false, out: $stdout)
|
15
|
+
print_summary(out)
|
16
|
+
print_header(out)
|
17
|
+
|
18
|
+
list = frames(sort_by_total)
|
19
|
+
if pattern
|
20
|
+
list = list.filter do |_frame, info|
|
21
|
+
%i[file name].any? { |s| info[s].match?(pattern) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
list = list.first(limit) if limit
|
25
|
+
print_body(list, out)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def print_summary(out)
|
31
|
+
out.puts '=================================='
|
32
|
+
out.printf " Mode: #{modeline}\n"
|
33
|
+
print_summary_samples(out)
|
34
|
+
print_summary_gc(out)
|
35
|
+
out.puts '=================================='
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_summary_samples(out)
|
39
|
+
out.printf " Samples: #{@data[:samples]} (%.2f%% miss rate)\n",
|
40
|
+
100.0 * @data[:missed_samples] / (@data[:missed_samples] + @data[:samples])
|
41
|
+
end
|
42
|
+
|
43
|
+
def print_summary_gc(out)
|
44
|
+
out.printf " GC: #{@data[:gc_samples]} (%.2f%%)\n", 100.0 * @data[:gc_samples] / @data[:samples]
|
45
|
+
end
|
46
|
+
|
47
|
+
def print_header(out)
|
48
|
+
out.printf format("%<total>10s (pct) %<samples>10s (pct) FRAME\n", total: 'TOTAL', samples: 'SAMPLES')
|
49
|
+
end
|
50
|
+
|
51
|
+
def print_body(list, out)
|
52
|
+
list.each do |_frame, info|
|
53
|
+
call, total = info.values_at(:samples, :total_samples)
|
54
|
+
out.printf(
|
55
|
+
"%<total>10d %<total_pct>8s %<samples>10d %<samples_pct>8s %<frame>s\n",
|
56
|
+
total: total, total_pct: format('(%2.1f%%)', (total * 100.0 / overall_samples)),
|
57
|
+
samples: call, samples_pct: format('(%2.1f%%)', (call * 100.0 / overall_samples)),
|
58
|
+
frame: frame(info[:name], info[:file], info[:line])
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def frame(name, file, line)
|
64
|
+
file_path = name
|
65
|
+
begin
|
66
|
+
file_path += "(#{Pathname(file).relative_path_from(Dir.pwd)}:#{line})"
|
67
|
+
rescue StandardError
|
68
|
+
# NOP
|
69
|
+
end
|
70
|
+
file_path
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stackprof'
|
4
|
+
|
5
|
+
module Estackprof
|
6
|
+
def top(files:, options:)
|
7
|
+
io = StringIO.new
|
8
|
+
Report.create(files).print_text(**parse_options(options), out: io)
|
9
|
+
io.rewind
|
10
|
+
io.read.to_s
|
11
|
+
rescue StandardError
|
12
|
+
puts 'Dump files are missing or incorrect.'
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_options(options)
|
18
|
+
limit = options[:limit] || 10
|
19
|
+
pattern = options[:pattern] && Regexp.new(options[:pattern])
|
20
|
+
sort_by_total = options[:cumlative]
|
21
|
+
{ limit: limit, pattern: pattern, sort_by_total: sort_by_total }
|
22
|
+
end
|
23
|
+
|
24
|
+
module_function :top, :parse_options
|
25
|
+
end
|
data/lib/estackprof.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'estackprof/cli'
|
4
|
+
require_relative 'estackprof/top'
|
5
|
+
require_relative 'estackprof/list'
|
6
|
+
require_relative 'estackprof/flamegraph'
|
7
|
+
require_relative 'estackprof/version'
|
8
|
+
require_relative 'estackprof/report'
|
9
|
+
require_relative 'estackprof/middleware'
|
10
|
+
|
11
|
+
module Estackprof
|
12
|
+
class Error < StandardError; end
|
13
|
+
# Your code goes here...
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: estackprof
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yuhei Okazaki
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: launchy
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: stackprof
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thor
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.1.0
|
55
|
+
description: Estackprof is a wrapper to make it easier to use Stackprof in your rack
|
56
|
+
application
|
57
|
+
email:
|
58
|
+
- okazaki@fusic.co.jp
|
59
|
+
executables:
|
60
|
+
- estackprof
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".circleci/config.yml"
|
65
|
+
- ".gitignore"
|
66
|
+
- ".rspec"
|
67
|
+
- ".rubocop.yml"
|
68
|
+
- ".ruby-version"
|
69
|
+
- Gemfile
|
70
|
+
- Guardfile
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- bin/console
|
74
|
+
- bin/setup
|
75
|
+
- estackprof.gemspec
|
76
|
+
- example/app.rb
|
77
|
+
- exe/estackprof
|
78
|
+
- flamegraph.html
|
79
|
+
- lib/estackprof.rb
|
80
|
+
- lib/estackprof/cli.rb
|
81
|
+
- lib/estackprof/flamegraph.rb
|
82
|
+
- lib/estackprof/list.rb
|
83
|
+
- lib/estackprof/middleware.rb
|
84
|
+
- lib/estackprof/report.rb
|
85
|
+
- lib/estackprof/top.rb
|
86
|
+
- lib/estackprof/version.rb
|
87
|
+
homepage: https://github.com/fusic/estackprof
|
88
|
+
licenses: []
|
89
|
+
metadata:
|
90
|
+
homepage_uri: https://github.com/fusic/estackprof
|
91
|
+
source_code_uri: https://github.com/fusic/estackprof
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 2.5.0
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubygems_version: 3.2.15
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: We want to make stackprof easier to use.
|
111
|
+
test_files: []
|