From 4d168068ce5b440300120ed31b765324dd70d082 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 8 May 2024 11:56:11 -0400 Subject: [PATCH 01/11] start factoring out profiler specific code --- lib/singed.rb | 1 + lib/singed/flamegraph.rb | 23 ++++++------------ lib/singed/profiler.rb | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 16 deletions(-) create mode 100644 lib/singed/profiler.rb diff --git a/lib/singed.rb b/lib/singed.rb index 1091aa3..e9f4579 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -47,6 +47,7 @@ def filter_line(line) end autoload :Flamegraph, "singed/flamegraph" + autoload :Profiler, "singed/profiler" autoload :Report, "singed/report" autoload :RackMiddleware, "singed/rack_middleware" end diff --git a/lib/singed/flamegraph.rb b/lib/singed/flamegraph.rb index 6f6518a..cdb22ae 100644 --- a/lib/singed/flamegraph.rb +++ b/lib/singed/flamegraph.rb @@ -20,36 +20,27 @@ def initialize(label: nil, ignore_gc: false, interval: 1000, filename: nil) @time = Time.now # rubocop:disable Rails/TimeZone @filename = self.class.generate_filename(label: label, time: @time) end + + @profiler = Singed::Profiler::StackprofPlusSpeedscopeProfiler.new(filename: @filename) end - def record + def record(&block) return yield unless Singed.enabled? return yield if filename.exist? # file existing means its been captured already - result = nil - @profile = StackProf.run(mode: :wall, raw: true, ignore_gc: @ignore_gc, interval: @interval) do - result = yield - end - result + @profiler.record(&block) end def save - if filename.exist? - raise ArgumentError, "File #{filename} already exists" - end - - report = Singed::Report.new(@profile) - report.filter! - filename.dirname.mkpath - filename.open("w") { |f| report.print_json(f) } + @profiler.save end def open - system open_command + @profiler.open end def open_command - @open_command ||= "npx speedscope #{@filename}" + @profiler.open_command end def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/TimeZone diff --git a/lib/singed/profiler.rb b/lib/singed/profiler.rb new file mode 100644 index 0000000..5808684 --- /dev/null +++ b/lib/singed/profiler.rb @@ -0,0 +1,50 @@ +module Singed + class Profiler + attr_accessor :filename + + def initialize(filename:) + @filename = filename + end + + def record + raise UnimplementedError + end + + def save + raise UnimplementedError + end + + def open + system open_command + end + + def open_command + raise UnimplementedError + end + + class StackprofPlusSpeedscopeProfiler < Profiler + def record + result = nil + @profile = StackProf.run(mode: :wall, raw: true, ignore_gc: @ignore_gc, interval: @interval) do + result = yield + end + result + end + + def open_command + @open_command ||= "npx speedscope #{@filename}" + end + + def save + if filename.exist? + raise ArgumentError, "File #{filename} already exists" + end + + report = Singed::Report.new(@profile) + report.filter! + filename.dirname.mkpath + filename.open("w") { |f| report.print_json(f) } + end + end + end +end From 63032fcb96f48b8e049e913867fe6c0e8c2ebc0c Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 8 May 2024 12:14:35 -0400 Subject: [PATCH 02/11] working vernier! --- lib/singed.rb | 1 + lib/singed/flamegraph.rb | 3 ++- lib/singed/profiler.rb | 22 +++++++++++++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/singed.rb b/lib/singed.rb index e9f4579..13363b2 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -3,6 +3,7 @@ require "json" require "stackprof" require "colorize" +require "vernier" module Singed extend self diff --git a/lib/singed/flamegraph.rb b/lib/singed/flamegraph.rb index cdb22ae..2406575 100644 --- a/lib/singed/flamegraph.rb +++ b/lib/singed/flamegraph.rb @@ -21,7 +21,8 @@ def initialize(label: nil, ignore_gc: false, interval: 1000, filename: nil) @filename = self.class.generate_filename(label: label, time: @time) end - @profiler = Singed::Profiler::StackprofPlusSpeedscopeProfiler.new(filename: @filename) + @profiler = Singed::Profiler::VernierProfiler.new(filename: @filename) + # @profiler = Singed::Profiler::StackprofPlusSpeedscopeProfiler.new(filename: @filename) end def record(&block) diff --git a/lib/singed/profiler.rb b/lib/singed/profiler.rb index 5808684..769f04d 100644 --- a/lib/singed/profiler.rb +++ b/lib/singed/profiler.rb @@ -7,11 +7,11 @@ def initialize(filename:) end def record - raise UnimplementedError + raise NotImplementedError end def save - raise UnimplementedError + raise NotImplementedError end def open @@ -19,7 +19,7 @@ def open end def open_command - raise UnimplementedError + raise NotImplementedError end class StackprofPlusSpeedscopeProfiler < Profiler @@ -46,5 +46,21 @@ def save filename.open("w") { |f| report.print_json(f) } end end + + class VernierProfiler < Profiler + def record + Vernier.run(out: filename.to_s) do + yield + end + end + + def open_command + @open_command ||= "profile-viewer #{@filename}" + end + + def save + # no-op, since it already writes out + end + end end end From 4acadaa80a4ccf8bf13ecb63111e18ddb3079bc4 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 8 May 2024 13:53:09 -0400 Subject: [PATCH 03/11] remove profiler, and just put it in flamegraph --- lib/singed/flamegraph.rb | 87 ++++++++++++++++++++++++++-------------- lib/singed/kernel_ext.rb | 2 +- lib/singed/profiler.rb | 66 ------------------------------ 3 files changed, 59 insertions(+), 96 deletions(-) delete mode 100644 lib/singed/profiler.rb diff --git a/lib/singed/flamegraph.rb b/lib/singed/flamegraph.rb index 2406575..1ab1aa5 100644 --- a/lib/singed/flamegraph.rb +++ b/lib/singed/flamegraph.rb @@ -2,46 +2,29 @@ module Singed class Flamegraph attr_accessor :profile, :filename - def initialize(label: nil, ignore_gc: false, interval: 1000, filename: nil) - # it's been created elsewhere, ie rbspy - if filename - if ignore_gc - raise ArgumentError, "ignore_gc not supported when given an existing file" - end - - if label - raise ArgumentError, "label not supported when given an existing file" - end - - @filename = filename - else - @ignore_gc = ignore_gc - @interval = interval - @time = Time.now # rubocop:disable Rails/TimeZone - @filename = self.class.generate_filename(label: label, time: @time) - end - - @profiler = Singed::Profiler::VernierProfiler.new(filename: @filename) - # @profiler = Singed::Profiler::StackprofPlusSpeedscopeProfiler.new(filename: @filename) + def initialize(label: nil) + @time = Time.now + @filename ||= self.class.generate_filename(label: label, time: @time) end def record(&block) - return yield unless Singed.enabled? - return yield if filename.exist? # file existing means its been captured already + raise NotImplementedError + end - @profiler.record(&block) + def record? + Singed.enabled? end def save - @profiler.save + raise NotImplementedError end - def open - @profiler.open + def open_command + raise NotImplementedError end - def open_command - @profiler.open_command + def open + system open_command end def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/TimeZone @@ -54,5 +37,51 @@ def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/T file = file.relative_path_from(pwd) if file.absolute? && file.to_s.start_with?(pwd.to_s) file end + + class Stackprof < Flamegraph + def initialize(label: nil, ignore_gc: false, interval: 1000, filename: nil) + super(label: label) + end + + def record(&block) + result = nil + @profile = StackProf.run(mode: :wall, raw: true, ignore_gc: @ignore_gc, interval: @interval) do + result = yield + end + result + end + + def save + if filename.exist? + raise ArgumentError, "File #{filename} already exists" + end + + report = Singed::Report.new(@profile) + report.filter! + filename.dirname.mkpath + filename.open("w") { |f| report.print_json(f) } + end + + def open_command + # use npx, so we don't have to add it as a dependency + @open_command ||= "npx speedscope #{@filename}" + end + end + + class Vernier < Flamegraph + def record + ::Vernier.run(out: filename.to_s) do + yield + end + end + + def open_command + @open_command ||= "profile-viewer #{@filename}" + end + + def save + # no-op, since it already writes out + end + end end end diff --git a/lib/singed/kernel_ext.rb b/lib/singed/kernel_ext.rb index 3320ee9..2b18878 100644 --- a/lib/singed/kernel_ext.rb +++ b/lib/singed/kernel_ext.rb @@ -1,6 +1,6 @@ module Kernel def flamegraph(label = nil, open: true, ignore_gc: false, interval: 1000, io: $stdout, &) - fg = Singed::Flamegraph.new(label: label, ignore_gc: ignore_gc, interval: interval) + fg = Singed::Flamegraph::Stackprof.new(label: label, ignore_gc: ignore_gc, interval: interval) result = fg.record(&) fg.save diff --git a/lib/singed/profiler.rb b/lib/singed/profiler.rb deleted file mode 100644 index 769f04d..0000000 --- a/lib/singed/profiler.rb +++ /dev/null @@ -1,66 +0,0 @@ -module Singed - class Profiler - attr_accessor :filename - - def initialize(filename:) - @filename = filename - end - - def record - raise NotImplementedError - end - - def save - raise NotImplementedError - end - - def open - system open_command - end - - def open_command - raise NotImplementedError - end - - class StackprofPlusSpeedscopeProfiler < Profiler - def record - result = nil - @profile = StackProf.run(mode: :wall, raw: true, ignore_gc: @ignore_gc, interval: @interval) do - result = yield - end - result - end - - def open_command - @open_command ||= "npx speedscope #{@filename}" - end - - def save - if filename.exist? - raise ArgumentError, "File #{filename} already exists" - end - - report = Singed::Report.new(@profile) - report.filter! - filename.dirname.mkpath - filename.open("w") { |f| report.print_json(f) } - end - end - - class VernierProfiler < Profiler - def record - Vernier.run(out: filename.to_s) do - yield - end - end - - def open_command - @open_command ||= "profile-viewer #{@filename}" - end - - def save - # no-op, since it already writes out - end - end - end -end From 4dba46e727d2254a24c4cc0c072554322aaa208b Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 8 May 2024 15:22:18 -0400 Subject: [PATCH 04/11] Add vernier options. enable vernier hooks. move announcement of flamegraph to open method --- lib/singed.rb | 22 ++++++++++++++++++++++ lib/singed/flamegraph.rb | 28 +++++++++++++++++++++------- lib/singed/kernel_ext.rb | 9 +-------- lib/singed/railtie.rb | 2 ++ 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/lib/singed.rb b/lib/singed.rb index 13363b2..643cd82 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -35,6 +35,10 @@ def backtrace_cleaner @backtrace_cleaner end + def vernier_hooks + @vernier_hooks ||= [] + end + def silence_line?(line) return backtrace_cleaner.silence_line?(line) if backtrace_cleaner @@ -47,6 +51,24 @@ def filter_line(line) line end + def stackprof(label = "stackprof", open: true, ignore_gc: false, interval: 1000, io: $stdout, &) + fg = Singed::Flamegraph::Stackprof.new(label: label, ignore_gc: ignore_gc, interval: interval) + result = fg.record(&) + fg.save + fg.open if open + + result + end + + def vernier(label = "vernier", open: true, interval: 1000, hooks: nil, gc: true, io: $stdout, &) + fg = Singed::Flamegraph::Vernier.new(label: label, interval: interval, hooks: hooks, gc: gc) + result = fg.record(&) + fg.save + fg.open if open + + result + end + autoload :Flamegraph, "singed/flamegraph" autoload :Profiler, "singed/profiler" autoload :Report, "singed/report" diff --git a/lib/singed/flamegraph.rb b/lib/singed/flamegraph.rb index 1ab1aa5..350b819 100644 --- a/lib/singed/flamegraph.rb +++ b/lib/singed/flamegraph.rb @@ -1,9 +1,10 @@ module Singed class Flamegraph - attr_accessor :profile, :filename + attr_accessor :profile, :filename, :announce_io - def initialize(label: nil) + def initialize(label: nil, announce_io: $stdout) @time = Time.now + @announce_io = announce_io @filename ||= self.class.generate_filename(label: label, time: @time) end @@ -23,8 +24,14 @@ def open_command raise NotImplementedError end - def open - system open_command + def open(open: true) + if open + # use npx, so we don't have to add it as a dependency + announce_io.puts "🔥📈 #{"Captured flamegraph, opening with".colorize(:bold).colorize(:red)}: #{open_command}" + system open_command + else + announce_io.puts "🔥📈 #{"Captured flamegraph to file".colorize(:bold).colorize(:red)}: #{filename}" + end end def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/TimeZone @@ -39,13 +46,13 @@ def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/T end class Stackprof < Flamegraph - def initialize(label: nil, ignore_gc: false, interval: 1000, filename: nil) + def initialize(label: nil, ignore_gc: false, interval: 1000) super(label: label) end def record(&block) result = nil - @profile = StackProf.run(mode: :wall, raw: true, ignore_gc: @ignore_gc, interval: @interval) do + @profile = ::StackProf.run(mode: :wall, raw: true, ignore_gc: @ignore_gc, interval: @interval) do result = yield end result @@ -69,8 +76,15 @@ def open_command end class Vernier < Flamegraph + def initialize(label: nil, interval: 1000, hooks: nil, gc: true) + super(label: label) + @interval = interval + @hooks = hooks || Singed.vernier_hooks + @gc = gc + end + def record - ::Vernier.run(out: filename.to_s) do + ::Vernier.run(out: filename.to_s, interval: @interval, hooks: @hooks, gc: @gc) do yield end end diff --git a/lib/singed/kernel_ext.rb b/lib/singed/kernel_ext.rb index 2b18878..a088224 100644 --- a/lib/singed/kernel_ext.rb +++ b/lib/singed/kernel_ext.rb @@ -3,14 +3,7 @@ def flamegraph(label = nil, open: true, ignore_gc: false, interval: 1000, io: $s fg = Singed::Flamegraph::Stackprof.new(label: label, ignore_gc: ignore_gc, interval: interval) result = fg.record(&) fg.save - - if open - # use npx, so we don't have to add it as a dependency - io.puts "🔥📈 #{"Captured flamegraph, opening with".colorize(:bold).colorize(:red)}: #{fg.open_command}" - fg.open - else - io.puts "🔥📈 #{"Captured flamegraph to file".colorize(:bold).colorize(:red)}: #{fg.filename}" - end + fg.open if open result end diff --git a/lib/singed/railtie.rb b/lib/singed/railtie.rb index 2c7122b..8200ab8 100644 --- a/lib/singed/railtie.rb +++ b/lib/singed/railtie.rb @@ -11,6 +11,8 @@ class Railtie < Rails::Railtie ActiveSupport.on_load(:action_controller) do ActionController::Base.include(Singed::ControllerExt) end + + Singed.vernier_hooks << :rails end def self.init! From d579ac79271b1c03e4e31ae11657ffc70c61a07c Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 8 May 2024 15:50:38 -0400 Subject: [PATCH 05/11] try to reduce how many options we duplicate between vernier/stackprof and singed --- lib/singed.rb | 8 ++++---- lib/singed/flamegraph.rb | 23 +++++++++++++++-------- lib/singed/kernel_ext.rb | 8 ++++++-- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/singed.rb b/lib/singed.rb index 643cd82..12e693b 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -51,8 +51,8 @@ def filter_line(line) line end - def stackprof(label = "stackprof", open: true, ignore_gc: false, interval: 1000, io: $stdout, &) - fg = Singed::Flamegraph::Stackprof.new(label: label, ignore_gc: ignore_gc, interval: interval) + def stackprof(label = "stackprof", open: true, announce_io: $stdout, **stackprof_options, &) + fg = Singed::Flamegraph::Stackprof.new(label: label, **stackprof_options) result = fg.record(&) fg.save fg.open if open @@ -60,8 +60,8 @@ def stackprof(label = "stackprof", open: true, ignore_gc: false, interval: 1000, result end - def vernier(label = "vernier", open: true, interval: 1000, hooks: nil, gc: true, io: $stdout, &) - fg = Singed::Flamegraph::Vernier.new(label: label, interval: interval, hooks: hooks, gc: gc) + def vernier(label = "vernier", open: true, announce_io: $stdout, **vernier_options, &) + fg = Singed::Flamegraph::Vernier.new(label: label, announce_io: announce_io, **vernier_options) result = fg.record(&) fg.save fg.open if open diff --git a/lib/singed/flamegraph.rb b/lib/singed/flamegraph.rb index 350b819..d3a7004 100644 --- a/lib/singed/flamegraph.rb +++ b/lib/singed/flamegraph.rb @@ -46,13 +46,20 @@ def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/T end class Stackprof < Flamegraph - def initialize(label: nil, ignore_gc: false, interval: 1000) + DEFAULT_OPTIONS = { + mode: :wall, + raw: true, + }.freeze + + def initialize(label: nil, announce_io: $stdout, **stackprof_options) super(label: label) + @stackprof_options = stackprof_options end def record(&block) result = nil - @profile = ::StackProf.run(mode: :wall, raw: true, ignore_gc: @ignore_gc, interval: @interval) do + stackprof_options = DEFAULT_OPTIONS.merge(@stackprof_options) + @profile = ::StackProf.run(**stackprof_options) do result = yield end result @@ -76,15 +83,15 @@ def open_command end class Vernier < Flamegraph - def initialize(label: nil, interval: 1000, hooks: nil, gc: true) - super(label: label) - @interval = interval - @hooks = hooks || Singed.vernier_hooks - @gc = gc + def initialize(label: nil, announce_io: $stdout, **vernier_options) + super(label: label, announce_io: announce_io) + + @vernier_options = {hooks: Singed.vernier_hooks}.merge(vernier_options) end def record - ::Vernier.run(out: filename.to_s, interval: @interval, hooks: @hooks, gc: @gc) do + vernier_options = {out: filename.to_s}.merge(@vernier_options) + ::Vernier.run(**vernier_options) do yield end end diff --git a/lib/singed/kernel_ext.rb b/lib/singed/kernel_ext.rb index a088224..62d13f6 100644 --- a/lib/singed/kernel_ext.rb +++ b/lib/singed/kernel_ext.rb @@ -1,6 +1,10 @@ module Kernel - def flamegraph(label = nil, open: true, ignore_gc: false, interval: 1000, io: $stdout, &) - fg = Singed::Flamegraph::Stackprof.new(label: label, ignore_gc: ignore_gc, interval: interval) + def flamegraph(label = nil, open: true, io: $stdout, **stackprof_kwargs, &) + fg = Singed::Flamegraph::Stackprof.new( + label: label, + announce_io: io, + **stackprof_kwargs + ) result = fg.record(&) fg.save fg.open if open From f63fae136e98900690e09b5fd4294e6d5702861d Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 8 May 2024 16:31:19 -0400 Subject: [PATCH 06/11] Add general profile method --- lib/singed.rb | 30 ++++++++++++++++++++++-------- lib/singed/kernel_ext.rb | 13 ++----------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/singed.rb b/lib/singed.rb index 12e693b..7534014 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -51,8 +51,23 @@ def filter_line(line) line end - def stackprof(label = "stackprof", open: true, announce_io: $stdout, **stackprof_options, &) - fg = Singed::Flamegraph::Stackprof.new(label: label, **stackprof_options) + def profiler_klass(profiler) + case profiler + when :stackprof, nil then Singed::Flamegraph::Stackprof + when :vernier then Singed::Flamegraph::Vernier + else + raise ArgumentError, "Unknown profiler: #{profiler}" + end + end + + def profile(label = "flamegraph", profiler: nil, open: true, io: $stdout, **profiler_options, &) + profiler_klass = profiler_klass(profiler) + fg = profiler_klass.new( + label: label, + announce_io: io, + **profiler_options + ) + result = fg.record(&) fg.save fg.open if open @@ -60,13 +75,12 @@ def stackprof(label = "stackprof", open: true, announce_io: $stdout, **stackprof result end - def vernier(label = "vernier", open: true, announce_io: $stdout, **vernier_options, &) - fg = Singed::Flamegraph::Vernier.new(label: label, announce_io: announce_io, **vernier_options) - result = fg.record(&) - fg.save - fg.open if open + def stackprof(label = "stackprof", open: true, announce_io: $stdout, **stackprof_options, &) + profile(label, profiler: :stackprof, open: open, announce_io: announce_io, **stackprof_options, &) + end - result + def vernier(label = "vernier", open: true, announce_io: $stdout, **vernier_options, &) + profile(label, profiler: :vernier, open: open, announce_io: announce_io, **vernier_options, &) end autoload :Flamegraph, "singed/flamegraph" diff --git a/lib/singed/kernel_ext.rb b/lib/singed/kernel_ext.rb index 62d13f6..e773aed 100644 --- a/lib/singed/kernel_ext.rb +++ b/lib/singed/kernel_ext.rb @@ -1,14 +1,5 @@ module Kernel - def flamegraph(label = nil, open: true, io: $stdout, **stackprof_kwargs, &) - fg = Singed::Flamegraph::Stackprof.new( - label: label, - announce_io: io, - **stackprof_kwargs - ) - result = fg.record(&) - fg.save - fg.open if open - - result + def flamegraph(label = nil, profiler: nil, open: true, io: $stdout, **profiler_options, &) + Singed.profile(label, profiler: profiler, open: open, io: io, **profiler_options, &) end end From 73e39c565990e6fc7a0bafa2a2c0f579ec2120a7 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Wed, 8 May 2024 16:36:08 -0400 Subject: [PATCH 07/11] remove now gone file --- lib/singed.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/singed.rb b/lib/singed.rb index 7534014..4989c49 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -84,7 +84,6 @@ def vernier(label = "vernier", open: true, announce_io: $stdout, **vernier_optio end autoload :Flamegraph, "singed/flamegraph" - autoload :Profiler, "singed/profiler" autoload :Report, "singed/report" autoload :RackMiddleware, "singed/rack_middleware" end From 4d17937710e278d1101a3832e14d1f63866614de Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 9 May 2024 10:16:26 -0400 Subject: [PATCH 08/11] rubocop --- lib/singed.rb | 2 +- lib/singed/flamegraph.rb | 2 +- lib/singed/kernel_ext.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/singed.rb b/lib/singed.rb index 4989c49..17d4b08 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -60,7 +60,7 @@ def profiler_klass(profiler) end end - def profile(label = "flamegraph", profiler: nil, open: true, io: $stdout, **profiler_options, &) + def profile(label = "flamegraph", profiler: nil, open: true, announce_io: $stdout, **profiler_options, &) profiler_klass = profiler_klass(profiler) fg = profiler_klass.new( label: label, diff --git a/lib/singed/flamegraph.rb b/lib/singed/flamegraph.rb index d3a7004..ee90362 100644 --- a/lib/singed/flamegraph.rb +++ b/lib/singed/flamegraph.rb @@ -48,7 +48,7 @@ def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/T class Stackprof < Flamegraph DEFAULT_OPTIONS = { mode: :wall, - raw: true, + raw: true }.freeze def initialize(label: nil, announce_io: $stdout, **stackprof_options) diff --git a/lib/singed/kernel_ext.rb b/lib/singed/kernel_ext.rb index e773aed..317adf7 100644 --- a/lib/singed/kernel_ext.rb +++ b/lib/singed/kernel_ext.rb @@ -1,5 +1,5 @@ module Kernel def flamegraph(label = nil, profiler: nil, open: true, io: $stdout, **profiler_options, &) - Singed.profile(label, profiler: profiler, open: open, io: io, **profiler_options, &) + Singed.profile(label, profiler: profiler, open: open, announce_io: io, **profiler_options, &) end end From 2bc5cb2e2c088952b4c1b7c3c042bc5a01aa6027 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 9 May 2024 10:18:45 -0400 Subject: [PATCH 09/11] missing vernier dep --- Gemfile.lock | 2 ++ singed.gemspec | 1 + 2 files changed, 3 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 26a6bb8..734f29e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,6 +4,7 @@ PATH singed (0.2.2) colorize stackprof (>= 0.2.13) + vernier GEM remote: https://rubygems.org/ @@ -67,6 +68,7 @@ GEM lint_roller (~> 1.1) rubocop-performance (~> 1.20.2) unicode-display_width (2.5.0) + vernier (1.0.1) PLATFORMS ruby diff --git a/singed.gemspec b/singed.gemspec index 071f570..47cb378 100644 --- a/singed.gemspec +++ b/singed.gemspec @@ -18,6 +18,7 @@ Gem::Specification.new do |spec| spec.add_dependency "colorize" spec.add_dependency "stackprof", ">= 0.2.13" + spec.add_dependency "vernier" spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "rspec" From b97237de0d691d8e07bacb7235529c15f71062f2 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 9 May 2024 10:19:22 -0400 Subject: [PATCH 10/11] fix var name --- lib/singed.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/singed.rb b/lib/singed.rb index 17d4b08..34cdd2d 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -64,7 +64,7 @@ def profile(label = "flamegraph", profiler: nil, open: true, announce_io: $stdou profiler_klass = profiler_klass(profiler) fg = profiler_klass.new( label: label, - announce_io: io, + announce_io: announce_io, **profiler_options ) From 82a410e69a709475c2ce8569f090f2903094a782 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 9 May 2024 12:46:07 -0400 Subject: [PATCH 11/11] WIP validation --- lib/singed.rb | 7 ++++--- lib/singed/flamegraph.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/singed.rb b/lib/singed.rb index 34cdd2d..3886fa8 100644 --- a/lib/singed.rb +++ b/lib/singed.rb @@ -51,7 +51,7 @@ def filter_line(line) line end - def profiler_klass(profiler) + def profiler_class_for(profiler) case profiler when :stackprof, nil then Singed::Flamegraph::Stackprof when :vernier then Singed::Flamegraph::Vernier @@ -61,8 +61,9 @@ def profiler_klass(profiler) end def profile(label = "flamegraph", profiler: nil, open: true, announce_io: $stdout, **profiler_options, &) - profiler_klass = profiler_klass(profiler) - fg = profiler_klass.new( + profiler_class = profiler_class_for(profiler) + + fg = profiler_class.new( label: label, announce_io: announce_io, **profiler_options diff --git a/lib/singed/flamegraph.rb b/lib/singed/flamegraph.rb index ee90362..1488444 100644 --- a/lib/singed/flamegraph.rb +++ b/lib/singed/flamegraph.rb @@ -45,6 +45,15 @@ def self.generate_filename(label: nil, time: Time.now) # rubocop:disable Rails/T file end + def self.validate_options(klass, method_name, options) + method = klass.instance_method(:method_name) + options.each do |key, value| + if method.parameters.none? { |type, name| type == :key && name == key } + raise ArgumentError, "Unknown option #{key} for #{klass}.#{method_name}" + end + end + end + class Stackprof < Flamegraph DEFAULT_OPTIONS = { mode: :wall, @@ -80,6 +89,8 @@ def open_command # use npx, so we don't have to add it as a dependency @open_command ||= "npx speedscope #{@filename}" end + + end class Vernier < Flamegraph @@ -91,6 +102,7 @@ def initialize(label: nil, announce_io: $stdout, **vernier_options) def record vernier_options = {out: filename.to_s}.merge(@vernier_options) + validate_options(::Vernier, :run, vernier_options) ::Vernier.run(**vernier_options) do yield end