Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
sl0thentr0py committed Mar 21, 2023
1 parent 9c90d2d commit 2b18591
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 5 deletions.
5 changes: 1 addition & 4 deletions sentry-ruby/lib/sentry/envelope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ def type
end

def to_s
<<~ITEM
#{JSON.generate(@headers)}
#{JSON.generate(@payload)}
ITEM
[JSON.generate(@headers), JSON.generate(@payload)].join("\n")
end

def serialize
Expand Down
4 changes: 4 additions & 0 deletions sentry-ruby/lib/sentry/hub.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ def start_transaction(transaction: nil, custom_sampling_context: {}, instrumente
sampling_context.merge!(custom_sampling_context)

transaction.set_initial_sample_decision(sampling_context: sampling_context)

#TODO-neel profiles sample
transaction.profiler.start

transaction
end

Expand Down
87 changes: 87 additions & 0 deletions sentry-ruby/lib/sentry/profiler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# frozen_string_literal: true

require "etc"
require "stackprof"
require "securerandom"

module Sentry
class Profiler

VERSION = "1"
PLATFORM = "ruby"
# 101 Hz in microseconds
DEFAULT_INTERVAL = 1e6 / 101

def initialize
@event_id = SecureRandom.uuid.delete("-")
end

def start
# TODO-neel profiler args
StackProf.start(interval: DEFAULT_INTERVAL, raw: true, aggregate: false)
end

def stop
StackProf.stop
end

def to_hash
return nil unless Sentry.initialized?

results = StackProf.results
return nil unless results
return nil if results.empty?

report = StackProf::Report.new(results)
frame_map = {}

frames = report.frames.to_enum.with_index.map do |frame, idx|
frame_id, frame_data = frame

# need to map over stackprof frame ids to ours
frame_map[frame_id] = idx

# TODO-neel module, filename
{
abs_path: frame_data[:file],
function: frame_data[:name],
lineno: frame_data[:line]
}
end

raw_stacks, * = report.flamegraph_stacks(results[:raw])

# map over frame ids
# reverse each stack since perl flamegraph is reverse order
# TODO-neel compact ok??
stacks = raw_stacks.map { |stack| stack.reverse.map { |id| frame_map[id] }.compact }

# stacks are all unique so stack ids are just 0...len
elapsed_since_start_ns = 0

samples = stacks.size.times.map do |stack_id|
# stackprof deltas are in microseconds
elapsed_since_start_ns += (results[:raw_timestamp_deltas][stack_id] * 1e3).to_i

{
stack_id: stack_id,
thread_id: '42', # TODO-neel not available???
elapsed_since_start_ns: elapsed_since_start_ns
}
end

profile = {
frames: frames,
stacks: stacks,
samples: samples
}

{
event_id: @event_id,
platform: PLATFORM,
version: VERSION,
profile: profile
}
end
end
end
3 changes: 2 additions & 1 deletion sentry-ruby/lib/sentry/scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ def os_context
name: uname[:sysname] || RbConfig::CONFIG["host_os"],
version: uname[:version],
build: uname[:release],
kernel_version: uname[:version]
kernel_version: uname[:version],
machine: uname[:machine]
}
end
end
Expand Down
11 changes: 11 additions & 0 deletions sentry-ruby/lib/sentry/transaction.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "sentry/baggage"
require "sentry/profiler"

module Sentry
class Transaction < Span
Expand Down Expand Up @@ -83,6 +84,7 @@ def initialize(
@effective_sample_rate = nil
@contexts = {}
@measurements = {}
@profiler = nil
init_span_recorder
end

Expand Down Expand Up @@ -254,6 +256,9 @@ def finish(hub: nil, end_timestamp: nil)
@name = UNLABELD_NAME
end

# TODO-neel profiles sample
@profiler&.stop

if @sampled
event = hub.current_client.event_from_transaction(self)
hub.capture_event(event)
Expand Down Expand Up @@ -288,6 +293,12 @@ def set_context(key, value)
@contexts[key] = value
end

# The stackprof profiler instance
# @return [Profiler]
def profiler
@profiler ||= Profiler.new
end

protected

def init_span_recorder(limit = 1000)
Expand Down
27 changes: 27 additions & 0 deletions sentry-ruby/lib/sentry/transaction_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class TransactionEvent < Event
# @return [Float, nil]
attr_reader :start_timestamp

# @return [Hash, nil]
attr_accessor :profile

def initialize(transaction:, **options)
super(**options)

Expand All @@ -32,6 +35,9 @@ def initialize(transaction:, **options)

finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
self.spans = finished_spans.map(&:to_hash)

# TODO-neel profiler cleanup
self.profile = populate_profile(transaction)
end

# Sets the event's start_timestamp.
Expand All @@ -49,5 +55,26 @@ def to_hash
data[:measurements] = @measurements
data
end

private

def populate_profile(transaction)
return nil unless transaction.profiler

transaction.profiler.to_hash.merge(
environment: environment,
release: release,
timestamp: Time.at(start_timestamp).iso8601,
device: { architecture: Scope.os_context[:machine] },
os: { name: Scope.os_context[:name], version: Scope.os_context[:version] },
runtime: Scope.runtime_context,
transaction: {
id: event_id,
name: transaction.name,
trace_id: transaction.trace_id,
active_thead_id: '42' # TODO-neel
}
)
end
end
end
8 changes: 8 additions & 0 deletions sentry-ruby/lib/sentry/transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def send_envelope(envelope)

if data
log_info("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
File.open('/tmp/dump', 'w') { |file| file.write(data) } if envelope.items.map(&:type).include?('profile')
send_data(data)
end
end
Expand Down Expand Up @@ -154,6 +155,13 @@ def envelope_from_event(event)
event_payload
)

if event.is_a?(TransactionEvent) && event.profile
envelope.add_item(
{ type: 'profile', content_type: 'application/json' },
event.profile
)
end

client_report_headers, client_report_payload = fetch_pending_client_report
envelope.add_item(client_report_headers, client_report_payload) if client_report_headers

Expand Down
1 change: 1 addition & 0 deletions sentry-ruby/sentry-ruby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]

spec.add_dependency "concurrent-ruby", '~> 1.0', '>= 1.0.2'
spec.add_dependency "stackprof", '~> 0.2.23'
end

0 comments on commit 2b18591

Please sign in to comment.