singed 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/README.md +102 -0
- data/exe/singed +9 -0
- data/lib/singed/backtrace_cleaner_ext.rb +16 -0
- data/lib/singed/cli.rb +168 -0
- data/lib/singed/controller_ext.rb +16 -0
- data/lib/singed/flamegraph.rb +66 -0
- data/lib/singed/kernel_ext.rb +17 -0
- data/lib/singed/rack_middleware.rb +35 -0
- data/lib/singed/railtie.rb +21 -0
- data/lib/singed/report.rb +37 -0
- data/lib/singed/rspec.rb +11 -0
- data/lib/singed.rb +56 -0
- data/singed.gemspec +28 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5276eb42b69baf43de15c7ad5a3549f048d00865f225998eb4479b6e7723f2b8
|
4
|
+
data.tar.gz: 0f46bfb076f12a4766e060a017f2cb0e9ad6af98a96b8f96ebf05b58205aac21
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 81b34cf130fbe187680d65ba593ee68ce606e72cbb5db99284aba82beb6bf9ac17cef5104850a49ebba7bce87cc8a11b39f4b0897accb9bc6e0cb268ee35168e
|
7
|
+
data.tar.gz: 23e265212f1f70ebe105c218dde2025728782ae1574ef2abcf4705b04e13eeab7bd9b6197442b950517492c5a540a2ebaa99478d7841b4170a8611ff668094a9
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Singed
|
2
|
+
|
3
|
+
Singed makes it easy to get a flamegraph anywhere in your code base:
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
To profile your code, and launch [speedscope](https://github.com/jlfwong/speedscope) for viewing it:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
flamegraph {
|
11
|
+
# your code here
|
12
|
+
}
|
13
|
+
```
|
14
|
+
|
15
|
+
Flamegraphs are saved for later review to `Singed.output_directory`, which is `tmp/speedscope` on Rails. You can adjust this like:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
Singed.output_directory = "tmp/slowness-exploration"
|
19
|
+
```
|
20
|
+
|
21
|
+
### Blockage
|
22
|
+
If you are calling it in a loop, or with different variations, you can include a label on the filename:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
flamegraph(label: "rspec") {
|
26
|
+
# your code here
|
27
|
+
}
|
28
|
+
```
|
29
|
+
|
30
|
+
You can also skip opening speedscope automatically:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
flamegraph(open: false) {
|
34
|
+
# your code here
|
35
|
+
}
|
36
|
+
```
|
37
|
+
|
38
|
+
### RSpec
|
39
|
+
|
40
|
+
If you are using RSpec, you can use the `flamegraph` metadata to capture it for you.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# make sure this is required at somepoint, like in a spec/support file!
|
44
|
+
require 'singed/rspec'
|
45
|
+
|
46
|
+
RSpec.describe YourClass do
|
47
|
+
it "is slow :(", flamegraph: true do
|
48
|
+
# your code here
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
### Controllers
|
54
|
+
|
55
|
+
If you want to capture a flamegraph of a controller action, you can call it like:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class EmployeesController < ApplicationController
|
59
|
+
flamegraph :show
|
60
|
+
|
61
|
+
def show
|
62
|
+
# your code here
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
This won't catch the entire request though, just once it's been routed to controller and a response has been served.
|
68
|
+
|
69
|
+
### Rack/Rails requests
|
70
|
+
|
71
|
+
To capture the whole request, there is a middleware which checks for the `X-Singed` header to be 'true'. With curl, you can do this like:
|
72
|
+
|
73
|
+
```shell
|
74
|
+
curl -H 'X-Singed: true' https://localhost:3000
|
75
|
+
```
|
76
|
+
|
77
|
+
PROTIP: use Chrome Developer Tools to record network activity, and copy requests as a curl command. Add `-H 'X-Singed: true'` to it, and you get flamegraphs!
|
78
|
+
|
79
|
+
This can also be enabled to always run by setting `SINGED_MIDDLEWARE_ALWAYS_CAPTURE=1` in the environment.
|
80
|
+
|
81
|
+
### Command Line
|
82
|
+
|
83
|
+
There is a `singed` command line you can use that will record a flamegraph from the entirety of a command run:
|
84
|
+
|
85
|
+
```shell
|
86
|
+
$ bundle binstub singed # if you want to be able to call it like bin/singed
|
87
|
+
$ bundle exec singed -- bin/rails
|
88
|
+
```
|
89
|
+
|
90
|
+
The flamegraph is opened afterwards.
|
91
|
+
|
92
|
+
|
93
|
+
## Limitations
|
94
|
+
|
95
|
+
When using the auto-opening feature, it's assumed that you are have a browser available on the same host you are profiling code.
|
96
|
+
|
97
|
+
The `open` is expected to be available.
|
98
|
+
|
99
|
+
## Alternatives
|
100
|
+
|
101
|
+
- using [rbspy](https://rbspy.github.io/) directory
|
102
|
+
- using [stackprof](https://github.com/tmm1/stackprof) (a dependency of singed) directly
|
data/exe/singed
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
class BacktraceCleaner
|
3
|
+
def filter_line(line)
|
4
|
+
filtered_line = line
|
5
|
+
@filters.each do |f|
|
6
|
+
filtered_line = f.call(filtered_line)
|
7
|
+
end
|
8
|
+
|
9
|
+
filtered_line
|
10
|
+
end
|
11
|
+
|
12
|
+
def silence_line?(line)
|
13
|
+
@silencers.any? { |s| s.call(line) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/singed/cli.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'optionparser'
|
4
|
+
|
5
|
+
# NOTE: we defer requiring singed until we run. that lets Rails load it if its in the gemfile, so the railtie has had a chance to run
|
6
|
+
|
7
|
+
module Singed
|
8
|
+
class CLI
|
9
|
+
attr_accessor :argv, :filename, :opts
|
10
|
+
|
11
|
+
def initialize(argv)
|
12
|
+
@argv = argv
|
13
|
+
@opts = OptionParser.new
|
14
|
+
|
15
|
+
parse_argv!
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_argv!
|
19
|
+
opts.banner = 'Usage: singed [options] <command>'
|
20
|
+
|
21
|
+
opts.on('-h', '--help', 'Show this message') do
|
22
|
+
@show_help = true
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('-o', '--output-directory DIRECTORY', 'Directory to write flamegraph to') do |directory|
|
26
|
+
@output_directory = directory
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.order(@argv) do |arg|
|
30
|
+
opts.terminate if arg == '--'
|
31
|
+
break
|
32
|
+
end
|
33
|
+
|
34
|
+
if @argv.empty?
|
35
|
+
@show_help = true
|
36
|
+
@error_message = 'missing command to profile'
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
return if @show_help
|
41
|
+
|
42
|
+
begin
|
43
|
+
@opts.parse!(argv)
|
44
|
+
rescue OptionParser::InvalidOption => e
|
45
|
+
@show_help = true
|
46
|
+
@error_message = e
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def run
|
51
|
+
require 'singed'
|
52
|
+
|
53
|
+
if @error_message
|
54
|
+
puts @error_message
|
55
|
+
puts
|
56
|
+
puts @opts.help
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
|
60
|
+
if show_help?
|
61
|
+
puts @opts.help
|
62
|
+
exit 0
|
63
|
+
end
|
64
|
+
|
65
|
+
Singed.output_directory = @output_directory if @output_directory
|
66
|
+
Singed.output_directory ||= Dir.tmpdir
|
67
|
+
@filename = Singed::Flamegraph.generate_filename(label: 'cli')
|
68
|
+
|
69
|
+
options = {
|
70
|
+
format: 'speedscope',
|
71
|
+
file: filename.to_s,
|
72
|
+
silent: nil,
|
73
|
+
}
|
74
|
+
|
75
|
+
rbspy_args = [
|
76
|
+
'record',
|
77
|
+
*options.map { |k, v| ["--#{k}", v].compact }.flatten,
|
78
|
+
'--',
|
79
|
+
*argv,
|
80
|
+
]
|
81
|
+
|
82
|
+
loop do
|
83
|
+
break unless password_needed?
|
84
|
+
|
85
|
+
puts '🔥📈 Singed needs to run as root, but will drop permissions back to your user. Prompting with sudo now...'
|
86
|
+
prompt_password
|
87
|
+
end
|
88
|
+
|
89
|
+
Bundler.with_unbundled_env do
|
90
|
+
# don't run things with spring, because it forks and rbspy won't see it
|
91
|
+
sudo ['rbspy', *rbspy_args], reason: 'Singed needs to run as root, but will drop permissions back to your user.', env: { 'DISABLE_SPRING' => '1' }
|
92
|
+
end
|
93
|
+
|
94
|
+
unless filename.exist?
|
95
|
+
puts "#{filename} doesn't exist. Maybe rbspy had a failure capturing it? Check the scrollback."
|
96
|
+
exit 1
|
97
|
+
end
|
98
|
+
|
99
|
+
unless adjust_ownership!
|
100
|
+
puts "#{filename} isn't writable!"
|
101
|
+
exit 1
|
102
|
+
end
|
103
|
+
|
104
|
+
# clean the report, similar to how Singed::Report does
|
105
|
+
json = JSON.parse(filename.read).with_indifferent_access
|
106
|
+
json['shared']['frames'].each do |frame|
|
107
|
+
frame[:file] = Singed.filter_line(frame[:file])
|
108
|
+
end
|
109
|
+
filename.write(JSON.dump(json))
|
110
|
+
|
111
|
+
flamegraph = Singed::Flamegraph.new(filename: filename)
|
112
|
+
flamegraph.open
|
113
|
+
end
|
114
|
+
|
115
|
+
def password_needed?
|
116
|
+
!system('sudo --non-interactive true >/dev/null 2>&1')
|
117
|
+
end
|
118
|
+
|
119
|
+
def prompt_password
|
120
|
+
system('sudo true')
|
121
|
+
end
|
122
|
+
|
123
|
+
def adjust_ownership!
|
124
|
+
sudo ['chown', ENV['USER'], filename], reason: "Adjusting ownership of #{filename}, but need root."
|
125
|
+
end
|
126
|
+
|
127
|
+
def show_help?
|
128
|
+
@show_help
|
129
|
+
end
|
130
|
+
|
131
|
+
def sudo(system_args, reason:, env: {})
|
132
|
+
loop do
|
133
|
+
break unless password_needed?
|
134
|
+
|
135
|
+
puts "🔥📈 #{reason} Prompting with sudo now..."
|
136
|
+
prompt_password
|
137
|
+
end
|
138
|
+
|
139
|
+
sudo_args = [
|
140
|
+
'sudo',
|
141
|
+
'--preserve-env',
|
142
|
+
*system_args.map(&:to_s),
|
143
|
+
]
|
144
|
+
|
145
|
+
puts "$ #{Shellwords.join(sudo_args)}"
|
146
|
+
|
147
|
+
system(env, *sudo_args, exception: true)
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.chdir_rails_root
|
151
|
+
original_cwd = Dir.pwd
|
152
|
+
|
153
|
+
loop do
|
154
|
+
if File.file?('config/environment.rb')
|
155
|
+
return Dir.pwd
|
156
|
+
end
|
157
|
+
|
158
|
+
if Pathname.new(Dir.pwd).root?
|
159
|
+
Dir.chdir(original_cwd)
|
160
|
+
return
|
161
|
+
end
|
162
|
+
|
163
|
+
# Otherwise keep moving upwards in search of an executable.
|
164
|
+
Dir.chdir('..')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Singed
|
2
|
+
module ControllerExt
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Define an around_action to generate flamegraph for a controller action.
|
9
|
+
def flamegraph(target_action, ignore_gc: false, interval: 1000)
|
10
|
+
around_action(only: target_action) do |controller, action|
|
11
|
+
controller.flamegraph(ignore_gc: ignore_gc, interval: interval, &action)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Singed
|
2
|
+
class Flamegraph
|
3
|
+
attr_accessor :profile, :filename
|
4
|
+
|
5
|
+
def initialize(label: nil, ignore_gc: false, interval: 1000, filename: nil)
|
6
|
+
# it's been created elsewhere, ie rbspy
|
7
|
+
if filename
|
8
|
+
if ignore_gc
|
9
|
+
raise ArgumentError, 'ignore_gc not supported when given an existing file'
|
10
|
+
end
|
11
|
+
|
12
|
+
if label
|
13
|
+
raise ArgumentError, 'label not supported when given an existing file'
|
14
|
+
end
|
15
|
+
|
16
|
+
@filename = filename
|
17
|
+
else
|
18
|
+
@ignore_gc = ignore_gc
|
19
|
+
@interval = interval
|
20
|
+
@time = Time.now # rubocop:disable Rails/TimeZone
|
21
|
+
@filename = self.class.generate_filename(label: label, time: @time)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def record
|
26
|
+
return yield unless Singed.enabled?
|
27
|
+
return yield if filename.exist? # file existing means its been captured already
|
28
|
+
|
29
|
+
result = nil
|
30
|
+
@profile = StackProf.run(mode: :wall, raw: true, ignore_gc: @ignore_gc, interval: @interval) do
|
31
|
+
result = yield
|
32
|
+
end
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
def save
|
37
|
+
if filename.exist?
|
38
|
+
raise ArgumentError, "File #{filename} already exists"
|
39
|
+
end
|
40
|
+
|
41
|
+
report = Singed::Report.new(@profile)
|
42
|
+
report.filter!
|
43
|
+
filename.dirname.mkpath
|
44
|
+
filename.open('w') { |f| report.print_json(f) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def open
|
48
|
+
system open_command
|
49
|
+
end
|
50
|
+
|
51
|
+
def open_command
|
52
|
+
@open_command ||= "npx speedscope #{@filename}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/TimeZone
|
56
|
+
formatted_time = time.to_formatted_s(:number)
|
57
|
+
basename_parts = ['speedscope', label, formatted_time].compact
|
58
|
+
|
59
|
+
file = Singed.output_directory.join("#{basename_parts.join('-')}.json")
|
60
|
+
# convert to relative directory if it's an absolute path and within the current
|
61
|
+
pwd = Pathname.pwd
|
62
|
+
file = file.relative_path_from(pwd) if file.absolute? && file.to_s.start_with?(pwd.to_s)
|
63
|
+
file
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Kernel
|
2
|
+
def flamegraph(label = nil, open: true, ignore_gc: false, interval: 1000, &block)
|
3
|
+
fg = Singed::Flamegraph.new(label: label, ignore_gc: ignore_gc, interval: interval)
|
4
|
+
result = fg.record(&block)
|
5
|
+
fg.save
|
6
|
+
|
7
|
+
if open
|
8
|
+
# use npx, so we don't have to add it as a dependency
|
9
|
+
puts "🔥📈 #{'Captured flamegraph, opening with'.colorize(:bold).colorize(:red)}: #{fg.open_command}"
|
10
|
+
fg.open
|
11
|
+
else
|
12
|
+
puts "🔥📈 #{'Captured flamegraph to file'.colorize(:bold).colorize(:red)}: #{fg.filename}"
|
13
|
+
end
|
14
|
+
|
15
|
+
result
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Rack Middleware
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module Singed
|
6
|
+
class RackMiddleware
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
status, headers, body = if capture_flamegraph?(env)
|
13
|
+
flamegraph do
|
14
|
+
@app.call(env)
|
15
|
+
end
|
16
|
+
else
|
17
|
+
@app.call(env)
|
18
|
+
end
|
19
|
+
|
20
|
+
[status, headers, body]
|
21
|
+
end
|
22
|
+
|
23
|
+
def capture_flamegraph?(env)
|
24
|
+
self.class.always_capture? || env['HTTP_X_SINGED'] == 'true'
|
25
|
+
end
|
26
|
+
|
27
|
+
TRUTHY_STRINGS = ['true', '1', 'yes'].freeze
|
28
|
+
|
29
|
+
def self.always_capture?
|
30
|
+
return @always_capture if defined?(@always_capture)
|
31
|
+
|
32
|
+
@always_capture = TRUTHY_STRINGS.include?(ENV.fetch('SINGED_MIDDLEWARE_ALWAYS_CAPTURE', 'false'))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'singed/backtrace_cleaner_ext'
|
2
|
+
require 'singed/controller_ext'
|
3
|
+
|
4
|
+
module Singed
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer 'singed.configure_rails_initialization' do |app|
|
7
|
+
self.class.init!
|
8
|
+
|
9
|
+
app.middleware.use Singed::RackMiddleware
|
10
|
+
|
11
|
+
ActiveSupport.on_load(:action_controller) do
|
12
|
+
ActionController::Base.include(Singed::ControllerExt)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.init!
|
17
|
+
Singed.output_directory = Rails.root.join('tmp/speedscope')
|
18
|
+
Singed.backtrace_cleaner = Rails.backtrace_cleaner
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Singed
|
2
|
+
class Report < StackProf::Report
|
3
|
+
def filter!
|
4
|
+
# copy and paste from StackProf::Report#print_graphviz that does filtering
|
5
|
+
# mark_stack = []
|
6
|
+
list = frames(true)
|
7
|
+
# WIP to filter out frames we care about... unfortunately, speedscope just hangs while loading as is
|
8
|
+
# # build list of frames to mark for keeping
|
9
|
+
# list.each do |addr, frame|
|
10
|
+
# mark_stack << addr unless Singed.silence_line?(frame[:file])
|
11
|
+
# end
|
12
|
+
|
13
|
+
# # while more addresses to mark
|
14
|
+
# while addr = mark_stack.pop
|
15
|
+
# frame = list[addr]
|
16
|
+
# # if it hasn't been marked yet
|
17
|
+
# unless frame[:marked]
|
18
|
+
# # collect edges to mark
|
19
|
+
# if frame[:edges]
|
20
|
+
# mark_stack += frame[:edges].map{ |addr, weight| addr if list[addr][:total_samples] <= weight*1.2 }.compact
|
21
|
+
# end
|
22
|
+
# # mark it so we don't process again
|
23
|
+
# frame[:marked] = true
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# list = list.select{ |_addr, frame| frame[:marked] }
|
27
|
+
# list.each{ |_addr, frame| frame[:edges]&.delete_if{ |k,v| list[k].nil? } }
|
28
|
+
# end copy-pasted section
|
29
|
+
|
30
|
+
list.each do |_addr, frame|
|
31
|
+
frame[:file] = Singed.filter_line(frame[:file])
|
32
|
+
end
|
33
|
+
|
34
|
+
@data[:frames] = list
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/singed/rspec.rb
ADDED
data/lib/singed.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'stackprof'
|
5
|
+
require 'colorize'
|
6
|
+
|
7
|
+
module Singed
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# Where should flamegraphs be saved?
|
11
|
+
def output_directory=(directory)
|
12
|
+
@output_directory = Pathname.new(directory)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.output_directory
|
16
|
+
@output_directory || raise("output directory hasn't been set!")
|
17
|
+
end
|
18
|
+
|
19
|
+
def enabled=(enabled)
|
20
|
+
@enabled = enabled
|
21
|
+
end
|
22
|
+
|
23
|
+
def enabled?
|
24
|
+
return @enabled if defined?(@enabled)
|
25
|
+
|
26
|
+
@enabled = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def backtrace_cleaner=(backtrace_cleaner)
|
30
|
+
@backtrace_cleaner = backtrace_cleaner
|
31
|
+
end
|
32
|
+
|
33
|
+
def backtrace_cleaner
|
34
|
+
@backtrace_cleaner
|
35
|
+
end
|
36
|
+
|
37
|
+
def silence_line?(line)
|
38
|
+
return backtrace_cleaner.silence_line?(line) if backtrace_cleaner
|
39
|
+
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def filter_line(line)
|
44
|
+
return backtrace_cleaner.filter_line(line) if backtrace_cleaner
|
45
|
+
|
46
|
+
line
|
47
|
+
end
|
48
|
+
|
49
|
+
autoload :Flamegraph, 'singed/flamegraph'
|
50
|
+
autoload :Report, 'singed/report'
|
51
|
+
autoload :RackMiddleware, 'singed/rack_middleware'
|
52
|
+
end
|
53
|
+
|
54
|
+
require 'singed/kernel_ext'
|
55
|
+
require 'singed/railtie' if defined?(Rails::Railtie)
|
56
|
+
require 'singed/rspec' if defined?(RSpec)
|
data/singed.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'singed'
|
5
|
+
|
6
|
+
spec.version = '0.1.0'
|
7
|
+
spec.authors = ['Josh Nichols']
|
8
|
+
spec.email = ['josh.nichols@gusto.com']
|
9
|
+
|
10
|
+
spec.summary = 'Quick and easy way to get flamegraphs from a specific part of your code base'
|
11
|
+
spec.required_ruby_version = '>= 2.7.0'
|
12
|
+
|
13
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to your gem server 'https://example.com'"
|
14
|
+
|
15
|
+
spec.files = Dir['README.md', '*.gemspec', 'lib/**/*', 'exe/**/*']
|
16
|
+
spec.bindir = 'exe'
|
17
|
+
spec.executables = spec.files.grep(%r(\Aexe/)) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
# Uncomment to register a new dependency of your gem
|
21
|
+
spec.add_dependency 'colorize'
|
22
|
+
spec.add_dependency 'stackprof'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
25
|
+
|
26
|
+
# For more information and examples about making a new gem, checkout our
|
27
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: singed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Josh Nichols
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
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'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- josh.nichols@gusto.com
|
58
|
+
executables:
|
59
|
+
- singed
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- README.md
|
64
|
+
- exe/singed
|
65
|
+
- lib/singed.rb
|
66
|
+
- lib/singed/backtrace_cleaner_ext.rb
|
67
|
+
- lib/singed/cli.rb
|
68
|
+
- lib/singed/controller_ext.rb
|
69
|
+
- lib/singed/flamegraph.rb
|
70
|
+
- lib/singed/kernel_ext.rb
|
71
|
+
- lib/singed/rack_middleware.rb
|
72
|
+
- lib/singed/railtie.rb
|
73
|
+
- lib/singed/report.rb
|
74
|
+
- lib/singed/rspec.rb
|
75
|
+
- singed.gemspec
|
76
|
+
homepage:
|
77
|
+
licenses: []
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 2.7.0
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubygems_version: 3.4.4
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: Quick and easy way to get flamegraphs from a specific part of your code base
|
98
|
+
test_files: []
|