Skip to content

Commit

Permalink
Add RouteTrace
Browse files Browse the repository at this point in the history
  • Loading branch information
kyrylo committed Apr 26, 2019
1 parent e4d3dd8 commit 9961f64
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 4 deletions.
1 change: 1 addition & 0 deletions lib/airbrake-ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
require 'airbrake-ruby/performance_breakdown'
require 'airbrake-ruby/benchmark'
require 'airbrake-ruby/monotonic_time'
require 'airbrake-ruby/route_trace'

# Airbrake is a thin wrapper around instances of the notifier classes (such as
# notice, performance & deploy notifiers). It creates a way to access them via a
Expand Down
31 changes: 28 additions & 3 deletions lib/airbrake-ruby/benchmark.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,37 @@ module Airbrake
#
# @since v4.3.0
# @api private
module Benchmark
class Benchmark
# Measures monotonic time for the given operation.
#
# @yieldreturn [void]
def self.measure
start = MonotonicTime.time_in_ms
benchmark = new

yield
MonotonicTime.time_in_ms - start

benchmark.stop
benchmark.duration
end

# @return [Float]
attr_reader :duration

# @since v4.3.0
def initialize
@start = MonotonicTime.time_in_ms
@duration = 0.0
end

# Stops the benchmark and stores `duration`.
#
# @since v4.3.0
# @return [Boolean] true for the first invocation, false in all other cases
def stop
return false if @duration > 0.0

@duration = MonotonicTime.time_in_ms - @start
true
end
end
end
38 changes: 38 additions & 0 deletions lib/airbrake-ruby/route_trace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module Airbrake
# RouteTrace represents a named & timed sequence of events that consists of
# spans.
#
# @api public
# @since v4.3.0
class RouteTrace
def initialize
@spans = {}
end

def span(label)
start_span(label)
yield
stop_span(label)
end

def start_span(label)
return false if @spans.key?(label)

@spans[label] = Airbrake::Benchmark.new
true
end

def stop_span(label)
return false unless @spans.key?(label)

@spans[label].stop
true
end

def spans
@spans.each_with_object({}) do |(label, benchmark), new_groups|
new_groups[label] = benchmark.duration
end
end
end
end
14 changes: 14 additions & 0 deletions spec/airbrake_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,18 @@
expect(described_class.deploy_notifier).to be_an(Airbrake::DeployNotifier)
end
end

describe ".start_span" do
it "starts a span" do
described_class.start_span('operation name')
end
end
end

# client = Net::HTTP.new("myservice.com")
# req = Net::HTTP::Post.new("/")

# span = OpenTracing.start_span("my_span")
# OpenTracing.inject(span.context, OpenTracing::FORMAT_RACK, req)
# res = client.request(req)
# #...
28 changes: 27 additions & 1 deletion spec/benchmark_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
RSpec.describe Airbrake::Benchmark do
describe ".measure" do
it "returns measured performance time" do
expect(subject.measure { '10' * 10 }).to be_kind_of(Numeric)
expect(described_class.measure { '10' * 10 }).to be_kind_of(Numeric)
end
end

describe "#stop" do
before { subject }

context "when called one time" do
its(:stop) { is_expected.to eq(true) }
end

context "when called twice or more" do
before { subject.stop }

its(:stop) { is_expected.to eq(false) }
end
end

describe "#duration" do
context "when #stop wasn't called yet" do
its(:duration) { is_expected.to be_zero }
end

context "when #stop was called" do
before { subject.stop }

its(:duration) { is_expected.to be > 0 }
end
end
end
114 changes: 114 additions & 0 deletions spec/route_trace_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
RSpec.describe Airbrake::RouteTrace do
describe "#span" do
it "captures a span" do
subject.span('operation') {}
expect(subject.spans).to match('operation' => be > 0)
end
end

describe "#start_span" do
context "when called once" do
it "returns true" do
expect(subject.start_span('operation')).to eq(true)
end
end

context "when called multiple times" do
before { subject.start_span('operation') }

it "returns false" do
expect(subject.start_span('operation')).to eq(false)
end
end

context "when another span was started" do
before { subject.start_span('operation') }

it "returns true" do
expect(subject.start_span('another operation')).to eq(true)
end
end

context "when #spans was called" do
before { subject.start_span('operation') }

it "returns spans with zero values" do
expect(subject.spans).to eq('operation' => 0.0)
end
end
end

describe "#stop_span" do
context "when #start_span wasn't invoked" do
it "returns false" do
expect(subject.stop_span('operation')).to eq(false)
end
end

context "when #start_span was invoked" do
before { subject.start_span('operation') }

it "returns true" do
expect(subject.stop_span('operation')).to eq(true)
end
end

context "when multiple spans were started" do
before do
subject.start_span('operation')
subject.start_span('another operation')
end

context "and when stopping in LIFO order" do
it "returns true for all spans" do
expect(subject.stop_span('another operation')).to eq(true)
expect(subject.stop_span('operation')).to eq(true)
end
end

context "and when stopping in FIFO order" do
it "returns true for all spans" do
expect(subject.stop_span('operation')).to eq(true)
expect(subject.stop_span('another operation')).to eq(true)
end
end
end
end

describe "#spans" do
context "when no spans were captured" do
it "returns an empty hash" do
expect(subject.spans).to eq({})
end
end

context "when a span was captured" do
before do
subject.start_span('operation')
subject.stop_span('operation')
end

it "returns a Hash with the corresponding span" do
subject.stop_span('operation')
expect(subject.spans).to match('operation' => be > 0)
end
end

context "when multiple spans were captured" do
before do
subject.start_span('operation')
subject.stop_span('operation')

subject.start_span('another operation')
subject.stop_span('another operation')
end

it "returns a Hash with all spans" do
expect(subject.spans).to match(
'operation' => be > 0,
'another operation' => be > 0
)
end
end
end
end

0 comments on commit 9961f64

Please sign in to comment.