singed 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/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: []
|