singed 0.2.1 → 0.2.3
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 +4 -4
- data/exe/singed +2 -3
- data/lib/singed/cli.rb +28 -28
- data/lib/singed/flamegraph.rb +6 -6
- data/lib/singed/kernel_ext.rb +4 -4
- data/lib/singed/rack_middleware.rb +4 -4
- data/lib/singed/railtie.rb +4 -4
- data/lib/singed/rspec.rb +3 -7
- data/lib/singed.rb +9 -9
- data/singed.gemspec +21 -19
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5eee9e4b8b057bfe41522213ce622d4e60d24c123bd84efd86198d2d36254f7
|
4
|
+
data.tar.gz: f10be7d1314b44e80a0fb1f1487af69ee491de18aec00ce54fa87efe13189e7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8dadca73464601533e7c235f465654c4f4066b9dd688f74f90bbb2191bdeaa24b9276dc0e4575fd224dbb95936c4368164be923bae6c43d9d95ba451949fb175
|
7
|
+
data.tar.gz: a473d79f866560f7def5960c8f38631f89543798adc6fe868906fd2294fd84adbaa5635353772763893579db1b34cf2de93b1f61162f69b3fadd1788b1dd834e
|
data/exe/singed
CHANGED
data/lib/singed/cli.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "shellwords"
|
2
|
+
require "tmpdir"
|
3
|
+
require "optionparser"
|
4
|
+
require "pathname"
|
5
5
|
|
6
6
|
# 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
|
7
7
|
|
@@ -17,24 +17,24 @@ module Singed
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def parse_argv!
|
20
|
-
opts.banner =
|
20
|
+
opts.banner = "Usage: singed [options] <command>"
|
21
21
|
|
22
|
-
opts.on(
|
22
|
+
opts.on("-h", "--help", "Show this message") do
|
23
23
|
@show_help = true
|
24
24
|
end
|
25
25
|
|
26
|
-
opts.on(
|
26
|
+
opts.on("-o", "--output-directory DIRECTORY", "Directory to write flamegraph to") do |directory|
|
27
27
|
@output_directory = directory
|
28
28
|
end
|
29
29
|
|
30
30
|
opts.order(@argv) do |arg|
|
31
|
-
opts.terminate if arg ==
|
31
|
+
opts.terminate if arg == "--"
|
32
32
|
break
|
33
33
|
end
|
34
34
|
|
35
35
|
if @argv.empty?
|
36
36
|
@show_help = true
|
37
|
-
@error_message =
|
37
|
+
@error_message = "missing command to profile"
|
38
38
|
return
|
39
39
|
end
|
40
40
|
|
@@ -49,7 +49,7 @@ module Singed
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def run
|
52
|
-
require
|
52
|
+
require "singed"
|
53
53
|
|
54
54
|
if @error_message
|
55
55
|
puts @error_message
|
@@ -66,31 +66,31 @@ module Singed
|
|
66
66
|
Singed.output_directory = @output_directory if @output_directory
|
67
67
|
Singed.output_directory ||= Dir.tmpdir
|
68
68
|
FileUtils.mkdir_p Singed.output_directory
|
69
|
-
@filename = Singed::Flamegraph.generate_filename(label:
|
69
|
+
@filename = Singed::Flamegraph.generate_filename(label: "cli")
|
70
70
|
|
71
71
|
options = {
|
72
|
-
format:
|
72
|
+
format: "speedscope",
|
73
73
|
file: filename.to_s,
|
74
|
-
silent: nil
|
74
|
+
silent: nil
|
75
75
|
}
|
76
76
|
|
77
77
|
rbspy_args = [
|
78
|
-
|
78
|
+
"record",
|
79
79
|
*options.map { |k, v| ["--#{k}", v].compact }.flatten,
|
80
|
-
|
81
|
-
*argv
|
80
|
+
"--",
|
81
|
+
*argv
|
82
82
|
]
|
83
83
|
|
84
84
|
loop do
|
85
85
|
break unless password_needed?
|
86
86
|
|
87
|
-
puts
|
87
|
+
puts "🔥📈 Singed needs to run as root, but will drop permissions back to your user. Prompting with sudo now..."
|
88
88
|
prompt_password
|
89
89
|
end
|
90
90
|
|
91
91
|
rbspy = lambda do
|
92
92
|
# don't run things with spring, because it forks and rbspy won't see it
|
93
|
-
sudo [
|
93
|
+
sudo ["rbspy", *rbspy_args], reason: "Singed needs to run as root, but will drop permissions back to your user.", env: {"DISABLE_SPRING" => "1"}
|
94
94
|
end
|
95
95
|
|
96
96
|
if defined?(Bundler)
|
@@ -113,8 +113,8 @@ module Singed
|
|
113
113
|
|
114
114
|
# clean the report, similar to how Singed::Report does
|
115
115
|
json = JSON.parse(filename.read)
|
116
|
-
json[
|
117
|
-
frame[
|
116
|
+
json["shared"]["frames"].each do |frame|
|
117
|
+
frame["file"] = Singed.filter_line(frame["file"])
|
118
118
|
end
|
119
119
|
filename.write(JSON.dump(json))
|
120
120
|
|
@@ -123,15 +123,15 @@ module Singed
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def password_needed?
|
126
|
-
!system(
|
126
|
+
!system("sudo --non-interactive true >/dev/null 2>&1")
|
127
127
|
end
|
128
128
|
|
129
129
|
def prompt_password
|
130
|
-
system(
|
130
|
+
system("sudo true")
|
131
131
|
end
|
132
132
|
|
133
133
|
def adjust_ownership!
|
134
|
-
sudo [
|
134
|
+
sudo ["chown", ENV["USER"], filename], reason: "Adjusting ownership of #{filename}, but need root."
|
135
135
|
end
|
136
136
|
|
137
137
|
def show_help?
|
@@ -147,9 +147,9 @@ module Singed
|
|
147
147
|
end
|
148
148
|
|
149
149
|
sudo_args = [
|
150
|
-
|
151
|
-
|
152
|
-
*system_args.map(&:to_s)
|
150
|
+
"sudo",
|
151
|
+
"--preserve-env",
|
152
|
+
*system_args.map(&:to_s)
|
153
153
|
]
|
154
154
|
|
155
155
|
puts "$ #{Shellwords.join(sudo_args)}"
|
@@ -161,7 +161,7 @@ module Singed
|
|
161
161
|
original_cwd = Dir.pwd
|
162
162
|
|
163
163
|
loop do
|
164
|
-
if File.file?(
|
164
|
+
if File.file?("config/environment.rb")
|
165
165
|
return Dir.pwd
|
166
166
|
end
|
167
167
|
|
@@ -171,7 +171,7 @@ module Singed
|
|
171
171
|
end
|
172
172
|
|
173
173
|
# Otherwise keep moving upwards in search of an executable.
|
174
|
-
Dir.chdir(
|
174
|
+
Dir.chdir("..")
|
175
175
|
end
|
176
176
|
end
|
177
177
|
end
|
data/lib/singed/flamegraph.rb
CHANGED
@@ -6,11 +6,11 @@ module Singed
|
|
6
6
|
# it's been created elsewhere, ie rbspy
|
7
7
|
if filename
|
8
8
|
if ignore_gc
|
9
|
-
raise ArgumentError,
|
9
|
+
raise ArgumentError, "ignore_gc not supported when given an existing file"
|
10
10
|
end
|
11
11
|
|
12
12
|
if label
|
13
|
-
raise ArgumentError,
|
13
|
+
raise ArgumentError, "label not supported when given an existing file"
|
14
14
|
end
|
15
15
|
|
16
16
|
@filename = filename
|
@@ -41,7 +41,7 @@ module Singed
|
|
41
41
|
report = Singed::Report.new(@profile)
|
42
42
|
report.filter!
|
43
43
|
filename.dirname.mkpath
|
44
|
-
filename.open(
|
44
|
+
filename.open("w") { |f| report.print_json(f) }
|
45
45
|
end
|
46
46
|
|
47
47
|
def open
|
@@ -53,10 +53,10 @@ module Singed
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/TimeZone
|
56
|
-
formatted_time = time.strftime(
|
57
|
-
basename_parts = [
|
56
|
+
formatted_time = time.strftime("%Y%m%d%H%M%S-%6N")
|
57
|
+
basename_parts = ["speedscope", label, formatted_time].compact
|
58
58
|
|
59
|
-
file = Singed.output_directory.join("#{basename_parts.join(
|
59
|
+
file = Singed.output_directory.join("#{basename_parts.join("-")}.json")
|
60
60
|
# convert to relative directory if it's an absolute path and within the current
|
61
61
|
pwd = Pathname.pwd
|
62
62
|
file = file.relative_path_from(pwd) if file.absolute? && file.to_s.start_with?(pwd.to_s)
|
data/lib/singed/kernel_ext.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
module Kernel
|
2
|
-
def flamegraph(label = nil, open: true, ignore_gc: false, interval: 1000, io: $stdout, &
|
2
|
+
def flamegraph(label = nil, open: true, ignore_gc: false, interval: 1000, io: $stdout, &)
|
3
3
|
fg = Singed::Flamegraph.new(label: label, ignore_gc: ignore_gc, interval: interval)
|
4
|
-
result = fg.record(&
|
4
|
+
result = fg.record(&)
|
5
5
|
fg.save
|
6
6
|
|
7
7
|
if open
|
8
8
|
# use npx, so we don't have to add it as a dependency
|
9
|
-
io.puts "🔥📈 #{
|
9
|
+
io.puts "🔥📈 #{"Captured flamegraph, opening with".colorize(:bold).colorize(:red)}: #{fg.open_command}"
|
10
10
|
fg.open
|
11
11
|
else
|
12
|
-
io.puts "🔥📈 #{
|
12
|
+
io.puts "🔥📈 #{"Captured flamegraph to file".colorize(:bold).colorize(:red)}: #{fg.filename}"
|
13
13
|
end
|
14
14
|
|
15
15
|
result
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Rack Middleware
|
2
2
|
|
3
|
-
require
|
3
|
+
require "rack"
|
4
4
|
|
5
5
|
module Singed
|
6
6
|
class RackMiddleware
|
@@ -21,15 +21,15 @@ module Singed
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def capture_flamegraph?(env)
|
24
|
-
self.class.always_capture? || env[
|
24
|
+
self.class.always_capture? || env["HTTP_X_SINGED"] == "true"
|
25
25
|
end
|
26
26
|
|
27
|
-
TRUTHY_STRINGS = [
|
27
|
+
TRUTHY_STRINGS = ["true", "1", "yes"].freeze
|
28
28
|
|
29
29
|
def self.always_capture?
|
30
30
|
return @always_capture if defined?(@always_capture)
|
31
31
|
|
32
|
-
@always_capture = TRUTHY_STRINGS.include?(ENV.fetch(
|
32
|
+
@always_capture = TRUTHY_STRINGS.include?(ENV.fetch("SINGED_MIDDLEWARE_ALWAYS_CAPTURE", "false"))
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
data/lib/singed/railtie.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "singed/backtrace_cleaner_ext"
|
2
|
+
require "singed/controller_ext"
|
3
3
|
|
4
4
|
module Singed
|
5
5
|
class Railtie < Rails::Railtie
|
6
|
-
initializer
|
6
|
+
initializer "singed.configure_rails_initialization" do |app|
|
7
7
|
self.class.init!
|
8
8
|
|
9
9
|
app.middleware.use Singed::RackMiddleware
|
@@ -14,7 +14,7 @@ module Singed
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.init!
|
17
|
-
Singed.output_directory
|
17
|
+
Singed.output_directory ||= Rails.root.join("tmp/speedscope")
|
18
18
|
Singed.backtrace_cleaner = Rails.backtrace_cleaner
|
19
19
|
end
|
20
20
|
end
|
data/lib/singed/rspec.rb
CHANGED
@@ -1,11 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "singed"
|
2
2
|
|
3
3
|
RSpec.configure do |config|
|
4
|
-
config.around do |example|
|
5
|
-
|
6
|
-
flamegraph { example.run }
|
7
|
-
else
|
8
|
-
example.run
|
9
|
-
end
|
4
|
+
config.around(flamegraph: true) do |example|
|
5
|
+
flamegraph { example.run }
|
10
6
|
end
|
11
7
|
end
|
data/lib/singed.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "json"
|
4
|
+
require "stackprof"
|
5
|
+
require "colorize"
|
6
6
|
|
7
7
|
module Singed
|
8
8
|
extend self
|
@@ -46,11 +46,11 @@ module Singed
|
|
46
46
|
line
|
47
47
|
end
|
48
48
|
|
49
|
-
autoload :Flamegraph,
|
50
|
-
autoload :Report,
|
51
|
-
autoload :RackMiddleware,
|
49
|
+
autoload :Flamegraph, "singed/flamegraph"
|
50
|
+
autoload :Report, "singed/report"
|
51
|
+
autoload :RackMiddleware, "singed/rack_middleware"
|
52
52
|
end
|
53
53
|
|
54
|
-
require
|
55
|
-
require
|
56
|
-
require
|
54
|
+
require "singed/kernel_ext"
|
55
|
+
require "singed/railtie" if defined?(Rails::Railtie)
|
56
|
+
require "singed/rspec" if defined?(RSpec) && RSpec.respond_to?(:configure)
|
data/singed.gemspec
CHANGED
@@ -1,27 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name
|
4
|
+
spec.name = "singed"
|
5
5
|
|
6
|
-
spec.version
|
7
|
-
spec.license
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
6
|
+
spec.version = "0.2.3"
|
7
|
+
spec.license = "MIT"
|
8
|
+
spec.authors = ["Josh Nichols"]
|
9
|
+
spec.email = ["josh.nichols@gusto.com"]
|
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
|
+
spec.homepage = "https://github.com/rubyatscale/singed"
|
13
|
+
spec.metadata = {
|
14
|
+
"source_code_uri" => "https://github.com/rubyatscale/singed",
|
15
|
+
"bug_tracker_uri" => "https://github.com/rubyatscale/singed/issues",
|
16
|
+
"homepage_uri" => "https://github.com/rubyatscale/singed"
|
17
|
+
}
|
10
18
|
|
11
|
-
spec.
|
12
|
-
spec.
|
19
|
+
spec.files = Dir["README.md", "*.gemspec", "lib/**/*", "exe/**/*"]
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
13
23
|
|
14
|
-
spec.
|
15
|
-
spec.
|
16
|
-
spec.executables = spec.files.grep(%r(\Aexe/)) { |f| File.basename(f) }
|
17
|
-
spec.require_paths = ['lib']
|
24
|
+
spec.add_dependency "colorize"
|
25
|
+
spec.add_dependency "stackprof", ">= 0.2.13"
|
18
26
|
|
19
|
-
spec.
|
20
|
-
spec.
|
21
|
-
|
22
|
-
spec.add_development_dependency 'rake', '~> 13.0'
|
23
|
-
spec.add_development_dependency 'rspec'
|
24
|
-
|
25
|
-
# For more information and examples about making a new gem, checkout our
|
26
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
27
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
28
|
+
spec.add_development_dependency "rspec"
|
27
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: singed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Nichols
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -87,10 +87,13 @@ files:
|
|
87
87
|
- lib/singed/report.rb
|
88
88
|
- lib/singed/rspec.rb
|
89
89
|
- singed.gemspec
|
90
|
-
homepage:
|
90
|
+
homepage: https://github.com/rubyatscale/singed
|
91
91
|
licenses:
|
92
92
|
- MIT
|
93
|
-
metadata:
|
93
|
+
metadata:
|
94
|
+
source_code_uri: https://github.com/rubyatscale/singed
|
95
|
+
bug_tracker_uri: https://github.com/rubyatscale/singed/issues
|
96
|
+
homepage_uri: https://github.com/rubyatscale/singed
|
94
97
|
post_install_message:
|
95
98
|
rdoc_options: []
|
96
99
|
require_paths:
|
@@ -106,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
109
|
- !ruby/object:Gem::Version
|
107
110
|
version: '0'
|
108
111
|
requirements: []
|
109
|
-
rubygems_version: 3.
|
112
|
+
rubygems_version: 3.0.3.1
|
110
113
|
signing_key:
|
111
114
|
specification_version: 4
|
112
115
|
summary: Quick and easy way to get flamegraphs from a specific part of your code base
|