estackprof 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|