Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profiler pprof encoding benchmark and first improvements #1511

Merged
merged 2 commits into from
May 21, 2021
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
Binary file not shown.
90 changes: 90 additions & 0 deletions benchmarks/profiler_submission.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
require 'benchmark/ips'
require 'ddtrace'
require 'pry'

# This benchmark measures the performance of encoding pprofs and trying to submit them
#
# The FLUSH_DUMP_FILE (by default benchmarks/data/profiler-submission-marshal.gz, gathered from benchmarking using
# the discourse forum rails app) can be generated by changing the scheduler.rb#flush_events to dump the contents of
# "flush" to a file during a benchmark execution:
#
# dump_file = "marshal-#{Time.now.utc.to_i}.dump"
# File.open(dump_file, "w") { |f| Marshal.dump(flush, f) }
# Datadog.logger.info("Dumped to #{dump_file}")
#
# And then gzipping the result. (This can probably be automated a bit by adding an extra exporter, but the above worked
# for me).

class ProfilerSubmission
def create_profiler
@adapter_buffer = []

Datadog.configure do |c|
# c.diagnostics.debug = true
c.profiling.enabled = true
c.tracer.transport_options = proc { |t| t.adapter :test, @adapter_buffer }
end

# Stop background threads
Datadog.profiler.shutdown!

# Call exporter directly
@exporter = Datadog.profiler.scheduler.exporters.first
@flush = Marshal.load(
Zlib::GzipReader.new(File.open(ENV['FLUSH_DUMP_FILE'] || 'benchmarks/data/profiler-submission-marshal.gz'))
)
end

def check_valid_pprof
output_pprof = @adapter_buffer.last[:form]["data[0]"].io

expected_hashes = [
"75c65dec2d5d750faca3e905486c39ba44219636ac1bea15d5d624b741d4b62a",
"6780c47b3e271f2abe0346fd40ac358f6a34270b3a8f5743aacd1970bbbbc6f5"
]
current_hash = Digest::SHA256.hexdigest(Zlib::GzipReader.new(output_pprof).read)

if expected_hashes.include?(current_hash)
puts "Output hash #{current_hash} matches known signature"
else
puts "WARNING: Unexpected pprof output -- unknown hash. Hashes seem to differ due to some of our dependencies changing, " \
"but it can also indicate that encoding output has become corrupted."
end
end

def run_benchmark
Benchmark.ips do |x|
x.config(time: 10, warmup: 2)

x.report("exporter #{ENV['CONFIG']}") do
run_once
end

x.save! 'profiler-submission-results.ipsbench'
x.compare!
end
end

def run_forever
run_once while true
end

def run_once
@adapter_buffer.clear
@exporter.export(@flush)
end
end

puts "Current pid is #{Process.pid}"

ProfilerSubmission.new.instance_exec do
create_profiler
run_once
check_valid_pprof

if ARGV.include?('--forever')
run_forever
else
run_benchmark
end
end
9 changes: 6 additions & 3 deletions lib/ddtrace/profiling/pprof/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ def initialize
@sample_types = MessageSet.new
@samples = []
@string_table = StringTable.new
# Cache these procs, since it's pretty expensive to keep recreating them
@build_location = method(:build_location).to_proc
@build_function = method(:build_function).to_proc
end

def encode_profile(profile)
Expand Down Expand Up @@ -60,7 +63,7 @@ def build_locations(backtrace_locations, length)
# Function name
backtrace_location.base_label,
# Build function
&method(:build_location)
&@build_location
)
end

Expand All @@ -73,7 +76,7 @@ def build_locations(backtrace_locations, length)
''.freeze,
0,
"#{omitted} #{desc}",
&method(:build_location)
&@build_location
)
end

Expand All @@ -87,7 +90,7 @@ def build_location(id, filename, line_number, function_name = nil)
@functions.fetch(
filename,
function_name,
&method(:build_function)
&@build_function
).id,
line_number
)]
Expand Down