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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
/spec/reports/
/tmp/

#macos
.DS_Store

# rspec failure tracking
.rspec_status
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Opera Changelog

### 0.3.5 - February 24, 2025

- Simplify instrumentation configuration
- Allow instrumentation logic to be defined at the source

### 0.3.4 - February 19, 2025

- Add support for `benchmark` label
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
opera (0.3.4)
opera (0.3.5)

GEM
remote: https://rubygems.org/
Expand Down
2 changes: 1 addition & 1 deletion lib/opera/operation/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class << self
def call(args = {})
operation = new(params: args.fetch(:params, {}), dependencies: args.fetch(:dependencies, {}))
executor = Executor.new(operation)
Instrumentation.new(config).instrument(name: self.name, level: :operation) do
Instrumentation.new(operation).instrument(name: self.name, level: :operation) do
executor.evaluate_instructions(instructions)
end
executor.result
Expand Down
10 changes: 4 additions & 6 deletions lib/opera/operation/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ class Config
DEVELOPMENT_MODE = :development
PRODUCTION_MODE = :production

attr_accessor :transaction_class, :transaction_method, :transaction_options,
:instrumentation_class, :instrumentation_method, :instrumentation_options, :mode, :reporter
attr_accessor :transaction_class, :transaction_method, :transaction_options, :instrumentation_class,
:mode, :reporter

def initialize
@transaction_class = self.class.transaction_class
@transaction_method = self.class.transaction_method || :transaction
@transaction_options = self.class.transaction_options

@instrumentation_class = self.class.instrumentation_class
@instrumentation_method = self.class.instrumentation_method || :instrument
@instrumentation_options = self.class.instrumentation_options || {}

@mode = self.class.mode || DEVELOPMENT_MODE
@reporter = custom_reporter || self.class.reporter
Expand All @@ -41,8 +39,8 @@ def validate!
end

class << self
attr_accessor :transaction_class, :transaction_method, :transaction_options,
:instrumentation_class, :instrumentation_method, :instrumentation_options, :mode, :reporter
attr_accessor :transaction_class, :transaction_method, :transaction_options, :instrumentation_class,
:mode, :reporter

def configure
yield self
Expand Down
2 changes: 1 addition & 1 deletion lib/opera/operation/instructions/executors/step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Step < Executor
def call(instruction)
method = instruction[:method]

Instrumentation.new(config).instrument(name: "##{method}", level: :step) do
Instrumentation.new(operation).instrument(name: "##{method}", level: :step) do
operation.result.add_execution(method) unless production_mode?
operation.send(method)
end
Expand Down
28 changes: 15 additions & 13 deletions lib/opera/operation/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,31 @@
module Opera
module Operation
class Instrumentation
attr_reader :config
class Base
def self.instrument(operation, name:, level: :operation)
raise NotImplementedError, "#{self.class} must implement #instrument"
end
end

attr_reader :operation

def initialize(config)
@config = config
def initialize(operation)
@operation = operation
end

def instrument(name:, level: :operation)
return yield if !instrumentation_enabled?
return yield if level == :step && instrumentation_level != :step
return yield if !instrumentation_compatible?

instrumentation_class.send(instrumentation_method, name, **instrumentation_options.except(:level)) do
instrumentation_class.instrument(operation, name: name, level: level) do
yield
end
end

private

def instrumentation_options
config.instrumentation_options
end

def instrumentation_method
config.instrumentation_method
def config
operation.config
end

def instrumentation_class
Expand All @@ -36,8 +38,8 @@ def instrumentation_enabled?
!!config.instrumentation_class
end

def instrumentation_level
instrumentation_options[:level] || :operation
def instrumentation_compatible?
config.instrumentation_class.ancestors.include?(Opera::Operation::Instrumentation::Base)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/opera/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Opera
VERSION = '0.3.4'
VERSION = '0.3.5'
end
54 changes: 19 additions & 35 deletions spec/opera/operation/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1044,10 +1044,11 @@ def step_3
end

context 'for instrumentation' do
let(:instrumentation_class) do
Class.new do
def self.trace(name)
puts "Trace #{name}"
let(:instrumentation_wrapper) do
Class.new(Opera::Operation::Instrumentation::Base) do

def self.instrument(operation, name:, level:)
puts "Trace #{name} with level #{level}"
yield
end
end
Expand Down Expand Up @@ -1083,42 +1084,25 @@ def step_5
end
end

context 'for operation level' do
before do
Operation::Config.configure do |config|
config.instrumentation_class = instrumentation_class
config.instrumentation_method = :trace
end
end

it 'evaluates all steps' do
expect(subject.executions).to match_array(%i[step_1 step_2 step_3 step_4 step_5])
end

it 'calls instrumentation with correct params' do
expect(instrumentation_class).to receive(:trace).with('Opera::FakeName').and_call_original
subject
before do
Operation::Config.configure do |config|
config.instrumentation_class = instrumentation_wrapper
end
end

context 'for step level' do
before do
Operation::Config.configure do |config|
config.instrumentation_class = instrumentation_class
config.instrumentation_method = :trace
config.instrumentation_options = { level: :step }
end
end

it 'evaluates all steps' do
expect(subject.executions).to match_array(%i[step_1 step_2 step_3 step_4 step_5])
end
it 'evaluates all steps' do
expect(subject.executions).to match_array(%i[step_1 step_2 step_3 step_4 step_5])
end

it 'calls instrumentation with correct params' do
expect(instrumentation_class).to receive(:trace).exactly(6).times.and_call_original
it 'calls instrumentation with correct params' do
expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: 'Opera::FakeName', level: :operation).once.and_call_original
expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_1', level: :step).once.and_call_original
expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_2', level: :step).once.and_call_original
expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_3', level: :step).once.and_call_original
expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_4', level: :step).once.and_call_original
expect(instrumentation_wrapper).to receive(:instrument).with(anything, name: '#step_5', level: :step).once

subject
end
subject
end
end

Expand Down