Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [OpenAPI] Add support for the `root:` option in Blueprinter response annotations (#343).
- Add `FiberScheduler#timeout_after` (#374).
- [Telemetry] Add `Rage::Telemetry.every(interval_ms, &block)`, a generic scheduling primitive for running recurring work on the reactor — e.g. sampling metrics or measuring event loop lag. (#379)

### Fixed

Expand Down
13 changes: 13 additions & 0 deletions lib/rage/telemetry/telemetry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ def self.available_spans
__registry.keys
end

# Registers a block to be executed repeatedly at a fixed interval while the server is running.
# The block is run inside a fiber, so blocking I/O inside it (e.g. flushing metrics to a
# collector) will not block the server.
#
# @param interval_ms [Integer] the execution interval in milliseconds
# @example Periodically sample GC stats
# Rage::Telemetry.every(1000) { MyMetrics.record_gc_stats(GC.stat) }
def self.every(interval_ms, &block)
Comment thread
Abishekcs marked this conversation as resolved.
Iodine.run_every(interval_ms) do
Fiber.schedule { block.call }
end
end

# @private
def self.__registry
@__registry ||= Spans.constants.each_with_object({}) do |const, memo|
Expand Down
36 changes: 36 additions & 0 deletions spec/telemetry/telemetry_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,42 @@
end
end

describe ".every" do
context "when the reactor is not running" do
it "registers the timer, which Iodine defers until the server starts" do
allow(Iodine).to receive(:running?).and_return(false)
expect(Iodine).to receive(:run_every).with(100)

described_class.every(100) {}
end
end

context "when the reactor is running" do
it "registers the timer immediately" do
allow(Iodine).to receive(:running?).and_return(true)
expect(Iodine).to receive(:run_every).with(100)

described_class.every(100) {}
end

it "executes the block in a fiber via Iodine.run_every" do
allow(Iodine).to receive(:running?).and_return(true)

received_block = nil
allow(Iodine).to receive(:run_every) { |_ms, &block| received_block = block }

Fiber.set_scheduler(Rage::FiberScheduler.new)
my_block = -> { :did_run }
described_class.every(100, &my_block)

fiber = received_block.call
expect(fiber.__get_result).to eq(:did_run)
ensure
Fiber.set_scheduler(nil)
end
end
end

describe "SpanResult" do
subject { described_class::SpanResult }

Expand Down