diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 18e2c317325..30f1ed4da90 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -23,3 +23,4 @@ aba860197ce4f708e917e27323884d8efe3692ca acee9c7f3d953551cc0f20ef60e5045432bcf7e6 6e4193c5bf3c2b948c91598c7a70bc34b59872fa b1481b215d8b1bf33a1d53419d1b95ebd1a70877 +01ec575b25bc68edfea7de2046ce3c43bc3ed4af diff --git a/.standard_todo.yml b/.standard_todo.yml index 1af845b00e0..45c061abc4f 100644 --- a/.standard_todo.yml +++ b/.standard_todo.yml @@ -42,15 +42,3 @@ ignore: - spec/support/**/** - tasks/**/** - yard/**/** - -# These profiling ignores are ALL going to be fixed in separate PRs -- lib/datadog/profiling/**/**: - - Style/StringLiterals -- spec/datadog/profiling/**/**: - - Style/StringLiterals -- ext/datadog_profiling_loader/extconf.rb: - - Style/StringLiterals -- ext/datadog_profiling_native_extension/extconf.rb: - - Style/StringLiterals -- ext/datadog_profiling_native_extension/native_extension_helpers.rb: - - Style/StringLiterals diff --git a/ext/datadog_profiling_loader/extconf.rb b/ext/datadog_profiling_loader/extconf.rb index 49cf06fdb54..b815215a409 100644 --- a/ext/datadog_profiling_loader/extconf.rb +++ b/ext/datadog_profiling_loader/extconf.rb @@ -1,22 +1,22 @@ # rubocop:disable Style/StderrPuts # rubocop:disable Style/GlobalVars -if RUBY_ENGINE != 'ruby' || Gem.win_platform? +if RUBY_ENGINE != "ruby" || Gem.win_platform? $stderr.puts( - 'WARN: Skipping build of Datadog profiling loader. See Datadog profiling native extension note for details.' + "WARN: Skipping build of Datadog profiling loader. See Datadog profiling native extension note for details." ) - File.write('Makefile', 'all install clean: # dummy makefile that does nothing') + File.write("Makefile", "all install clean: # dummy makefile that does nothing") exit end -require 'mkmf' +require "mkmf" # mkmf on modern Rubies actually has an append_cflags that does something similar # (see https://github.com/ruby/ruby/pull/5760), but as usual we need a bit more boilerplate to deal with legacy Rubies def add_compiler_flag(flag) if try_cflags(flag) - $CFLAGS << ' ' << flag + $CFLAGS << " " << flag else $stderr.puts("WARNING: '#{flag}' not accepted by compiler, skipping it") end @@ -24,26 +24,26 @@ def add_compiler_flag(flag) # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go. # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced. -add_compiler_flag '-Werror' if ENV['DATADOG_GEM_CI'] == 'true' +add_compiler_flag "-Werror" if ENV["DATADOG_GEM_CI"] == "true" # Older gcc releases may not default to C99 and we need to ask for this. This is also used: # * by upstream Ruby -- search for gnu99 in the codebase # * by msgpack, another datadog gem dependency # (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8) -add_compiler_flag '-std=gnu99' +add_compiler_flag "-std=gnu99" # Gets really noisy when we include the MJIT header, let's omit it (TODO: Use #pragma GCC diagnostic instead?) -add_compiler_flag '-Wno-unused-function' +add_compiler_flag "-Wno-unused-function" # Allow defining variables at any point in a function -add_compiler_flag '-Wno-declaration-after-statement' +add_compiler_flag "-Wno-declaration-after-statement" # If we forget to include a Ruby header, the function call may still appear to work, but then # cause a segfault later. Let's ensure that never happens. -add_compiler_flag '-Werror-implicit-function-declaration' +add_compiler_flag "-Werror-implicit-function-declaration" # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used. -add_compiler_flag '-Wunused-parameter' +add_compiler_flag "-Wunused-parameter" # The native extension is not intended to expose any symbols/functions for other native libraries to use; # the sole exception being `Init_datadog_profiling_loader` which needs to be visible for Ruby to call it when @@ -51,14 +51,14 @@ def add_compiler_flag(flag) # # By setting this compiler flag, we tell it to assume that everything is private unless explicitly stated. # For more details see https://gcc.gnu.org/wiki/Visibility -add_compiler_flag '-fvisibility=hidden' +add_compiler_flag "-fvisibility=hidden" # Avoid legacy C definitions -add_compiler_flag '-Wold-style-definition' +add_compiler_flag "-Wold-style-definition" # Enable all other compiler warnings -add_compiler_flag '-Wall' -add_compiler_flag '-Wextra' +add_compiler_flag "-Wall" +add_compiler_flag "-Wextra" # Tag the native extension library with the Ruby version and Ruby platform. # This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that diff --git a/ext/datadog_profiling_native_extension/extconf.rb b/ext/datadog_profiling_native_extension/extconf.rb index cbf0843e8d4..b7a0ef819b3 100644 --- a/ext/datadog_profiling_native_extension/extconf.rb +++ b/ext/datadog_profiling_native_extension/extconf.rb @@ -1,8 +1,8 @@ # rubocop:disable Style/StderrPuts # rubocop:disable Style/GlobalVars -require_relative 'native_extension_helpers' -require_relative '../libdatadog_extconf_helpers' +require_relative "native_extension_helpers" +require_relative "../libdatadog_extconf_helpers" SKIPPED_REASON_FILE = "#{__dir__}/skipped_reason.txt".freeze # Not a problem if the file doesn't exist or we can't delete it @@ -29,13 +29,13 @@ def skip_building_extension!(reason) ) if fail_install_if_missing_extension - require 'mkmf' + require "mkmf" Logging.message( - '[datadog] Failure cause: ' \ + "[datadog] Failure cause: " \ "#{Datadog::Profiling::NativeExtensionHelpers::Supported.render_skipped_reason_file(**reason)}\n" ) else - File.write('Makefile', 'all install clean: # dummy makefile that does nothing') + File.write("Makefile", "all install clean: # dummy makefile that does nothing") end exit @@ -68,7 +68,7 @@ def skip_building_extension!(reason) # NOTE: we MUST NOT require 'mkmf' before we check the #skip_building_extension? because the require triggers checks # that may fail on an environment not properly setup for building Ruby extensions. -require 'mkmf' +require "mkmf" Logging.message("[datadog] Using compiler:\n") xsystem("#{CONFIG["CC"]} -v") @@ -78,7 +78,7 @@ def skip_building_extension!(reason) # (see https://github.com/ruby/ruby/pull/5760), but as usual we need a bit more boilerplate to deal with legacy Rubies def add_compiler_flag(flag) if try_cflags(flag) - $CFLAGS << ' ' << flag + $CFLAGS << " " << flag else $stderr.puts("WARNING: '#{flag}' not accepted by compiler, skipping it") end @@ -86,23 +86,23 @@ def add_compiler_flag(flag) # Because we can't control what compiler versions our customers use, shipping with -Werror by default is a no-go. # But we can enable it in CI, so that we quickly spot any new warnings that just got introduced. -add_compiler_flag '-Werror' if ENV['DATADOG_GEM_CI'] == 'true' +add_compiler_flag "-Werror" if ENV["DATADOG_GEM_CI"] == "true" # Older gcc releases may not default to C99 and we need to ask for this. This is also used: # * by upstream Ruby -- search for gnu99 in the codebase # * by msgpack, another datadog gem dependency # (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8) -add_compiler_flag '-std=gnu99' +add_compiler_flag "-std=gnu99" # Gets really noisy when we include the MJIT header, let's omit it (TODO: Use #pragma GCC diagnostic instead?) -add_compiler_flag '-Wno-unused-function' +add_compiler_flag "-Wno-unused-function" # Allow defining variables at any point in a function -add_compiler_flag '-Wno-declaration-after-statement' +add_compiler_flag "-Wno-declaration-after-statement" # If we forget to include a Ruby header, the function call may still appear to work, but then # cause a segfault later. Let's ensure that never happens. -add_compiler_flag '-Werror-implicit-function-declaration' +add_compiler_flag "-Werror-implicit-function-declaration" # The native extension is not intended to expose any symbols/functions for other native libraries to use; # the sole exception being `Init_datadog_profiling_native_extension` which needs to be visible for Ruby to call it when @@ -110,22 +110,22 @@ def add_compiler_flag(flag) # # By setting this compiler flag, we tell it to assume that everything is private unless explicitly stated. # For more details see https://gcc.gnu.org/wiki/Visibility -add_compiler_flag '-fvisibility=hidden' +add_compiler_flag "-fvisibility=hidden" # Avoid legacy C definitions -add_compiler_flag '-Wold-style-definition' +add_compiler_flag "-Wold-style-definition" # Enable all other compiler warnings -add_compiler_flag '-Wall' -add_compiler_flag '-Wextra' +add_compiler_flag "-Wall" +add_compiler_flag "-Wextra" -if ENV['DDTRACE_DEBUG'] == 'true' - $defs << '-DDD_DEBUG' - CONFIG['optflags'] = '-O0' - CONFIG['debugflags'] = '-ggdb3' +if ENV["DDTRACE_DEBUG"] == "true" + $defs << "-DDD_DEBUG" + CONFIG["optflags"] = "-O0" + CONFIG["debugflags"] = "-ggdb3" end -if RUBY_PLATFORM.include?('linux') +if RUBY_PLATFORM.include?("linux") # Supposedly, the correct way to do this is # ``` # have_library 'pthread' @@ -133,73 +133,73 @@ def add_compiler_flag(flag) # ``` # but it's slower to build # so instead we just assume that we have the function we need on Linux, and nowhere else - $defs << '-DHAVE_PTHREAD_GETCPUCLOCKID' + $defs << "-DHAVE_PTHREAD_GETCPUCLOCKID" # Not available on macOS - $defs << '-DHAVE_CLOCK_MONOTONIC_COARSE' + $defs << "-DHAVE_CLOCK_MONOTONIC_COARSE" end -have_func 'malloc_stats' +have_func "malloc_stats" # On older Rubies, rb_postponed_job_preregister/rb_postponed_job_trigger did not exist -$defs << '-DNO_POSTPONED_TRIGGER' if RUBY_VERSION < '3.3' +$defs << "-DNO_POSTPONED_TRIGGER" if RUBY_VERSION < "3.3" # On older Rubies, M:N threads were not available -$defs << '-DNO_MN_THREADS_AVAILABLE' if RUBY_VERSION < '3.3' +$defs << "-DNO_MN_THREADS_AVAILABLE" if RUBY_VERSION < "3.3" # On older Rubies, we did not need to include the ractor header (this was built into the MJIT header) -$defs << '-DNO_RACTOR_HEADER_INCLUDE' if RUBY_VERSION < '3.3' +$defs << "-DNO_RACTOR_HEADER_INCLUDE" if RUBY_VERSION < "3.3" # On older Rubies, some of the Ractor internal APIs were directly accessible -$defs << '-DUSE_RACTOR_INTERNAL_APIS_DIRECTLY' if RUBY_VERSION < '3.3' +$defs << "-DUSE_RACTOR_INTERNAL_APIS_DIRECTLY" if RUBY_VERSION < "3.3" # On older Rubies, there was no struct rb_native_thread. See private_vm_api_acccess.c for details. -$defs << '-DNO_RB_NATIVE_THREAD' if RUBY_VERSION < '3.2' +$defs << "-DNO_RB_NATIVE_THREAD" if RUBY_VERSION < "3.2" # On older Rubies, there was no struct rb_thread_sched (it was struct rb_global_vm_lock_struct) -$defs << '-DNO_RB_THREAD_SCHED' if RUBY_VERSION < '3.2' +$defs << "-DNO_RB_THREAD_SCHED" if RUBY_VERSION < "3.2" # On older Rubies, the first_lineno inside a location was a VALUE and not a int (https://github.com/ruby/ruby/pull/6430) -$defs << '-DNO_INT_FIRST_LINENO' if RUBY_VERSION < '3.2' +$defs << "-DNO_INT_FIRST_LINENO" if RUBY_VERSION < "3.2" # On older Rubies, "pop" was not a primitive operation -$defs << '-DNO_PRIMITIVE_POP' if RUBY_VERSION < '3.2' +$defs << "-DNO_PRIMITIVE_POP" if RUBY_VERSION < "3.2" # On older Rubies, there was no tid member in the internal thread structure -$defs << '-DNO_THREAD_TID' if RUBY_VERSION < '3.1' +$defs << "-DNO_THREAD_TID" if RUBY_VERSION < "3.1" # On older Rubies, there was no jit_return member on the rb_control_frame_t struct -$defs << '-DNO_JIT_RETURN' if RUBY_VERSION < '3.1' +$defs << "-DNO_JIT_RETURN" if RUBY_VERSION < "3.1" # On older Rubies, rb_gc_force_recycle allowed to free objects in a way that # would be invisible to free tracepoints, finalizers and without cleaning # obj_to_id_tbl mappings. -$defs << '-DHAVE_WORKING_RB_GC_FORCE_RECYCLE' if RUBY_VERSION < '3.1' +$defs << "-DHAVE_WORKING_RB_GC_FORCE_RECYCLE" if RUBY_VERSION < "3.1" # On older Rubies, there are no Ractors -$defs << '-DNO_RACTORS' if RUBY_VERSION < '3' +$defs << "-DNO_RACTORS" if RUBY_VERSION < "3" # On older Rubies, rb_imemo_name did not exist -$defs << '-DNO_IMEMO_NAME' if RUBY_VERSION < '3' +$defs << "-DNO_IMEMO_NAME" if RUBY_VERSION < "3" # On older Rubies, objects would not move -$defs << '-DNO_T_MOVED' if RUBY_VERSION < '2.7' +$defs << "-DNO_T_MOVED" if RUBY_VERSION < "2.7" # On older Rubies, there was no RUBY_SEEN_OBJ_ID flag -$defs << '-DNO_SEEN_OBJ_ID_FLAG' if RUBY_VERSION < '2.7' +$defs << "-DNO_SEEN_OBJ_ID_FLAG" if RUBY_VERSION < "2.7" # On older Rubies, rb_global_vm_lock_struct did not include the owner field -$defs << '-DNO_GVL_OWNER' if RUBY_VERSION < '2.6' +$defs << "-DNO_GVL_OWNER" if RUBY_VERSION < "2.6" # On older Rubies, there was no thread->invoke_arg -$defs << '-DNO_THREAD_INVOKE_ARG' if RUBY_VERSION < '2.6' +$defs << "-DNO_THREAD_INVOKE_ARG" if RUBY_VERSION < "2.6" # If we got here, libdatadog is available and loaded -ENV['PKG_CONFIG_PATH'] = "#{ENV["PKG_CONFIG_PATH"]}:#{Libdatadog.pkgconfig_folder}" +ENV["PKG_CONFIG_PATH"] = "#{ENV["PKG_CONFIG_PATH"]}:#{Libdatadog.pkgconfig_folder}" Logging.message("[datadog] PKG_CONFIG_PATH set to #{ENV["PKG_CONFIG_PATH"].inspect}\n") $stderr.puts("Using libdatadog #{Libdatadog::VERSION} from #{Libdatadog.pkgconfig_folder}") -unless pkg_config('datadog_profiling_with_rpath') +unless pkg_config("datadog_profiling_with_rpath") Logging.message("[datadog] Ruby detected the pkg-config command is #{$PKGCONFIG.inspect}\n") skip_building_extension!( @@ -212,7 +212,7 @@ def add_compiler_flag(flag) ) end -unless have_type('atomic_int', ['stdatomic.h']) +unless have_type("atomic_int", ["stdatomic.h"]) skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::COMPILER_ATOMIC_MISSING) end @@ -242,8 +242,8 @@ def add_compiler_flag(flag) # use the MJIT header. # Finally, the `COMMON_HEADERS` conflict with the MJIT header so we need to temporarily disable them for this check. original_common_headers = MakeMakefile::COMMON_HEADERS - MakeMakefile::COMMON_HEADERS = ''.freeze - unless have_macro('RUBY_MJIT_H', mjit_header_file_name) + MakeMakefile::COMMON_HEADERS = "".freeze + unless have_macro("RUBY_MJIT_H", mjit_header_file_name) skip_building_extension!(Datadog::Profiling::NativeExtensionHelpers::Supported::COMPILATION_BROKEN) end MakeMakefile::COMMON_HEADERS = original_common_headers @@ -255,7 +255,7 @@ def add_compiler_flag(flag) # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used. # See the comment on the same flag below for why this is done last. - add_compiler_flag '-Wunused-parameter' + add_compiler_flag "-Wunused-parameter" create_makefile EXTENSION_NAME else @@ -266,23 +266,23 @@ def add_compiler_flag(flag) create_header - require 'debase/ruby_core_source' - dir_config('ruby') # allow user to pass in non-standard core include directory + require "debase/ruby_core_source" + dir_config("ruby") # allow user to pass in non-standard core include directory Debase::RubyCoreSource .create_makefile_with_core( proc do headers_available = - have_header('vm_core.h') && - have_header('iseq.h') && - (RUBY_VERSION < '3.3' || have_header('ractor_core.h')) + have_header("vm_core.h") && + have_header("iseq.h") && + (RUBY_VERSION < "3.3" || have_header("ractor_core.h")) if headers_available # Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used. # This is added as late as possible because in some Rubies we support (e.g. 3.3), adding this flag before # checking if internal VM headers are available causes those checks to fail because of this warning (and not # because the headers are not available.) - add_compiler_flag '-Wunused-parameter' + add_compiler_flag "-Wunused-parameter" end headers_available diff --git a/ext/datadog_profiling_native_extension/native_extension_helpers.rb b/ext/datadog_profiling_native_extension/native_extension_helpers.rb index 6147891f38f..baec1aa92ac 100644 --- a/ext/datadog_profiling_native_extension/native_extension_helpers.rb +++ b/ext/datadog_profiling_native_extension/native_extension_helpers.rb @@ -5,15 +5,15 @@ module Profiling # Helpers for extconf.rb module NativeExtensionHelpers # Can be set when customers want to skip compiling the native extension entirely - ENV_NO_EXTENSION = 'DD_PROFILING_NO_EXTENSION' + ENV_NO_EXTENSION = "DD_PROFILING_NO_EXTENSION" # Can be set to force rubygems to fail gem installation when profiling extension could not be built - ENV_FAIL_INSTALL_IF_MISSING_EXTENSION = 'DD_PROFILING_FAIL_INSTALL_IF_MISSING_EXTENSION' + ENV_FAIL_INSTALL_IF_MISSING_EXTENSION = "DD_PROFILING_FAIL_INSTALL_IF_MISSING_EXTENSION" # The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on debase-ruby_core_source - CAN_USE_MJIT_HEADER = RUBY_VERSION.start_with?('2.6', '2.7', '3.0.', '3.1.', '3.2.') + CAN_USE_MJIT_HEADER = RUBY_VERSION.start_with?("2.6", "2.7", "3.0.", "3.1.", "3.2.") def self.fail_install_if_missing_extension? - ENV[ENV_FAIL_INSTALL_IF_MISSING_EXTENSION].to_s.strip.downcase == 'true' + ENV[ENV_FAIL_INSTALL_IF_MISSING_EXTENSION].to_s.strip.downcase == "true" end # Used to check if profiler is supported, including user-visible clear messages explaining why their @@ -46,16 +46,16 @@ def self.failure_banner_for(reason:, suggested:, fail_install:) outcome = if fail_install [ - 'Failing installation immediately because the ', + "Failing installation immediately because the ", "`#{ENV_FAIL_INSTALL_IF_MISSING_EXTENSION}` environment variable is set", - 'to `true`.', - 'When contacting support, please include the file that is shown ', - 'below.', + "to `true`.", + "When contacting support, please include the file that is shown ", + "below.", ] else [ - 'The Datadog Continuous Profiler will not be available,', - 'but all other datadog features will work fine!', + "The Datadog Continuous Profiler will not be available,", + "but all other datadog features will work fine!", ] end @@ -73,14 +73,14 @@ def self.failure_banner_for(reason:, suggested:, fail_install:) # This will be saved in a file to later be presented while operating the gem def self.render_skipped_reason_file(reason:, suggested:) - [*reason, *suggested].join(' ') + [*reason, *suggested].join(" ") end CONTACT_SUPPORT = [ - 'For help solving this issue, please contact Datadog support at', - '.', - 'You can also check out the Continuous Profiler troubleshooting page at', - '.' + "For help solving this issue, please contact Datadog support at", + ".", + "You can also check out the Continuous Profiler troubleshooting page at", + "." ].freeze GET_IN_TOUCH = [ @@ -88,83 +88,83 @@ def self.render_skipped_reason_file(reason:, suggested:) ].freeze UPGRADE_RUBY = [ - 'Upgrade to a modern Ruby to enable profiling for your app.' + "Upgrade to a modern Ruby to enable profiling for your app." ].freeze # Validation for this check is done in extconf.rb because it relies on mkmf FAILED_TO_CONFIGURE_LIBDATADOG = explain_issue( - 'there was a problem in setting up the `libdatadog` dependency.', + "there was a problem in setting up the `libdatadog` dependency.", suggested: CONTACT_SUPPORT, ) # Validation for this check is done in extconf.rb because it relies on mkmf COMPILATION_BROKEN = explain_issue( - 'compilation of the Ruby VM just-in-time header failed.', - 'Your C compiler or Ruby VM just-in-time compiler seem to be broken.', + "compilation of the Ruby VM just-in-time header failed.", + "Your C compiler or Ruby VM just-in-time compiler seem to be broken.", suggested: CONTACT_SUPPORT, ) # Validation for this check is done in extconf.rb because it relies on mkmf PKG_CONFIG_IS_MISSING = explain_issue( # ----------------------------------------------------------------------------+ - 'the `pkg-config` system tool is missing.', - 'This issue can usually be fixed by installing one of the following:', - 'the `pkg-config` package on Homebrew and Debian/Ubuntu-based Linux;', - 'the `pkgconf` package on Arch and Alpine-based Linux;', - 'the `pkgconf-pkg-config` package on Fedora/Red Hat-based Linux.', - '(Tip: When fixing this, ensure `pkg-config` is installed **before**', - 'running `bundle install`, and remember to clear any installed gems cache).', + "the `pkg-config` system tool is missing.", + "This issue can usually be fixed by installing one of the following:", + "the `pkg-config` package on Homebrew and Debian/Ubuntu-based Linux;", + "the `pkgconf` package on Arch and Alpine-based Linux;", + "the `pkgconf-pkg-config` package on Fedora/Red Hat-based Linux.", + "(Tip: When fixing this, ensure `pkg-config` is installed **before**", + "running `bundle install`, and remember to clear any installed gems cache).", suggested: CONTACT_SUPPORT, ) # Validation for this check is done in extconf.rb because it relies on mkmf COMPILER_ATOMIC_MISSING = explain_issue( - 'your C compiler is missing support for the header.', - 'This issue can usually be fixed by upgrading to a later version of your', - 'operating system image or compiler.', + "your C compiler is missing support for the header.", + "This issue can usually be fixed by upgrading to a later version of your", + "operating system image or compiler.", suggested: CONTACT_SUPPORT, ) private_class_method def self.disabled_via_env? report_disabled = [ - 'If you needed to use this, please tell us why on', - ' so we can fix it :)', + "If you needed to use this, please tell us why on", + " so we can fix it :)", ].freeze disabled_via_env = explain_issue( - 'the `DD_PROFILING_NO_EXTENSION` environment variable is/was set to', - '`true` during installation.', + "the `DD_PROFILING_NO_EXTENSION` environment variable is/was set to", + "`true` during installation.", suggested: report_disabled, ) - return unless ENV[ENV_NO_EXTENSION].to_s.strip.downcase == 'true' + return unless ENV[ENV_NO_EXTENSION].to_s.strip.downcase == "true" disabled_via_env end private_class_method def self.on_jruby? jruby_not_supported = explain_issue( - 'JRuby is not supported by the Datadog Continuous Profiler.', + "JRuby is not supported by the Datadog Continuous Profiler.", suggested: GET_IN_TOUCH, ) - jruby_not_supported if RUBY_ENGINE == 'jruby' + jruby_not_supported if RUBY_ENGINE == "jruby" end private_class_method def self.on_truffleruby? truffleruby_not_supported = explain_issue( - 'TruffleRuby is not supported by the datadog gem.', + "TruffleRuby is not supported by the datadog gem.", suggested: GET_IN_TOUCH, ) - truffleruby_not_supported if RUBY_ENGINE == 'truffleruby' + truffleruby_not_supported if RUBY_ENGINE == "truffleruby" end # See https://docs.datadoghq.com/tracing/setup_overview/setup/ruby/#microsoft-windows-support for current # state of Windows support in the datadog gem. private_class_method def self.on_windows? windows_not_supported = explain_issue( - 'Microsoft Windows is not supported by the Datadog Continuous Profiler.', + "Microsoft Windows is not supported by the Datadog Continuous Profiler.", suggested: GET_IN_TOUCH, ) @@ -173,54 +173,54 @@ def self.render_skipped_reason_file(reason:, suggested:) private_class_method def self.on_macos? macos_not_supported = explain_issue( - 'macOS is currently not supported by the Datadog Continuous Profiler.', + "macOS is currently not supported by the Datadog Continuous Profiler.", suggested: GET_IN_TOUCH, ) # For development only; not supported otherwise - macos_testing_override = ENV['DD_PROFILING_MACOS_TESTING'] == 'true' + macos_testing_override = ENV["DD_PROFILING_MACOS_TESTING"] == "true" - macos_not_supported if RUBY_PLATFORM.include?('darwin') && !macos_testing_override + macos_not_supported if RUBY_PLATFORM.include?("darwin") && !macos_testing_override end private_class_method def self.on_unknown_os? unknown_os_not_supported = explain_issue( - 'your operating system is not supported by the Datadog Continuous Profiler.', + "your operating system is not supported by the Datadog Continuous Profiler.", suggested: GET_IN_TOUCH, ) - unknown_os_not_supported unless RUBY_PLATFORM.include?('darwin') || RUBY_PLATFORM.include?('linux') + unknown_os_not_supported unless RUBY_PLATFORM.include?("darwin") || RUBY_PLATFORM.include?("linux") end private_class_method def self.on_unsupported_cpu_arch? architecture_not_supported = explain_issue( - 'your CPU architecture is not supported by the Datadog Continuous Profiler.', + "your CPU architecture is not supported by the Datadog Continuous Profiler.", suggested: GET_IN_TOUCH, ) - architecture_not_supported unless RUBY_PLATFORM.start_with?('x86_64', 'aarch64', 'arm64') + architecture_not_supported unless RUBY_PLATFORM.start_with?("x86_64", "aarch64", "arm64") end # On some Rubies, we require the mjit header to be present. If Ruby was installed without MJIT support, we also skip # building the extension. private_class_method def self.expected_to_use_mjit_but_mjit_is_disabled? ruby_without_mjit = explain_issue( - 'your Ruby has been compiled without JIT support (--disable-jit-support).', - 'The profiling native extension requires a Ruby compiled with JIT support,', - 'even if the JIT is not in use by the application itself.', + "your Ruby has been compiled without JIT support (--disable-jit-support).", + "The profiling native extension requires a Ruby compiled with JIT support,", + "even if the JIT is not in use by the application itself.", suggested: CONTACT_SUPPORT, ) - ruby_without_mjit if CAN_USE_MJIT_HEADER && RbConfig::CONFIG['MJIT_SUPPORT'] != 'yes' + ruby_without_mjit if CAN_USE_MJIT_HEADER && RbConfig::CONFIG["MJIT_SUPPORT"] != "yes" end private_class_method def self.libdatadog_not_available? Datadog::LibdatadogExtconfHelpers.try_loading_libdatadog do |exception| explain_issue( - 'there was an exception during loading of the `libdatadog` gem:', + "there was an exception during loading of the `libdatadog` gem:", exception.class.name, *exception.message.split("\n"), *Array(exception.backtrace), - '.', + ".", suggested: CONTACT_SUPPORT, ) end @@ -228,10 +228,10 @@ def self.render_skipped_reason_file(reason:, suggested:) private_class_method def self.libdatadog_not_usable? no_binaries_for_current_platform = explain_issue( - 'the `libdatadog` gem installed on your system is missing binaries for your', - 'platform variant.', + "the `libdatadog` gem installed on your system is missing binaries for your", + "platform variant.", "(Your platform: `#{Libdatadog.current_platform}`)", - '(Available binaries:', + "(Available binaries:", "`#{Libdatadog.available_binaries.join("`, `")}`)", suggested: CONTACT_SUPPORT, ) diff --git a/lib/datadog/profiling/collectors/code_provenance.rb b/lib/datadog/profiling/collectors/code_provenance.rb index 2f338815daf..d72683c1932 100644 --- a/lib/datadog/profiling/collectors/code_provenance.rb +++ b/lib/datadog/profiling/collectors/code_provenance.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'set' -require 'json' +require "set" +require "json" module Datadog module Profiling @@ -14,7 +14,7 @@ module Collectors # # This class acts both as a collector (collecting data) as well as a recorder (records/serializes it) class CodeProvenance - def initialize(standard_library_path: RbConfig::CONFIG.fetch('rubylibdir')) + def initialize(standard_library_path: RbConfig::CONFIG.fetch("rubylibdir")) @libraries_by_name = {} @libraries_by_path = {} @seen_files = Set.new @@ -22,8 +22,8 @@ def initialize(standard_library_path: RbConfig::CONFIG.fetch('rubylibdir')) record_library( Library.new( - kind: 'standard library', - name: 'stdlib', + kind: "standard library", + name: "stdlib", version: RUBY_VERSION, path: standard_library_path, ) @@ -79,7 +79,7 @@ def record_loaded_specs(loaded_specs) loaded_specs.each do |spec| next if libraries_by_name.key?(spec.name) - record_library(Library.new(kind: 'library', name: spec.name, version: spec.version, path: spec.gem_dir)) + record_library(Library.new(kind: "library", name: spec.name, version: spec.version, path: spec.gem_dir)) recorded_library = true end diff --git a/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb b/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb index bd19f286a24..f77cbfd0cef 100644 --- a/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +++ b/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb @@ -30,7 +30,7 @@ def initialize( ) unless dynamic_sampling_rate_enabled Datadog.logger.warn( - 'Profiling dynamic sampling rate disabled. This should only be used for testing, and will increase overhead!' + "Profiling dynamic sampling rate disabled. This should only be used for testing, and will increase overhead!" ) end @@ -67,11 +67,11 @@ def start(on_failure_proc: nil) self.class._native_sampling_loop(self) - Datadog.logger.debug('CpuAndWallTimeWorker thread stopping cleanly') + Datadog.logger.debug("CpuAndWallTimeWorker thread stopping cleanly") rescue Exception => e # rubocop:disable Lint/RescueException @failure_exception = e Datadog.logger.warn( - 'CpuAndWallTimeWorker thread error. ' \ + "CpuAndWallTimeWorker thread error. " \ "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}" ) on_failure_proc&.call @@ -85,7 +85,7 @@ def start(on_failure_proc: nil) def stop @start_stop_mutex.synchronize do - Datadog.logger.debug('Requesting CpuAndWallTimeWorker thread shut down') + Datadog.logger.debug("Requesting CpuAndWallTimeWorker thread shut down") @idle_sampling_helper.stop diff --git a/lib/datadog/profiling/collectors/idle_sampling_helper.rb b/lib/datadog/profiling/collectors/idle_sampling_helper.rb index 4d89eb936b7..c065c2125dc 100644 --- a/lib/datadog/profiling/collectors/idle_sampling_helper.rb +++ b/lib/datadog/profiling/collectors/idle_sampling_helper.rb @@ -34,11 +34,11 @@ def start self.class._native_idle_sampling_loop(self) - Datadog.logger.debug('IdleSamplingHelper thread stopping cleanly') + Datadog.logger.debug("IdleSamplingHelper thread stopping cleanly") rescue Exception => e # rubocop:disable Lint/RescueException @failure_exception = e Datadog.logger.warn( - 'IdleSamplingHelper thread error. ' \ + "IdleSamplingHelper thread error. " \ "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}" ) end @@ -51,7 +51,7 @@ def start def stop @start_stop_mutex.synchronize do - Datadog.logger.debug('Requesting IdleSamplingHelper thread shut down') + Datadog.logger.debug("Requesting IdleSamplingHelper thread shut down") return unless @worker_thread diff --git a/lib/datadog/profiling/collectors/info.rb b/lib/datadog/profiling/collectors/info.rb index 69a44187213..7c327b4d3f3 100644 --- a/lib/datadog/profiling/collectors/info.rb +++ b/lib/datadog/profiling/collectors/info.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'set' -require 'time' +require "set" +require "time" module Datadog module Profiling @@ -61,7 +61,7 @@ def collect_application_info(settings) def collect_profiler_info(settings) unless @profiler_info - lib_datadog_gem = ::Gem.loaded_specs['libdatadog'] + lib_datadog_gem = ::Gem.loaded_specs["libdatadog"] @profiler_info = { # TODO: If profiling is extracted and its version diverges from the datadog gem, this is inaccurate. # Update if this ever occurs. diff --git a/lib/datadog/profiling/component.rb b/lib/datadog/profiling/component.rb index 926bdb55fc2..bf3dbc865b0 100644 --- a/lib/datadog/profiling/component.rb +++ b/lib/datadog/profiling/component.rb @@ -27,7 +27,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # On the other hand, if datadog/core is loaded by a different product and no general `require 'datadog'` is # done, then profiling may not be loaded, and thus to avoid this issue we do a require here (which is a # no-op if profiling is already loaded). - require_relative '../profiling' + require_relative "../profiling" return [nil, {profiling_enabled: false}] unless Profiling.supported? @@ -47,7 +47,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) upload_period_seconds = [60, settings.profiling.advanced.upload_period_seconds].max recorder = Datadog::Profiling::StackRecorder.new( - cpu_time_enabled: RUBY_PLATFORM.include?('linux'), # Only supported on Linux currently + cpu_time_enabled: RUBY_PLATFORM.include?("linux"), # Only supported on Linux currently alloc_samples_enabled: allocation_profiling_enabled, heap_samples_enabled: heap_profiling_enabled, heap_size_enabled: heap_size_profiling_enabled, @@ -127,7 +127,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # we can't use the crashtracker, even if enabled. unless transport.respond_to?(:exporter_configuration) Datadog.logger.debug( - 'Cannot enable profiling crash tracking as a custom settings.profiling.exporter.transport is configured' + "Cannot enable profiling crash tracking as a custom settings.profiling.exporter.transport is configured" ) return end @@ -154,19 +154,19 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # that causes a segmentation fault during garbage collection of Ractors # (https://bugs.ruby-lang.org/issues/18464). We don't allow enabling gc profiling on such Rubies. # This bug is fixed on Ruby versions 3.1.4, 3.2.3 and 3.3.0. - if RUBY_VERSION.start_with?('3.0.') || - (RUBY_VERSION.start_with?('3.1.') && RUBY_VERSION < '3.1.4') || - (RUBY_VERSION.start_with?('3.2.') && RUBY_VERSION < '3.2.3') + if RUBY_VERSION.start_with?("3.0.") || + (RUBY_VERSION.start_with?("3.1.") && RUBY_VERSION < "3.1.4") || + (RUBY_VERSION.start_with?("3.2.") && RUBY_VERSION < "3.2.3") Datadog.logger.warn( "Current Ruby version (#{RUBY_VERSION}) has a VM bug where enabling GC profiling would cause " \ - 'crashes (https://bugs.ruby-lang.org/issues/18464). GC profiling has been disabled.' + "crashes (https://bugs.ruby-lang.org/issues/18464). GC profiling has been disabled." ) return false - elsif RUBY_VERSION.start_with?('3.') + elsif RUBY_VERSION.start_with?("3.") Datadog.logger.debug( - 'In all known versions of Ruby 3.x, using Ractors may result in GC profiling unexpectedly ' \ - 'stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your ' \ - 'application stability or performance. This does not happen if Ractors are not used.' + "In all known versions of Ruby 3.x, using Ractors may result in GC profiling unexpectedly " \ + "stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your " \ + "application stability or performance. This does not happen if Ractors are not used." ) end @@ -190,11 +190,11 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # Ruby 3.2.0 to 3.2.2 have a bug in the newobj tracepoint (https://bugs.ruby-lang.org/issues/19482, # https://github.com/ruby/ruby/pull/7464) that makes this crash in any configuration. This bug is # fixed on Ruby versions 3.2.3 and 3.3.0. - if RUBY_VERSION.start_with?('3.2.') && RUBY_VERSION < '3.2.3' + if RUBY_VERSION.start_with?("3.2.") && RUBY_VERSION < "3.2.3" Datadog.logger.warn( - 'Allocation profiling is not supported in Ruby versions 3.2.0, 3.2.1 and 3.2.2 and will be forcibly ' \ - 'disabled. This is due to a VM bug that can lead to crashes (https://bugs.ruby-lang.org/issues/19482). ' \ - 'Other Ruby versions do not suffer from this issue.' + "Allocation profiling is not supported in Ruby versions 3.2.0, 3.2.1 and 3.2.2 and will be forcibly " \ + "disabled. This is due to a VM bug that can lead to crashes (https://bugs.ruby-lang.org/issues/19482). " \ + "Other Ruby versions do not suffer from this issue." ) return false end @@ -204,26 +204,26 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # that causes a segmentation fault during garbage collection of Ractors # (https://bugs.ruby-lang.org/issues/18464). We don't recommend using this feature on such Rubies. # This bug is fixed on Ruby versions 3.1.4, 3.2.3 and 3.3.0. - if RUBY_VERSION.start_with?('3.0.') || - (RUBY_VERSION.start_with?('3.1.') && RUBY_VERSION < '3.1.4') || - (RUBY_VERSION.start_with?('3.2.') && RUBY_VERSION < '3.2.3') + if RUBY_VERSION.start_with?("3.0.") || + (RUBY_VERSION.start_with?("3.1.") && RUBY_VERSION < "3.1.4") || + (RUBY_VERSION.start_with?("3.2.") && RUBY_VERSION < "3.2.3") Datadog.logger.warn( "Current Ruby version (#{RUBY_VERSION}) has a VM bug where enabling allocation profiling while using " \ - 'Ractors may cause unexpected issues, including crashes (https://bugs.ruby-lang.org/issues/18464). ' \ - 'This does not happen if Ractors are not used.' + "Ractors may cause unexpected issues, including crashes (https://bugs.ruby-lang.org/issues/18464). " \ + "This does not happen if Ractors are not used." ) # ANNOYANCE - Only with Ractors # On all known versions of Ruby 3.x, due to https://bugs.ruby-lang.org/issues/19112, when a ractor gets # garbage collected, Ruby will disable all active tracepoints, which this feature internally relies on. - elsif RUBY_VERSION.start_with?('3.') + elsif RUBY_VERSION.start_with?("3.") Datadog.logger.warn( - 'In all known versions of Ruby 3.x, using Ractors may result in allocation profiling unexpectedly ' \ - 'stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your ' \ - 'application stability or performance. This does not happen if Ractors are not used.' + "In all known versions of Ruby 3.x, using Ractors may result in allocation profiling unexpectedly " \ + "stopping (https://bugs.ruby-lang.org/issues/19112). Note that this stop has no impact in your " \ + "application stability or performance. This does not happen if Ractors are not used." ) end - Datadog.logger.debug('Enabled allocation profiling') + Datadog.logger.debug("Enabled allocation profiling") true end @@ -233,33 +233,33 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) return false unless heap_profiling_enabled - if RUBY_VERSION.start_with?('2.') && RUBY_VERSION < '2.7' + if RUBY_VERSION.start_with?("2.") && RUBY_VERSION < "2.7" Datadog.logger.warn( - 'Heap profiling currently relies on features introduced in Ruby 2.7 and will be forcibly disabled. ' \ - 'Please upgrade to Ruby >= 2.7 in order to use this feature.' + "Heap profiling currently relies on features introduced in Ruby 2.7 and will be forcibly disabled. " \ + "Please upgrade to Ruby >= 2.7 in order to use this feature." ) return false end - if RUBY_VERSION < '3.1' + if RUBY_VERSION < "3.1" Datadog.logger.debug( "Current Ruby version (#{RUBY_VERSION}) supports forced object recycling which has a bug that the " \ - 'heap profiler is forced to work around to remain accurate. This workaround requires force-setting ' \ + "heap profiler is forced to work around to remain accurate. This workaround requires force-setting " \ "the SEEN_OBJ_ID flag on objects that should have it but don't. Full details can be found in " \ - 'https://github.com/DataDog/dd-trace-rb/pull/3360. This workaround should be safe but can be ' \ - 'bypassed by disabling the heap profiler or upgrading to Ruby >= 3.1 where forced object recycling ' \ - 'was completely removed (https://bugs.ruby-lang.org/issues/18290).' + "https://github.com/DataDog/dd-trace-rb/pull/3360. This workaround should be safe but can be " \ + "bypassed by disabling the heap profiler or upgrading to Ruby >= 3.1 where forced object recycling " \ + "was completely removed (https://bugs.ruby-lang.org/issues/18290)." ) end unless allocation_profiling_enabled raise ArgumentError, - 'Heap profiling requires allocation profiling to be enabled' + "Heap profiling requires allocation profiling to be enabled" end Datadog.logger.warn( "Enabled experimental heap profiling: heap_sample_rate=#{heap_sample_rate}. This is experimental, not " \ - 'recommended, and will increase overhead!' + "recommended, and will increase overhead!" ) true @@ -271,7 +271,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) return false unless heap_profiling_enabled && heap_size_profiling_enabled Datadog.logger.warn( - 'Enabled experimental heap size profiling. This is experimental, not recommended, and will increase overhead!' + "Enabled experimental heap size profiling. This is experimental, not recommended, and will increase overhead!" ) true @@ -279,12 +279,12 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) private_class_method def self.no_signals_workaround_enabled?(settings) # rubocop:disable Metrics/MethodLength setting_value = settings.profiling.advanced.no_signals_workaround_enabled - legacy_ruby_that_should_use_workaround = RUBY_VERSION.start_with?('2.5.') + legacy_ruby_that_should_use_workaround = RUBY_VERSION.start_with?("2.5.") unless [true, false, :auto].include?(setting_value) Datadog.logger.error( "Ignoring invalid value for profiling no_signals_workaround_enabled setting: #{setting_value.inspect}. " \ - 'Valid options are `true`, `false` or (default) `:auto`.' + "Valid options are `true`, `false` or (default) `:auto`." ) setting_value = :auto @@ -294,10 +294,10 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) if legacy_ruby_that_should_use_workaround Datadog.logger.warn( 'The profiling "no signals" workaround has been disabled via configuration on a legacy Ruby version ' \ - '(< 2.6). This is not recommended ' \ - 'in production environments, as due to limitations in Ruby APIs, we suspect it may lead to crashes ' \ - 'in very rare situations. Please report any issues you run into to Datadog support or ' \ - 'via !' + "(< 2.6). This is not recommended " \ + "in production environments, as due to limitations in Ruby APIs, we suspect it may lead to crashes " \ + "in very rare situations. Please report any issues you run into to Datadog support or " \ + "via !" ) else Datadog.logger.warn('Profiling "no signals" workaround disabled via configuration') @@ -319,30 +319,30 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # We don't warn users in this situation because "upgrade your Ruby" is not a great warning return true if legacy_ruby_that_should_use_workaround - if Gem.loaded_specs['mysql2'] && incompatible_libmysqlclient_version?(settings) + if Gem.loaded_specs["mysql2"] && incompatible_libmysqlclient_version?(settings) Datadog.logger.warn( 'Enabling the profiling "no signals" workaround because an incompatible version of the mysql2 gem is ' \ - 'installed. Profiling data will have lower quality. ' \ - 'To fix this, upgrade the libmysqlclient in your OS image to version 8.0.0 or above.' + "installed. Profiling data will have lower quality. " \ + "To fix this, upgrade the libmysqlclient in your OS image to version 8.0.0 or above." ) return true end - if Gem.loaded_specs['rugged'] + if Gem.loaded_specs["rugged"] Datadog.logger.warn( 'Enabling the profiling "no signals" workaround because the rugged gem is installed. ' \ - 'This is needed because some operations on this gem are currently incompatible with the normal working mode ' \ - 'of the profiler, as detailed in . ' \ - 'Profiling data will have lower quality.' + "This is needed because some operations on this gem are currently incompatible with the normal working mode " \ + "of the profiler, as detailed in . " \ + "Profiling data will have lower quality." ) return true end - if (defined?(::PhusionPassenger) || Gem.loaded_specs['passenger']) && incompatible_passenger_version? + if (defined?(::PhusionPassenger) || Gem.loaded_specs["passenger"]) && incompatible_passenger_version? Datadog.logger.warn( 'Enabling the profiling "no signals" workaround because an incompatible version of the passenger gem is ' \ - 'installed. Profiling data will have lower quality.' \ - 'To fix this, upgrade the passenger gem to version 6.0.19 or above.' + "installed. Profiling data will have lower quality." \ + "To fix this, upgrade the passenger gem to version 6.0.19 or above." ) return true end @@ -363,11 +363,11 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) return true if settings.profiling.advanced.skip_mysql2_check Datadog.logger.debug( - 'Requiring `mysql2` to check if the `libmysqlclient` version it uses is compatible with profiling' + "Requiring `mysql2` to check if the `libmysqlclient` version it uses is compatible with profiling" ) begin - require 'mysql2' + require "mysql2" # The mysql2-aurora gem likes to monkey patch itself in replacement of Mysql2::Client, and uses # `method_missing` to delegate to the original BUT unfortunately does not implement `respond_to_missing?` and @@ -388,7 +388,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) libmysqlclient_version = Gem::Version.new(info[:version]) compatible = - libmysqlclient_version >= Gem::Version.new('8.0.0') || + libmysqlclient_version >= Gem::Version.new("8.0.0") || looks_like_mariadb?(info, libmysqlclient_version) Datadog.logger.debug( @@ -399,7 +399,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) !compatible rescue StandardError, LoadError => e Datadog.logger.warn( - 'Failed to probe `mysql2` gem information. ' \ + "Failed to probe `mysql2` gem information. " \ "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}" ) @@ -409,10 +409,10 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) # See https://github.com/datadog/dd-trace-rb/issues/2976 for details. private_class_method def self.incompatible_passenger_version? - first_compatible_version = Gem::Version.new('6.0.19') + first_compatible_version = Gem::Version.new("6.0.19") - if Gem.loaded_specs['passenger'] - Gem.loaded_specs['passenger'].version < first_compatible_version + if Gem.loaded_specs["passenger"] + Gem.loaded_specs["passenger"].version < first_compatible_version elsif defined?(PhusionPassenger::VERSION_STRING) Gem::Version.new(PhusionPassenger::VERSION_STRING) < first_compatible_version else @@ -425,7 +425,7 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) overhead_target_percentage else Datadog.logger.error( - 'Ignoring invalid value for profiling overhead_target_percentage setting: ' \ + "Ignoring invalid value for profiling overhead_target_percentage setting: " \ "#{overhead_target_percentage.inspect}. Falling back to default value." ) @@ -458,8 +458,8 @@ def self.build_profiler_component(settings:, agent_settings:, optional_tracer:) header_version = Gem::Version.new(info[:header_version]) if info[:header_version] !!(header_version && - libmysqlclient_version < Gem::Version.new('5.0.0') && - header_version >= Gem::Version.new('10.0.0')) + libmysqlclient_version < Gem::Version.new("5.0.0") && + header_version >= Gem::Version.new("10.0.0")) end private_class_method def self.dir_interruption_workaround_enabled?(settings, no_signals_workaround_enabled) diff --git a/lib/datadog/profiling/crashtracker.rb b/lib/datadog/profiling/crashtracker.rb index f16e0296122..14fcad81688 100644 --- a/lib/datadog/profiling/crashtracker.rb +++ b/lib/datadog/profiling/crashtracker.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'libdatadog' +require "libdatadog" module Datadog module Profiling @@ -56,7 +56,7 @@ def reset_after_fork def stop self.class._native_stop - Datadog.logger.debug('Crash tracking stopped successfully') + Datadog.logger.debug("Crash tracking stopped successfully") rescue => e Datadog.logger.error("Failed to stop crash tracking: #{e.message}") end diff --git a/lib/datadog/profiling/exporter.rb b/lib/datadog/profiling/exporter.rb index 1423a120b91..1f2ce3b6557 100644 --- a/lib/datadog/profiling/exporter.rb +++ b/lib/datadog/profiling/exporter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative 'ext' -require_relative 'tag_builder' +require_relative "ext" +require_relative "tag_builder" module Datadog module Profiling @@ -61,7 +61,7 @@ def flush @last_flush_finish_at = finish if duration_below_threshold?(start, finish) - Datadog.logger.debug('Skipped exporting profiling events as profile duration is below minimum') + Datadog.logger.debug("Skipped exporting profiling events as profile duration is below minimum") return end diff --git a/lib/datadog/profiling/ext.rb b/lib/datadog/profiling/ext.rb index 9dea808ddf0..40e32c2c8e2 100644 --- a/lib/datadog/profiling/ext.rb +++ b/lib/datadog/profiling/ext.rb @@ -3,31 +3,31 @@ module Datadog module Profiling module Ext - ENV_ENABLED = 'DD_PROFILING_ENABLED' - ENV_UPLOAD_TIMEOUT = 'DD_PROFILING_UPLOAD_TIMEOUT' - ENV_MAX_FRAMES = 'DD_PROFILING_MAX_FRAMES' - ENV_AGENTLESS = 'DD_PROFILING_AGENTLESS' - ENV_ENDPOINT_COLLECTION_ENABLED = 'DD_PROFILING_ENDPOINT_COLLECTION_ENABLED' + ENV_ENABLED = "DD_PROFILING_ENABLED" + ENV_UPLOAD_TIMEOUT = "DD_PROFILING_UPLOAD_TIMEOUT" + ENV_MAX_FRAMES = "DD_PROFILING_MAX_FRAMES" + ENV_AGENTLESS = "DD_PROFILING_AGENTLESS" + ENV_ENDPOINT_COLLECTION_ENABLED = "DD_PROFILING_ENDPOINT_COLLECTION_ENABLED" module Transport module HTTP - FORM_FIELD_TAG_ENV = 'env' - FORM_FIELD_TAG_HOST = 'host' - FORM_FIELD_TAG_LANGUAGE = 'language' - FORM_FIELD_TAG_PID = 'process_id' - FORM_FIELD_TAG_PROFILER_VERSION = 'profiler_version' - FORM_FIELD_TAG_RUNTIME = 'runtime' - FORM_FIELD_TAG_RUNTIME_ENGINE = 'runtime_engine' - FORM_FIELD_TAG_RUNTIME_ID = 'runtime-id' - FORM_FIELD_TAG_RUNTIME_PLATFORM = 'runtime_platform' - FORM_FIELD_TAG_RUNTIME_VERSION = 'runtime_version' - FORM_FIELD_TAG_SERVICE = 'service' - FORM_FIELD_TAG_VERSION = 'version' - TAG_GIT_REPOSITORY_URL = 'git.repository_url' - TAG_GIT_COMMIT_SHA = 'git.commit.sha' + FORM_FIELD_TAG_ENV = "env" + FORM_FIELD_TAG_HOST = "host" + FORM_FIELD_TAG_LANGUAGE = "language" + FORM_FIELD_TAG_PID = "process_id" + FORM_FIELD_TAG_PROFILER_VERSION = "profiler_version" + FORM_FIELD_TAG_RUNTIME = "runtime" + FORM_FIELD_TAG_RUNTIME_ENGINE = "runtime_engine" + FORM_FIELD_TAG_RUNTIME_ID = "runtime-id" + FORM_FIELD_TAG_RUNTIME_PLATFORM = "runtime_platform" + FORM_FIELD_TAG_RUNTIME_VERSION = "runtime_version" + FORM_FIELD_TAG_SERVICE = "service" + FORM_FIELD_TAG_VERSION = "version" + TAG_GIT_REPOSITORY_URL = "git.repository_url" + TAG_GIT_COMMIT_SHA = "git.commit.sha" - PPROF_DEFAULT_FILENAME = 'rubyprofile.pprof' - CODE_PROVENANCE_FILENAME = 'code-provenance.json' + PPROF_DEFAULT_FILENAME = "rubyprofile.pprof" + CODE_PROVENANCE_FILENAME = "code-provenance.json" end end end diff --git a/lib/datadog/profiling/ext/dir_monkey_patches.rb b/lib/datadog/profiling/ext/dir_monkey_patches.rb index efc34194055..75a7a31cc19 100644 --- a/lib/datadog/profiling/ext/dir_monkey_patches.rb +++ b/lib/datadog/profiling/ext/dir_monkey_patches.rb @@ -27,7 +27,7 @@ def self.apply! end end - if RUBY_VERSION.start_with?('2.') + if RUBY_VERSION.start_with?("2.") # Monkey patches for Dir.singleton_class (Ruby 2 version). See DirMonkeyPatches above for more details. module DirClassMonkeyPatches def [](*args, &block) @@ -260,7 +260,7 @@ def home(*args, **kwargs, &block) end end - if RUBY_VERSION.start_with?('2.') + if RUBY_VERSION.start_with?("2.") # Monkey patches for Dir (Ruby 2 version). See DirMonkeyPatches above for more details. module DirInstanceMonkeyPatches # See note on methods that yield above. @@ -286,7 +286,7 @@ def each(*args, &block) end end - unless RUBY_VERSION.start_with?('2.5.') # This is Ruby 2.6+ + unless RUBY_VERSION.start_with?("2.5.") # This is Ruby 2.6+ # See note on methods that yield above. def each_child(*args, &block) if block diff --git a/lib/datadog/profiling/flush.rb b/lib/datadog/profiling/flush.rb index b8c93227247..2cd968ef9d8 100644 --- a/lib/datadog/profiling/flush.rb +++ b/lib/datadog/profiling/flush.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'json' +require "json" module Datadog module Profiling diff --git a/lib/datadog/profiling/http_transport.rb b/lib/datadog/profiling/http_transport.rb index 8fc7e66b838..d249ff94faf 100644 --- a/lib/datadog/profiling/http_transport.rb +++ b/lib/datadog/profiling/http_transport.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative '../core/transport/ext' +require_relative "../core/transport/ext" module Datadog module Profiling @@ -53,7 +53,7 @@ def export(flush) if status == :ok if (200..299).cover?(result) - Datadog.logger.debug('Successfully reported profiling data') + Datadog.logger.debug("Successfully reported profiling data") true else Datadog.logger.error( @@ -89,7 +89,7 @@ def validate_agent_settings(agent_settings) unless supported_adapters.include?(agent_settings.adapter) raise ArgumentError, "Unsupported transport configuration for profiling: Adapter #{agent_settings.adapter} " \ - ' is not supported' + " is not supported" end end diff --git a/lib/datadog/profiling/load_native_extension.rb b/lib/datadog/profiling/load_native_extension.rb index 062468fc9ce..88b983792cf 100644 --- a/lib/datadog/profiling/load_native_extension.rb +++ b/lib/datadog/profiling/load_native_extension.rb @@ -15,7 +15,7 @@ require "datadog_profiling_loader.#{RUBY_VERSION}_#{RUBY_PLATFORM}" rescue LoadError => e raise LoadError, - 'Failed to load the profiling loader extension. To fix this, please remove and then reinstall datadog ' \ + "Failed to load the profiling loader extension. To fix this, please remove and then reinstall datadog " \ "(Details: #{e.message})" end @@ -24,7 +24,7 @@ full_file_path = "#{__dir__}/../../#{file_name}" unless File.exist?(full_file_path) - extension_dir = Gem.loaded_specs['datadog'].extension_dir + extension_dir = Gem.loaded_specs["datadog"].extension_dir candidate_path = "#{extension_dir}/#{file_name}" if File.exist?(candidate_path) full_file_path = candidate_path diff --git a/lib/datadog/profiling/preload.rb b/lib/datadog/profiling/preload.rb index 192b6e39416..1e5be627fd0 100644 --- a/lib/datadog/profiling/preload.rb +++ b/lib/datadog/profiling/preload.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require_relative '../../datadog' +require_relative "../../datadog" Datadog::Profiling.start_if_enabled diff --git a/lib/datadog/profiling/profiler.rb b/lib/datadog/profiling/profiler.rb index 5e4e57bc089..1e5ff2ef244 100644 --- a/lib/datadog/profiling/profiler.rb +++ b/lib/datadog/profiling/profiler.rb @@ -31,7 +31,7 @@ def start end def shutdown! - Datadog.logger.debug('Shutting down profiler') + Datadog.logger.debug("Shutting down profiler") stop_worker stop_scheduler @@ -52,7 +52,7 @@ def stop_scheduler def component_failed(failed_component) Datadog.logger.warn( "Detected issue with profiler (#{failed_component} component), stopping profiling. " \ - 'See previous log messages for details.' + "See previous log messages for details." ) # We explicitly not stop the crash tracker in this situation, under the assumption that, if a component failed, diff --git a/lib/datadog/profiling/scheduler.rb b/lib/datadog/profiling/scheduler.rb index ab1206c30cf..709b512ab20 100644 --- a/lib/datadog/profiling/scheduler.rb +++ b/lib/datadog/profiling/scheduler.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative '../core/utils/time' +require_relative "../core/utils/time" -require_relative '../core/worker' -require_relative '../core/workers/polling' +require_relative "../core/worker" +require_relative "../core/workers/polling" module Datadog module Profiling @@ -62,13 +62,13 @@ def perform(on_failure_proc) interrupted = false rescue Exception => e # rubocop:disable Lint/RescueException Datadog.logger.warn( - 'Profiling::Scheduler thread error. ' \ + "Profiling::Scheduler thread error. " \ "Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}" ) on_failure_proc&.call raise ensure - Datadog.logger.debug('#flush was interrupted or failed before it could complete') if interrupted + Datadog.logger.debug("#flush was interrupted or failed before it could complete") if interrupted end # Configure Workers::IntervalLoop to not report immediately when scheduler starts diff --git a/lib/datadog/profiling/tag_builder.rb b/lib/datadog/profiling/tag_builder.rb index 3f8cd07bfdb..a244b1c1719 100644 --- a/lib/datadog/profiling/tag_builder.rb +++ b/lib/datadog/profiling/tag_builder.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative '../core/utils' -require_relative '../core/environment/git' +require_relative "../core/utils" +require_relative "../core/environment/git" module Datadog module Profiling diff --git a/lib/datadog/profiling/tasks/exec.rb b/lib/datadog/profiling/tasks/exec.rb index 6fcb3e14281..abf39bbf9c1 100644 --- a/lib/datadog/profiling/tasks/exec.rb +++ b/lib/datadog/profiling/tasks/exec.rb @@ -18,16 +18,16 @@ def run def rubyopts [ - '-rdatadog/profiling/preload' + "-rdatadog/profiling/preload" ] end private def set_rubyopt! - existing_rubyopt = ENV['RUBYOPT'] + existing_rubyopt = ENV["RUBYOPT"] - ENV['RUBYOPT'] = existing_rubyopt ? "#{existing_rubyopt} #{rubyopts.join(" ")}" : rubyopts.join(' ') + ENV["RUBYOPT"] = existing_rubyopt ? "#{existing_rubyopt} #{rubyopts.join(" ")}" : rubyopts.join(" ") end # If there's an error here, rather than throwing a cryptic stack trace, let's instead have clearer messages, and diff --git a/lib/datadog/profiling/tasks/setup.rb b/lib/datadog/profiling/tasks/setup.rb index f4d638858a1..9eccbf5b8f5 100644 --- a/lib/datadog/profiling/tasks/setup.rb +++ b/lib/datadog/profiling/tasks/setup.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative '../../core/utils/only_once' -require_relative '../../core/utils/at_fork_monkey_patch' +require_relative "../../core/utils/only_once" +require_relative "../../core/utils/at_fork_monkey_patch" module Datadog module Profiling diff --git a/spec/datadog/profiling/collectors/code_provenance_spec.rb b/spec/datadog/profiling/collectors/code_provenance_spec.rb index 08ec41371f5..ff78d2ee4b9 100644 --- a/spec/datadog/profiling/collectors/code_provenance_spec.rb +++ b/spec/datadog/profiling/collectors/code_provenance_spec.rb @@ -1,76 +1,76 @@ -require 'datadog/profiling/collectors/code_provenance' -require 'json-schema' -require 'yaml' +require "datadog/profiling/collectors/code_provenance" +require "json-schema" +require "yaml" RSpec.describe Datadog::Profiling::Collectors::CodeProvenance do subject(:code_provenance) { described_class.new } - describe '#refresh' do + describe "#refresh" do subject(:refresh) { code_provenance.refresh } - it 'records libraries that are currently loaded' do + it "records libraries that are currently loaded" do refresh expect(code_provenance.generate).to include( have_attributes( - kind: 'standard library', - name: 'stdlib', + kind: "standard library", + name: "stdlib", version: RUBY_VERSION.to_s, - path: start_with('/'), + path: start_with("/"), ), have_attributes( - kind: 'library', - name: 'datadog', + kind: "library", + name: "datadog", version: Datadog::VERSION::STRING, - path: start_with('/'), + path: start_with("/"), ), have_attributes( - kind: 'library', - name: 'rspec-core', - version: start_with('3.'), # This will one day need to be bumped for RSpec 4 - path: start_with('/'), + kind: "library", + name: "rspec-core", + version: start_with("3."), # This will one day need to be bumped for RSpec 4 + path: start_with("/"), ) ) end - it 'records the correct path for datadog' do + it "records the correct path for datadog" do refresh current_file_directory = __dir__ - datadog_gem_root_directory = code_provenance.generate.find { |lib| lib.name == 'datadog' }.path + datadog_gem_root_directory = code_provenance.generate.find { |lib| lib.name == "datadog" }.path expect(current_file_directory).to start_with(datadog_gem_root_directory) end - it 'skips libraries not present in the loaded files' do + it "skips libraries not present in the loaded files" do code_provenance.refresh( - loaded_files: ['/is_loaded/is_loaded.rb'], + loaded_files: ["/is_loaded/is_loaded.rb"], loaded_specs: [ instance_double( Gem::Specification, - name: 'not_loaded', - version: 'not_loaded_version', - gem_dir: '/not_loaded/' + name: "not_loaded", + version: "not_loaded_version", + gem_dir: "/not_loaded/" ), instance_double( Gem::Specification, - name: 'is_loaded', - version: 'is_loaded_version', - gem_dir: '/is_loaded/' + name: "is_loaded", + version: "is_loaded_version", + gem_dir: "/is_loaded/" ) ], ) expect(code_provenance.generate).to have(1).item expect(code_provenance.generate.first).to have_attributes( - name: 'is_loaded', - version: 'is_loaded_version', - path: '/is_loaded/', - kind: 'library', + name: "is_loaded", + version: "is_loaded_version", + path: "/is_loaded/", + kind: "library", ) end - it 'returns self' do + it "returns self" do expect(code_provenance.refresh).to be code_provenance end @@ -78,32 +78,32 @@ # I'm not entirely sure if this can happen in end-user apps, but can happen in CI if bundler is configured to # install dependencies into a subfolder of datadog. In particular GitHub Actions does this. - it 'matches the loaded file to the longest matching path' do + it "matches the loaded file to the longest matching path" do code_provenance.refresh( - loaded_files: ['/dd-trace-rb/vendor/bundle/ruby/2.7.0/gems/byebug-11.1.3/lib/byebug.rb'], + loaded_files: ["/dd-trace-rb/vendor/bundle/ruby/2.7.0/gems/byebug-11.1.3/lib/byebug.rb"], loaded_specs: [ instance_double( Gem::Specification, - name: 'datadog', - version: '1.2.3', - gem_dir: '/dd-trace-rb' + name: "datadog", + version: "1.2.3", + gem_dir: "/dd-trace-rb" ), instance_double( Gem::Specification, - name: 'byebug', - version: '4.5.6', - gem_dir: '/dd-trace-rb/vendor/bundle/ruby/2.7.0/gems/byebug-11.1.3' + name: "byebug", + version: "4.5.6", + gem_dir: "/dd-trace-rb/vendor/bundle/ruby/2.7.0/gems/byebug-11.1.3" ) ], ) expect(code_provenance.generate).to have(1).item - expect(code_provenance.generate.first).to have_attributes(name: 'byebug') + expect(code_provenance.generate.first).to have_attributes(name: "byebug") end end end - describe '#generate_json' do + describe "#generate_json" do before do code_provenance.refresh end @@ -162,30 +162,30 @@ ).freeze end - it 'renders the list of loaded libraries as json' do - expect(JSON.parse(code_provenance.generate_json).fetch('v1')).to include( + it "renders the list of loaded libraries as json" do + expect(JSON.parse(code_provenance.generate_json).fetch("v1")).to include( hash_including( - 'name' => 'stdlib', - 'kind' => 'standard library', - 'version' => RUBY_VERSION.to_s, - 'paths' => include(start_with('/')), + "name" => "stdlib", + "kind" => "standard library", + "version" => RUBY_VERSION.to_s, + "paths" => include(start_with("/")), ), hash_including( - 'name' => 'datadog', - 'kind' => 'library', - 'version' => Datadog::VERSION::STRING, - 'paths' => include(start_with('/')), + "name" => "datadog", + "kind" => "library", + "version" => Datadog::VERSION::STRING, + "paths" => include(start_with("/")), ), hash_including( - 'name' => 'rspec-core', - 'kind' => 'library', - 'version' => start_with('3.'), # This will one day need to be bumped for RSpec 4 - 'paths' => include(start_with('/')), + "name" => "rspec-core", + "kind" => "library", + "version" => start_with("3."), # This will one day need to be bumped for RSpec 4 + "paths" => include(start_with("/")), ) ) end - it 'renders the list of loaded libraries using the expected schema' do + it "renders the list of loaded libraries using the expected schema" do JSON::Validator.validate!(code_provenance_schema, code_provenance.generate_json) end @@ -232,30 +232,30 @@ # {"world":2} # {"hello":1} # - describe 'when JSON encoder is broken and skips #to_json' do + describe "when JSON encoder is broken and skips #to_json" do let(:library_class_without_to_json) do Class.new(Datadog::Profiling::Collectors::CodeProvenance::Library) do undef to_json end end - it 'is still able to correctly encode a library instance' do + it "is still able to correctly encode a library instance" do instance = library_class_without_to_json.new( - name: 'datadog', - kind: 'library', - version: '1.2.3', - path: '/example/path/to/datadog/gem', + name: "datadog", + kind: "library", + version: "1.2.3", + path: "/example/path/to/datadog/gem", ) serialized_without_to_json = YAML.dump(instance) # Remove class annotation, so it deserializes back as a hash and not an instance of our class - serialized_without_to_json.gsub!(/---.*/, '---') + serialized_without_to_json.gsub!(/---.*/, "---") expect(YAML.safe_load(serialized_without_to_json)).to eq( - 'name' => 'datadog', - 'kind' => 'library', - 'version' => '1.2.3', - 'paths' => ['/example/path/to/datadog/gem'], + "name" => "datadog", + "kind" => "library", + "version" => "1.2.3", + "paths" => ["/example/path/to/datadog/gem"], ) end end diff --git a/spec/datadog/profiling/collectors/cpu_and_wall_time_worker_spec.rb b/spec/datadog/profiling/collectors/cpu_and_wall_time_worker_spec.rb index 1787fa64c5d..0acca631148 100644 --- a/spec/datadog/profiling/collectors/cpu_and_wall_time_worker_spec.rb +++ b/spec/datadog/profiling/collectors/cpu_and_wall_time_worker_spec.rb @@ -1,6 +1,6 @@ -require 'datadog/profiling/spec_helper' +require "datadog/profiling/spec_helper" -require 'datadog/profiling/collectors/cpu_and_wall_time_worker' +require "datadog/profiling/collectors/cpu_and_wall_time_worker" RSpec.describe Datadog::Profiling::Collectors::CpuAndWallTimeWorker do before { skip_if_profiling_not_supported(self) } @@ -30,8 +30,8 @@ subject(:cpu_and_wall_time_worker) { described_class.new(**worker_settings, **options) } - describe '.new' do - it 'creates the garbage collection tracepoint in the disabled state' do + describe ".new" do + it "creates the garbage collection tracepoint in the disabled state" do expect(described_class::Testing._native_gc_tracepoint(cpu_and_wall_time_worker)).to_not be_enabled end @@ -60,7 +60,7 @@ end end - describe '#start' do + describe "#start" do let(:expected_worker_initialization_error) { nil } subject(:start) do @@ -72,20 +72,20 @@ cpu_and_wall_time_worker.stop end - it 'creates a new thread' do + it "creates a new thread" do start expect(Thread.list.map(&:name)).to include(described_class.name) end # See https://github.com/puma/puma/blob/32e011ab9e029c757823efb068358ed255fb7ef4/lib/puma/cluster.rb#L353-L359 - it 'marks the new thread as fork-safe' do + it "marks the new thread as fork-safe" do start expect(cpu_and_wall_time_worker.instance_variable_get(:@worker_thread).thread_variable_get(:fork_safe)).to be true end - it 'does not create a second thread if start is called again' do + it "does not create a second thread if start is called again" do start expect(Thread).to_not receive(:new) @@ -93,7 +93,7 @@ cpu_and_wall_time_worker.start end - it 'does not allow other instances of the CpuAndWallTimeWorker to start' do + it "does not allow other instances of the CpuAndWallTimeWorker to start" do start allow(Datadog.logger).to receive(:warn) @@ -103,38 +103,38 @@ exception = try_wait_until(backoff: 0.01) { another_instance.send(:failure_exception) } - expect(exception.message).to include 'another instance' + expect(exception.message).to include "another instance" another_instance.stop end - it 'installs the profiling SIGPROF signal handler' do + it "installs the profiling SIGPROF signal handler" do start expect(described_class::Testing._native_current_sigprof_signal_handler).to be :profiling end - context 'when gc_profiling_enabled is true' do + context "when gc_profiling_enabled is true" do let(:gc_profiling_enabled) { true } - it 'enables the garbage collection tracepoint' do + it "enables the garbage collection tracepoint" do start expect(described_class::Testing._native_gc_tracepoint(cpu_and_wall_time_worker)).to be_enabled end end - context 'when gc_profiling_enabled is false' do + context "when gc_profiling_enabled is false" do let(:gc_profiling_enabled) { false } - it 'does not enable the garbage collection tracepoint' do + it "does not enable the garbage collection tracepoint" do start expect(described_class::Testing._native_gc_tracepoint(cpu_and_wall_time_worker)).to_not be_enabled end end - context 'when a previous signal handler existed' do + context "when a previous signal handler existed" do before do described_class::Testing._native_install_testing_signal_handler expect(described_class::Testing._native_current_sigprof_signal_handler).to be :other @@ -146,15 +146,15 @@ described_class::Testing._native_remove_testing_signal_handler end - it 'does not start the sampling loop' do + it "does not start the sampling loop" do cpu_and_wall_time_worker.start exception = try_wait_until(backoff: 0.01) { cpu_and_wall_time_worker.send(:failure_exception) } - expect(exception.message).to include 'pre-existing SIGPROF' + expect(exception.message).to include "pre-existing SIGPROF" end - it 'leaves the existing signal handler in place' do + it "leaves the existing signal handler in place" do cpu_and_wall_time_worker.start try_wait_until(backoff: 0.01) { cpu_and_wall_time_worker.send(:failure_exception) } @@ -163,12 +163,12 @@ end end - context 'sampling of active threads' do + context "sampling of active threads" do # This option makes sure our samples are taken via thread interruptions (and not via idle sampling). # See native bits for more details. let(:options) { {**super(), skip_idle_samples_for_testing: true} } - it 'triggers sampling and records the results' do + it "triggers sampling and records the results" do start all_samples = loop_until do @@ -180,8 +180,8 @@ end it( - 'keeps statistics on how many samples were triggered by the background thread, ' \ - 'as well as how many samples were requested from the VM', + "keeps statistics on how many samples were triggered by the background thread, " \ + "as well as how many samples were requested from the VM", ) do start @@ -207,7 +207,7 @@ end end - it 'keeps statistics on how long sampling is taking' do + it "keeps statistics on how long sampling is taking" do start try_wait_until do @@ -231,11 +231,11 @@ expect(sampling_time_ns_max).to be < one_second_in_ns, "A single sample should not take longer than 1s, #{stats}" end - context 'with allocation profiling enabled' do + context "with allocation profiling enabled" do # We need this otherwise allocations_during_sample will never change let(:allocation_profiling_enabled) { true } - it 'does not allocate Ruby objects during the regular operation of sampling' do + it "does not allocate Ruby objects during the regular operation of sampling" do # The intention of this test is to warn us if we accidentally trigger object allocations during "happy path" # sampling. # Note that when something does go wrong during sampling, we do allocate exceptions (and then raise them). @@ -255,7 +255,7 @@ end end - it 'records garbage collection cycles' do + it "records garbage collection cycles" do start described_class::Testing._native_trigger_sample @@ -270,22 +270,22 @@ all_samples = samples_from_pprof(recorder.serialize!) - gc_sample = all_samples.find { |sample| sample.labels[:"gc cause"] == 'GC.start()' } + gc_sample = all_samples.find { |sample| sample.labels[:"gc cause"] == "GC.start()" } expect(gc_sample.labels).to match a_hash_including( - state: 'had cpu', - "thread id": 'GC', - "thread name": 'Garbage Collection', - event: 'gc', + state: "had cpu", + "thread id": "GC", + "thread name": "Garbage Collection", + event: "gc", "gc reason": an_instance_of(String), - "gc cause": 'GC.start()', - "gc type": 'major', + "gc cause": "GC.start()", + "gc type": "major", ) - expect(gc_sample.locations.first.path).to eq 'Garbage Collection' + expect(gc_sample.locations.first.path).to eq "Garbage Collection" end - context 'when the background thread dies without cleaning up (after Ruby forks)' do - it 'allows the CpuAndWallTimeWorker to be restarted' do + context "when the background thread dies without cleaning up (after Ruby forks)" do + it "allows the CpuAndWallTimeWorker to be restarted" do start expect_in_fork do @@ -294,7 +294,7 @@ end end - it 'allows a different instance of the CpuAndWallTimeWorker to be started' do + it "allows a different instance of the CpuAndWallTimeWorker to be started" do start expect_in_fork do @@ -305,7 +305,7 @@ end end - it 'disables the existing gc_tracepoint before starting another CpuAndWallTimeWorker' do + it "disables the existing gc_tracepoint before starting another CpuAndWallTimeWorker" do start expect(described_class::Testing._native_gc_tracepoint(cpu_and_wall_time_worker)).to be_enabled @@ -322,7 +322,7 @@ end end - context 'when main thread is sleeping but a background thread is working' do + context "when main thread is sleeping but a background thread is working" do let(:ready_queue) { Queue.new } let(:background_thread) do Thread.new do @@ -337,7 +337,7 @@ background_thread.join end - it 'is able to sample even when the main thread is sleeping' do + it "is able to sample even when the main thread is sleeping" do background_thread ready_queue.pop @@ -371,12 +371,12 @@ end end - context 'when all threads are sleeping (no thread holds the Global VM Lock)' do + context "when all threads are sleeping (no thread holds the Global VM Lock)" do let(:options) { {dynamic_sampling_rate_enabled: false} } before { expect(Datadog.logger).to receive(:warn).with(/dynamic sampling rate disabled/) } - it 'is able to sample even when all threads are sleeping' do + it "is able to sample even when all threads are sleeping" do start wait_until_running @@ -405,7 +405,7 @@ # expect(sample_count).to be >= 8, "sample_count: #{sample_count}, stats: #{stats}, debug_failures: #{debug_failures}" - if RUBY_VERSION >= '3.3.0' + if RUBY_VERSION >= "3.3.0" expect(trigger_sample_attempts).to be >= sample_count else # @ivoanjo: We've seen this assertion become flaky once in CI for Ruby 3.1, where @@ -431,10 +431,10 @@ end end - context 'when using the no signals workaround' do + context "when using the no signals workaround" do let(:no_signals_workaround_enabled) { true } - it 'always simulates signal delivery' do + it "always simulates signal delivery" do start all_samples = try_wait_until do @@ -477,7 +477,7 @@ end end - context 'when allocation profiling is enabled' do + context "when allocation profiling is enabled" do let(:allocation_profiling_enabled) { true } let(:test_num_allocated_object) { 123 } # Explicitly disable dynamic sampling in these tests so we can deterministically verify @@ -489,8 +489,8 @@ allow(Datadog.logger).to receive(:warn).with(/dynamic sampling rate disabled/) end - it 'records allocated objects' do - stub_const('CpuAndWallTimeWorkerSpec::TestStruct', Struct.new(:foo)) + it "records allocated objects" do + stub_const("CpuAndWallTimeWorkerSpec::TestStruct", Struct.new(:foo)) start @@ -501,17 +501,17 @@ allocation_sample = samples_for_thread(samples_from_pprof(recorder.serialize!), Thread.current) - .find { |s| s.labels[:"allocation class"] == 'CpuAndWallTimeWorkerSpec::TestStruct' } + .find { |s| s.labels[:"allocation class"] == "CpuAndWallTimeWorkerSpec::TestStruct" } expect(allocation_sample.values).to include("alloc-samples": test_num_allocated_object) expect(allocation_sample.locations.first.lineno).to eq allocation_line end - context 'with dynamic_sampling_rate_enabled' do + context "with dynamic_sampling_rate_enabled" do let(:options) { {dynamic_sampling_rate_enabled: true} } - it 'keeps statistics on how allocation sampling is doing' do - stub_const('CpuAndWallTimeWorkerSpec::TestStruct', Struct.new(:foo)) + it "keeps statistics on how allocation sampling is doing" do + stub_const("CpuAndWallTimeWorkerSpec::TestStruct", Struct.new(:foo)) start @@ -546,7 +546,7 @@ # and we clamp it if it goes over the limit. # But the total amount of allocations recorded should match the number we observed, and thus we record the # remainder above the clamped value as a separate "Skipped Samples" step. - context 'with a high allocation rate' do + context "with a high allocation rate" do let(:options) { {**super(), dynamic_sampling_rate_overhead_target_percentage: 0.1} } let(:thread_that_allocates_as_fast_as_possible) { Thread.new { loop { BasicObject.new } } } @@ -555,7 +555,7 @@ thread_that_allocates_as_fast_as_possible.join end - it 'records skipped allocation samples when weights are clamped' do + it "records skipped allocation samples when weights are clamped" do start # Trigger thread creation @@ -563,7 +563,7 @@ allocation_samples = try_wait_until do samples = samples_from_pprof(recorder.serialize!).select { |it| it.values[:"alloc-samples"] > 0 } - samples if samples.any? { |it| it.labels[:"thread name"] == 'Skipped Samples' } + samples if samples.any? { |it| it.labels[:"thread name"] == "Skipped Samples" } end # Stop thread earlier, since it will slow down the Ruby VM @@ -577,7 +577,7 @@ end end - context 'when sampling optimized Ruby strings' do + context "when sampling optimized Ruby strings" do # Regression test: Some internal Ruby classes use a `rb_str_tmp_frozen_acquire` function which allocates a # weird "intermediate" string object that has its class pointer set to 0. # @@ -585,22 +585,22 @@ # # In practice, this test is actually validating behavior of the `ThreadContext` collector, but we can only # really trigger this situation when using the allocation tracepoint, which lives in the `CpuAndWallTimeWorker`. - it 'does not crash' do + it "does not crash" do start - expect(Time.now.strftime(+'Potato')).to_not be nil + expect(Time.now.strftime(+"Potato")).to_not be nil end end - context 'T_IMEMO internal VM objects' do + context "T_IMEMO internal VM objects" do let(:something_that_triggers_creation_of_imemo_objects) do - eval('proc { def self.foo; rand; end; foo }.call', binding, __FILE__, __LINE__) + eval("proc { def self.foo; rand; end; foo }.call", binding, __FILE__, __LINE__) end - context 'on Ruby 2.x' do - before { skip 'Behavior only applies on Ruby 2.x' unless RUBY_VERSION.start_with?('2.') } + context "on Ruby 2.x" do + before { skip "Behavior only applies on Ruby 2.x" unless RUBY_VERSION.start_with?("2.") } - it 'records internal VM objects, not including their specific kind' do + it "records internal VM objects, not including their specific kind" do start something_that_triggers_creation_of_imemo_objects @@ -609,16 +609,16 @@ imemo_samples = samples_for_thread(samples_from_pprof(recorder.serialize!), Thread.current) - .select { |s| s.labels.fetch(:"allocation class", '') == '(VM Internal, T_IMEMO)' } + .select { |s| s.labels.fetch(:"allocation class", "") == "(VM Internal, T_IMEMO)" } expect(imemo_samples.size).to be >= 1 # We should always get some T_IMEMO objects end end - context 'on Ruby 3.x' do - before { skip 'Behavior only applies on Ruby 3.x' if RUBY_VERSION.start_with?('2.') } + context "on Ruby 3.x" do + before { skip "Behavior only applies on Ruby 3.x" if RUBY_VERSION.start_with?("2.") } - it 'records internal VM objects, including their specific kind' do + it "records internal VM objects, including their specific kind" do start something_that_triggers_creation_of_imemo_objects @@ -627,7 +627,7 @@ imemo_samples = samples_for_thread(samples_from_pprof(recorder.serialize!), Thread.current) - .select { |s| s.labels.fetch(:"allocation class", '').start_with?('(VM Internal, T_IMEMO') } + .select { |s| s.labels.fetch(:"allocation class", "").start_with?("(VM Internal, T_IMEMO") } expect(imemo_samples.size).to be >= 1 # We should always get some T_IMEMO objects @@ -643,11 +643,11 @@ end end - context 'when allocation sampling is disabled' do + context "when allocation sampling is disabled" do let(:allocation_profiling_enabled) { false } - it 'does not record allocations' do - stub_const('CpuAndWallTimeWorkerSpec::TestStruct', Struct.new(:foo)) + it "does not record allocations" do + stub_const("CpuAndWallTimeWorkerSpec::TestStruct", Struct.new(:foo)) start @@ -659,7 +659,7 @@ end end - context 'when heap profiling is enabled' do + context "when heap profiling is enabled" do let(:allocation_profiling_enabled) { true } let(:heap_profiling_enabled) { true } let(:test_num_allocated_object) { 123 } @@ -668,13 +668,13 @@ let(:options) { {dynamic_sampling_rate_enabled: false} } before do - skip 'Heap profiling is only supported on Ruby >= 2.7' if RUBY_VERSION < '2.7' + skip "Heap profiling is only supported on Ruby >= 2.7" if RUBY_VERSION < "2.7" allow(Datadog.logger).to receive(:warn) expect(Datadog.logger).to receive(:warn).with(/dynamic sampling rate disabled/) end - it 'records live heap objects' do - stub_const('CpuAndWallTimeWorkerSpec::TestStruct', Struct.new(:foo)) + it "records live heap objects" do + stub_const("CpuAndWallTimeWorkerSpec::TestStruct", Struct.new(:foo)) start @@ -693,8 +693,8 @@ first_frame = sample.locations.first first_frame.lineno == allocation_line && first_frame.path == __FILE__ && - first_frame.base_label == 'new' && - sample.labels[:"allocation class"] == 'CpuAndWallTimeWorkerSpec::TestStruct' && + first_frame.base_label == "new" && + sample.labels[:"allocation class"] == "CpuAndWallTimeWorkerSpec::TestStruct" && (sample.values[:"heap-live-samples"] || 0) > 0 } @@ -713,11 +713,11 @@ end end - context 'when heap profiling is disabled' do + context "when heap profiling is disabled" do let(:heap_profiling_enabled) { false } - it 'does not record heap samples' do - stub_const('CpuAndWallTimeWorkerSpec::TestStruct', Struct.new(:foo)) + it "does not record heap samples" do + stub_const("CpuAndWallTimeWorkerSpec::TestStruct", Struct.new(:foo)) start @@ -729,7 +729,7 @@ end end - context 'Process::Waiter crash regression tests' do + context "Process::Waiter crash regression tests" do # On Ruby 2.3 to 2.6, there's a crash when accessing instance variables of the `process_waiter_thread`, # see https://bugs.ruby-lang.org/issues/17807 . # @@ -740,7 +740,7 @@ # @ivoanjo: This affected the old profiler at some point (but never affected the new profiler), but I think # it's useful to keep around so that we don't regress if we decide to start reading/writing some # info to thread objects to implement some future feature. - it 'can sample an instance of Process::Waiter without crashing' do + it "can sample an instance of Process::Waiter without crashing" do forked_process = fork { sleep } process_waiter_thread = Process.detach(forked_process) @@ -755,16 +755,16 @@ sample = samples_for_thread(all_samples, process_waiter_thread).first - expect(sample.locations.first.path).to eq 'In native code' + expect(sample.locations.first.path).to eq "In native code" - Process.kill('TERM', forked_process) + Process.kill("TERM", forked_process) process_waiter_thread.join end end - context 'when the _native_sampling_loop terminates with an exception' do - it 'calls the on_failure_proc' do - expect(described_class).to receive(:_native_sampling_loop).and_raise(StandardError.new('Simulated error')) + context "when the _native_sampling_loop terminates with an exception" do + it "calls the on_failure_proc" do + expect(described_class).to receive(:_native_sampling_loop).and_raise(StandardError.new("Simulated error")) expect(Datadog.logger).to receive(:warn) proc_called = Queue.new @@ -776,16 +776,16 @@ end end - describe 'Ractor safety' do + describe "Ractor safety" do before do - skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION < '3.' + skip "Behavior does not apply to current Ruby version" if RUBY_VERSION < "3." # See native_extension_spec.rb for more details on the issues we saw on 3.0 - skip 'Ruby 3.0 Ractors are too buggy to run this spec' if RUBY_VERSION.start_with?('3.0.') + skip "Ruby 3.0 Ractors are too buggy to run this spec" if RUBY_VERSION.start_with?("3.0.") end - shared_examples_for 'does not trigger a sample' do |run_ractor| - it 'does not trigger a sample' do + shared_examples_for "does not trigger a sample" do |run_ractor| + it "does not trigger a sample" do cpu_and_wall_time_worker.start wait_until_running @@ -795,13 +795,13 @@ samples_from_ractor = samples_from_pprof(recorder.serialize!) - .select { |it| it.labels[:"thread name"] == 'background ractor' } + .select { |it| it.labels[:"thread name"] == "background ractor" } expect(samples_from_ractor).to be_empty end end - context 'when called from a background ractor', ractors: true do + context "when called from a background ractor", ractors: true do # Even though we're not testing it explicitly, the GC profiling hooks can sometimes be called when running these # specs. Unfortunately, there's a VM crash in that case as well -- https://bugs.ruby-lang.org/issues/18464 -- # so this must be disabled when interacting with Ractors. @@ -809,24 +809,24 @@ # ...same thing for the tracepoint for allocation counting/profiling :( let(:allocation_profiling_enabled) { false } - describe 'handle_sampling_signal' do - include_examples 'does not trigger a sample', + describe "handle_sampling_signal" do + include_examples "does not trigger a sample", ( proc do Ractor.new do - Thread.current.name = 'background ractor' + Thread.current.name = "background ractor" Datadog::Profiling::Collectors::CpuAndWallTimeWorker::Testing._native_simulate_handle_sampling_signal end.take end ) end - describe 'sample_from_postponed_job' do - include_examples 'does not trigger a sample', + describe "sample_from_postponed_job" do + include_examples "does not trigger a sample", ( proc do Ractor.new do - Thread.current.name = 'background ractor' + Thread.current.name = "background ractor" Datadog::Profiling::Collectors::CpuAndWallTimeWorker::Testing._native_simulate_sample_from_postponed_job end.take end @@ -839,11 +839,11 @@ end end - describe '#stop' do + describe "#stop" do subject(:stop) { cpu_and_wall_time_worker.stop } - context 'when called immediately after start' do - it 'stops the CpuAndWallTimeWorker' do + context "when called immediately after start" do + it "stops the CpuAndWallTimeWorker" do cpu_and_wall_time_worker.start stop @@ -852,41 +852,41 @@ end end - context 'after starting' do + context "after starting" do before do cpu_and_wall_time_worker.start wait_until_running end - it 'shuts down the background thread' do + it "shuts down the background thread" do stop expect(Thread.list.map(&:name)).to_not include(described_class.name) end - it 'replaces the profiling sigprof signal handler with an empty one' do + it "replaces the profiling sigprof signal handler with an empty one" do stop expect(described_class::Testing._native_current_sigprof_signal_handler).to be :empty end - it 'disables the garbage collection tracepoint' do + it "disables the garbage collection tracepoint" do stop expect(described_class::Testing._native_gc_tracepoint(cpu_and_wall_time_worker)).to_not be_enabled end - it 'leaves behind an empty SIGPROF signal handler' do + it "leaves behind an empty SIGPROF signal handler" do stop # Without an empty SIGPROF signal handler (e.g. with no signal handler) the following command will make the VM # instantly terminate with a confusing "Profiling timer expired" message left behind. (This message doesn't # come from us -- it's the default message for an unhandled SIGPROF. Pretty confusing UNIX/POSIX behavior...) - Process.kill('SIGPROF', Process.pid) + Process.kill("SIGPROF", Process.pid) end end - it 'unblocks SIGPROF signal handling from the worker thread' do + it "unblocks SIGPROF signal handling from the worker thread" do inner_ran = false expect(described_class).to receive(:_native_sampling_loop).and_wrap_original do |native, *args| @@ -905,7 +905,7 @@ end end - describe '#reset_after_fork' do + describe "#reset_after_fork" do subject(:reset_after_fork) { cpu_and_wall_time_worker.reset_after_fork } let(:thread_context_collector) do @@ -932,13 +932,13 @@ cpu_and_wall_time_worker.stop end - it 'disables the gc_tracepoint' do + it "disables the gc_tracepoint" do expect { reset_after_fork } .to change { described_class::Testing._native_gc_tracepoint(cpu_and_wall_time_worker).enabled? } .from(true).to(false) end - it 'resets the CpuAndWallTime collector only after disabling the tracepoint' do + it "resets the CpuAndWallTime collector only after disabling the tracepoint" do expect(thread_context_collector).to receive(:reset_after_fork) do expect(described_class::Testing._native_gc_tracepoint(cpu_and_wall_time_worker)).to_not be_enabled end @@ -946,7 +946,7 @@ reset_after_fork end - it 'resets all stats' do + it "resets all stats" do cpu_and_wall_time_worker.stop reset_after_fork @@ -984,14 +984,14 @@ end end - describe '._native_allocation_count' do + describe "._native_allocation_count" do subject(:_native_allocation_count) { described_class._native_allocation_count } - context 'when CpuAndWallTimeWorker has not been started' do + context "when CpuAndWallTimeWorker has not been started" do it { is_expected.to be nil } end - context 'when CpuAndWallTimeWorker has been started' do + context "when CpuAndWallTimeWorker has been started" do before do cpu_and_wall_time_worker.start wait_until_running @@ -1001,15 +1001,15 @@ cpu_and_wall_time_worker.stop end - context 'when allocation profiling and allocation counting is enabled' do + context "when allocation profiling and allocation counting is enabled" do let(:allocation_profiling_enabled) { true } let(:allocation_counting_enabled) { true } - it 'always returns a >= 0 value' do + it "always returns a >= 0 value" do expect(described_class._native_allocation_count).to be >= 0 end - it 'returns the exact number of allocations between two calls of the method' do + it "returns the exact number of allocations between two calls of the method" do # In rare situations (once every few thousand runs) we've witnessed this test failing with # more than 100 allocations being reported. With some extra debugging logs and callstack # dumps we've tracked the extra allocations to the calling of finalizers with complex @@ -1047,7 +1047,7 @@ GC.enable end - it 'returns different numbers of allocations for different threads' do + it "returns different numbers of allocations for different threads" do # To get the exact expected number of allocations, we run this once before so that Ruby can create and cache all # it needs to new_object = proc { Object.new } @@ -1081,10 +1081,10 @@ expect(after_allocations - before_allocations).to be < 10 end - context 'when allocation profiling is enabled but allocation counting is disabled' do + context "when allocation profiling is enabled but allocation counting is disabled" do let(:allocation_counting_enabled) { false } - it 'always returns a nil value' do + it "always returns a nil value" do 100.times { Object.new } expect(described_class._native_allocation_count).to be nil @@ -1092,10 +1092,10 @@ end end - context 'when allocation profiling is disabled' do + context "when allocation profiling is disabled" do let(:allocation_profiling_enabled) { false } - it 'always returns a nil value' do + it "always returns a nil value" do 100.times { Object.new } expect(described_class._native_allocation_count).to be nil @@ -1104,10 +1104,10 @@ end end - describe '#stats_reset_not_thread_safe' do + describe "#stats_reset_not_thread_safe" do let(:allocation_profiling_enabled) { true } - it 'returns accumulated stats and resets them back to 0' do + it "returns accumulated stats and resets them back to 0" do cpu_and_wall_time_worker.start wait_until_running @@ -1121,7 +1121,7 @@ samples if samples.any? end - stub_const('CpuAndWallTimeWorkerSpec::TestStruct', Struct.new(:foo)) + stub_const("CpuAndWallTimeWorkerSpec::TestStruct", Struct.new(:foo)) 1000.times { CpuAndWallTimeWorkerSpec::TestStruct.new } cpu_and_wall_time_worker.stop @@ -1150,15 +1150,15 @@ end end - describe '.delayed_error' do + describe ".delayed_error" do before { allow(Datadog.logger).to receive(:warn) } - it 'on allocation, raises on start' do + it "on allocation, raises on start" do worker = described_class.allocate # Simulate a delayed failure pre-initialization (i.e. during new) Datadog::Profiling::Collectors::CpuAndWallTimeWorker::Testing._native_delayed_error( worker, - 'test failure' + "test failure" ) worker.send(:initialize, **worker_settings, **options) @@ -1174,12 +1174,12 @@ # And we expect the worker to be shutdown with a failure exception expect(described_class._native_is_running?(worker)).to be false exception = try_wait_until(backoff: 0.01) { worker.send(:failure_exception) } - expect(exception.message).to include 'test failure' + expect(exception.message).to include "test failure" worker.stop end - it 'raises on next iteration' do + it "raises on next iteration" do proc_called = Queue.new cpu_and_wall_time_worker.start(on_failure_proc: proc { proc_called << true }) @@ -1194,7 +1194,7 @@ # Simulate a delayed failure while running Datadog::Profiling::Collectors::CpuAndWallTimeWorker::Testing._native_delayed_error( cpu_and_wall_time_worker, - 'test failure' + "test failure" ) # We expect this to have been filled by the on_failure_proc @@ -1203,14 +1203,14 @@ # And we expect the worker to be shutdown with a failure exception expect(described_class._native_is_running?(cpu_and_wall_time_worker)).to be false exception = try_wait_until(backoff: 0.01) { cpu_and_wall_time_worker.send(:failure_exception) } - expect(exception.message).to include 'test failure' + expect(exception.message).to include "test failure" cpu_and_wall_time_worker.stop end end - describe '#wait_until_running' do - context 'when the worker starts' do + describe "#wait_until_running" do + context "when the worker starts" do it do cpu_and_wall_time_worker.start @@ -1221,14 +1221,14 @@ end context "when worker doesn't start on time" do - it 'raises an exception' do + it "raises an exception" do expect { cpu_and_wall_time_worker.wait_until_running(timeout_seconds: 0) }.to raise_error(/Timeout waiting/) end end end - describe '._native_hold_signals and _native_resume_signals' do - it 'blocks/unblocks interruptions for the current thread' do + describe "._native_hold_signals and _native_resume_signals" do + it "blocks/unblocks interruptions for the current thread" do expect(described_class::Testing._native_is_sigprof_blocked_in_current_thread).to be false described_class._native_hold_signals @@ -1251,7 +1251,7 @@ def wait_until_running # We have separate specs that assert on these behaviors. def samples_from_pprof_without_gc_and_overhead(pprof_data) samples_from_pprof(pprof_data) - .reject { |it| it.locations.first.path == 'Garbage Collection' } + .reject { |it| it.locations.first.path == "Garbage Collection" } .reject { |it| it.labels.include?(:"profiler overhead") } end @@ -1277,6 +1277,6 @@ def loop_until(timeout_seconds: 5) return result if result end - raise('Wait time exhausted!') + raise("Wait time exhausted!") end end diff --git a/spec/datadog/profiling/collectors/discrete_dynamic_sampler_spec.rb b/spec/datadog/profiling/collectors/discrete_dynamic_sampler_spec.rb index 22e1dd5e6a2..6d1eaca350d 100644 --- a/spec/datadog/profiling/collectors/discrete_dynamic_sampler_spec.rb +++ b/spec/datadog/profiling/collectors/discrete_dynamic_sampler_spec.rb @@ -1,7 +1,7 @@ -require 'datadog/profiling/spec_helper' -require 'datadog/profiling' +require "datadog/profiling/spec_helper" +require "datadog/profiling" -RSpec.describe 'Datadog::Profiling::Collectors::DiscreteDynamicSampler' do +RSpec.describe "Datadog::Profiling::Collectors::DiscreteDynamicSampler" do let(:max_overhead_target) { 2.0 } before do @@ -70,7 +70,7 @@ def sampler_current_events_per_sec sampler._native_state_snapshot.fetch(:events_per_sec) end - context 'when under a constant' do + context "when under a constant" do let(:stats) do # Warm things up a little to overcome the hardcoded starting parameters simulate_load(duration_seconds: 5, events_per_second: events_per_second, sampling_seconds: 0.01) @@ -78,10 +78,10 @@ def sampler_current_events_per_sec simulate_load(duration_seconds: 60, events_per_second: events_per_second, sampling_seconds: 0.01) end - context 'low load' do + context "low load" do let(:events_per_second) { 1 } - it 'samples everything that comes' do + it "samples everything that comes" do # Max overhead of 2% over 1 second means a max of 0.02 seconds of sampling. # With each sample taking 0.01 seconds, we can afford to do 2 of these every second. # At an event rate of 1/sec we can sample all. @@ -91,10 +91,10 @@ def sampler_current_events_per_sec end end - context 'moderate load' do + context "moderate load" do let(:events_per_second) { 8 } - it 'samples only as many samples as it can to keep to the overhead target' do + it "samples only as many samples as it can to keep to the overhead target" do # Max overhead of 2% over 1 second means a max of 0.02 seconds of sampling. # With each sample taking 0.01 seconds, we can afford to do 2 of these every second. # At an event rate of 8/sec we can sample 1/4 of total events. @@ -104,10 +104,10 @@ def sampler_current_events_per_sec end end - context 'heavy load' do + context "heavy load" do let(:events_per_second) { 100 } - it 'will heavily restrict sampling' do + it "will heavily restrict sampling" do # Max overhead of 2% over 1 second means a max of 0.02 seconds of sampling. # With each sample taking 0.01 seconds, we can afford to do 2 of these every second. # At an event rate of 100/sec we can sample 2% of total events. @@ -118,9 +118,9 @@ def sampler_current_events_per_sec end end - context 'when under a variable load' do - context 'containing lots of short spikes' do - it 'will readjust to decrease sampling rate' do + context "when under a variable load" do + context "containing lots of short spikes" do + it "will readjust to decrease sampling rate" do # Baseline simulate_load(duration_seconds: 10, events_per_second: 10, sampling_seconds: 0.01) p_baseline = sampler_current_probability @@ -140,7 +140,7 @@ def sampler_current_events_per_sec end end - context 'with a big spike at the beginning' do + context "with a big spike at the beginning" do it "won't wait until the next sample to adjust" do # We'll start with a very big load at the beginning. This should move the sampler towards # having very low sampling probabilities (i.e. a big sampling interval). We want to validate @@ -175,8 +175,8 @@ def sampler_current_events_per_sec end end - context 'with a big spike that fits within an adjustment window' do - it 'will readjust preemptively with smaller windows to prevent sampling overload' do + context "with a big spike that fits within an adjustment window" do + it "will readjust preemptively with smaller windows to prevent sampling overload" do # Start with a very small constant load during a long time. So low in fact that we'll # decide to sample everything simulate_load(duration_seconds: 60, events_per_second: 1, sampling_seconds: 0.0001) @@ -198,8 +198,8 @@ def sampler_current_events_per_sec end end - context 'when sampling time worsens' do - it 'will readjust to decrease sampling rate' do + context "when sampling time worsens" do + it "will readjust to decrease sampling rate" do # Start with an initial load of 8 eps @ 0.01s sampling time should give us a sampling # probability of around 25% given our 2% overhead target (see similar test case above) stats = simulate_load(duration_seconds: 60, events_per_second: 8, sampling_seconds: 0.01) @@ -212,8 +212,8 @@ def sampler_current_events_per_sec end end - context 'when sampling time improves' do - it 'will readjust to increase sampling rate' do + context "when sampling time improves" do + it "will readjust to increase sampling rate" do # Start with an initial load of 8 eps @ 0.01s sampling time should give us a sampling # probability of around 25% given our 2% overhead target (see similar test case above) stats = simulate_load(duration_seconds: 60, events_per_second: 8, sampling_seconds: 0.01) @@ -226,7 +226,7 @@ def sampler_current_events_per_sec end end - context 'given a constant load' do + context "given a constant load" do it "the higher the target overhead, the more we'll sample" do # Warm-up to overcome initial hardcoded window simulate_load(duration_seconds: 5, events_per_second: 4, sampling_seconds: 0.01) @@ -246,7 +246,7 @@ def sampler_current_events_per_sec end end - it 'disables sampling for next window if sampling overhead is deemed extremely high but relaxes over time' do + it "disables sampling for next window if sampling overhead is deemed extremely high but relaxes over time" do # Max overhead of 2% over 1 seconds means a max of 0.02 seconds of sampling each second. If each # of our samples takes 0.08 seconds, there's no way for us to sample and meet the target # so probability and intervals must go down to 0. @@ -289,10 +289,10 @@ def sampler_current_events_per_sec expect(stats[:num_samples]).to be >= 60 end - describe '.state_snapshot' do + describe ".state_snapshot" do let(:state_snapshot) { sampler._native_state_snapshot } - it 'fills a Ruby hash with relevant data from a sampler instance' do + it "fills a Ruby hash with relevant data from a sampler instance" do # Max overhead of 2% over 1 second means a max of 0.02 seconds of sampling. # With each sample taking 0.01 seconds, we can afford to do 2 of these every second. # At an event rate of 8/sec we can sample 1/4 of total events. @@ -314,10 +314,10 @@ def sampler_current_events_per_sec ) end - context 'in a situation that triggers our minimum sampling target mechanism' do + context "in a situation that triggers our minimum sampling target mechanism" do let(:max_overhead_target) { 1.0 } - it 'captures time clamps accurately' do + it "captures time clamps accurately" do # Max overhead of 1% over 1 second means a max of 0.01 seconds of sampling. # With each sample taking 0.02 seconds, we can in theory only do one sample every 2 seconds. # However, our minimum sampling target ensure we try at least one sample per window and we'll diff --git a/spec/datadog/profiling/collectors/dynamic_sampling_rate_spec.rb b/spec/datadog/profiling/collectors/dynamic_sampling_rate_spec.rb index 8faf38c8db7..9b3f89ba403 100644 --- a/spec/datadog/profiling/collectors/dynamic_sampling_rate_spec.rb +++ b/spec/datadog/profiling/collectors/dynamic_sampling_rate_spec.rb @@ -1,15 +1,15 @@ -require 'datadog/profiling/spec_helper' -require 'datadog/profiling/collectors/dynamic_sampling_rate' +require "datadog/profiling/spec_helper" +require "datadog/profiling/collectors/dynamic_sampling_rate" RSpec.describe Datadog::Profiling::Collectors::DynamicSamplingRate do before { skip_if_profiling_not_supported(self) } let(:max_overhead_target) { 2.0 } - describe 'dynamic_sampling_rate_after_sample' do + describe "dynamic_sampling_rate_after_sample" do let(:current_monotonic_wall_time_ns) { 123 } - it 'sets the next_sample_after_monotonic_wall_time_ns based on the current timestamp and max overhead target' do + it "sets the next_sample_after_monotonic_wall_time_ns based on the current timestamp and max overhead target" do sampling_time_ns = 456 # The idea here is -- if sampling_time_ns is 2% of the time we spend working, how much is the 98% we should spend @@ -26,8 +26,8 @@ .to be(current_monotonic_wall_time_ns + expected_time_to_sleep.to_i) end - context 'when next_sample_after_monotonic_wall_time_ns would be too far in the future' do - it 'sets the next_sample_after_monotonic_wall_time_ns to be current timestamp + MAX_TIME_UNTIL_NEXT_SAMPLE_NS' do + context "when next_sample_after_monotonic_wall_time_ns would be too far in the future" do + it "sets the next_sample_after_monotonic_wall_time_ns to be current timestamp + MAX_TIME_UNTIL_NEXT_SAMPLE_NS" do max_time_until_next_sample_ns = 10_000_000_000 # MAX_TIME_UNTIL_NEXT_SAMPLE_NS sampling_time_ns = 60_000_000_000 @@ -43,7 +43,7 @@ end end - describe 'dynamic_sampling_rate_should_sample' do + describe "dynamic_sampling_rate_should_sample" do let(:next_sample_after_monotonic_wall_time_ns) { 10 } subject(:dynamic_sampling_rate_should_sample) do @@ -54,18 +54,18 @@ ) end - context 'when wall_time_ns_before_sample is before next_sample_after_monotonic_wall_time_ns' do + context "when wall_time_ns_before_sample is before next_sample_after_monotonic_wall_time_ns" do let(:wall_time_ns_before_sample) { next_sample_after_monotonic_wall_time_ns - 1 } it { is_expected.to be false } end - context 'when wall_time_ns_before_sample is after next_sample_after_monotonic_wall_time_ns' do + context "when wall_time_ns_before_sample is after next_sample_after_monotonic_wall_time_ns" do let(:wall_time_ns_before_sample) { next_sample_after_monotonic_wall_time_ns + 1 } it { is_expected.to be true } end end - describe 'dynamic_sampling_rate_get_sleep' do + describe "dynamic_sampling_rate_get_sleep" do let(:next_sample_after_monotonic_wall_time_ns) { 1_000_000_000 } subject(:dynamic_sampling_rate_get_sleep) do @@ -76,31 +76,31 @@ ) end - context 'when current_monotonic_wall_time_ns is before next_sample_after_monotonic_wall_time_ns' do + context "when current_monotonic_wall_time_ns is before next_sample_after_monotonic_wall_time_ns" do context( - 'when current_monotonic_wall_time_ns is less than MAX_SLEEP_TIME_NS ' \ - 'from next_sample_after_monotonic_wall_time_ns' + "when current_monotonic_wall_time_ns is less than MAX_SLEEP_TIME_NS " \ + "from next_sample_after_monotonic_wall_time_ns" ) do let(:current_monotonic_wall_time_ns) { next_sample_after_monotonic_wall_time_ns - 1234 } - it 'returns the time between current_monotonic_wall_time_ns and next_sample_after_monotonic_wall_time_ns' do + it "returns the time between current_monotonic_wall_time_ns and next_sample_after_monotonic_wall_time_ns" do expect(dynamic_sampling_rate_get_sleep).to be 1234 end end context( - 'when current_monotonic_wall_time_ns is more than MAX_SLEEP_TIME_NS ' \ - 'from next_sample_after_monotonic_wall_time_ns' + "when current_monotonic_wall_time_ns is more than MAX_SLEEP_TIME_NS " \ + "from next_sample_after_monotonic_wall_time_ns" ) do let(:current_monotonic_wall_time_ns) { next_sample_after_monotonic_wall_time_ns - 123_456_789 } - it 'returns MAX_SLEEP_TIME_NS' do + it "returns MAX_SLEEP_TIME_NS" do expect(dynamic_sampling_rate_get_sleep).to be 100_000_000 end end end - context 'when current_monotonic_wall_time_ns is after next_sample_after_monotonic_wall_time_ns' do + context "when current_monotonic_wall_time_ns is after next_sample_after_monotonic_wall_time_ns" do let(:current_monotonic_wall_time_ns) { next_sample_after_monotonic_wall_time_ns + 1 } it { is_expected.to be 0 } end diff --git a/spec/datadog/profiling/collectors/idle_sampling_helper_spec.rb b/spec/datadog/profiling/collectors/idle_sampling_helper_spec.rb index 1bd8821e512..d971f032a97 100644 --- a/spec/datadog/profiling/collectors/idle_sampling_helper_spec.rb +++ b/spec/datadog/profiling/collectors/idle_sampling_helper_spec.rb @@ -1,39 +1,39 @@ -require 'datadog/profiling/spec_helper' -require 'datadog/profiling/collectors/idle_sampling_helper' +require "datadog/profiling/spec_helper" +require "datadog/profiling/collectors/idle_sampling_helper" RSpec.describe Datadog::Profiling::Collectors::IdleSamplingHelper do before { skip_if_profiling_not_supported(self) } subject(:idle_sampling_helper) { described_class.new } - describe '#start' do + describe "#start" do subject(:start) { idle_sampling_helper.start } after do idle_sampling_helper.stop end - it 'resets the IdleSamplingHelper before creating a new thread' do + it "resets the IdleSamplingHelper before creating a new thread" do expect(described_class).to receive(:_native_reset).with(idle_sampling_helper).ordered expect(Thread).to receive(:new).ordered.and_call_original start end - it 'creates a new thread' do + it "creates a new thread" do expect(Thread).to receive(:new).ordered.and_call_original start end # See https://github.com/puma/puma/blob/32e011ab9e029c757823efb068358ed255fb7ef4/lib/puma/cluster.rb#L353-L359 - it 'marks the new thread as fork-safe' do + it "marks the new thread as fork-safe" do start expect(idle_sampling_helper.instance_variable_get(:@worker_thread).thread_variable_get(:fork_safe)).to be true end - it 'does not create a second thread if start is called again' do + it "does not create a second thread if start is called again" do start expect(Thread).to_not receive(:new) @@ -42,10 +42,10 @@ end end - describe '#stop' do + describe "#stop" do subject(:stop) { idle_sampling_helper.stop } - it 'shuts down the background thread' do + it "shuts down the background thread" do worker_thread = idle_sampling_helper.instance_variable_get(:@worker_thread) stop @@ -54,12 +54,12 @@ end end - describe 'idle_sampling_helper_request_action' do + describe "idle_sampling_helper_request_action" do before { idle_sampling_helper.start } after { idle_sampling_helper.stop } # rubocop:disable Style/GlobalVars - it 'runs the requested function in a background thread' do + it "runs the requested function in a background thread" do action_ran = Queue.new # idle_sampling_helper_request_action is built to be called from C code, not Ruby code, so the testing interface diff --git a/spec/datadog/profiling/collectors/info_spec.rb b/spec/datadog/profiling/collectors/info_spec.rb index e38f7f924cc..afee4913464 100644 --- a/spec/datadog/profiling/collectors/info_spec.rb +++ b/spec/datadog/profiling/collectors/info_spec.rb @@ -1,5 +1,5 @@ -require 'datadog/profiling/collectors/info' -require 'json-schema' +require "datadog/profiling/collectors/info" +require "json-schema" RSpec.describe Datadog::Profiling::Collectors::Info do let(:settings) { Datadog::Core::Configuration::Settings.new } @@ -7,9 +7,9 @@ subject(:info_collector) { described_class.new(settings) } - describe '#info' do - it 'records useful info in multiple categories' do - settings.service = 'test' + describe "#info" do + it "records useful info in multiple categories" do + settings.service = "test" expect(info).to match( { platform: hash_including( @@ -28,7 +28,7 @@ ) end - it 'records a sensible application start time' do + it "records a sensible application start time" do now = Time.now # We approximate the start time to the loading time of info. For this not to be @@ -37,7 +37,7 @@ expect(parsed_start_time).to be_between(now - 60 * 60, now) end - it 'records profiler info including a json dump of settings' do + it "records profiler info including a json dump of settings" do settings.profiling.advanced.max_frames = 600 settings.profiling.advanced.experimental_heap_enabled = true @@ -49,40 +49,40 @@ ) end - it 'caches data' do + it "caches data" do expect(info_collector.info).to be(info_collector.info) end - context 'with exotic-typed profile settings' do + context "with exotic-typed profile settings" do let(:settings) do TestSettings.new end - it 'handles multiple types nicely' do + it "handles multiple types nicely" do expect(info[:profiler][:settings]).to match( { boolean_opt: true, symbol_opt: :a_symbol, - string_opt: 'a string', + string_opt: "a string", integer_opt: 123, float_opt: 123.456, nil_opt: nil, advanced: { - list_opt: [false, 1, 2.0, '3', nil, [1, 2, 3], {'a' => 'a', 'b' => 'b'}, :a_symbol, - a_string_including('# "a", "b" => "b"}, :a_symbol, + a_string_including("# 'a', 'b' => 'b'}, + g: {"a" => "a", "b" => "b"}, h: :a_symbol, - i: a_string_including('# 'a', 'b' => 'b'}, :a_symbol, ComplexObject.new] + o.default [false, 1, 2.0, "3", nil, [1, 2, 3], {"a" => "a", "b" => "b"}, :a_symbol, ComplexObject.new] end option :hash_opt do |o| @@ -152,10 +152,10 @@ class TestSettings a: false, b: 1, c: 2.0, - d: '3', + d: "3", e: nil, f: [1, 2, 3], - g: {'a' => 'a', 'b' => 'b'}, + g: {"a" => "a", "b" => "b"}, h: :a_symbol, i: ComplexObject.new, } @@ -166,7 +166,7 @@ class TestSettings o.type :proc o.default do proc { - 'proc result' + "proc result" } end end diff --git a/spec/datadog/profiling/collectors/stack_spec.rb b/spec/datadog/profiling/collectors/stack_spec.rb index ce7408897d0..03da15324d1 100644 --- a/spec/datadog/profiling/collectors/stack_spec.rb +++ b/spec/datadog/profiling/collectors/stack_spec.rb @@ -1,5 +1,5 @@ -require 'datadog/profiling/spec_helper' -require 'datadog/profiling/collectors/stack' +require "datadog/profiling/spec_helper" +require "datadog/profiling/collectors/stack" # This file has a few lines that cannot be broken because we want some things to have the same line number when looking # at their stack traces. Hence, we disable Rubocop's complaints here. @@ -10,8 +10,8 @@ subject(:collectors_stack) { described_class.new } - let(:metric_values) { {'cpu-time' => 123, 'cpu-samples' => 456, 'wall-time' => 789} } - let(:labels) { {'label_a' => 'value_a', 'label_b' => 'value_b', 'state' => 'unknown'}.to_a } + let(:metric_values) { {"cpu-time" => 123, "cpu-samples" => 456, "wall-time" => 789} } + let(:labels) { {"label_a" => "value_a", "label_b" => "value_b", "state" => "unknown"}.to_a } let(:raw_reference_stack) { stacks.fetch(:reference) } let(:reference_stack) { convert_reference_stack(raw_reference_stack) } @@ -25,14 +25,14 @@ def sample(thread, recorder_instance, metric_values_hash, labels_array, max_fram # This spec explicitly tests the main thread because an unpatched rb_profile_frames returns one more frame in the # main thread than the reference Ruby API. This is almost-surely a bug in rb_profile_frames, since the same frame # gets excluded from the reference Ruby API. - context 'when sampling the main thread' do + context "when sampling the main thread" do let(:in_gc) { false } let(:stacks) { {reference: Thread.current.backtrace_locations, gathered: sample_and_decode(Thread.current, in_gc: in_gc)} } let(:reference_stack) do # To make the stacks comparable we slice off the actual Ruby `Thread#backtrace_locations` frame since that part # will necessarily be different - expect(super().first.base_label).to eq 'backtrace_locations' + expect(super().first.base_label).to eq "backtrace_locations" super()[1..-1] end @@ -41,38 +41,38 @@ def sample(thread, recorder_instance, metric_values_hash, labels_array, max_fram # also necessarily be different expect(super()[0..2]).to match( [ - have_attributes(base_label: '_native_sample'), - have_attributes(base_label: 'sample'), - have_attributes(base_label: 'sample_and_decode'), + have_attributes(base_label: "_native_sample"), + have_attributes(base_label: "sample"), + have_attributes(base_label: "sample_and_decode"), ] ) super()[3..-1] end before do - expect(Thread.current).to be(Thread.main), 'Unexpected: RSpec is not running on the main thread' + expect(Thread.current).to be(Thread.main), "Unexpected: RSpec is not running on the main thread" end - it 'matches the Ruby backtrace API' do + it "matches the Ruby backtrace API" do expect(gathered_stack).to eq reference_stack end - context 'when marking sample as being in garbage collection' do + context "when marking sample as being in garbage collection" do let(:in_gc) { true } it 'gathers a one-element stack with a "Garbage Collection" placeholder' do - expect(stacks.fetch(:gathered)).to contain_exactly(have_attributes(base_label: '', path: 'Garbage Collection', lineno: 0)) + expect(stacks.fetch(:gathered)).to contain_exactly(have_attributes(base_label: "", path: "Garbage Collection", lineno: 0)) end end end - context 'in a background thread' do + context "in a background thread" do let(:ready_queue) { Queue.new } let(:stacks) { {reference: background_thread.backtrace_locations, gathered: sample_and_decode(background_thread)} } let(:background_thread) { Thread.new(ready_queue, &do_in_background_thread) } let(:expected_eval_path) do # Starting in Ruby 3.3, the path on evals went from being "(eval)" to being "(eval at some_file.rb:line)" - (RUBY_VERSION < '3.3.') ? '(eval)' : match(/\(eval at .+stack_spec.rb:\d+\)/) + (RUBY_VERSION < "3.3.") ? "(eval)" : match(/\(eval at .+stack_spec.rb:\d+\)/) end before do @@ -88,7 +88,7 @@ def sample(thread, recorder_instance, metric_values_hash, labels_array, max_fram # Kernel#sleep is one of many Ruby standard library APIs that are implemented using native code. Older versions of # rb_profile_frames did not include these frames in their output, so this spec tests that our rb_profile_frames fixes # do correctly overcome this. - context 'when sampling a sleeping thread' do + context "when sampling a sleeping thread" do let(:do_in_background_thread) do proc do |ready_queue| ready_queue << true @@ -96,17 +96,17 @@ def sample(thread, recorder_instance, metric_values_hash, labels_array, max_fram end end - it 'matches the Ruby backtrace API' do + it "matches the Ruby backtrace API" do expect(gathered_stack).to eq reference_stack end - it 'has a sleeping frame at the top of the stack' do - expect(reference_stack.first.base_label).to eq 'sleep' + it "has a sleeping frame at the top of the stack" do + expect(reference_stack.first.base_label).to eq "sleep" end end # rubocop:disable Style/EvalWithLocation - context 'when sampling a top-level eval' do + context "when sampling a top-level eval" do let(:do_in_background_thread) do proc do eval( @@ -118,21 +118,21 @@ def sample(thread, recorder_instance, metric_values_hash, labels_array, max_fram end end - it 'matches the Ruby backtrace API' do + it "matches the Ruby backtrace API" do expect(gathered_stack).to eq reference_stack end - it 'has eval frames on the stack' do + it "has eval frames on the stack" do expect(reference_stack[0..2]).to contain_exactly( - have_attributes(base_label: 'sleep', path: expected_eval_path), - have_attributes(base_label: '', path: expected_eval_path), - have_attributes(base_label: 'eval', path: end_with('stack_spec.rb')), + have_attributes(base_label: "sleep", path: expected_eval_path), + have_attributes(base_label: "", path: expected_eval_path), + have_attributes(base_label: "eval", path: end_with("stack_spec.rb")), ) end end # We needed to patch our custom rb_profile_frames to match the reference stack on this case - context 'when sampling an eval/instance eval inside an object' do + context "when sampling an eval/instance eval inside an object" do let(:eval_test_class) do Class.new do def initialize(ready_queue) @@ -140,11 +140,11 @@ def initialize(ready_queue) end def call_eval - eval('call_instance_eval') + eval("call_instance_eval") end def call_instance_eval - instance_eval('call_sleep') + instance_eval("call_sleep") end def call_sleep @@ -159,41 +159,41 @@ def call_sleep end end - it 'matches the Ruby backtrace API' do + it "matches the Ruby backtrace API" do expect(gathered_stack).to eq reference_stack end - it 'has two eval frames on the stack' do + it "has two eval frames on the stack" do expect(reference_stack).to include( # These two frames are the frames that get created with the evaluation of the string, e.g. if instead of # `eval("foo")` we did `eval { foo }` then it is the block containing foo; eval with a string works similarly, # although you don't see a block there. - have_attributes(base_label: 'call_eval', path: expected_eval_path, lineno: 1), - have_attributes(base_label: 'call_instance_eval', path: expected_eval_path, lineno: 1), + have_attributes(base_label: "call_eval", path: expected_eval_path, lineno: 1), + have_attributes(base_label: "call_instance_eval", path: expected_eval_path, lineno: 1), ) end end - context 'when sampling an eval with a custom file and line provided' do + context "when sampling an eval with a custom file and line provided" do let(:do_in_background_thread) do proc do |ready_queue| - eval('ready_queue << true; sleep', binding, '/this/is/a/fake_file_.rb', -123456789) + eval("ready_queue << true; sleep", binding, "/this/is/a/fake_file_.rb", -123456789) end end - it 'matches the Ruby backtrace API' do + it "matches the Ruby backtrace API" do expect(gathered_stack).to eq reference_stack end - it 'has a frame with the custom file and line provided on the stack' do + it "has a frame with the custom file and line provided on the stack" do expect(reference_stack).to include( - have_attributes(path: '/this/is/a/fake_file_.rb', lineno: -123456789), + have_attributes(path: "/this/is/a/fake_file_.rb", lineno: -123456789), ) end end # rubocop:enable Style/EvalWithLocation - context 'when sampling the interesting backtrace helper' do + context "when sampling the interesting backtrace helper" do # rubocop:disable Style/GlobalVars let(:do_in_background_thread) do proc do |ready_queue| @@ -208,19 +208,19 @@ def call_sleep # rubocop:enable Style/GlobalVars # I opted to join these two expects to avoid running the `load` above more than once - it 'matches the Ruby backtrace API AND has a sleeping frame at the top of the stack' do + it "matches the Ruby backtrace API AND has a sleeping frame at the top of the stack" do expect(gathered_stack).to eq reference_stack - expect(reference_stack.first.base_label).to eq 'sleep' + expect(reference_stack.first.base_label).to eq "sleep" end end - describe 'approximate thread state categorization based on current stack' do + describe "approximate thread state categorization based on current stack" do before do wait_for { background_thread.backtrace_locations.first.base_label }.to eq(expected_method_name) end - describe 'state label validation' do - let(:expected_method_name) { 'sleep' } + describe "state label validation" do + let(:expected_method_name) { "sleep" } let(:do_in_background_thread) do proc do |ready_queue| ready_queue << true @@ -229,55 +229,55 @@ def call_sleep end let(:labels) { [] } - context 'when taking a cpu/wall-time sample and the state label is missing' do - let(:metric_values) { {'cpu-samples' => 1} } + context "when taking a cpu/wall-time sample and the state label is missing" do + let(:metric_values) { {"cpu-samples" => 1} } - it 'raises an exception' do + it "raises an exception" do expect { gathered_stack }.to raise_error(RuntimeError, /BUG: Unexpected missing state_label/) end end - context 'when taking a non-cpu/wall-time sample and the state label is missing' do - let(:metric_values) { {'cpu-samples' => 0} } + context "when taking a non-cpu/wall-time sample and the state label is missing" do + let(:metric_values) { {"cpu-samples" => 0} } - it 'does not raise an exception' do + it "does not raise an exception" do expect(gathered_stack).to be_truthy end end end - context 'when sampling a thread with cpu-time' do - let(:expected_method_name) { 'sleep' } + context "when sampling a thread with cpu-time" do + let(:expected_method_name) { "sleep" } let(:do_in_background_thread) do proc do |ready_queue| ready_queue << true sleep end end - let(:metric_values) { {'cpu-time' => 123, 'cpu-samples' => 456, 'wall-time' => 789} } + let(:metric_values) { {"cpu-time" => 123, "cpu-samples" => 456, "wall-time" => 789} } it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'had cpu') + expect(sample_and_decode(background_thread, :labels)).to include(state: "had cpu") end end - context 'when sampling a sleeping thread with no cpu-time' do - let(:expected_method_name) { 'sleep' } + context "when sampling a sleeping thread with no cpu-time" do + let(:expected_method_name) { "sleep" } let(:do_in_background_thread) do proc do |ready_queue| ready_queue << true sleep end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'sleeping') + expect(sample_and_decode(background_thread, :labels)).to include(state: "sleeping") end end - context 'when sampling a thread waiting on a select' do - let(:expected_method_name) { 'select' } + context "when sampling a thread waiting on a select" do + let(:expected_method_name) { "select" } let(:server_socket) { TCPServer.new(6006) } let(:background_thread) { Thread.new(ready_queue, server_socket, &do_in_background_thread) } let(:do_in_background_thread) do @@ -286,7 +286,7 @@ def call_sleep IO.select([server_socket]) end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } after do background_thread.kill @@ -295,12 +295,12 @@ def call_sleep end it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'waiting') + expect(sample_and_decode(background_thread, :labels)).to include(state: "waiting") end end - context 'when sampling a thread blocked on Thread#join' do - let(:expected_method_name) { 'join' } + context "when sampling a thread blocked on Thread#join" do + let(:expected_method_name) { "join" } let(:another_thread) { Thread.new { sleep } } let(:background_thread) { Thread.new(ready_queue, another_thread, &do_in_background_thread) } let(:do_in_background_thread) do @@ -309,7 +309,7 @@ def call_sleep another_thread.join end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } after do another_thread.kill @@ -319,15 +319,15 @@ def call_sleep it do sample = sample_and_decode(background_thread, :itself) expect(sample.labels).to( - include(state: 'blocked'), + include(state: "blocked"), "**If you see this test flaking, please report it to @ivoanjo!**\n\n" \ "sample: #{sample}", ) end end - context 'when sampling a thread blocked on Mutex#synchronize' do - let(:expected_method_name) { 'synchronize' } + context "when sampling a thread blocked on Mutex#synchronize" do + let(:expected_method_name) { "synchronize" } let(:locked_mutex) { Mutex.new.tap(&:lock) } let(:background_thread) { Thread.new(ready_queue, locked_mutex, &do_in_background_thread) } let(:do_in_background_thread) do @@ -336,15 +336,15 @@ def call_sleep locked_mutex.synchronize {} end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'blocked') + expect(sample_and_decode(background_thread, :labels)).to include(state: "blocked") end end - context 'when sampling a thread blocked on Mutex#lock' do - let(:expected_method_name) { 'lock' } + context "when sampling a thread blocked on Mutex#lock" do + let(:expected_method_name) { "lock" } let(:locked_mutex) { Mutex.new.tap(&:lock) } let(:background_thread) { Thread.new(ready_queue, locked_mutex, &do_in_background_thread) } let(:do_in_background_thread) do @@ -353,20 +353,20 @@ def call_sleep locked_mutex.lock end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'blocked') + expect(sample_and_decode(background_thread, :labels)).to include(state: "blocked") end end - context 'when sampling a thread blocked on Monitor#synchronize' do + context "when sampling a thread blocked on Monitor#synchronize" do let(:expected_method_name) do # On older Rubies Monitor is implemented using Mutex instead of natively - if RUBY_VERSION.start_with?('2.5', '2.6') - 'lock' + if RUBY_VERSION.start_with?("2.5", "2.6") + "lock" else - 'synchronize' + "synchronize" end end let(:locked_monitor) { Monitor.new.tap(&:enter) } @@ -377,15 +377,15 @@ def call_sleep locked_monitor.synchronize {} end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'blocked') + expect(sample_and_decode(background_thread, :labels)).to include(state: "blocked") end end - context 'when sampling a thread waiting on a IO object' do - let(:expected_method_name) { 'wait_readable' } + context "when sampling a thread waiting on a IO object" do + let(:expected_method_name) { "wait_readable" } let(:server_socket) { TCPServer.new(6006) } let(:background_thread) { Thread.new(ready_queue, server_socket, &do_in_background_thread) } let(:do_in_background_thread) do @@ -394,7 +394,7 @@ def call_sleep server_socket.wait_readable end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } after do background_thread.kill @@ -403,45 +403,45 @@ def call_sleep end it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'network') + expect(sample_and_decode(background_thread, :labels)).to include(state: "network") end end - context 'when sampling a thread waiting on a Queue object' do - let(:expected_method_name) { 'pop' } + context "when sampling a thread waiting on a Queue object" do + let(:expected_method_name) { "pop" } let(:do_in_background_thread) do proc do |ready_queue| ready_queue << true Queue.new.pop end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'waiting') + expect(sample_and_decode(background_thread, :labels)).to include(state: "waiting") end end - context 'when sampling a thread in an unknown state' do - let(:expected_method_name) { 'stop' } + context "when sampling a thread in an unknown state" do + let(:expected_method_name) { "stop" } let(:do_in_background_thread) do proc do |ready_queue| ready_queue << true Thread.stop end end - let(:metric_values) { {'cpu-time' => 0, 'cpu-samples' => 1, 'wall-time' => 1} } + let(:metric_values) { {"cpu-time" => 0, "cpu-samples" => 1, "wall-time" => 1} } it do - expect(sample_and_decode(background_thread, :labels)).to include(state: 'unknown') + expect(sample_and_decode(background_thread, :labels)).to include(state: "unknown") end end end - context 'when sampling a stack with a dynamically-generated template method name' do - let(:method_name) { '_app_views_layouts_explore_html_haml__2304485752546535910_211320' } - let(:filename) { '/myapp/app/views/layouts/explore.html.haml' } - let(:dummy_template) { double('Dummy template object') } + context "when sampling a stack with a dynamically-generated template method name" do + let(:method_name) { "_app_views_layouts_explore_html_haml__2304485752546535910_211320" } + let(:filename) { "/myapp/app/views/layouts/explore.html.haml" } + let(:dummy_template) { double("Dummy template object") } let(:do_in_background_thread) do # rubocop:disable Security/Eval @@ -465,55 +465,55 @@ def dummy_template.#{method_name}(ready_queue) # rubocop:enable Style/DocumentDynamicEvalDefinition end - it 'samples the frame with a simplified method name' do + it "samples the frame with a simplified method name" do expect(gathered_stack).to include( have_attributes( - path: '/myapp/app/views/layouts/explore.html.haml', - base_label: '_app_views_layouts_explore_html_haml', + path: "/myapp/app/views/layouts/explore.html.haml", + base_label: "_app_views_layouts_explore_html_haml", ) ) end - context 'when method name ends with three ___ instead of two' do - let(:method_name) { super().gsub('__', '___') } + context "when method name ends with three ___ instead of two" do + let(:method_name) { super().gsub("__", "___") } - it 'samples the frame with a simplified method name' do + it "samples the frame with a simplified method name" do expect(gathered_stack).to include( have_attributes( - path: '/myapp/app/views/layouts/explore.html.haml', - base_label: '_app_views_layouts_explore_html_haml', + path: "/myapp/app/views/layouts/explore.html.haml", + base_label: "_app_views_layouts_explore_html_haml", ) ) end end - context 'when filename ends with .rb' do - let(:filename) { 'example.rb' } + context "when filename ends with .rb" do + let(:filename) { "example.rb" } - it 'does not trim the method name' do + it "does not trim the method name" do expect(gathered_stack).to eq reference_stack end end - context 'when method_name does not end with __number_number' do - let(:method_name) { super().gsub('__', '_') } + context "when method_name does not end with __number_number" do + let(:method_name) { super().gsub("__", "_") } - it 'does not trim the method name' do + it "does not trim the method name" do expect(gathered_stack).to eq reference_stack end end - context 'when method only has __number_number' do - let(:method_name) { '__2304485752546535910_211320' } + context "when method only has __number_number" do + let(:method_name) { "__2304485752546535910_211320" } - it 'does not trim the method name' do + it "does not trim the method name" do expect(gathered_stack).to eq reference_stack end end end end - context 'when sampling a thread with a stack that is deeper than the configured max_frames' do + context "when sampling a thread with a stack that is deeper than the configured max_frames" do let(:max_frames) { 5 } let(:target_stack_depth) { 100 } let(:thread_with_deep_stack) { DeepStackSimulator.thread_with_stack_depth(target_stack_depth) } @@ -526,64 +526,64 @@ def dummy_template.#{method_name}(ready_queue) thread_with_deep_stack.join end - it 'gathers exactly max_frames frames' do + it "gathers exactly max_frames frames" do expect(gathered_stack.size).to be max_frames end - it 'matches the Ruby backtrace API up to max_frames - 1' do + it "matches the Ruby backtrace API up to max_frames - 1" do expect(gathered_stack[0...(max_frames - 1)]).to eq reference_stack[0...(max_frames - 1)] end - it 'includes a placeholder frame including the number of skipped frames' do + it "includes a placeholder frame including the number of skipped frames" do placeholder = 1 omitted_frames = target_stack_depth - max_frames + placeholder expect(omitted_frames).to be 96 - expect(gathered_stack.last).to have_attributes(base_label: '', path: '96 frames omitted', lineno: 0) + expect(gathered_stack.last).to have_attributes(base_label: "", path: "96 frames omitted", lineno: 0) end - context 'when stack is exactly 1 item deeper than the configured max_frames' do + context "when stack is exactly 1 item deeper than the configured max_frames" do let(:target_stack_depth) { 6 } - it 'includes a placeholder frame stating that 2 frames were omitted' do + it "includes a placeholder frame stating that 2 frames were omitted" do # Why 2 frames omitted and not 1? That's because the placeholder takes over 1 space in the buffer, so # if there were 6 frames on the stack and the limit is 5, then 4 of those frames will be present in the output - expect(gathered_stack.last).to have_attributes(base_label: '', path: '2 frames omitted', lineno: 0) + expect(gathered_stack.last).to have_attributes(base_label: "", path: "2 frames omitted", lineno: 0) end end - context 'when stack is exactly as deep as the configured max_frames' do + context "when stack is exactly as deep as the configured max_frames" do let(:target_stack_depth) { 5 } - it 'matches the Ruby backtrace API' do + it "matches the Ruby backtrace API" do expect(gathered_stack).to eq reference_stack end end end - context 'when sampling a dead thread' do + context "when sampling a dead thread" do let(:dead_thread) { Thread.new {}.tap(&:join) } let(:in_gc) { false } let(:stacks) { {reference: dead_thread.backtrace_locations, gathered: sample_and_decode(dead_thread, in_gc: in_gc)} } - it 'gathers an empty stack' do + it "gathers an empty stack" do expect(gathered_stack).to be_empty end - context 'when marking sample as being in garbage collection' do + context "when marking sample as being in garbage collection" do let(:in_gc) { true } - it 'gathers a stack with a garbage collection placeholder' do + it "gathers a stack with a garbage collection placeholder" do # @ivoanjo: I... don't think this can happen in practice. It's debatable if we should still have the placeholder # frame or not, but for ease of implementation I chose this path, and I added this spec just to get coverage on # this corner case. - expect(gathered_stack).to contain_exactly(have_attributes(base_label: '', path: 'Garbage Collection', lineno: 0)) + expect(gathered_stack).to contain_exactly(have_attributes(base_label: "", path: "Garbage Collection", lineno: 0)) end end end - context 'when sampling a thread with empty locations' do + context "when sampling a thread with empty locations" do let(:ready_pipe) { IO.pipe } let(:in_gc) { false } let(:stacks) { {reference: thread_with_empty_locations.backtrace_locations, gathered: sample_and_decode(thread_with_empty_locations, in_gc: in_gc)} } @@ -599,7 +599,7 @@ def dummy_template.#{method_name}(ready_queue) fork do # Signal ready to parent read_ready_pipe.close - write_ready_pipe.write('ready') + write_ready_pipe.write("ready") write_ready_pipe.close # Wait for parent to signal we can exit @@ -616,7 +616,7 @@ def dummy_template.#{method_name}(ready_queue) # Wait for child to signal ready read_ready_pipe, write_ready_pipe = ready_pipe write_ready_pipe.close - expect(read_ready_pipe.read).to eq 'ready' + expect(read_ready_pipe.read).to eq "ready" read_ready_pipe.close expect(reference_stack).to be_empty @@ -630,36 +630,36 @@ def dummy_template.#{method_name}(ready_queue) end it 'gathers a one-element stack with a "In native code" placeholder' do - expect(gathered_stack).to contain_exactly(have_attributes(base_label: '', path: 'In native code', lineno: 0)) + expect(gathered_stack).to contain_exactly(have_attributes(base_label: "", path: "In native code", lineno: 0)) end - context 'when marking sample as being in garbage collection' do + context "when marking sample as being in garbage collection" do let(:in_gc) { true } it 'gathers a one-element stack with a "Garbage Collection" placeholder' do - expect(stacks.fetch(:gathered)).to contain_exactly(have_attributes(base_label: '', path: 'Garbage Collection', lineno: 0)) + expect(stacks.fetch(:gathered)).to contain_exactly(have_attributes(base_label: "", path: "Garbage Collection", lineno: 0)) end end end - context 'when trying to sample something which is not a thread' do - it 'raises a TypeError' do + context "when trying to sample something which is not a thread" do + it "raises a TypeError" do expect do sample(:not_a_thread, build_stack_recorder, metric_values, labels) end.to raise_error(TypeError) end end - context 'when max_frames is too small' do - it 'raises an ArgumentError' do + context "when max_frames is too small" do + it "raises an ArgumentError" do expect do sample(Thread.current, build_stack_recorder, metric_values, labels, max_frames: 4) end.to raise_error(ArgumentError) end end - context 'when max_frames is too large' do - it 'raises an ArgumentError' do + context "when max_frames is too large" do + it "raises an ArgumentError" do expect do sample(Thread.current, build_stack_recorder, metric_values, labels, max_frames: 10_001) end.to raise_error(ArgumentError) diff --git a/spec/datadog/profiling/collectors/thread_context_spec.rb b/spec/datadog/profiling/collectors/thread_context_spec.rb index 0e0e83fa3ba..a0112ae2f37 100644 --- a/spec/datadog/profiling/collectors/thread_context_spec.rb +++ b/spec/datadog/profiling/collectors/thread_context_spec.rb @@ -1,5 +1,5 @@ -require 'datadog/profiling/spec_helper' -require 'datadog/profiling/collectors/thread_context' +require "datadog/profiling/spec_helper" +require "datadog/profiling/collectors/thread_context" RSpec.describe Datadog::Profiling::Collectors::ThreadContext do before do @@ -109,44 +109,44 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current sample(profiler_overhead_stack_thread: profiler_overhead_stack_thread) end - describe '#sample' do - it 'samples all threads' do + describe "#sample" do + it "samples all threads" do all_threads = Thread.list sample - expect(Thread.list).to eq(all_threads), 'Threads finished during this spec, causing flakiness!' + expect(Thread.list).to eq(all_threads), "Threads finished during this spec, causing flakiness!" seen_threads = samples.map(&:labels).map { |it| it.fetch(:"thread id") }.uniq expect(seen_threads.size).to be all_threads.size end - it 'tags the samples with the object ids of the Threads they belong to' do + it "tags the samples with the object ids of the Threads they belong to" do sample expect(samples.map { |it| object_id_from(it.labels.fetch(:"thread id")) }) .to include(*[Thread.main, t1, t2, t3].map(&:object_id)) end - it 'includes the thread names' do - t1.name = 'thread t1' - t2.name = 'thread t2' + it "includes the thread names" do + t1.name = "thread t1" + t2.name = "thread t2" sample t1_sample = samples_for_thread(samples, t1).first t2_sample = samples_for_thread(samples, t2).first - expect(t1_sample.labels).to include("thread name": 'thread t1') - expect(t2_sample.labels).to include("thread name": 'thread t2') + expect(t1_sample.labels).to include("thread name": "thread t1") + expect(t2_sample.labels).to include("thread name": "thread t2") end - context 'when no thread names are available' do + context "when no thread names are available" do # NOTE: As of this writing, the dd-trace-rb spec_helper.rb includes a monkey patch to Thread creation that we use # to track specs that leak threads. This means that the invoke_location of every thread will point at the # spec_helper in our test suite. Just in case you're looking at the output and being a bit confused :) - it 'uses the thread_invoke_location as a thread name' do + it "uses the thread_invoke_location as a thread name" do t1.name = nil sample t1_sample = samples_for_thread(samples, t1).first @@ -156,19 +156,19 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current end end - it 'includes a fallback name for the main thread, when not set' do - expect(Thread.main.name).to eq('Thread.main') # We set this in the spec_helper.rb + it "includes a fallback name for the main thread, when not set" do + expect(Thread.main.name).to eq("Thread.main") # We set this in the spec_helper.rb Thread.main.name = nil sample - expect(samples_for_thread(samples, Thread.main).first.labels).to include("thread name": 'main') + expect(samples_for_thread(samples, Thread.main).first.labels).to include("thread name": "main") - Thread.main.name = 'Thread.main' + Thread.main.name = "Thread.main" end - it 'includes the wall-time elapsed between samples' do + it "includes the wall-time elapsed between samples" do sample wall_time_at_first_sample = per_thread_context.fetch(t1).fetch(:wall_time_at_previous_sample_ns) @@ -183,7 +183,7 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current expect(wall_time).to be(wall_time_at_second_sample - wall_time_at_first_sample) end - it 'tags samples with how many times they were seen' do + it "tags samples with how many times they were seen" do 5.times { sample } t1_samples = samples_for_thread(samples, t1) @@ -191,10 +191,10 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current expect(t1_samples.map(&:values).map { |it| it.fetch(:"cpu-samples") }.reduce(:+)).to eq 5 end - context 'when a thread is marked as being in garbage collection by on_gc_start' do + context "when a thread is marked as being in garbage collection by on_gc_start" do # @ivoanjo: This spec exists because for cpu-time the behavior is not this one (e.g. we don't keep recording # cpu-time), and I wanted to validate that the different behavior does not get applied to wall-time. - it 'keeps recording the wall-time after every sample' do + it "keeps recording the wall-time after every sample" do sample wall_time_at_first_sample = per_thread_context.fetch(Thread.current).fetch(:wall_time_at_previous_sample_ns) @@ -213,25 +213,25 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current end end - context 'cpu-time behavior' do - context 'when not on Linux' do + context "cpu-time behavior" do + context "when not on Linux" do before do - skip 'The fallback behavior only applies when not on Linux' if PlatformHelpers.linux? + skip "The fallback behavior only applies when not on Linux" if PlatformHelpers.linux? end - it 'sets the cpu-time on every sample to zero' do + it "sets the cpu-time on every sample to zero" do 5.times { sample } expect(samples).to all have_attributes(values: include("cpu-time": 0)) end end - context 'on Linux' do + context "on Linux" do before do - skip 'Test only runs on Linux' unless PlatformHelpers.linux? + skip "Test only runs on Linux" unless PlatformHelpers.linux? end - it 'includes the cpu-time for the samples' do + it "includes the cpu-time for the samples" do rspec_thread_spent_time = Datadog::Core::Utils::Time.measure(:nanosecond) do 5.times { sample } samples # to trigger serialization @@ -250,8 +250,8 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current expect(total_cpu_for_rspec_thread).to be_between(1, rspec_thread_spent_time) end - context 'when a thread is marked as being in garbage collection by on_gc_start' do - it 'records the cpu-time between a previous sample and the start of garbage collection, and no further time' do + context "when a thread is marked as being in garbage collection by on_gc_start" do + it "records the cpu-time between a previous sample and the start of garbage collection, and no further time" do sample cpu_time_at_first_sample = per_thread_context.fetch(Thread.current).fetch(:cpu_time_at_previous_sample_ns) @@ -264,7 +264,7 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current total_cpu_for_rspec_thread = samples_for_thread(samples, Thread.current) - .select { |it| it.locations.find { |frame| frame.base_label == 'another_way_of_calling_sample' } } + .select { |it| it.locations.find { |frame| frame.base_label == "another_way_of_calling_sample" } } .map { |it| it.values.fetch(:"cpu-time") } .reduce(:+) @@ -273,7 +273,7 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current # When a thread is marked as being in GC the cpu_time_at_previous_sample_ns is not allowed to advance until # the GC finishes. - it 'does not advance cpu_time_at_previous_sample_ns for the thread beyond gc_tracking.cpu_time_at_start_ns' do + it "does not advance cpu_time_at_previous_sample_ns for the thread beyond gc_tracking.cpu_time_at_start_ns" do sample on_gc_start @@ -289,8 +289,8 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current end end - context 'when a thread is unmarked as being in garbage collection by on_gc_finish' do - it 'lets cpu_time_at_previous_sample_ns advance again' do + context "when a thread is unmarked as being in garbage collection by on_gc_finish" do + it "lets cpu_time_at_previous_sample_ns advance again" do sample on_gc_start @@ -310,11 +310,11 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current end end - describe 'code hotspots' do + describe "code hotspots" do let(:t1_sample) { samples_for_thread(samples, t1).first } - shared_examples_for 'samples without code hotspots information' do - it 'samples successfully' do + shared_examples_for "samples without code hotspots information" do + it "samples successfully" do sample expect(t1_sample).to_not be_nil @@ -332,45 +332,45 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current end end - context 'when there is no tracer instance available' do + context "when there is no tracer instance available" do let(:tracer) { nil } - it_behaves_like 'samples without code hotspots information' + it_behaves_like "samples without code hotspots information" end - context 'when tracer has no provider API' do - let(:tracer) { double('Tracer without provider API') } - it_behaves_like 'samples without code hotspots information' + context "when tracer has no provider API" do + let(:tracer) { double("Tracer without provider API") } + it_behaves_like "samples without code hotspots information" end - context 'when tracer provider is nil' do - let(:tracer) { double('Tracer with nil provider', provider: nil) } - it_behaves_like 'samples without code hotspots information' + context "when tracer provider is nil" do + let(:tracer) { double("Tracer with nil provider", provider: nil) } + it_behaves_like "samples without code hotspots information" end - context 'when there is a tracer instance available' do + context "when there is a tracer instance available" do let(:tracer) { Datadog::Tracing.send(:tracer) } after { Datadog::Tracing.shutdown! } - context 'when thread does not have a tracer context' do + context "when thread does not have a tracer context" do # NOTE: Since t1 is newly created for this test, and never had any active trace, it won't have a context - it_behaves_like 'samples without code hotspots information' + it_behaves_like "samples without code hotspots information" end - context 'when thread has a tracer context, but no trace is in progress' do + context "when thread has a tracer context, but no trace is in progress" do before { tracer.active_trace(t1) } # Trigger context setting - it_behaves_like 'samples without code hotspots information' + it_behaves_like "samples without code hotspots information" end - context 'when thread has a tracer context, and a trace is in progress' do - let(:root_span_type) { 'not-web' } + context "when thread has a tracer context, and a trace is in progress" do + let(:root_span_type) { "not-web" } let(:t1) do Thread.new(ready_queue) do |ready_queue| - Datadog::Tracing.trace('profiler.test', type: root_span_type) do |_span, trace| + Datadog::Tracing.trace("profiler.test", type: root_span_type) do |_span, trace| @t1_trace = trace - Datadog::Tracing.trace('profiler.test.inner') do |inner_span| + Datadog::Tracing.trace("profiler.test.inner") do |inner_span| @t1_span_id = inner_span.id @t1_local_root_span_id = trace.send(:root_span).id ready_queue << true @@ -402,14 +402,14 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current expect(t1_sample.labels).to_not include("trace endpoint": anything) end - shared_examples_for 'samples with code hotspots information' do + shared_examples_for "samples with code hotspots information" do it 'includes the "trace endpoint" label in the samples' do sample - expect(t1_sample.labels).to include("trace endpoint": 'profiler.test') + expect(t1_sample.labels).to include("trace endpoint": "profiler.test") end - context 'when endpoint_collection_enabled is false' do + context "when endpoint_collection_enabled is false" do let(:endpoint_collection_enabled) { false } it 'still includes "local root span id" and "span id" labels in the samples' do @@ -428,14 +428,14 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current end end - describe 'trace vs root span resource mutation' do + describe "trace vs root span resource mutation" do let(:t1) do Thread.new(ready_queue) do |ready_queue| - Datadog::Tracing.trace('profiler.test', type: root_span_type) do |span, trace| + Datadog::Tracing.trace("profiler.test", type: root_span_type) do |span, trace| trace.resource = trace_resource span.resource = root_span_resource - Datadog::Tracing.trace('profiler.test.inner') do |inner_span| + Datadog::Tracing.trace("profiler.test.inner") do |inner_span| @t1_span_id = inner_span.id @t1_local_root_span_id = trace.send(:root_span).id ready_queue << true @@ -445,29 +445,29 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current end end - context 'when the trace resource is nil but the root span resource is not nil' do + context "when the trace resource is nil but the root span resource is not nil" do let(:trace_resource) { nil } - let(:root_span_resource) { 'root_span_resource' } + let(:root_span_resource) { "root_span_resource" } it 'includes the "trace endpoint" label in the samples with the root span resource' do sample - expect(t1_sample.labels).to include("trace endpoint": 'root_span_resource') + expect(t1_sample.labels).to include("trace endpoint": "root_span_resource") end end - context 'when both the trace resource and the root span resource are specified' do - let(:trace_resource) { 'trace_resource' } - let(:root_span_resource) { 'root_span_resource' } + context "when both the trace resource and the root span resource are specified" do + let(:trace_resource) { "trace_resource" } + let(:root_span_resource) { "root_span_resource" } it 'includes the "trace endpoint" label in the samples with the trace resource' do sample - expect(t1_sample.labels).to include("trace endpoint": 'trace_resource') + expect(t1_sample.labels).to include("trace endpoint": "trace_resource") end end - context 'when both the trace resource and the root span resource are nil' do + context "when both the trace resource and the root span resource are nil" do let(:trace_resource) { nil } let(:root_span_resource) { nil } @@ -479,10 +479,10 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current end end - context 'when resource is changed after a sample was taken' do + context "when resource is changed after a sample was taken" do before do sample - @t1_trace.resource = 'changed_after_first_sample' + @t1_trace.resource = "changed_after_first_sample" end it 'changes the "trace endpoint" label in all samples' do @@ -491,65 +491,65 @@ def another_way_of_calling_sample(profiler_overhead_stack_thread: Thread.current t1_samples = samples_for_thread(samples, t1) expect(t1_samples) - .to all have_attributes(labels: include("trace endpoint": 'changed_after_first_sample')) + .to all have_attributes(labels: include("trace endpoint": "changed_after_first_sample")) expect(t1_samples.map(&:values).map { |it| it.fetch(:"cpu-samples") }.reduce(:+)).to eq 2 end - context 'when the resource is changed multiple times' do + context "when the resource is changed multiple times" do it 'changes the "trace endpoint" label in all samples' do sample - @t1_trace.resource = 'changed_after_second_sample' + @t1_trace.resource = "changed_after_second_sample" sample t1_samples = samples_for_thread(samples, t1) expect(t1_samples) - .to all have_attributes(labels: include("trace endpoint": 'changed_after_second_sample')) + .to all have_attributes(labels: include("trace endpoint": "changed_after_second_sample")) expect(t1_samples.map(&:values).map { |it| it.fetch(:"cpu-samples") }.reduce(:+)).to eq 3 end end end end - context 'when local root span type is web' do - let(:root_span_type) { 'web' } + context "when local root span type is web" do + let(:root_span_type) { "web" } - it_behaves_like 'samples with code hotspots information' + it_behaves_like "samples with code hotspots information" end # Used by the rack integration with request_queuing: true - context 'when local root span type is proxy' do - let(:root_span_type) { 'proxy' } + context "when local root span type is proxy" do + let(:root_span_type) { "proxy" } - it_behaves_like 'samples with code hotspots information' + it_behaves_like "samples with code hotspots information" end - context 'when local root span type is worker' do - let(:root_span_type) { 'worker' } + context "when local root span type is worker" do + let(:root_span_type) { "worker" } - it_behaves_like 'samples with code hotspots information' + it_behaves_like "samples with code hotspots information" end def self.otel_sdk_available? - require 'opentelemetry/sdk' + require "opentelemetry/sdk" true rescue LoadError false end - context 'when trace comes from otel sdk', if: otel_sdk_available? do + context "when trace comes from otel sdk", if: otel_sdk_available? do let(:otel_tracer) do - require 'datadog/opentelemetry' + require "datadog/opentelemetry" OpenTelemetry::SDK.configure - OpenTelemetry.tracer_provider.tracer('datadog-profiling-test') + OpenTelemetry.tracer_provider.tracer("datadog-profiling-test") end let(:t1) do Thread.new(ready_queue, otel_tracer) do |ready_queue, otel_tracer| - otel_tracer.in_span('profiler.test') do + otel_tracer.in_span("profiler.test") do @t1_span_id = Datadog::Tracing.correlation.span_id @t1_local_root_span_id = Datadog::Tracing.correlation.span_id ready_queue << true @@ -573,14 +573,14 @@ def self.otel_sdk_available? expect(t1_sample.labels).to_not include("trace endpoint": anything) end - context 'when there are multiple otel spans nested' do + context "when there are multiple otel spans nested" do let(:t1) do Thread.new(ready_queue, otel_tracer) do |ready_queue, otel_tracer| - otel_tracer.in_span('profiler.test') do + otel_tracer.in_span("profiler.test") do @t1_local_root_span_id = Datadog::Tracing.correlation.span_id - otel_tracer.in_span('profiler.test.nested.1') do - otel_tracer.in_span('profiler.test.nested.2') do - otel_tracer.in_span('profiler.test.nested.3') do + otel_tracer.in_span("profiler.test.nested.1") do + otel_tracer.in_span("profiler.test.nested.2") do + otel_tracer.in_span("profiler.test.nested.3") do @t1_span_id = Datadog::Tracing.correlation.span_id ready_queue << true sleep @@ -601,18 +601,18 @@ def self.otel_sdk_available? end end - context 'mixing of otel sdk and datadog' do - context 'when top-level span is started from datadog' do + context "mixing of otel sdk and datadog" do + context "when top-level span is started from datadog" do let(:t1) do Thread.new(ready_queue, otel_tracer) do |ready_queue, otel_tracer| - Datadog::Tracing.trace('profiler.test', type: :web) do |_span, trace| - trace.resource = 'example_resource' + Datadog::Tracing.trace("profiler.test", type: :web) do |_span, trace| + trace.resource = "example_resource" @t1_local_root_span_id = Datadog::Tracing.correlation.span_id - otel_tracer.in_span('profiler.test.nested.1') do - Datadog::Tracing.trace('profiler.test.nested.2') do - otel_tracer.in_span('profiler.test.nested.3') do - Datadog::Tracing.trace('profiler.test.nested.4') do + otel_tracer.in_span("profiler.test.nested.1") do + Datadog::Tracing.trace("profiler.test.nested.2") do + otel_tracer.in_span("profiler.test.nested.3") do + Datadog::Tracing.trace("profiler.test.nested.4") do @t1_span_id = Datadog::Tracing.correlation.span_id ready_queue << true sleep @@ -624,7 +624,7 @@ def self.otel_sdk_available? end end - it 'uses the local root span id from the top-level span, and the span id from the leaf span' do + it "uses the local root span id from the top-level span, and the span id from the leaf span" do sample expect(t1_sample.labels).to include( @@ -636,19 +636,19 @@ def self.otel_sdk_available? it 'includes the "trace endpoint" label in the samples with the trace resource' do sample - expect(t1_sample.labels).to include("trace endpoint": 'example_resource') + expect(t1_sample.labels).to include("trace endpoint": "example_resource") end end - context 'when top-level span is started from otel' do + context "when top-level span is started from otel" do let(:t1) do Thread.new(ready_queue, otel_tracer) do |ready_queue, otel_tracer| - otel_tracer.in_span('profiler.test') do + otel_tracer.in_span("profiler.test") do @t1_local_root_span_id = Datadog::Tracing.correlation.span_id - otel_tracer.in_span('profiler.test.nested.1') do - Datadog::Tracing.trace('profiler.test.nested.2') do - otel_tracer.in_span('profiler.test.nested.3') do - Datadog::Tracing.trace('profiler.test.nested.4') do + otel_tracer.in_span("profiler.test.nested.1") do + Datadog::Tracing.trace("profiler.test.nested.2") do + otel_tracer.in_span("profiler.test.nested.3") do + Datadog::Tracing.trace("profiler.test.nested.4") do @t1_span_id = Datadog::Tracing.correlation.span_id ready_queue << true sleep @@ -660,7 +660,7 @@ def self.otel_sdk_available? end end - it 'uses the local root span id from the top-level span, and the span id from the leaf span' do + it "uses the local root span id from the top-level span, and the span id from the leaf span" do sample expect(t1_sample.labels).to include( @@ -672,9 +672,9 @@ def self.otel_sdk_available? end end - context 'when trace comes from otel sdk (warning)', unless: otel_sdk_available? do - it 'is not being tested' do - skip 'Skipping OpenTelemetry tests because `opentelemetry-sdk` gem is not available' + context "when trace comes from otel sdk (warning)", unless: otel_sdk_available? do + it "is not being tested" do + skip "Skipping OpenTelemetry tests because `opentelemetry-sdk` gem is not available" end end end @@ -695,7 +695,7 @@ def self.otel_sdk_available? # This way it's clear what overhead comes from profiling. Without this feature (aka if profiler_overhead_stack_thread # is set to Thread.current), then all 1.5s get attributed to the current stack, and the profiler overhead would be # invisible. - it 'attributes the time sampling to the stack of the worker_thread_to_blame' do + it "attributes the time sampling to the stack of the worker_thread_to_blame" do sample wall_time_at_first_sample = per_thread_context.fetch(Thread.current).fetch(:wall_time_at_previous_sample_ns) @@ -704,13 +704,13 @@ def self.otel_sdk_available? second_sample_stack = samples_for_thread(samples, Thread.current) - .select { |it| it.locations.find { |frame| frame.base_label == 'another_way_of_calling_sample' } } + .select { |it| it.locations.find { |frame| frame.base_label == "another_way_of_calling_sample" } } # The stack from the profiler_overhead_stack_thread (t1) above has showed up attributed to Thread.current, as we # are using it to represent the profiler overhead. profiler_overhead_stack = samples_for_thread(samples, Thread.current) - .select { |it| it.locations.find { |frame| frame.base_label == 'inside_t1' } } + .select { |it| it.locations.find { |frame| frame.base_label == "inside_t1" } } expect(second_sample_stack.size).to be 1 expect(profiler_overhead_stack.size).to be 1 @@ -723,21 +723,21 @@ def self.otel_sdk_available? expect(profiler_overhead_stack.first.labels).to include("profiler overhead": 1) end - describe 'timeline support' do - context 'when timeline is disabled' do + describe "timeline support" do + context "when timeline is disabled" do let(:timeline_enabled) { false } - it 'does not include end_timestamp_ns labels in samples' do + it "does not include end_timestamp_ns labels in samples" do sample expect(samples.map(&:labels).flat_map(&:keys).uniq).to_not include(:end_timestamp_ns) end end - context 'when timeline is enabled' do + context "when timeline is enabled" do let(:timeline_enabled) { true } - it 'includes a end_timestamp_ns containing epoch time in every sample' do + it "includes a end_timestamp_ns containing epoch time in every sample" do time_before = Datadog::Core::Utils::Time.as_utc_epoch_ns(Time.now) sample time_after = Datadog::Core::Utils::Time.as_utc_epoch_ns(Time.now) @@ -748,20 +748,20 @@ def self.otel_sdk_available? end end - describe '#on_gc_start' do - context 'if a thread has not been sampled before' do + describe "#on_gc_start" do + context "if a thread has not been sampled before" do it "does not record anything in the caller thread's context" do on_gc_start expect(per_thread_context.keys).to_not include(Thread.current) end - it 'increments the gc_samples_missed_due_to_missing_context stat' do + it "increments the gc_samples_missed_due_to_missing_context stat" do expect { on_gc_start }.to change { stats.fetch(:gc_samples_missed_due_to_missing_context) }.from(0).to(1) end end - context 'after the first sample' do + context "after the first sample" do before { sample } it "records the wall-time when garbage collection started in the caller thread's context" do @@ -774,10 +774,10 @@ def self.otel_sdk_available? ) end - context 'cpu-time behavior' do - context 'when not on Linux' do + context "cpu-time behavior" do + context "when not on Linux" do before do - skip 'The fallback behavior only applies when not on Linux' if PlatformHelpers.linux? + skip "The fallback behavior only applies when not on Linux" if PlatformHelpers.linux? end it "records the cpu-time when garbage collection started in the caller thread's context as zero" do @@ -787,9 +787,9 @@ def self.otel_sdk_available? end end - context 'on Linux' do + context "on Linux" do before do - skip 'Test only runs on Linux' unless PlatformHelpers.linux? + skip "Test only runs on Linux" unless PlatformHelpers.linux? end it "records the cpu-time when garbage collection started in the caller thread's context" do @@ -805,8 +805,8 @@ def self.otel_sdk_available? end end - describe '#on_gc_finish' do - context 'when thread has not been sampled before' do + describe "#on_gc_finish" do + context "when thread has not been sampled before" do it "does not record anything in the caller thread's context" do on_gc_start @@ -814,23 +814,23 @@ def self.otel_sdk_available? end end - context 'when thread has been sampled before' do + context "when thread has been sampled before" do before { sample } - context 'when on_gc_start was not called before' do + context "when on_gc_start was not called before" do # See comment in the actual implementation on when/why this can happen - it 'does not change the wall_time_at_previous_gc_ns' do + it "does not change the wall_time_at_previous_gc_ns" do on_gc_finish expect(gc_tracking.fetch(:wall_time_at_previous_gc_ns)).to be invalid_time end end - context 'when on_gc_start was previously called' do + context "when on_gc_start was previously called" do before { on_gc_start } - it 'records the wall-time when garbage collection finished in the gc_tracking' do + it "records the wall-time when garbage collection finished in the gc_tracking" do wall_time_before_on_gc_finish_ns = Datadog::Core::Utils::Time.get_time(:nanosecond) on_gc_finish wall_time_after_on_gc_finish_ns = Datadog::Core::Utils::Time.get_time(:nanosecond) @@ -839,7 +839,7 @@ def self.otel_sdk_available? .to be_between(wall_time_before_on_gc_finish_ns, wall_time_after_on_gc_finish_ns) end - it 'resets the gc tracking fields back to invalid_time' do + it "resets the gc tracking fields back to invalid_time" do on_gc_finish expect(per_thread_context.fetch(Thread.current)).to include( @@ -848,7 +848,7 @@ def self.otel_sdk_available? ) end - it 'records the wall-time time spent between calls to on_gc_start and on_gc_finish' do + it "records the wall-time time spent between calls to on_gc_start and on_gc_finish" do wall_time_at_start_ns = per_thread_context.fetch(Thread.current).fetch(:"gc_tracking.wall_time_at_start_ns") wall_time_before_on_gc_finish_ns = Datadog::Core::Utils::Time.get_time(:nanosecond) @@ -858,31 +858,31 @@ def self.otel_sdk_available? .to be >= (wall_time_before_on_gc_finish_ns - wall_time_at_start_ns) end - context 'cpu-time behavior' do - context 'when not on Linux' do + context "cpu-time behavior" do + context "when not on Linux" do before do - skip 'The fallback behavior only applies when not on Linux' if PlatformHelpers.linux? + skip "The fallback behavior only applies when not on Linux" if PlatformHelpers.linux? end - it 'records the accumulated_cpu_time_ns as zero' do + it "records the accumulated_cpu_time_ns as zero" do on_gc_finish expect(gc_tracking.fetch(:accumulated_cpu_time_ns)).to be 0 end end - context 'on Linux' do + context "on Linux" do before do - skip 'Test only runs on Linux' unless PlatformHelpers.linux? + skip "Test only runs on Linux" unless PlatformHelpers.linux? end - it 'records the cpu-time spent between calls to on_gc_start and on_gc_finish' do + it "records the cpu-time spent between calls to on_gc_start and on_gc_finish" do on_gc_finish expect(gc_tracking.fetch(:accumulated_cpu_time_ns)).to be > 0 end - it 'advances the cpu_time_at_previous_sample_ns for the sampled thread by the time spent in GC' do + it "advances the cpu_time_at_previous_sample_ns for the sampled thread by the time spent in GC" do cpu_time_at_previous_sample_ns_before = per_thread_context.fetch(Thread.current).fetch(:cpu_time_at_previous_sample_ns) @@ -896,7 +896,7 @@ def self.otel_sdk_available? end end - context 'when going through multiple cycles of on_gc_start/on_gc_finish without sample_after_gc getting called' do + context "when going through multiple cycles of on_gc_start/on_gc_finish without sample_after_gc getting called" do let(:context_tracking) { [] } before do @@ -908,7 +908,7 @@ def self.otel_sdk_available? end end - it 'accumulates the cpu-time and wall-time from the multiple GCs' do + it "accumulates the cpu-time and wall-time from the multiple GCs" do all_accumulated_wall_time = context_tracking.map { |it| it.fetch(:accumulated_wall_time_ns) } expect(all_accumulated_wall_time).to eq all_accumulated_wall_time.sort @@ -920,7 +920,7 @@ def self.otel_sdk_available? expect(all_accumulated_cpu_time.first).to be < all_accumulated_cpu_time.last if all_accumulated_cpu_time.first > 0 end - it 'updates the wall_time_at_previous_gc_ns with the latest one' do + it "updates the wall_time_at_previous_gc_ns with the latest one" do all_wall_time_at_previous_gc_ns = context_tracking.map { |it| it.fetch(:wall_time_at_previous_gc_ns) } expect(all_wall_time_at_previous_gc_ns.last).to be all_wall_time_at_previous_gc_ns.max @@ -929,17 +929,17 @@ def self.otel_sdk_available? end end - describe '#sample_after_gc' do + describe "#sample_after_gc" do before { sample } - context 'when called before on_gc_start/on_gc_finish' do + context "when called before on_gc_start/on_gc_finish" do it do expect { sample_after_gc }.to raise_error(RuntimeError, /Unexpected call to sample_after_gc/) end end - context 'when there is gc information to record' do - let(:gc_sample) { samples.find { |it| it.labels.fetch(:"thread name") == 'Garbage Collection' } } + context "when there is gc information to record" do + let(:gc_sample) { samples.find { |it| it.labels.fetch(:"thread name") == "Garbage Collection" } } before do on_gc_start @@ -948,7 +948,7 @@ def self.otel_sdk_available? @time_after = Datadog::Core::Utils::Time.as_utc_epoch_ns(Time.now) end - context 'when called more than once in a row' do + context "when called more than once in a row" do it do sample_after_gc @@ -956,11 +956,11 @@ def self.otel_sdk_available? end end - it 'increments the gc_samples counter' do + it "increments the gc_samples counter" do expect { sample_after_gc }.to change { stats.fetch(:gc_samples) }.from(0).to(1) end - it 'sets the wall_time_at_last_flushed_gc_event_ns from the wall_time_at_previous_gc_ns' do + it "sets the wall_time_at_last_flushed_gc_event_ns from the wall_time_at_previous_gc_ns" do wall_time_at_previous_gc_ns = gc_tracking.fetch(:wall_time_at_previous_gc_ns) sample_after_gc @@ -968,28 +968,28 @@ def self.otel_sdk_available? expect(gc_tracking.fetch(:wall_time_at_last_flushed_gc_event_ns)).to be wall_time_at_previous_gc_ns end - it 'resets the wall_time_at_previous_gc_ns to invalid_time' do + it "resets the wall_time_at_previous_gc_ns to invalid_time" do sample_after_gc expect(gc_tracking.fetch(:wall_time_at_previous_gc_ns)).to be invalid_time end - it 'creates a Garbage Collection sample' do + it "creates a Garbage Collection sample" do sample_after_gc expect(gc_sample.values.fetch(:"cpu-samples")).to be 1 expect(gc_sample.labels).to match a_hash_including( - state: 'had cpu', - "thread id": 'GC', - "thread name": 'Garbage Collection', - event: 'gc', + state: "had cpu", + "thread id": "GC", + "thread name": "Garbage Collection", + event: "gc", "gc cause": an_instance_of(String), "gc type": an_instance_of(String), ) - expect(gc_sample.locations.first.path).to eq 'Garbage Collection' + expect(gc_sample.locations.first.path).to eq "Garbage Collection" end - it 'creates a Garbage Collection sample using the accumulated_cpu_time_ns and accumulated_wall_time_ns' do + it "creates a Garbage Collection sample using the accumulated_cpu_time_ns and accumulated_wall_time_ns" do accumulated_cpu_time_ns = gc_tracking.fetch(:accumulated_cpu_time_ns) accumulated_wall_time_ns = gc_tracking.fetch(:accumulated_wall_time_ns) @@ -1001,16 +1001,16 @@ def self.otel_sdk_available? ) end - it 'does not include the timeline timestamp' do + it "does not include the timeline timestamp" do sample_after_gc expect(gc_sample.labels.keys).to_not include(:end_timestamp_ns) end - context 'when timeline is enabled' do + context "when timeline is enabled" do let(:timeline_enabled) { true } - it 'creates a Garbage Collection sample using the accumulated_wall_time_ns as the timeline duration' do + it "creates a Garbage Collection sample using the accumulated_wall_time_ns as the timeline duration" do accumulated_wall_time_ns = gc_tracking.fetch(:accumulated_wall_time_ns) sample_after_gc @@ -1018,7 +1018,7 @@ def self.otel_sdk_available? expect(gc_sample.values.fetch(:timeline)).to be accumulated_wall_time_ns end - it 'creates a Garbage Collection sample using the timestamp set by on_gc_finish, converted to epoch ns' do + it "creates a Garbage Collection sample using the timestamp set by on_gc_finish, converted to epoch ns" do sample_after_gc expect(gc_sample.labels.fetch(:end_timestamp_ns)).to be_between(@time_before, @time_after) @@ -1027,54 +1027,54 @@ def self.otel_sdk_available? end end - describe '#sample_allocation' do + describe "#sample_allocation" do let(:single_sample) do expect(samples.size).to be 1 samples.first end - it 'samples the caller thread' do + it "samples the caller thread" do sample_allocation(weight: 123) expect(object_id_from(single_sample.labels.fetch(:"thread id"))).to be Thread.current.object_id end - it 'tags the sample with the provided weight' do + it "tags the sample with the provided weight" do sample_allocation(weight: 123) expect(single_sample.values).to include("alloc-samples": 123) end - it 'tags the sample with the unscaled weight' do + it "tags the sample with the unscaled weight" do sample_allocation(weight: 123) expect(single_sample.values).to include("alloc-samples-unscaled": 1) end - it 'includes the thread names, if available' do + it "includes the thread names, if available" do thread_with_name = Thread.new do - Thread.current.name = 'thread_with_name' + Thread.current.name = "thread_with_name" sample_allocation(weight: 123) end.join sample_with_name = samples_for_thread(samples, thread_with_name).first - expect(sample_with_name.labels).to include("thread name": 'thread_with_name') + expect(sample_with_name.labels).to include("thread name": "thread_with_name") end - describe 'code hotspots' do + describe "code hotspots" do # NOTE: To avoid duplicating all of the similar-but-slightly different tests from `#sample` (due to how # `#sample` includes every thread, but `#sample_allocation` includes only the caller thread), here is a simpler # test to make sure this works in the common case - context 'when there is an active trace on the sampled thread' do + context "when there is an active trace on the sampled thread" do let(:tracer) { Datadog::Tracing.send(:tracer) } let(:t1) do Thread.new(ready_queue) do |ready_queue| inside_t1 do - Datadog::Tracing.trace('profiler.test', type: 'web') do |_span, trace| - trace.resource = 'trace_resource' + Datadog::Tracing.trace("profiler.test", type: "web") do |_span, trace| + trace.resource = "trace_resource" - Datadog::Tracing.trace('profiler.test.inner') do |inner_span| + Datadog::Tracing.trace("profiler.test.inner") do |inner_span| @t1_span_id = inner_span.id @t1_local_root_span_id = trace.send(:root_span).id sample_allocation(weight: 456) @@ -1092,16 +1092,16 @@ def self.otel_sdk_available? expect(single_sample.labels).to include( "local root span id": @t1_local_root_span_id.to_i, "span id": @t1_span_id.to_i, - "trace endpoint": 'trace_resource', + "trace endpoint": "trace_resource", ) end end end - context 'when timeline is enabled' do + context "when timeline is enabled" do let(:timeline_enabled) { true } - it 'does not include end_timestamp_ns labels in GC samples' do + it "does not include end_timestamp_ns labels in GC samples" do sample_allocation(weight: 123) expect(single_sample.labels.keys).to_not include(:end_timestamp_ns) @@ -1109,47 +1109,47 @@ def self.otel_sdk_available? end [ - {expected_type: :T_OBJECT, object: Object.new, klass: 'Object'}, - {expected_type: :T_CLASS, object: Object, klass: 'Class'}, - {expected_type: :T_MODULE, object: Kernel, klass: 'Module'}, - {expected_type: :T_FLOAT, object: 1.0, klass: 'Float'}, - {expected_type: :T_STRING, object: 'Hello!', klass: 'String'}, - {expected_type: :T_REGEXP, object: /Hello/, klass: 'Regexp'}, - {expected_type: :T_ARRAY, object: [], klass: 'Array'}, - {expected_type: :T_HASH, object: {}, klass: 'Hash'}, - {expected_type: :T_BIGNUM, object: 2**256, klass: 'Integer'}, + {expected_type: :T_OBJECT, object: Object.new, klass: "Object"}, + {expected_type: :T_CLASS, object: Object, klass: "Class"}, + {expected_type: :T_MODULE, object: Kernel, klass: "Module"}, + {expected_type: :T_FLOAT, object: 1.0, klass: "Float"}, + {expected_type: :T_STRING, object: "Hello!", klass: "String"}, + {expected_type: :T_REGEXP, object: /Hello/, klass: "Regexp"}, + {expected_type: :T_ARRAY, object: [], klass: "Array"}, + {expected_type: :T_HASH, object: {}, klass: "Hash"}, + {expected_type: :T_BIGNUM, object: 2**256, klass: "Integer"}, # ThreadContext is a T_DATA; we create here a dummy instance just as an example - {expected_type: :T_DATA, object: described_class.allocate, klass: 'Datadog::Profiling::Collectors::ThreadContext'}, - {expected_type: :T_MATCH, object: 'a'.match(Regexp.new('a')), klass: 'MatchData'}, - {expected_type: :T_COMPLEX, object: Complex(1), klass: 'Complex'}, - {expected_type: :T_RATIONAL, object: 1/2r, klass: 'Rational'}, - {expected_type: :T_NIL, object: nil, klass: 'NilClass'}, - {expected_type: :T_TRUE, object: true, klass: 'TrueClass'}, - {expected_type: :T_FALSE, object: false, klass: 'FalseClass'}, - {expected_type: :T_SYMBOL, object: :hello, klass: 'Symbol'}, - {expected_type: :T_FIXNUM, object: 1, klass: 'Integer'}, + {expected_type: :T_DATA, object: described_class.allocate, klass: "Datadog::Profiling::Collectors::ThreadContext"}, + {expected_type: :T_MATCH, object: "a".match(Regexp.new("a")), klass: "MatchData"}, + {expected_type: :T_COMPLEX, object: Complex(1), klass: "Complex"}, + {expected_type: :T_RATIONAL, object: 1/2r, klass: "Rational"}, + {expected_type: :T_NIL, object: nil, klass: "NilClass"}, + {expected_type: :T_TRUE, object: true, klass: "TrueClass"}, + {expected_type: :T_FALSE, object: false, klass: "FalseClass"}, + {expected_type: :T_SYMBOL, object: :hello, klass: "Symbol"}, + {expected_type: :T_FIXNUM, object: 1, klass: "Integer"}, ].each do |type| expected_type = type.fetch(:expected_type) object = type.fetch(:object) klass = type.fetch(:klass) context "when sampling a #{expected_type}" do - it 'includes the correct ruby vm type for the passed object' do + it "includes the correct ruby vm type for the passed object" do sample_allocation(weight: 123, new_object: object) expect(single_sample.labels.fetch(:"ruby vm type")).to eq expected_type.to_s end - it 'includes the correct class for the passed object' do + it "includes the correct class for the passed object" do sample_allocation(weight: 123, new_object: object) expect(single_sample.labels.fetch(:"allocation class")).to eq klass end - context 'when allocation_type_enabled is false' do + context "when allocation_type_enabled is false" do let(:allocation_type_enabled) { false } - it 'does not record the correct class for the passed object' do + it "does not record the correct class for the passed object" do sample_allocation(weight: 123, new_object: object) expect(single_sample.labels).to_not include("allocation class": anything) @@ -1158,27 +1158,27 @@ def self.otel_sdk_available? end end - context 'when sampling a T_FILE' do - it 'includes the correct ruby vm type for the passed object' do + context "when sampling a T_FILE" do + it "includes the correct ruby vm type for the passed object" do File.open(__FILE__) do |file| sample_allocation(weight: 123, new_object: file) end - expect(single_sample.labels.fetch(:"ruby vm type")).to eq 'T_FILE' + expect(single_sample.labels.fetch(:"ruby vm type")).to eq "T_FILE" end - it 'includes the correct class for the passed object' do + it "includes the correct class for the passed object" do File.open(__FILE__) do |file| sample_allocation(weight: 123, new_object: file) end - expect(single_sample.labels.fetch(:"allocation class")).to eq 'File' + expect(single_sample.labels.fetch(:"allocation class")).to eq "File" end - context 'when allocation_type_enabled is false' do + context "when allocation_type_enabled is false" do let(:allocation_type_enabled) { false } - it 'does not record the correct class for the passed object' do + it "does not record the correct class for the passed object" do File.open(__FILE__) do |file| sample_allocation(weight: 123, new_object: file) end @@ -1188,27 +1188,27 @@ def self.otel_sdk_available? end end - context 'when sampling a Struct' do + context "when sampling a Struct" do before do - stub_const('ThreadContextSpec::TestStruct', Struct.new(:a)) + stub_const("ThreadContextSpec::TestStruct", Struct.new(:a)) end - it 'includes the correct ruby vm type for the passed object' do + it "includes the correct ruby vm type for the passed object" do sample_allocation(weight: 123, new_object: ThreadContextSpec::TestStruct.new) - expect(single_sample.labels.fetch(:"ruby vm type")).to eq 'T_STRUCT' + expect(single_sample.labels.fetch(:"ruby vm type")).to eq "T_STRUCT" end - it 'includes the correct class for the passed object' do + it "includes the correct class for the passed object" do sample_allocation(weight: 123, new_object: ThreadContextSpec::TestStruct.new) - expect(single_sample.labels.fetch(:"allocation class")).to eq 'ThreadContextSpec::TestStruct' + expect(single_sample.labels.fetch(:"allocation class")).to eq "ThreadContextSpec::TestStruct" end - context 'when allocation_type_enabled is false' do + context "when allocation_type_enabled is false" do let(:allocation_type_enabled) { false } - it 'does not record the correct class for the passed object' do + it "does not record the correct class for the passed object" do sample_allocation(weight: 123, new_object: ThreadContextSpec::TestStruct.new) expect(single_sample.labels).to_not include("allocation class": anything) @@ -1217,77 +1217,77 @@ def self.otel_sdk_available? end end - describe '#sample_skipped_allocation_samples' do + describe "#sample_skipped_allocation_samples" do let(:single_sample) do expect(samples.size).to be 1 samples.first end before { sample_skipped_allocation_samples(123) } - it 'records the number of skipped allocations' do + it "records the number of skipped allocations" do expect(single_sample.values).to include("alloc-samples": 123) end it 'attributes the skipped samples to a "Skipped Samples" thread' do - expect(single_sample.labels).to include("thread id": 'SS', "thread name": 'Skipped Samples') + expect(single_sample.labels).to include("thread id": "SS", "thread name": "Skipped Samples") end it 'attributes the skipped samples to a "(Skipped Samples)" allocation class' do - expect(single_sample.labels).to include("allocation class": '(Skipped Samples)') + expect(single_sample.labels).to include("allocation class": "(Skipped Samples)") end it 'includes a placeholder stack attributed to "Skipped Samples"' do expect(single_sample.locations.size).to be 1 - expect(single_sample.locations.first.path).to eq 'Skipped Samples' + expect(single_sample.locations.first.path).to eq "Skipped Samples" end end - describe '#thread_list' do + describe "#thread_list" do it "returns the same as Ruby's Thread.list" do expect(thread_list).to eq Thread.list end end - describe '#per_thread_context' do - context 'before sampling' do + describe "#per_thread_context" do + context "before sampling" do it do expect(per_thread_context).to be_empty end end - context 'after sampling' do + context "after sampling" do before do @wall_time_before_sample_ns = Datadog::Core::Utils::Time.get_time(:nanosecond) sample @wall_time_after_sample_ns = Datadog::Core::Utils::Time.get_time(:nanosecond) end - it 'contains all the sampled threads' do + it "contains all the sampled threads" do expect(per_thread_context.keys).to include(Thread.main, t1, t2, t3) end - describe ':thread_id' do - it 'contains the object ids of all sampled threads' do + describe ":thread_id" do + it "contains the object ids of all sampled threads" do per_thread_context.each do |thread, context| expect(object_id_from(context.fetch(:thread_id))).to eq thread.object_id end end - context 'on Ruby >= 3.1' do - before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION < '3.1.' } + context "on Ruby >= 3.1" do + before { skip "Behavior does not apply to current Ruby version" if RUBY_VERSION < "3.1." } # Thread#native_thread_id was added on 3.1 - it 'contains the native thread ids of all sampled threads' do + it "contains the native thread ids of all sampled threads" do per_thread_context.each do |thread, context| expect(context.fetch(:thread_id).split.first).to eq thread.native_thread_id.to_s end end end - context 'on Ruby < 3.1' do - before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION >= '3.1.' } + context "on Ruby < 3.1" do + before { skip "Behavior does not apply to current Ruby version" if RUBY_VERSION >= "3.1." } - it 'contains a fallback native thread id' do + it "contains a fallback native thread id" do per_thread_context.each do |_thread, context| expect(Integer(context.fetch(:thread_id).split.first)).to be > 0 end @@ -1295,37 +1295,37 @@ def self.otel_sdk_available? end end - it 'sets the wall_time_at_previous_sample_ns to the current wall clock value' do + it "sets the wall_time_at_previous_sample_ns to the current wall clock value" do expect(per_thread_context.values).to all( include(wall_time_at_previous_sample_ns: be_between(@wall_time_before_sample_ns, @wall_time_after_sample_ns)) ) end - context 'cpu time behavior' do - context 'when not on Linux' do + context "cpu time behavior" do + context "when not on Linux" do before do - skip 'The fallback behavior only applies when not on Linux' if PlatformHelpers.linux? + skip "The fallback behavior only applies when not on Linux" if PlatformHelpers.linux? end - it 'sets the cpu_time_at_previous_sample_ns to zero' do + it "sets the cpu_time_at_previous_sample_ns to zero" do expect(per_thread_context.values).to all( include(cpu_time_at_previous_sample_ns: 0) ) end - it 'marks the thread_cpu_time_ids as not valid' do + it "marks the thread_cpu_time_ids as not valid" do expect(per_thread_context.values).to all( include(thread_cpu_time_id_valid?: false) ) end end - context 'on Linux' do + context "on Linux" do before do - skip 'Test only runs on Linux' unless PlatformHelpers.linux? + skip "Test only runs on Linux" unless PlatformHelpers.linux? end - it 'sets the cpu_time_at_previous_sample_ns to the current cpu clock value' do + it "sets the cpu_time_at_previous_sample_ns to the current cpu clock value" do # It's somewhat difficult to validate the actual value since this is an operating system-specific value # which should only be assessed in relation to other values for the same thread, not in absolute expect(per_thread_context.values).to all( @@ -1333,7 +1333,7 @@ def self.otel_sdk_available? ) end - it 'returns a bigger value for each sample' do + it "returns a bigger value for each sample" do sample_values = [] 3.times do @@ -1343,11 +1343,11 @@ def self.otel_sdk_available? per_thread_context[Thread.main].fetch(:cpu_time_at_previous_sample_ns) end - expect(sample_values.uniq.size).to be(3), 'Every sample is expected to have a differ cpu time value' - expect(sample_values).to eq(sample_values.sort), 'Samples are expected to be in ascending order' + expect(sample_values.uniq.size).to be(3), "Every sample is expected to have a differ cpu time value" + expect(sample_values).to eq(sample_values.sort), "Samples are expected to be in ascending order" end - it 'marks the thread_cpu_time_ids as valid' do + it "marks the thread_cpu_time_ids as valid" do expect(per_thread_context.values).to all( include(thread_cpu_time_id_valid?: true) ) @@ -1355,15 +1355,15 @@ def self.otel_sdk_available? end end - describe ':thread_invoke_location' do - it 'is empty for the main thread' do + describe ":thread_invoke_location" do + it "is empty for the main thread" do expect(per_thread_context.fetch(Thread.main).fetch(:thread_invoke_location)).to be_empty end # NOTE: As of this writing, the dd-trace-rb spec_helper.rb includes a monkey patch to Thread creation that we use # to track specs that leak threads. This means that the invoke_location of every thread will point at the # spec_helper in our test suite. Just in case you're looking at the output and being a bit confused :) - it 'contains the file and line for the started threads' do + it "contains the file and line for the started threads" do [t1, t2, t3].each do |thread| invoke_location = per_thread_context.fetch(thread).fetch(:thread_invoke_location) @@ -1372,7 +1372,7 @@ def self.otel_sdk_available? end end - it 'contains a fallback for threads started in native code' do + it "contains a fallback for threads started in native code" do native_thread = described_class::Testing._native_new_empty_thread sample @@ -1381,10 +1381,10 @@ def self.otel_sdk_available? native_thread.join invoke_location = per_thread_context.fetch(native_thread).fetch(:thread_invoke_location) - expect(invoke_location).to eq '(Unnamed thread from native code)' + expect(invoke_location).to eq "(Unnamed thread from native code)" end - context 'when the `logging` gem has monkey patched thread creation' do + context "when the `logging` gem has monkey patched thread creation" do # rubocop:disable Style/GlobalVars before do load("#{__dir__}/helper/lib/logging/diagnostic_context.rb") @@ -1404,20 +1404,20 @@ def self.otel_sdk_available? # # To simulate this on our test suite without having to bring in the `logging` gem (and monkey patch our # threads), a helper was created that has a matching partial path. - it 'contains a placeholder only' do + it "contains a placeholder only" do sample invoke_location = per_thread_context.fetch($simulated_logging_gem_monkey_patched_thread).fetch(:thread_invoke_location) - expect(invoke_location).to eq '(Unnamed thread)' + expect(invoke_location).to eq "(Unnamed thread)" end # rubocop:enable Style/GlobalVars end end end - context 'after sampling multiple times' do - it 'contains only the threads still alive' do + context "after sampling multiple times" do + it "contains only the threads still alive" do sample # All alive threads still in there @@ -1437,18 +1437,18 @@ def self.otel_sdk_available? end end - describe '#reset_after_fork' do + describe "#reset_after_fork" do subject(:reset_after_fork) { cpu_and_wall_time_collector.reset_after_fork } before do sample end - it 'clears the per_thread_context' do + it "clears the per_thread_context" do expect { reset_after_fork }.to change { per_thread_context.empty? }.from(false).to(true) end - it 'clears the stats' do + it "clears the stats" do # Simulate a GC sample, so the gc_samples stat will go to 1 on_gc_start on_gc_finish @@ -1457,7 +1457,7 @@ def self.otel_sdk_available? expect { reset_after_fork }.to change { stats.fetch(:gc_samples) }.from(1).to(0) end - it 'resets the stack recorder' do + it "resets the stack recorder" do expect(recorder).to receive(:reset_after_fork) reset_after_fork diff --git a/spec/datadog/profiling/component_spec.rb b/spec/datadog/profiling/component_spec.rb index 41b3dd12c07..769d34061c1 100644 --- a/spec/datadog/profiling/component_spec.rb +++ b/spec/datadog/profiling/component_spec.rb @@ -1,4 +1,4 @@ -require 'datadog/profiling/spec_helper' +require "datadog/profiling/spec_helper" RSpec.describe Datadog::Profiling::Component do let(:settings) { Datadog::Core::Configuration::Settings.new } @@ -14,36 +14,36 @@ end end - describe '.build_profiler_component' do + describe ".build_profiler_component" do let(:tracer) { instance_double(Datadog::Tracing::Tracer) } subject(:build_profiler_component) do described_class.build_profiler_component(settings: settings, agent_settings: agent_settings, optional_tracer: tracer) end - context 'when profiling is not supported' do + context "when profiling is not supported" do before { allow(Datadog::Profiling).to receive(:supported?).and_return(false) } it { is_expected.to eq [nil, {profiling_enabled: false}] } end - context 'by default' do - it 'does not build a profiler' do + context "by default" do + it "does not build a profiler" do is_expected.to eq [nil, {profiling_enabled: false}] end end - context 'with :enabled false' do + context "with :enabled false" do before do settings.profiling.enabled = false end - it 'does not build a profiler' do + it "does not build a profiler" do is_expected.to eq [nil, {profiling_enabled: false}] end end - context 'with :enabled true' do + context "with :enabled true" do before do skip_if_profiling_not_supported(self) @@ -51,14 +51,14 @@ allow(profiler_setup_task).to receive(:run) end - it 'builds a profiler instance' do + it "builds a profiler instance" do expect(build_profiler_component).to match([instance_of(Datadog::Profiling::Profiler), {profiling_enabled: true}]) end - context 'when using the new CPU Profiling 2.0 profiler' do - it 'initializes a ThreadContext collector' do + context "when using the new CPU Profiling 2.0 profiler" do + it "initializes a ThreadContext collector" do allow(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new) - dummy_stack_recorder = instance_double(Datadog::Profiling::StackRecorder, 'dummy_stack_recorder') + dummy_stack_recorder = instance_double(Datadog::Profiling::StackRecorder, "dummy_stack_recorder") allow(Datadog::Profiling::StackRecorder).to receive(:new).and_return(dummy_stack_recorder) expect(settings.profiling.advanced).to receive(:max_frames).and_return(:max_frames_config) @@ -78,7 +78,7 @@ build_profiler_component end - it 'initializes a CpuAndWallTimeWorker collector' do + it "initializes a CpuAndWallTimeWorker collector" do expect(described_class).to receive(:no_signals_workaround_enabled?).and_return(:no_signals_result) expect(settings.profiling.advanced).to receive(:overhead_target_percentage) .and_return(:overhead_target_percentage_config) @@ -99,17 +99,17 @@ build_profiler_component end - context 'when gc_enabled is true' do + context "when gc_enabled is true" do before do settings.profiling.advanced.gc_enabled = true - stub_const('RUBY_VERSION', testing_version) + stub_const("RUBY_VERSION", testing_version) end - ['2.7.0', '3.1.4', '3.2.3', '3.3.0'].each do |fixed_ruby| + ["2.7.0", "3.1.4", "3.2.3", "3.3.0"].each do |fixed_ruby| context "on a Ruby version not affected by https://bugs.ruby-lang.org/issues/18464 (#{fixed_ruby})" do let(:testing_version) { fixed_ruby } - it 'initializes a CpuAndWallTimeWorker collector with gc_profiling_enabled set to true' do + it "initializes a CpuAndWallTimeWorker collector with gc_profiling_enabled set to true" do expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new).with hash_including( gc_profiling_enabled: true, ) @@ -121,11 +121,11 @@ end end - ['3.0.0', '3.1.0', '3.1.3'].each do |broken_ractors_ruby| + ["3.0.0", "3.1.0", "3.1.3"].each do |broken_ractors_ruby| context "on a Ruby version affected by https://bugs.ruby-lang.org/issues/18464 (#{broken_ractors_ruby})" do let(:testing_version) { broken_ractors_ruby } - it 'initializes a CpuAndWallTimeWorker collector with gc_profiling_enabled set to false and warns' do + it "initializes a CpuAndWallTimeWorker collector with gc_profiling_enabled set to false and warns" do expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new).with hash_including( gc_profiling_enabled: false, ) @@ -137,10 +137,10 @@ end end - context 'on Ruby 3' do - let(:testing_version) { '3.3.0' } + context "on Ruby 3" do + let(:testing_version) { "3.3.0" } - it 'emits a debug log about Ractors interfering with GC profiling' do + it "emits a debug log about Ractors interfering with GC profiling" do expect(Datadog.logger) .to receive(:debug).with(/using Ractors may result in GC profiling unexpectedly stopping/) @@ -149,10 +149,10 @@ end end - context 'when gc_enabled is false' do + context "when gc_enabled is false" do before { settings.profiling.advanced.gc_enabled = false } - it 'initializes a CpuAndWallTimeWorker collector with gc_profiling_enabled set to false' do + it "initializes a CpuAndWallTimeWorker collector with gc_profiling_enabled set to false" do expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new).with hash_including( gc_profiling_enabled: false, ) @@ -161,17 +161,17 @@ end end - context 'when allocation profiling is enabled' do + context "when allocation profiling is enabled" do before do settings.profiling.allocation_enabled = true settings.profiling.advanced.gc_enabled = false # Disable this to avoid any additional warnings coming from it - stub_const('RUBY_VERSION', testing_version) + stub_const("RUBY_VERSION", testing_version) end - context 'on Ruby 2.x' do - let(:testing_version) { '2.5.0' } + context "on Ruby 2.x" do + let(:testing_version) { "2.5.0" } - it 'initializes CpuAndWallTimeWorker and StackRecorder with allocation sampling support' do + it "initializes CpuAndWallTimeWorker and StackRecorder with allocation sampling support" do expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new).with hash_including( allocation_profiling_enabled: true, ) @@ -186,11 +186,11 @@ end end - ['3.2.0', '3.2.1', '3.2.2'].each do |broken_ruby| + ["3.2.0", "3.2.1", "3.2.2"].each do |broken_ruby| context "on a Ruby 3 version affected by https://bugs.ruby-lang.org/issues/19482 (#{broken_ruby})" do let(:testing_version) { broken_ruby } - it 'initializes a CpuAndWallTimeWorker and StackRecorder with allocation sampling force-disabled and warns' do + it "initializes a CpuAndWallTimeWorker and StackRecorder with allocation sampling force-disabled and warns" do expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new).with hash_including( allocation_profiling_enabled: false, ) @@ -206,11 +206,11 @@ end end - ['3.0.0', '3.1.0', '3.1.3'].each do |broken_ractors_ruby| + ["3.0.0", "3.1.0", "3.1.3"].each do |broken_ractors_ruby| context "on a Ruby 3 version affected by https://bugs.ruby-lang.org/issues/18464 (#{broken_ractors_ruby})" do let(:testing_version) { broken_ractors_ruby } - it 'initializes CpuAndWallTimeWorker and StackRecorder with allocation sampling support and warns' do + it "initializes CpuAndWallTimeWorker and StackRecorder with allocation sampling support and warns" do expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new).with hash_including( allocation_profiling_enabled: true, ) @@ -226,10 +226,10 @@ end end - ['3.1.4', '3.2.3', '3.3.0'].each do |fixed_ruby| + ["3.1.4", "3.2.3", "3.3.0"].each do |fixed_ruby| context "on a Ruby 3 version where https://bugs.ruby-lang.org/issues/18464 is fixed (#{fixed_ruby})" do let(:testing_version) { fixed_ruby } - it 'initializes CpuAndWallTimeWorker and StackRecorder with allocation sampling support and warns' do + it "initializes CpuAndWallTimeWorker and StackRecorder with allocation sampling support and warns" do expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new).with hash_including( allocation_profiling_enabled: true, ) @@ -246,12 +246,12 @@ end end - context 'when allocation profiling is disabled' do + context "when allocation profiling is disabled" do before do settings.profiling.allocation_enabled = false end - it 'initializes CpuAndWallTimeWorker and StackRecorder without allocation sampling support' do + it "initializes CpuAndWallTimeWorker and StackRecorder without allocation sampling support" do expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new).with hash_including( allocation_profiling_enabled: false, ) @@ -263,20 +263,20 @@ end end - context 'when heap profiling is enabled' do + context "when heap profiling is enabled" do # Universally supported ruby version for allocation profiling by default - let(:testing_version) { '3.3.0' } + let(:testing_version) { "3.3.0" } before do settings.profiling.advanced.experimental_heap_enabled = true settings.profiling.advanced.gc_enabled = false # Disable this to avoid any additional warnings coming from it - stub_const('RUBY_VERSION', testing_version) + stub_const("RUBY_VERSION", testing_version) end - context 'on a Ruby older than 2.7' do - let(:testing_version) { '2.6' } + context "on a Ruby older than 2.7" do + let(:testing_version) { "2.6" } - it 'initializes StackRecorder without heap sampling support and warns' do + it "initializes StackRecorder without heap sampling support and warns" do expect(Datadog::Profiling::StackRecorder).to receive(:new) .with(hash_including(heap_samples_enabled: false, heap_size_enabled: false)) .and_call_original @@ -287,22 +287,22 @@ end end - context 'and allocation profiling disabled' do + context "and allocation profiling disabled" do before do settings.profiling.allocation_enabled = false end - it 'raises an ArgumentError during component initialization' do + it "raises an ArgumentError during component initialization" do expect { build_profiler_component }.to raise_error(ArgumentError, /requires allocation profiling/) end end - context 'and allocation profiling enabled and supported' do + context "and allocation profiling enabled and supported" do before do settings.profiling.allocation_enabled = true end - it 'initializes StackRecorder with heap sampling support and warns' do + it "initializes StackRecorder with heap sampling support and warns" do expect(Datadog::Profiling::StackRecorder).to receive(:new) .with(hash_including(heap_samples_enabled: true, heap_size_enabled: true)) .and_call_original @@ -315,12 +315,12 @@ build_profiler_component end - context 'but heap size profiling is disabled' do + context "but heap size profiling is disabled" do before do settings.profiling.advanced.experimental_heap_size_enabled = false end - it 'initializes StackRecorder without heap size profiling support' do + it "initializes StackRecorder without heap size profiling support" do expect(Datadog::Profiling::StackRecorder).to receive(:new) .with(hash_including(heap_samples_enabled: true, heap_size_enabled: false)) .and_call_original @@ -333,10 +333,10 @@ end end - context 'on a Ruby older than 3.1' do - let(:testing_version) { '2.7' } + context "on a Ruby older than 3.1" do + let(:testing_version) { "2.7" } - it 'initializes StackRecorder with heap sampling support but shows warning and debug messages' do + it "initializes StackRecorder with heap sampling support but shows warning and debug messages" do expect(Datadog::Profiling::StackRecorder).to receive(:new) .with(hash_including(heap_samples_enabled: true)) .and_call_original @@ -352,12 +352,12 @@ end end - context 'when heap profiling is disabled' do + context "when heap profiling is disabled" do before do settings.profiling.advanced.experimental_heap_enabled = false end - it 'initializes StackRecorder without heap sampling support' do + it "initializes StackRecorder without heap sampling support" do expect(Datadog::Profiling::StackRecorder).to receive(:new) .with(hash_including(heap_samples_enabled: false, heap_size_enabled: false)) .and_call_original @@ -366,10 +366,10 @@ end end - context 'when timeline is enabled' do + context "when timeline is enabled" do before { settings.profiling.advanced.timeline_enabled = true } - it 'sets up the StackRecorder with timeline_enabled: true' do + it "sets up the StackRecorder with timeline_enabled: true" do expect(Datadog::Profiling::StackRecorder) .to receive(:new).with(hash_including(timeline_enabled: true)).and_call_original @@ -377,10 +377,10 @@ end end - context 'when timeline is disabled' do + context "when timeline is disabled" do before { settings.profiling.advanced.timeline_enabled = false } - it 'sets up the StackRecorder with timeline_enabled: false' do + it "sets up the StackRecorder with timeline_enabled: false" do expect(Datadog::Profiling::StackRecorder) .to receive(:new).with(hash_including(timeline_enabled: false)).and_call_original @@ -388,7 +388,7 @@ end end - it 'sets up the Profiler with the CpuAndWallTimeWorker collector' do + it "sets up the Profiler with the CpuAndWallTimeWorker collector" do expect(Datadog::Profiling::Profiler).to receive(:new).with( worker: instance_of(Datadog::Profiling::Collectors::CpuAndWallTimeWorker), scheduler: anything, @@ -398,14 +398,14 @@ build_profiler_component end - it 'sets up the Exporter with the StackRecorder' do + it "sets up the Exporter with the StackRecorder" do expect(Datadog::Profiling::Exporter) .to receive(:new).with(hash_including(pprof_recorder: instance_of(Datadog::Profiling::StackRecorder))) build_profiler_component end - it 'sets up the Exporter internal_metadata with relevant settings' do + it "sets up the Exporter internal_metadata with relevant settings" do allow(Datadog::Profiling::Collectors::ThreadContext).to receive(:new) allow(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to receive(:new) allow(Datadog::Profiling::StackRecorder).to receive(:new) @@ -426,10 +426,10 @@ build_profiler_component end - context 'when on Linux' do - before { stub_const('RUBY_PLATFORM', 'some-linux-based-platform') } + context "when on Linux" do + before { stub_const("RUBY_PLATFORM", "some-linux-based-platform") } - it 'sets up the StackRecorder with cpu_time_enabled: true' do + it "sets up the StackRecorder with cpu_time_enabled: true" do expect(Datadog::Profiling::StackRecorder) .to receive(:new).with(hash_including(cpu_time_enabled: true)).and_call_original @@ -437,10 +437,10 @@ end end - context 'when not on Linux' do - before { stub_const('RUBY_PLATFORM', 'some-other-os') } + context "when not on Linux" do + before { stub_const("RUBY_PLATFORM", "some-other-os") } - it 'sets up the StackRecorder with cpu_time_enabled: false' do + it "sets up the StackRecorder with cpu_time_enabled: false" do expect(Datadog::Profiling::StackRecorder) .to receive(:new).with(hash_including(cpu_time_enabled: false)).and_call_original @@ -449,13 +449,13 @@ end end - it 'runs the setup task to set up any needed extensions for profiling' do + it "runs the setup task to set up any needed extensions for profiling" do expect(profiler_setup_task).to receive(:run) build_profiler_component end - it 'builds an HttpTransport with the current settings' do + it "builds an HttpTransport with the current settings" do expect(Datadog::Profiling::HttpTransport).to receive(:new).with( agent_settings: agent_settings, site: settings.site, @@ -466,7 +466,7 @@ build_profiler_component end - it 'creates a scheduler with an HttpTransport' do + it "creates a scheduler with an HttpTransport" do expect(Datadog::Profiling::Scheduler).to receive(:new) do |transport:, **_| expect(transport).to be_a_kind_of(Datadog::Profiling::HttpTransport) end @@ -474,27 +474,27 @@ build_profiler_component end - context 'when upload_period_seconds is below 60 seconds' do + context "when upload_period_seconds is below 60 seconds" do before { settings.profiling.advanced.upload_period_seconds = 59 } - it 'ignores this setting and creates a scheduler with an interval of 60 seconds' do + it "ignores this setting and creates a scheduler with an interval of 60 seconds" do expect(Datadog::Profiling::Scheduler).to receive(:new).with(a_hash_including(interval: 60)) build_profiler_component end end - context 'when upload_period_seconds is over 60 seconds' do + context "when upload_period_seconds is over 60 seconds" do before { settings.profiling.advanced.upload_period_seconds = 61 } - it 'creates a scheduler with the given interval' do + it "creates a scheduler with the given interval" do expect(Datadog::Profiling::Scheduler).to receive(:new).with(a_hash_including(interval: 61)) build_profiler_component end end - it 'initializes the exporter with a code provenance collector' do + it "initializes the exporter with a code provenance collector" do expect(Datadog::Profiling::Exporter).to receive(:new) do |code_provenance_collector:, **_| expect(code_provenance_collector).to be_a_kind_of(Datadog::Profiling::Collectors::CodeProvenance) end @@ -502,10 +502,10 @@ build_profiler_component end - context 'when code provenance is disabled' do + context "when code provenance is disabled" do before { settings.profiling.advanced.code_provenance_enabled = false } - it 'initializes the exporter with a nil code provenance collector' do + it "initializes the exporter with a nil code provenance collector" do expect(Datadog::Profiling::Exporter).to receive(:new) do |code_provenance_collector:, **_| expect(code_provenance_collector).to be nil end @@ -514,20 +514,20 @@ end end - context 'when a custom transport is provided' do - let(:custom_transport) { double('Custom transport') } + context "when a custom transport is provided" do + let(:custom_transport) { double("Custom transport") } before do settings.profiling.exporter.transport = custom_transport end - it 'does not initialize an HttpTransport' do + it "does not initialize an HttpTransport" do expect(Datadog::Profiling::HttpTransport).to_not receive(:new) build_profiler_component end - it 'sets up the scheduler to use the custom transport' do + it "sets up the scheduler to use the custom transport" do expect(Datadog::Profiling::Scheduler).to receive(:new) do |transport:, **_| expect(transport).to be custom_transport end @@ -536,68 +536,68 @@ end end - context 'when crash tracking is disabled' do + context "when crash tracking is disabled" do before { settings.profiling.advanced.experimental_crash_tracking_enabled = false } - it 'does not initialize the crash tracker' do + it "does not initialize the crash tracker" do expect(Datadog::Profiling::Crashtracker).to_not receive(:new) build_profiler_component end end - context 'when crash tracking is enabled' do - it 'initializes the crash tracker' do + context "when crash tracking is enabled" do + it "initializes the crash tracker" do expect(Datadog::Profiling::Crashtracker).to receive(:new).with( exporter_configuration: array_including(:agent), - tags: hash_including('runtime' => 'ruby'), + tags: hash_including("runtime" => "ruby"), upload_timeout_seconds: settings.profiling.upload.timeout_seconds, ) build_profiler_component end - context 'when a custom transport is provided' do - let(:custom_transport) { double('Custom transport') } + context "when a custom transport is provided" do + let(:custom_transport) { double("Custom transport") } before do settings.profiling.exporter.transport = custom_transport allow(Datadog.logger).to receive(:debug) end - it 'debug logs that crash tracking will not be enabled' do + it "debug logs that crash tracking will not be enabled" do expect(Datadog.logger).to receive(:debug).with(/Cannot enable profiling crash tracking/) build_profiler_component end - it 'does not initialize the crash tracker' do + it "does not initialize the crash tracker" do expect(Datadog::Profiling::Crashtracker).to_not receive(:new) build_profiler_component end end - context 'when there was a libdatadog_api failure during load' do + context "when there was a libdatadog_api failure during load" do before do allow(Datadog.logger).to receive(:debug) - stub_const('Datadog::Profiling::Crashtracker::LIBDATADOG_API_FAILURE', 'simulated load failure') + stub_const("Datadog::Profiling::Crashtracker::LIBDATADOG_API_FAILURE", "simulated load failure") end - it 'debug logs that crash tracking will not be enabled' do + it "debug logs that crash tracking will not be enabled" do expect(Datadog.logger).to receive(:debug).with(/Cannot enable crashtracking: simulated load failure/) build_profiler_component end - it 'does not initialize the crash tracker' do + it "does not initialize the crash tracker" do expect(Datadog::Profiling::Crashtracker).to_not receive(:new) build_profiler_component end end - it 'initializes the profiler instance with the crash tracker' do + it "initializes the profiler instance with the crash tracker" do expect(Datadog::Profiling::Profiler).to receive(:new).with( worker: anything, scheduler: anything, @@ -608,33 +608,33 @@ end end - describe 'dir interruption workaround' do + describe "dir interruption workaround" do let(:no_signals_workaround_enabled) { false } before do expect(described_class).to receive(:no_signals_workaround_enabled?).and_return(no_signals_workaround_enabled) end - it 'is enabled by default' do + it "is enabled by default" do expect(Datadog::Profiling::Ext::DirMonkeyPatches).to receive(:apply!) build_profiler_component end - context 'when the no signals workaround is enabled' do + context "when the no signals workaround is enabled" do let(:no_signals_workaround_enabled) { true } - it 'is not applied' do + it "is not applied" do expect(Datadog::Profiling::Ext::DirMonkeyPatches).to_not receive(:apply!) build_profiler_component end end - context 'when the dir interruption workaround is disabled via configuration' do + context "when the dir interruption workaround is disabled via configuration" do before { settings.profiling.advanced.dir_interruption_workaround_enabled = false } - it 'is not applied' do + it "is not applied" do expect(Datadog::Profiling::Ext::DirMonkeyPatches).to_not receive(:apply!) build_profiler_component @@ -644,14 +644,14 @@ end end - describe '.valid_overhead_target' do + describe ".valid_overhead_target" do subject(:valid_overhead_target) { described_class.send(:valid_overhead_target, overhead_target_percentage) } [0, 20.1].each do |invalid_value| let(:overhead_target_percentage) { invalid_value } context "when overhead_target_percentage is invalid value (#{invalid_value})" do - it 'logs an error' do + it "logs an error" do expect(Datadog.logger).to receive(:error).with( /Ignoring invalid value for profiling overhead_target_percentage/ ) @@ -659,7 +659,7 @@ valid_overhead_target end - it 'falls back to the default value' do + it "falls back to the default value" do allow(Datadog.logger).to receive(:error) expect(valid_overhead_target).to eq 2.0 @@ -667,21 +667,21 @@ end end - context 'when overhead_target_percentage is valid' do + context "when overhead_target_percentage is valid" do let(:overhead_target_percentage) { 1.5 } - it 'returns the value' do + it "returns the value" do expect(valid_overhead_target).to eq 1.5 end end end - describe '.no_signals_workaround_enabled?' do + describe ".no_signals_workaround_enabled?" do subject(:no_signals_workaround_enabled?) { described_class.send(:no_signals_workaround_enabled?, settings) } before { skip_if_profiling_not_supported(self) } - context 'when no_signals_workaround_enabled is false' do + context "when no_signals_workaround_enabled is false" do before do settings.profiling.advanced.no_signals_workaround_enabled = false allow(Datadog.logger).to receive(:warn) @@ -689,10 +689,10 @@ it { is_expected.to be false } - context 'on Ruby 2.5 and below' do - before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION >= '2.6.' } + context "on Ruby 2.5 and below" do + before { skip "Behavior does not apply to current Ruby version" if RUBY_VERSION >= "2.6." } - it 'logs a warning message mentioning that this is is not recommended' do + it "logs a warning message mentioning that this is is not recommended" do expect(Datadog.logger).to receive(:warn).with( /workaround has been disabled via configuration.*This is not recommended/ ) @@ -701,10 +701,10 @@ end end - context 'on Ruby 2.6 and above' do - before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION < '2.6.' } + context "on Ruby 2.6 and above" do + before { skip "Behavior does not apply to current Ruby version" if RUBY_VERSION < "2.6." } - it 'logs a warning message mentioning that the no signals mode has been disabled' do + it "logs a warning message mentioning that the no signals mode has been disabled" do expect(Datadog.logger).to receive(:warn).with('Profiling "no signals" workaround disabled via configuration') no_signals_workaround_enabled? @@ -712,7 +712,7 @@ end end - context 'when no_signals_workaround_enabled is true' do + context "when no_signals_workaround_enabled is true" do before do settings.profiling.advanced.no_signals_workaround_enabled = true allow(Datadog.logger).to receive(:warn) @@ -720,82 +720,82 @@ it { is_expected.to be true } - it 'logs a warning message mentioning that this setting is active' do + it "logs a warning message mentioning that this setting is active" do expect(Datadog.logger).to receive(:warn).with(/Profiling "no signals" workaround enabled via configuration/) no_signals_workaround_enabled? end end - shared_examples 'no_signals_workaround_enabled :auto behavior' do - context 'on Ruby 2.5 and below' do - before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION >= '2.6.' } + shared_examples "no_signals_workaround_enabled :auto behavior" do + context "on Ruby 2.5 and below" do + before { skip "Behavior does not apply to current Ruby version" if RUBY_VERSION >= "2.6." } it { is_expected.to be true } end - context 'on Ruby 2.6 and above' do - before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION < '2.6.' } + context "on Ruby 2.6 and above" do + before { skip "Behavior does not apply to current Ruby version" if RUBY_VERSION < "2.6." } - context 'when mysql2 gem is available' do - include_context('loaded gems', mysql2: Gem::Version.new('0.5.5'), rugged: nil) + context "when mysql2 gem is available" do + include_context("loaded gems", mysql2: Gem::Version.new("0.5.5"), rugged: nil) before do allow(Datadog.logger).to receive(:warn) allow(Datadog.logger).to receive(:debug) end - context 'when skip_mysql2_check is enabled' do + context "when skip_mysql2_check is enabled" do before { settings.profiling.advanced.skip_mysql2_check = true } it { is_expected.to be true } - it 'logs a warning message mentioning that the no signals workaround is going to be used' do + it "logs a warning message mentioning that the no signals workaround is going to be used" do expect(Datadog.logger).to receive(:warn).with(/Enabling the profiling "no signals" workaround/) no_signals_workaround_enabled? end end - context 'when there is an issue requiring mysql2' do - before { allow(described_class).to receive(:require).and_raise(LoadError.new('Simulated require failure')) } + context "when there is an issue requiring mysql2" do + before { allow(described_class).to receive(:require).and_raise(LoadError.new("Simulated require failure")) } it { is_expected.to be true } - it 'logs that probing mysql2 failed' do + it "logs that probing mysql2 failed" do expect(Datadog.logger).to receive(:warn).with(/Failed to probe `mysql2` gem information/) no_signals_workaround_enabled? end end - context 'when mysql2 is required successfully' do - before { allow(described_class).to receive(:require).with('mysql2') } + context "when mysql2 is required successfully" do + before { allow(described_class).to receive(:require).with("mysql2") } - it 'logs a debug message stating mysql2 will be required' do + it "logs a debug message stating mysql2 will be required" do expect(Datadog.logger).to receive(:debug).with(/Requiring `mysql2` to check/) no_signals_workaround_enabled? end - context 'when mysql2 gem does not provide the info method' do + context "when mysql2 gem does not provide the info method" do before do - stub_const('Mysql2::Client', double('Fake Mysql2::Client')) + stub_const("Mysql2::Client", double("Fake Mysql2::Client")) end it { is_expected.to be true } end - context 'when an error is raised while probing the mysql2 gem' do + context "when an error is raised while probing the mysql2 gem" do before do - fake_client = double('Fake Mysql2::Client') - stub_const('Mysql2::Client', fake_client) - expect(fake_client).to receive(:info).and_raise(ArgumentError.new('Simulated call failure')) + fake_client = double("Fake Mysql2::Client") + stub_const("Mysql2::Client", fake_client) + expect(fake_client).to receive(:info).and_raise(ArgumentError.new("Simulated call failure")) end it { is_expected.to be true } - it 'logs a warning including the error details' do + it "logs a warning including the error details" do expect(Datadog.logger).to receive(:warn).with(/Failed to probe `mysql2` gem information/) no_signals_workaround_enabled? @@ -805,73 +805,73 @@ # See comments on looks_like_mariadb? for details on how this matching works context "when mysql2 gem is linked to mariadb's version of libmysqlclient" do before do - fake_client = double('Fake Mysql2::Client') - stub_const('Mysql2::Client', fake_client) - expect(fake_client).to receive(:info).and_return({version: '4.9.99', header_version: '10.0.0'}) + fake_client = double("Fake Mysql2::Client") + stub_const("Mysql2::Client", fake_client) + expect(fake_client).to receive(:info).and_return({version: "4.9.99", header_version: "10.0.0"}) end it { is_expected.to be false } - it 'does not log any warning message' do + it "does not log any warning message" do expect(Datadog.logger).to_not receive(:warn) no_signals_workaround_enabled? end end - context 'when mysql2 gem is using a version of libmysqlclient < 8.0.0' do + context "when mysql2 gem is using a version of libmysqlclient < 8.0.0" do before do - fake_client = double('Fake Mysql2::Client') - stub_const('Mysql2::Client', fake_client) - expect(fake_client).to receive(:info).and_return({version: '7.9.9'}) + fake_client = double("Fake Mysql2::Client") + stub_const("Mysql2::Client", fake_client) + expect(fake_client).to receive(:info).and_return({version: "7.9.9"}) end it { is_expected.to be true } - it 'logs a warning message mentioning that the no signals workaround is going to be used' do + it "logs a warning message mentioning that the no signals workaround is going to be used" do expect(Datadog.logger).to receive(:warn).with(/Enabling the profiling "no signals" workaround/) no_signals_workaround_enabled? end end - context 'when mysql2 gem is using a version of libmysqlclient >= 8.0.0' do + context "when mysql2 gem is using a version of libmysqlclient >= 8.0.0" do before do - fake_client = double('Fake Mysql2::Client') - stub_const('Mysql2::Client', fake_client) - expect(fake_client).to receive(:info).and_return({version: '8.0.0'}) + fake_client = double("Fake Mysql2::Client") + stub_const("Mysql2::Client", fake_client) + expect(fake_client).to receive(:info).and_return({version: "8.0.0"}) end it { is_expected.to be false } - it 'does not log any warning message' do + it "does not log any warning message" do expect(Datadog.logger).to_not receive(:warn) no_signals_workaround_enabled? end end - context 'when mysql2-aurora gem is loaded and libmysqlclient < 8.0.0' do + context "when mysql2-aurora gem is loaded and libmysqlclient < 8.0.0" do before do - fake_original_client = double('Fake original Mysql2::Client') - stub_const('Mysql2::Aurora::ORIGINAL_CLIENT_CLASS', fake_original_client) - expect(fake_original_client).to receive(:info).and_return({version: '7.9.9'}) + fake_original_client = double("Fake original Mysql2::Client") + stub_const("Mysql2::Aurora::ORIGINAL_CLIENT_CLASS", fake_original_client) + expect(fake_original_client).to receive(:info).and_return({version: "7.9.9"}) - client_replaced_by_aurora = double('Fake Aurora Mysql2::Client') - stub_const('Mysql2::Client', client_replaced_by_aurora) + client_replaced_by_aurora = double("Fake Aurora Mysql2::Client") + stub_const("Mysql2::Client", client_replaced_by_aurora) end it { is_expected.to be true } end - context 'when mysql2-aurora gem is loaded and libmysqlclient >= 8.0.0' do + context "when mysql2-aurora gem is loaded and libmysqlclient >= 8.0.0" do before do - fake_original_client = double('Fake original Mysql2::Client') - stub_const('Mysql2::Aurora::ORIGINAL_CLIENT_CLASS', fake_original_client) - expect(fake_original_client).to receive(:info).and_return({version: '8.0.0'}) + fake_original_client = double("Fake original Mysql2::Client") + stub_const("Mysql2::Aurora::ORIGINAL_CLIENT_CLASS", fake_original_client) + expect(fake_original_client).to receive(:info).and_return({version: "8.0.0"}) - client_replaced_by_aurora = double('Fake Aurora Mysql2::Client') - stub_const('Mysql2::Client', client_replaced_by_aurora) + client_replaced_by_aurora = double("Fake Aurora Mysql2::Client") + stub_const("Mysql2::Client", client_replaced_by_aurora) end it { is_expected.to be false } @@ -879,52 +879,52 @@ end end - context 'when rugged gem is available' do - include_context('loaded gems', rugged: Gem::Version.new('1.6.3'), mysql2: nil) + context "when rugged gem is available" do + include_context("loaded gems", rugged: Gem::Version.new("1.6.3"), mysql2: nil) before { allow(Datadog.logger).to receive(:warn) } it { is_expected.to be true } - it 'logs a warning message mentioning that the no signals workaround is going to be used' do + it "logs a warning message mentioning that the no signals workaround is going to be used" do expect(Datadog.logger).to receive(:warn).with(/Enabling the profiling "no signals" workaround/) no_signals_workaround_enabled? end end - context 'when running inside the passenger web server, even when gem is not available' do - include_context('loaded gems', passenger: nil, rugged: nil, mysql2: nil) + context "when running inside the passenger web server, even when gem is not available" do + include_context("loaded gems", passenger: nil, rugged: nil, mysql2: nil) before do - stub_const('::PhusionPassenger', Module.new) + stub_const("::PhusionPassenger", Module.new) allow(Datadog.logger).to receive(:warn) end it { is_expected.to be true } - it 'logs a warning message mentioning that the no signals workaround is going to be used' do + it "logs a warning message mentioning that the no signals workaround is going to be used" do expect(Datadog.logger).to receive(:warn).with(/Enabling the profiling "no signals" workaround/) no_signals_workaround_enabled? end end - context 'when passenger gem is available' do - context 'on passenger >= 6.0.19' do - include_context('loaded gems', passenger: Gem::Version.new('6.0.19'), rugged: nil, mysql2: nil) + context "when passenger gem is available" do + context "on passenger >= 6.0.19" do + include_context("loaded gems", passenger: Gem::Version.new("6.0.19"), rugged: nil, mysql2: nil) it { is_expected.to be false } end - context 'on passenger < 6.0.19' do - include_context('loaded gems', passenger: Gem::Version.new('6.0.18'), rugged: nil, mysql2: nil) + context "on passenger < 6.0.19" do + include_context("loaded gems", passenger: Gem::Version.new("6.0.18"), rugged: nil, mysql2: nil) before { allow(Datadog.logger).to receive(:warn) } it { is_expected.to be true } - it 'logs a warning message mentioning that the no signals workaround is going to be used' do + it "logs a warning message mentioning that the no signals workaround is going to be used" do expect(Datadog.logger).to receive(:warn).with(/Enabling the profiling "no signals" workaround/) no_signals_workaround_enabled? @@ -932,22 +932,22 @@ end end - context 'when passenger gem is not available, but PhusionPassenger::VERSION_STRING is available' do - context 'on passenger >= 6.0.19' do - before { stub_const('PhusionPassenger::VERSION_STRING', '6.0.19') } + context "when passenger gem is not available, but PhusionPassenger::VERSION_STRING is available" do + context "on passenger >= 6.0.19" do + before { stub_const("PhusionPassenger::VERSION_STRING", "6.0.19") } it { is_expected.to be false } end - context 'on passenger < 6.0.19' do + context "on passenger < 6.0.19" do before do - stub_const('PhusionPassenger::VERSION_STRING', '6.0.18') + stub_const("PhusionPassenger::VERSION_STRING", "6.0.18") allow(Datadog.logger).to receive(:warn) end it { is_expected.to be true } - it 'logs a warning message mentioning that the no signals workaround is going to be used' do + it "logs a warning message mentioning that the no signals workaround is going to be used" do expect(Datadog.logger).to receive(:warn).with(/Enabling the profiling "no signals" workaround/) no_signals_workaround_enabled? @@ -955,33 +955,33 @@ end end - context 'when mysql2 / rugged gems + passenger are not available' do - include_context('loaded gems', passenger: nil, mysql2: nil, rugged: nil) + context "when mysql2 / rugged gems + passenger are not available" do + include_context("loaded gems", passenger: nil, mysql2: nil, rugged: nil) it { is_expected.to be false } end end end - context 'when no_signals_workaround_enabled is :auto' do + context "when no_signals_workaround_enabled is :auto" do before { settings.profiling.advanced.no_signals_workaround_enabled = :auto } - include_examples 'no_signals_workaround_enabled :auto behavior' + include_examples "no_signals_workaround_enabled :auto behavior" end - context 'when no_signals_workaround_enabled is an invalid value' do + context "when no_signals_workaround_enabled is an invalid value" do before do - settings.profiling.advanced.no_signals_workaround_enabled = 'invalid value' + settings.profiling.advanced.no_signals_workaround_enabled = "invalid value" allow(Datadog.logger).to receive(:error) end - it 'logs an error message mentioning that the invalid value will be ignored' do + it "logs an error message mentioning that the invalid value will be ignored" do expect(Datadog.logger).to receive(:error).with(/Ignoring invalid value/) no_signals_workaround_enabled? end - include_examples 'no_signals_workaround_enabled :auto behavior' + include_examples "no_signals_workaround_enabled :auto behavior" end end end diff --git a/spec/datadog/profiling/crashtracker_spec.rb b/spec/datadog/profiling/crashtracker_spec.rb index fc9a5b5bd3c..6ab12abe51c 100644 --- a/spec/datadog/profiling/crashtracker_spec.rb +++ b/spec/datadog/profiling/crashtracker_spec.rb @@ -1,8 +1,8 @@ -require 'datadog/profiling/spec_helper' -require 'datadog/profiling/crashtracker' +require "datadog/profiling/spec_helper" +require "datadog/profiling/crashtracker" -require 'webrick' -require 'fiddle' +require "webrick" +require "fiddle" RSpec.describe Datadog::Profiling::Crashtracker do before do @@ -17,51 +17,51 @@ wait_for { `pgrep -f libdatadog-crashtracking-receiver` }.to be_empty end - let(:exporter_configuration) { [:agent, 'http://localhost:6006'] } + let(:exporter_configuration) { [:agent, "http://localhost:6006"] } let(:crashtracker_options) do { exporter_configuration: exporter_configuration, - tags: {'tag1' => 'value1', 'tag2' => 'value2'}, + tags: {"tag1" => "value1", "tag2" => "value2"}, upload_timeout_seconds: 123, } end subject(:crashtracker) { described_class.new(**crashtracker_options) } - describe '#start' do + describe "#start" do subject(:start) { crashtracker.start } - context 'when _native_start_or_update_on_fork raises an exception' do - it 'logs the exception' do - expect(described_class).to receive(:_native_start_or_update_on_fork) { raise 'Test failure' } + context "when _native_start_or_update_on_fork raises an exception" do + it "logs the exception" do + expect(described_class).to receive(:_native_start_or_update_on_fork) { raise "Test failure" } expect(Datadog.logger).to receive(:error).with(/Failed to start crash tracking: Test failure/) start end end - context 'when path_to_crashtracking_receiver_binary is nil' do + context "when path_to_crashtracking_receiver_binary is nil" do subject(:crashtracker) { described_class.new(**crashtracker_options, path_to_crashtracking_receiver_binary: nil) } - it 'logs a warning' do + it "logs a warning" do expect(Datadog.logger).to receive(:warn).with(/no path_to_crashtracking_receiver_binary was found/) start end end - context 'when ld_library_path is nil' do + context "when ld_library_path is nil" do subject(:crashtracker) { described_class.new(**crashtracker_options, ld_library_path: nil) } - it 'logs a warning' do + it "logs a warning" do expect(Datadog.logger).to receive(:warn).with(/no ld_library_path was found/) start end end - it 'starts the crash tracker' do + it "starts the crash tracker" do start wait_for { `pgrep -f libdatadog-crashtracking-receiver` }.to_not be_empty @@ -69,8 +69,8 @@ crashtracker.stop end - context 'when calling start multiple times in a row' do - it 'only starts the crash tracker once' do + context "when calling start multiple times in a row" do + it "only starts the crash tracker once" do 3.times { crashtracker.start } wait_for { `pgrep -f libdatadog-crashtracking-receiver`.lines.size }.to be 1 @@ -79,10 +79,10 @@ end end - context 'when upload_timeout_seconds is not an Integer' do + context "when upload_timeout_seconds is not an Integer" do let(:crashtracker_options) { {**super(), upload_timeout_seconds: 12.34} } - it 'converts it to an Integer before calling _native_start_or_update_on_fork' do + it "converts it to an Integer before calling _native_start_or_update_on_fork" do expect(described_class) .to receive(:_native_start_or_update_on_fork).with(hash_including(upload_timeout_seconds: 12)) @@ -91,14 +91,14 @@ end end - describe '#reset_after_fork' do + describe "#reset_after_fork" do subject(:reset_after_fork) { crashtracker.reset_after_fork } - context 'when called in a fork' do + context "when called in a fork" do before { crashtracker.start } after { crashtracker.stop } - it 'starts a second crash tracker for the fork' do + it "starts a second crash tracker for the fork" do expect_in_fork do wait_for { `pgrep -f libdatadog-crashtracking-receiver`.lines.size }.to be 1 @@ -114,19 +114,19 @@ end end - describe '#stop' do + describe "#stop" do subject(:stop) { crashtracker.stop } - context 'when _native_stop_crashtracker raises an exception' do - it 'logs the exception' do - expect(described_class).to receive(:_native_stop) { raise 'Test failure' } + context "when _native_stop_crashtracker raises an exception" do + it "logs the exception" do + expect(described_class).to receive(:_native_stop) { raise "Test failure" } expect(Datadog.logger).to receive(:error).with(/Failed to stop crash tracking: Test failure/) stop end end - it 'stops the crash tracker' do + it "stops the crash tracker" do crashtracker.start stop @@ -135,8 +135,8 @@ end end - context 'integration testing' do - shared_context 'HTTP server' do + context "integration testing" do + shared_context "HTTP server" do let(:server) do WEBrick::HTTPServer.new( Port: 0, @@ -145,14 +145,14 @@ StartCallback: -> { init_signal.push(1) } ) end - let(:hostname) { '127.0.0.1' } + let(:hostname) { "127.0.0.1" } let(:log) { WEBrick::Log.new(StringIO.new, WEBrick::Log::WARN) } let(:access_log_buffer) { StringIO.new } let(:access_log) { [[access_log_buffer, WEBrick::AccessLog::COMBINED_LOG_FORMAT]] } let(:server_proc) do proc do |req, res| messages << req.tap { req.body } # Read body, store message before socket closes. - res.body = '{}' + res.body = "{}" end end let(:init_signal) { Queue.new } @@ -160,7 +160,7 @@ let(:messages) { [] } before do - server.mount_proc('/', &server_proc) + server.mount_proc("/", &server_proc) @server_thread = Thread.new { server.start } init_signal.pop end @@ -176,7 +176,7 @@ end end - include_context 'HTTP server' + include_context "HTTP server" let(:request) { messages.first } let(:port) { server[:Port] } @@ -186,8 +186,8 @@ [:fiddle, :signal].each do |trigger| it "reports crashes via http when app crashes with #{trigger}" do fork_expectations = proc do |status:, stdout:, stderr:| - expect(Signal.signame(status.termsig)).to eq('SEGV').or eq('ABRT') - expect(stderr).to include('[BUG] Segmentation fault') + expect(Signal.signame(status.termsig)).to eq("SEGV").or eq("ABRT") + expect(stderr).to include("[BUG] Segmentation fault") end expect_in_fork(fork_expectations: fork_expectations) do @@ -196,22 +196,22 @@ if trigger == :fiddle Fiddle.free(42) else - Process.kill('SEGV', Process.pid) + Process.kill("SEGV", Process.pid) end end crash_report = JSON.parse(request.body, symbolize_names: true)[:payload].first expect(crash_report[:stack_trace]).to_not be_empty - expect(crash_report[:tags]).to include('signum:11', 'signame:SIGSEGV') + expect(crash_report[:tags]).to include("signum:11", "signame:SIGSEGV") crash_report_message = JSON.parse(crash_report[:message], symbolize_names: true) expect(crash_report_message[:metadata]).to include( - profiling_library_name: 'dd-trace-rb', + profiling_library_name: "dd-trace-rb", profiling_library_version: Datadog::VERSION::STRING, - family: 'ruby', - tags: ['tag1:value1', 'tag2:value2'], + family: "ruby", + tags: ["tag1:value1", "tag2:value2"], ) expect(crash_report_message[:files][:"/proc/self/maps"]).to_not be_empty expect(crash_report_message[:os_info]).to_not be_empty diff --git a/spec/datadog/profiling/exporter_spec.rb b/spec/datadog/profiling/exporter_spec.rb index 26ffce8d566..9e9ee93f69c 100644 --- a/spec/datadog/profiling/exporter_spec.rb +++ b/spec/datadog/profiling/exporter_spec.rb @@ -1,8 +1,8 @@ -require 'datadog/profiling/spec_helper' +require "datadog/profiling/spec_helper" -require 'datadog/profiling/exporter' -require 'datadog/profiling/collectors/code_provenance' -require 'datadog/core/logger' +require "datadog/profiling/exporter" +require "datadog/profiling/collectors/code_provenance" +require "datadog/core/logger" RSpec.describe Datadog::Profiling::Exporter do before { skip_if_profiling_not_supported(self) } @@ -20,9 +20,9 @@ let(:start) { Time.now } let(:finish) { start + 60 } - let(:pprof_data) { 'dummy pprof data' } - let(:profile_stats) { {stat1: 1, stat2: 'a string', stat3: true} } - let(:code_provenance_data) { 'dummy code provenance data' } + let(:pprof_data) { "dummy pprof data" } + let(:profile_stats) { {stat1: 1, stat2: "a string", stat3: true} } + let(:code_provenance_data) { "dummy code provenance data" } let(:pprof_recorder_serialize) { [start, finish, pprof_data, profile_stats] } let(:pprof_recorder) do instance_double(Datadog::Profiling::StackRecorder, serialize: pprof_recorder_serialize, stats: recorder_stats) @@ -30,7 +30,7 @@ let(:worker) do # TODO: Change this to a direct reference when we drop support for old Rubies which currently error if we try # to `require 'profiling/collectors/cpu_and_wall_time_worker'` - instance_double('Datadog::Profiling::Collectors::CpuAndWallTimeWorker', stats_and_reset_not_thread_safe: worker_stats) + instance_double("Datadog::Profiling::Collectors::CpuAndWallTimeWorker", stats_and_reset_not_thread_safe: worker_stats) end let(:code_provenance_collector) do collector = instance_double(Datadog::Profiling::Collectors::CodeProvenance, generate_json: code_provenance_data) @@ -56,16 +56,16 @@ } end - describe '#flush' do + describe "#flush" do subject(:flush) { exporter.flush } - it 'returns a flush containing the data from the recorders' do + it "returns a flush containing the data from the recorders" do expect(flush).to have_attributes( start: start, finish: finish, - pprof_file_name: 'rubyprofile.pprof', - code_provenance_file_name: 'code-provenance.json', - tags_as_array: array_including(%w[language ruby], ['process_id', Process.pid.to_s]), + pprof_file_name: "rubyprofile.pprof", + code_provenance_file_name: "code-provenance.json", + tags_as_array: array_including(%w[language ruby], ["process_id", Process.pid.to_s]), ) expect(flush.pprof_data).to eq pprof_data expect(flush.code_provenance_data).to eq code_provenance_data @@ -82,48 +82,48 @@ expect(JSON.parse(flush.info_json, symbolize_names: true)).to eq(info) end - context 'when pprof recorder has no data' do + context "when pprof recorder has no data" do let(:pprof_recorder_serialize) { nil } it { is_expected.to be nil } end - context 'when no code provenance collector was provided' do + context "when no code provenance collector was provided" do let(:code_provenance_collector) { nil } - it 'returns a flush with nil code_provenance_data' do + it "returns a flush with nil code_provenance_data" do expect(flush.code_provenance_data).to be nil end end - context 'when duration of profile is below 1s' do + context "when duration of profile is below 1s" do let(:finish) { start + 0.99 } before { allow(logger).to receive(:debug) } it { is_expected.to be nil } - it 'logs a debug message' do + it "logs a debug message" do expect(logger).to receive(:debug).with(/Skipped exporting/) flush end end - context 'when duration of profile is 1s or above' do + context "when duration of profile is 1s or above" do let(:finish) { start + 1 } it { is_expected.to_not be nil } end - context 'when no_signals_workaround_enabled is true' do + context "when no_signals_workaround_enabled is true" do let(:no_signals_workaround_enabled) { true } it { is_expected.to have_attributes(internal_metadata_json: a_string_matching('"no_signals_workaround_enabled":true')) } end - context 'when no_signals_workaround_enabled is false' do + context "when no_signals_workaround_enabled is false" do let(:no_signals_workaround_enabled) { false } it { is_expected.to have_attributes(internal_metadata_json: a_string_matching('"no_signals_workaround_enabled":false')) @@ -131,7 +131,7 @@ end end - describe '#reset_after_fork' do + describe "#reset_after_fork" do let(:dummy_current_time) { Time.new(2022) } let(:time_provider) { class_double(Time, now: dummy_current_time) } let(:options) { {**super(), time_provider: class_double(Time, now: dummy_current_time)} } @@ -140,12 +140,12 @@ it { is_expected.to be nil } - it 'sets the last_flush_finish_at to be the current time' do + it "sets the last_flush_finish_at to be the current time" do expect { reset_after_fork }.to change { exporter.send(:last_flush_finish_at) }.from(nil).to(dummy_current_time) end end - describe '#can_flush?' do + describe "#can_flush?" do let(:time_provider) { class_double(Time) } let(:created_at) { start - 60 } let(:options) { {**super(), time_provider: time_provider} } @@ -157,30 +157,30 @@ exporter end - context 'when exporter has flushed before' do + context "when exporter has flushed before" do before { exporter.flush } - context 'when less than 1s has elapsed since last flush' do + context "when less than 1s has elapsed since last flush" do before { expect(time_provider).to receive(:now).and_return(finish + 0.99).once } it { is_expected.to be false } end - context 'when 1s or more has elapsed since last flush' do + context "when 1s or more has elapsed since last flush" do before { expect(time_provider).to receive(:now).and_return(finish + 1).once } it { is_expected.to be true } end end - context 'when exporter has never flushed' do - context 'when less than 1s has elapsed since exporter was created' do + context "when exporter has never flushed" do + context "when less than 1s has elapsed since exporter was created" do before { expect(time_provider).to receive(:now).and_return(created_at + 0.99).once } it { is_expected.to be false } end - context 'when 1s or more has elapsed since exporter was created' do + context "when 1s or more has elapsed since exporter was created" do before { expect(time_provider).to receive(:now).and_return(created_at + 1).once } it { is_expected.to be true } diff --git a/spec/datadog/profiling/ext/dir_monkey_patches_spec.rb b/spec/datadog/profiling/ext/dir_monkey_patches_spec.rb index 475b2ed3385..7696c1031bd 100644 --- a/spec/datadog/profiling/ext/dir_monkey_patches_spec.rb +++ b/spec/datadog/profiling/ext/dir_monkey_patches_spec.rb @@ -1,7 +1,7 @@ -require 'datadog/profiling/spec_helper' +require "datadog/profiling/spec_helper" -require 'datadog/profiling/collectors/cpu_and_wall_time_worker' -require 'datadog/profiling/ext/dir_monkey_patches' +require "datadog/profiling/collectors/cpu_and_wall_time_worker" +require "datadog/profiling/ext/dir_monkey_patches" # NOTE: Specs in this file are written so as to not leave the DirMonkeyPatches loaded into the Ruby VM after this # test executes. They do this by only applying these monkey patches in a separate process. @@ -9,9 +9,9 @@ before do skip_if_profiling_not_supported(self) - File.write("#{temporary_directory}/file1", 'file1') - File.write("#{temporary_directory}/file2", 'file2') - File.write("#{temporary_directory}/file3", 'file3') + File.write("#{temporary_directory}/file1", "file1") + File.write("#{temporary_directory}/file2", "file2") + File.write("#{temporary_directory}/file3", "file3") expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to_not receive(:_native_hold_signals) expect(Datadog::Profiling::Collectors::CpuAndWallTimeWorker).to_not receive(:_native_resume_signals) @@ -27,44 +27,44 @@ # Do nothing, it's ok end - describe 'DirClassMonkeyPatches' do - describe '.[]' do - it 'matches the ruby behavior without monkey patching' do + describe "DirClassMonkeyPatches" do + describe ".[]" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(Dir['*1', '*2', base: temporary_directory]).to contain_exactly('file1', 'file2') + expect(Dir["*1", "*2", base: temporary_directory]).to contain_exactly("file1", "file2") end end end - describe '.children' do - it 'matches the ruby behavior without monkey patching' do + describe ".children" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - result = Dir.children(temporary_directory, encoding: 'US-ASCII').sort + result = Dir.children(temporary_directory, encoding: "US-ASCII").sort expect(result.first.encoding).to be Encoding::US_ASCII - expect(result.first).to eq 'file1' + expect(result.first).to eq "file1" end end end - describe '.each_child' do + describe ".each_child" do let(:expected_hold_resume_calls_count) { 1 + temporary_files_count } - context 'with a block' do - it 'matches the ruby behavior without monkey patching' do + context "with a block" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do files = [] - Dir.each_child(temporary_directory, encoding: 'UTF-8') { |it| files << it } + Dir.each_child(temporary_directory, encoding: "UTF-8") { |it| files << it } - expect(files).to contain_exactly('file1', 'file2', 'file3') + expect(files).to contain_exactly("file1", "file2", "file3") end end - it 'allows signals to arrive inside the user block' do + it "allows signals to arrive inside the user block" do test_with_monkey_patch do ran_assertion = false - Dir.each_child(temporary_directory, encoding: 'UTF-8') do + Dir.each_child(temporary_directory, encoding: "UTF-8") do expect_sigprof_to_be(:unblocked) ran_assertion = true end @@ -74,50 +74,50 @@ end end - context 'without a block' do - it 'matches the ruby behavior without monkey patching' do + context "without a block" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(Dir.each_child(temporary_directory, encoding: 'UTF-8').to_a).to include('file1', 'file2', 'file3') + expect(Dir.each_child(temporary_directory, encoding: "UTF-8").to_a).to include("file1", "file2", "file3") end end end end - describe '.empty?' do - it 'matches the ruby behavior without monkey patching' do + describe ".empty?" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do expect(Dir.empty?(temporary_directory)).to be false end end end - describe '.entries' do - it 'matches the ruby behavior without monkey patching' do + describe ".entries" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(Dir.entries(temporary_directory)).to contain_exactly('.', '..', 'file1', 'file2', 'file3') + expect(Dir.entries(temporary_directory)).to contain_exactly(".", "..", "file1", "file2", "file3") end end end - describe '.foreach' do - let(:expected_hold_resume_calls_count) { 1 + temporary_files_count + ['.', '..'].size } + describe ".foreach" do + let(:expected_hold_resume_calls_count) { 1 + temporary_files_count + [".", ".."].size } - context 'with a block' do - it 'matches the ruby behavior without monkey patching' do + context "with a block" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do files = [] - Dir.foreach(temporary_directory, encoding: 'UTF-8') { |it| files << it } + Dir.foreach(temporary_directory, encoding: "UTF-8") { |it| files << it } - expect(files).to contain_exactly('file1', 'file2', 'file3', '.', '..') + expect(files).to contain_exactly("file1", "file2", "file3", ".", "..") end end - it 'allows signals to arrive inside the user block' do + it "allows signals to arrive inside the user block" do test_with_monkey_patch do ran_assertion = false - Dir.foreach(temporary_directory, encoding: 'UTF-8') do + Dir.foreach(temporary_directory, encoding: "UTF-8") do expect_sigprof_to_be(:unblocked) ran_assertion = true end @@ -127,41 +127,41 @@ end end - context 'without a block' do - it 'matches the ruby behavior without monkey patching' do + context "without a block" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(Dir.foreach(temporary_directory, encoding: 'UTF-8').to_a) - .to include('file1', 'file2', 'file3', '.', '..') + expect(Dir.foreach(temporary_directory, encoding: "UTF-8").to_a) + .to include("file1", "file2", "file3", ".", "..") end end end end - describe '.glob' do + describe ".glob" do before do - File.write("#{temporary_directory}/.hidden_file1", '.hidden_file1') + File.write("#{temporary_directory}/.hidden_file1", ".hidden_file1") end - let(:expected_files_result) { ['.hidden_file1', 'file1', 'file2'] } + let(:expected_files_result) { [".hidden_file1", "file1", "file2"] } - context 'with a block' do + context "with a block" do let(:expected_hold_resume_calls_count) { 1 + expected_files_result.size } - it 'matches the ruby behavior without monkey patching' do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do files = [] - Dir.glob(['*1', '*2'], base: temporary_directory, flags: File::FNM_DOTMATCH) { |it| files << it } + Dir.glob(["*1", "*2"], base: temporary_directory, flags: File::FNM_DOTMATCH) { |it| files << it } expect(files).to contain_exactly(*expected_files_result) end end - it 'allows signals to arrive inside the user block' do + it "allows signals to arrive inside the user block" do test_with_monkey_patch do ran_assertion = false - Dir.glob(['*1', '*2'], base: temporary_directory, flags: File::FNM_DOTMATCH) do + Dir.glob(["*1", "*2"], base: temporary_directory, flags: File::FNM_DOTMATCH) do expect_sigprof_to_be(:unblocked) ran_assertion = true end @@ -171,7 +171,7 @@ end end - context 'without a block' do + context "without a block" do # You may be wondering why this one has a call count of 1 when for instance .foreach and each_child have a call # count of > 1. The difference is the "without a block" versions of those calls **return an enumerator** and # the enumerator then just calls the block version when executed. @@ -180,42 +180,42 @@ # does not get turned into a "with a block" call. let(:expected_hold_resume_calls_count) { 1 } - it 'matches the ruby behavior without monkey patching' do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(Dir.glob(['*1', '*2'], base: temporary_directory, flags: File::FNM_DOTMATCH)) + expect(Dir.glob(["*1", "*2"], base: temporary_directory, flags: File::FNM_DOTMATCH)) .to contain_exactly(*expected_files_result) end end end end - describe '.home' do - it 'matches the ruby behavior without monkey patching' do + describe ".home" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(Dir.home).to start_with('/') + expect(Dir.home).to start_with("/") end end end end - describe 'DirInstanceMonkeyPatches' do + describe "DirInstanceMonkeyPatches" do let(:dir) { Dir.new(temporary_directory) } - describe '#each' do - let(:expected_hold_resume_calls_count) { 1 + temporary_files_count + ['.', '..'].size } + describe "#each" do + let(:expected_hold_resume_calls_count) { 1 + temporary_files_count + [".", ".."].size } - context 'with a block' do - it 'matches the ruby behavior without monkey patching' do + context "with a block" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do files = [] dir.each { |it| files << it } - expect(files).to contain_exactly('file1', 'file2', 'file3', '.', '..') + expect(files).to contain_exactly("file1", "file2", "file3", ".", "..") end end - it 'allows signals to arrive inside the user block' do + it "allows signals to arrive inside the user block" do test_with_monkey_patch do ran_assertion = false @@ -229,32 +229,32 @@ end end - context 'without a block' do - it 'matches the ruby behavior without monkey patching' do + context "without a block" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(dir.each.to_a).to contain_exactly('file1', 'file2', 'file3', '.', '..') + expect(dir.each.to_a).to contain_exactly("file1", "file2", "file3", ".", "..") end end end end - describe '#each_child' do - before { skip('API not available on Ruby 2.5') if RUBY_VERSION.start_with?('2.5.') } + describe "#each_child" do + before { skip("API not available on Ruby 2.5") if RUBY_VERSION.start_with?("2.5.") } let(:expected_hold_resume_calls_count) { 1 + temporary_files_count } - context 'with a block' do - it 'matches the ruby behavior without monkey patching' do + context "with a block" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do files = [] dir.each_child { |it| files << it } - expect(files).to contain_exactly('file1', 'file2', 'file3') + expect(files).to contain_exactly("file1", "file2", "file3") end end - it 'allows signals to arrive inside the user block' do + it "allows signals to arrive inside the user block" do test_with_monkey_patch do ran_assertion = false @@ -268,35 +268,35 @@ end end - context 'without a block' do - it 'matches the ruby behavior without monkey patching' do + context "without a block" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(dir.each_child.to_a).to include('file1', 'file2', 'file3') + expect(dir.each_child.to_a).to include("file1", "file2", "file3") end end end end - describe '#children' do - before { skip('API not available on Ruby 2.5') if RUBY_VERSION.start_with?('2.5.') } + describe "#children" do + before { skip("API not available on Ruby 2.5") if RUBY_VERSION.start_with?("2.5.") } - it 'matches the ruby behavior without monkey patching' do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do - expect(dir.children).to contain_exactly('file1', 'file2', 'file3') + expect(dir.children).to contain_exactly("file1", "file2", "file3") end end end - describe '#tell' do - it 'matches the ruby behavior without monkey patching' do + describe "#tell" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do expect(dir.tell).to be_a_kind_of(Integer) end end end - describe '#pos' do - it 'matches the ruby behavior without monkey patching' do + describe "#pos" do + it "matches the ruby behavior without monkey patching" do test_with_and_without_monkey_patch do expect(dir.pos).to be_a_kind_of(Integer) end diff --git a/spec/datadog/profiling/flush_spec.rb b/spec/datadog/profiling/flush_spec.rb index d64e2eedf76..419a796fd91 100644 --- a/spec/datadog/profiling/flush_spec.rb +++ b/spec/datadog/profiling/flush_spec.rb @@ -1,23 +1,23 @@ -require 'datadog/profiling/flush' +require "datadog/profiling/flush" RSpec.describe Datadog::Profiling::Flush do - describe '.new' do - let(:start) { instance_double(Time, 'start time') } - let(:finish) { instance_double(Time, 'finish time') } - let(:pprof_file_name) { 'the_pprof_file_name.pprof' } - let(:pprof_data) { 'the_pprof_data' } - let(:code_provenance_file_name) { 'the_code_provenance_file_name.json' } - let(:code_provenance_data) { 'the_code_provenance_data' } + describe ".new" do + let(:start) { instance_double(Time, "start time") } + let(:finish) { instance_double(Time, "finish time") } + let(:pprof_file_name) { "the_pprof_file_name.pprof" } + let(:pprof_data) { "the_pprof_data" } + let(:code_provenance_file_name) { "the_code_provenance_file_name.json" } + let(:code_provenance_data) { "the_code_provenance_data" } let(:tags_as_array) { [%w[tag_a value_a], %w[tag_b value_b]] } let(:internal_metadata) { {no_signals_workaround_enabled: false} } let(:info_json) do JSON.fast_generate( { application: { - start_time: '2024-01-24T11:17:22Z' + start_time: "2024-01-24T11:17:22Z" }, runtime: { - engine: 'ruby' + engine: "ruby" }, } ) diff --git a/spec/datadog/profiling/http_transport_spec.rb b/spec/datadog/profiling/http_transport_spec.rb index 6d2494876b6..de96bf87ebc 100644 --- a/spec/datadog/profiling/http_transport_spec.rb +++ b/spec/datadog/profiling/http_transport_spec.rb @@ -1,11 +1,11 @@ -require 'datadog/profiling/spec_helper' +require "datadog/profiling/spec_helper" -require 'datadog/profiling/http_transport' -require 'datadog/profiling' +require "datadog/profiling/http_transport" +require "datadog/profiling" -require 'json' -require 'socket' -require 'webrick' +require "json" +require "socket" +require "webrick" # Design note for this class's specs: from the Ruby code side, we're treating the `_native_` methods as an API # between the Ruby code and the native methods, and thus in this class we have a bunch of tests to make sure the @@ -38,8 +38,8 @@ let(:adapter) { Datadog::Core::Configuration::Ext::Agent::HTTP::ADAPTER } let(:uds_path) { nil } let(:ssl) { false } - let(:hostname) { '192.168.0.1' } - let(:port) { '12345' } + let(:hostname) { "192.168.0.1" } + let(:port) { "12345" } let(:site) { nil } let(:api_key) { nil } let(:upload_timeout_seconds) { 10 } @@ -57,23 +57,23 @@ info_json: info_json, ) end - let(:start_timestamp) { '2022-02-07T15:59:53.987654321Z' } - let(:end_timestamp) { '2023-11-11T16:00:00.123456789Z' } + let(:start_timestamp) { "2022-02-07T15:59:53.987654321Z" } + let(:end_timestamp) { "2023-11-11T16:00:00.123456789Z" } let(:start) { Time.iso8601(start_timestamp) } let(:finish) { Time.iso8601(end_timestamp) } - let(:pprof_file_name) { 'the_pprof_file_name.pprof' } - let(:pprof_data) { 'the_pprof_data' } - let(:code_provenance_file_name) { 'the_code_provenance_file_name.json' } - let(:code_provenance_data) { 'the_code_provenance_data' } + let(:pprof_file_name) { "the_pprof_file_name.pprof" } + let(:pprof_data) { "the_pprof_data" } + let(:code_provenance_file_name) { "the_code_provenance_file_name.json" } + let(:code_provenance_data) { "the_code_provenance_data" } let(:tags_as_array) { [%w[tag_a value_a], %w[tag_b value_b]] } let(:info_json) do JSON.fast_generate( { application: { - start_time: '2024-01-24T11:17:22Z' + start_time: "2024-01-24T11:17:22Z" }, runtime: { - engine: 'ruby' + engine: "ruby" }, } ) @@ -81,47 +81,47 @@ # Like above but with string keys (JSON parsing unsymbolizes keys by default) let(:info_string_keys) do { - 'application' => { - 'start_time' => '2024-01-24T11:17:22Z' + "application" => { + "start_time" => "2024-01-24T11:17:22Z" }, - 'runtime' => { - 'engine' => 'ruby' + "runtime" => { + "engine" => "ruby" }, } end - describe '#initialize' do - context 'when agent_settings are provided' do - it 'picks the :agent working mode for the exporter' do + describe "#initialize" do + context "when agent_settings are provided" do + it "picks the :agent working mode for the exporter" do expect(described_class) .to receive(:_native_validate_exporter) - .with([:agent, 'http://192.168.0.1:12345/']) + .with([:agent, "http://192.168.0.1:12345/"]) .and_return([:ok, nil]) http_transport end - context 'when ssl is enabled' do + context "when ssl is enabled" do let(:ssl) { true } - it 'picks the :agent working mode with https reporting' do + it "picks the :agent working mode with https reporting" do expect(described_class) .to receive(:_native_validate_exporter) - .with([:agent, 'https://192.168.0.1:12345/']) + .with([:agent, "https://192.168.0.1:12345/"]) .and_return([:ok, nil]) http_transport end end - context 'when agent_settings requests a unix domain socket' do + context "when agent_settings requests a unix domain socket" do let(:adapter) { Datadog::Core::Transport::Ext::UnixSocket::ADAPTER } - let(:uds_path) { '/var/run/datadog/apm.socket' } + let(:uds_path) { "/var/run/datadog/apm.socket" } - it 'picks the :agent working mode with unix domain stocket reporting' do + it "picks the :agent working mode with unix domain stocket reporting" do expect(described_class) .to receive(:_native_validate_exporter) - .with([:agent, 'unix:///var/run/datadog/apm.socket']) + .with([:agent, "unix:///var/run/datadog/apm.socket"]) .and_return([:ok, nil]) http_transport @@ -129,27 +129,27 @@ end end - context 'when additionally site and api_key are provided' do - let(:site) { 'test.datadoghq.com' } + context "when additionally site and api_key are provided" do + let(:site) { "test.datadoghq.com" } let(:api_key) { SecureRandom.uuid } - it 'ignores them and picks the :agent working mode using the agent_settings' do + it "ignores them and picks the :agent working mode using the agent_settings" do expect(described_class) .to receive(:_native_validate_exporter) - .with([:agent, 'http://192.168.0.1:12345/']) + .with([:agent, "http://192.168.0.1:12345/"]) .and_return([:ok, nil]) http_transport end - context 'when agentless mode is allowed' do + context "when agentless mode is allowed" do around do |example| - ClimateControl.modify('DD_PROFILING_AGENTLESS' => 'true') do + ClimateControl.modify("DD_PROFILING_AGENTLESS" => "true") do example.run end end - it 'picks the :agentless working mode with the given site and api key' do + it "picks the :agentless working mode with the given site and api key" do expect(described_class) .to receive(:_native_validate_exporter) .with([:agentless, site, api_key]) @@ -160,8 +160,8 @@ end end - context 'when an invalid configuration is provided' do - let(:hostname) { 'this:is:not:a:valid:hostname!!!!' } + context "when an invalid configuration is provided" do + let(:hostname) { "this:is:not:a:valid:hostname!!!!" } it do expect { http_transport }.to raise_error(ArgumentError, /Failed to initialize transport/) @@ -169,10 +169,10 @@ end end - describe '#export' do + describe "#export" do subject(:export) { http_transport.export(flush) } - it 'calls the native export method with the data from the flush' do + it "calls the native export method with the data from the flush" do # Manually converted from the lets above :) upload_timeout_milliseconds = 10_000 start_timespec_seconds = 1644249593 @@ -203,13 +203,13 @@ export end - context 'when successful' do + context "when successful" do before do expect(described_class).to receive(:_native_do_export).and_return([:ok, 200]) end - it 'logs a debug message' do - expect(Datadog.logger).to receive(:debug).with('Successfully reported profiling data') + it "logs a debug message" do + expect(Datadog.logger).to receive(:debug).with("Successfully reported profiling data") export end @@ -217,17 +217,17 @@ it { is_expected.to be true } end - context 'when failed' do - context 'with a http status code' do + context "when failed" do + context "with a http status code" do before do expect(described_class).to receive(:_native_do_export).and_return([:ok, 500]) allow(Datadog.logger).to receive(:error) end - it 'logs an error message' do + it "logs an error message" do expect(Datadog.logger).to receive(:error).with( 'Failed to report profiling data ({:agent=>"http://192.168.0.1:12345/"}): ' \ - 'server returned unexpected HTTP 500 status code' + "server returned unexpected HTTP 500 status code" ) export @@ -236,13 +236,13 @@ it { is_expected.to be false } end - context 'with a failure without an http status code' do + context "with a failure without an http status code" do before do - expect(described_class).to receive(:_native_do_export).and_return([:error, 'Some error message']) + expect(described_class).to receive(:_native_do_export).and_return([:error, "Some error message"]) allow(Datadog.logger).to receive(:error) end - it 'logs an error message' do + it "logs an error message" do expect(Datadog.logger).to receive(:error) .with('Failed to report profiling data ({:agent=>"http://192.168.0.1:12345/"}): Some error message') @@ -254,39 +254,39 @@ end end - describe '#config_without_api_key' do + describe "#config_without_api_key" do subject(:config_without_api_key) { http_transport.send(:config_without_api_key) } - context 'when using agentless mode' do - let(:site) { 'test.datadoghq.com' } + context "when using agentless mode" do + let(:site) { "test.datadoghq.com" } let(:api_key) { SecureRandom.uuid } around do |example| - ClimateControl.modify('DD_PROFILING_AGENTLESS' => 'true') do + ClimateControl.modify("DD_PROFILING_AGENTLESS" => "true") do example.run end end - it 'returns the mode and site, but not the api key' do - is_expected.to eq(agentless: 'test.datadoghq.com') + it "returns the mode and site, but not the api key" do + is_expected.to eq(agentless: "test.datadoghq.com") end end - context 'when using agent mode' do - it 'returns the mode the agent url' do - is_expected.to eq(agent: 'http://192.168.0.1:12345/') + context "when using agent mode" do + it "returns the mode the agent url" do + is_expected.to eq(agent: "http://192.168.0.1:12345/") end end end - describe '#exporter_configuration' do - it 'returns the current exporter configuration' do - expect(http_transport.exporter_configuration).to eq [:agent, 'http://192.168.0.1:12345/'] + describe "#exporter_configuration" do + it "returns the current exporter configuration" do + expect(http_transport.exporter_configuration).to eq [:agent, "http://192.168.0.1:12345/"] end end - context 'integration testing' do - shared_context 'HTTP server' do + context "integration testing" do + shared_context "HTTP server" do let(:server) do WEBrick::HTTPServer.new( Port: 0, @@ -295,14 +295,14 @@ StartCallback: -> { init_signal.push(1) } ) end - let(:hostname) { '127.0.0.1' } + let(:hostname) { "127.0.0.1" } let(:log) { WEBrick::Log.new($stderr, WEBrick::Log::WARN) } let(:access_log_buffer) { StringIO.new } let(:access_log) { [[access_log_buffer, WEBrick::AccessLog::COMBINED_LOG_FORMAT]] } let(:server_proc) do proc do |req, res| messages << req.tap { req.body } # Read body, store message before socket closes. - res.body = '{}' + res.body = "{}" end end let(:init_signal) { Queue.new } @@ -310,7 +310,7 @@ let(:messages) { [] } before do - server.mount_proc('/', &server_proc) + server.mount_proc("/", &server_proc) @server_thread = Thread.new { server.start } init_signal.pop end @@ -326,49 +326,49 @@ end end - include_context 'HTTP server' + include_context "HTTP server" let(:request) { messages.first } - let(:hostname) { '127.0.0.1' } + let(:hostname) { "127.0.0.1" } let(:port) { server[:Port] } - shared_examples 'correctly reports profiling data' do - it 'correctly reports profiling data' do + shared_examples "correctly reports profiling data" do + it "correctly reports profiling data" do success = http_transport.export(flush) expect(success).to be true expect(request.header).to include( - 'content-type' => [%r{^multipart/form-data; boundary=(.+)}], - 'dd-evp-origin' => ['dd-trace-rb'], - 'dd-evp-origin-version' => [Datadog::VERSION::STRING], + "content-type" => [%r{^multipart/form-data; boundary=(.+)}], + "dd-evp-origin" => ["dd-trace-rb"], + "dd-evp-origin-version" => [Datadog::VERSION::STRING], ) # check body - boundary = request['content-type'][%r{^multipart/form-data; boundary=(.+)}, 1] + boundary = request["content-type"][%r{^multipart/form-data; boundary=(.+)}, 1] body = WEBrick::HTTPUtils.parse_form_data(StringIO.new(request.body), boundary) - event_data = JSON.parse(body.fetch('event')) + event_data = JSON.parse(body.fetch("event")) expect(event_data).to match( - 'attachments' => contain_exactly(pprof_file_name, code_provenance_file_name), - 'tags_profiler' => 'tag_a:value_a,tag_b:value_b', - 'start' => start_timestamp, - 'end' => end_timestamp, - 'family' => 'ruby', - 'version' => '4', - 'endpoint_counts' => nil, - 'internal' => {'no_signals_workaround_enabled' => true}, - 'info' => info_string_keys, + "attachments" => contain_exactly(pprof_file_name, code_provenance_file_name), + "tags_profiler" => "tag_a:value_a,tag_b:value_b", + "start" => start_timestamp, + "end" => end_timestamp, + "family" => "ruby", + "version" => "4", + "endpoint_counts" => nil, + "internal" => {"no_signals_workaround_enabled" => true}, + "info" => info_string_keys, ) end - it 'reports the payload as lz4-compressed files, that get automatically compressed by libdatadog' do + it "reports the payload as lz4-compressed files, that get automatically compressed by libdatadog" do success = http_transport.export(flush) expect(success).to be true - boundary = request['content-type'][%r{^multipart/form-data; boundary=(.+)}, 1] + boundary = request["content-type"][%r{^multipart/form-data; boundary=(.+)}, 1] body = WEBrick::HTTPUtils.parse_form_data(StringIO.new(request.body), boundary) # The pprof data is compressed in the datadog serializer, nothing to do @@ -378,44 +378,44 @@ end end - include_examples 'correctly reports profiling data' + include_examples "correctly reports profiling data" - it 'exports data via http to the agent url' do + it "exports data via http to the agent url" do http_transport.export(flush) expect(request.request_uri.to_s).to eq "http://127.0.0.1:#{port}/profiling/v1/input" end - context 'when code provenance data is not available' do + context "when code provenance data is not available" do let(:code_provenance_data) { nil } - it 'correctly reports profiling data but does not include code provenance' do + it "correctly reports profiling data but does not include code provenance" do success = http_transport.export(flush) expect(success).to be true # check body - boundary = request['content-type'][%r{^multipart/form-data; boundary=(.+)}, 1] + boundary = request["content-type"][%r{^multipart/form-data; boundary=(.+)}, 1] body = WEBrick::HTTPUtils.parse_form_data(StringIO.new(request.body), boundary) - event_data = JSON.parse(body.fetch('event')) + event_data = JSON.parse(body.fetch("event")) expect(event_data).to eq( - 'attachments' => [pprof_file_name], - 'tags_profiler' => 'tag_a:value_a,tag_b:value_b', - 'start' => start_timestamp, - 'end' => end_timestamp, - 'family' => 'ruby', - 'version' => '4', - 'endpoint_counts' => nil, - 'internal' => {'no_signals_workaround_enabled' => true}, - 'info' => info_string_keys, + "attachments" => [pprof_file_name], + "tags_profiler" => "tag_a:value_a,tag_b:value_b", + "start" => start_timestamp, + "end" => end_timestamp, + "family" => "ruby", + "version" => "4", + "endpoint_counts" => nil, + "internal" => {"no_signals_workaround_enabled" => true}, + "info" => info_string_keys, ) expect(body[code_provenance_file_name]).to be nil end end - context 'via unix domain socket' do + context "via unix domain socket" do let(:temporary_directory) { Dir.mktmpdir } let(:socket_path) { "#{temporary_directory}/rspec_unix_domain_socket" } let(:unix_domain_socket) { UNIXServer.new(socket_path) } # Closing the socket is handled by webrick @@ -438,80 +438,80 @@ # Do nothing, it's ok end - include_examples 'correctly reports profiling data' + include_examples "correctly reports profiling data" end - context 'when agent is down' do + context "when agent is down" do before do server.shutdown @server_thread.join end - it 'logs an error' do + it "logs an error" do expect(Datadog.logger).to receive(:error).with(/error trying to connect/) http_transport.export(flush) end end - context 'when request times out' do + context "when request times out" do let(:upload_timeout_seconds) { 0.001 } let(:server_proc) { proc { sleep 0.05 } } - it 'logs an error' do + it "logs an error" do expect(Datadog.logger).to receive(:error).with(/timed out/) http_transport.export(flush) end end - context 'when server returns a 4xx failure' do + context "when server returns a 4xx failure" do let(:server_proc) { proc { |_req, res| res.status = 418 } } - it 'logs an error' do + it "logs an error" do expect(Datadog.logger).to receive(:error).with(/unexpected HTTP 418/) http_transport.export(flush) end end - context 'when server returns a 5xx failure' do + context "when server returns a 5xx failure" do let(:server_proc) { proc { |_req, res| res.status = 503 } } - it 'logs an error' do + it "logs an error" do expect(Datadog.logger).to receive(:error).with(/unexpected HTTP 503/) http_transport.export(flush) end end - context 'when tags contains invalid tags' do + context "when tags contains invalid tags" do let(:tags_as_array) { [%w[:invalid invalid:], %w[valid1 valid1], %w[valid2 valid2]] } before do allow(Datadog.logger).to receive(:warn) end - it 'reports using the valid tags and ignores the invalid tags' do + it "reports using the valid tags and ignores the invalid tags" do success = http_transport.export(flush) expect(success).to be true - boundary = request['content-type'][%r{^multipart/form-data; boundary=(.+)}, 1] + boundary = request["content-type"][%r{^multipart/form-data; boundary=(.+)}, 1] body = WEBrick::HTTPUtils.parse_form_data(StringIO.new(request.body), boundary) - event_data = JSON.parse(body.fetch('event')) + event_data = JSON.parse(body.fetch("event")) - expect(event_data['tags_profiler']).to eq 'valid1:valid1,valid2:valid2' + expect(event_data["tags_profiler"]).to eq "valid1:valid1,valid2:valid2" end - it 'logs a warning' do + it "logs a warning" do expect(Datadog.logger).to receive(:warn).with(/Failed to convert tag/) http_transport.export(flush) end end - describe 'cancellation behavior' do + describe "cancellation behavior" do let!(:request_received_queue) { Queue.new } let!(:request_finish_queue) { Queue.new } @@ -532,11 +532,11 @@ # nothing we could do on the Ruby VM side will interrupt it. # If it is correctly implemented, then the `exporter_thread.kill` will cause # `ddog_ProfileExporter_send` to return immediately and this test will quickly finish. - it 'can be interrupted' do + it "can be interrupted" do exporter_thread = Thread.new { http_transport.export(flush) } request_received_queue.pop - expect(exporter_thread.status).to eq 'sleep' + expect(exporter_thread.status).to eq "sleep" exporter_thread.kill exporter_thread.join diff --git a/spec/datadog/profiling/load_native_extension_spec.rb b/spec/datadog/profiling/load_native_extension_spec.rb index 7c131021e05..51873b1241d 100644 --- a/spec/datadog/profiling/load_native_extension_spec.rb +++ b/spec/datadog/profiling/load_native_extension_spec.rb @@ -1,32 +1,32 @@ -require 'spec_helper' -require 'datadog/profiling/spec_helper' +require "spec_helper" +require "datadog/profiling/spec_helper" -RSpec.describe 'Datadog::Profiling load_native_extension' do +RSpec.describe "Datadog::Profiling load_native_extension" do before { skip_if_profiling_not_supported(self) } subject(:load_native_extension) do load "#{__dir__}/../../../lib/datadog/profiling/load_native_extension.rb" end - context 'when native extension can be found inside lib' do - it 'loads the native extension from lib/' do + context "when native extension can be found inside lib" do + it "loads the native extension from lib/" do expect(Datadog::Profiling::Loader).to receive(:_native_load) do |full_file_path| absolute_path = File.absolute_path(full_file_path) - expect(absolute_path).to include('lib/datadog_profiling_native_extension') + expect(absolute_path).to include("lib/datadog_profiling_native_extension") end load_native_extension end end - context 'when native extension cannot be found inside lib' do - let(:extension_dir) { Gem.loaded_specs['datadog'].extension_dir } + context "when native extension cannot be found inside lib" do + let(:extension_dir) { Gem.loaded_specs["datadog"].extension_dir } before do expect(File).to receive(:exist?) do |full_file_path| absolute_path = File.absolute_path(full_file_path) - if absolute_path.include?('lib/datadog_profiling_native_extension') + if absolute_path.include?("lib/datadog_profiling_native_extension") false elsif absolute_path.include?(extension_dir) true @@ -36,7 +36,7 @@ end.twice end - it 'loads the native extension from the extension dir' do + it "loads the native extension from the extension dir" do expect(Datadog::Profiling::Loader).to receive(:_native_load) do |full_file_path| absolute_path = File.absolute_path(full_file_path) expect(absolute_path).to include(extension_dir) @@ -46,24 +46,24 @@ end end - context 'when native extension cannot be found on either directory' do + context "when native extension cannot be found on either directory" do before do expect(File).to receive(:exist?).twice.and_return(false) end - it 'tries to load the native extension from lib/' do + it "tries to load the native extension from lib/" do expect(Datadog::Profiling::Loader).to receive(:_native_load) do |full_file_path| absolute_path = File.absolute_path(full_file_path) - expect(absolute_path).to include('lib/datadog_profiling_native_extension') + expect(absolute_path).to include("lib/datadog_profiling_native_extension") end load_native_extension end end - context 'when the loader reports an error' do - it 'raises an exception' do - expect(Datadog::Profiling::Loader).to receive(:_native_load).and_return([:error, 'some error']) + context "when the loader reports an error" do + it "raises an exception" do + expect(Datadog::Profiling::Loader).to receive(:_native_load).and_return([:error, "some error"]) expect do load_native_extension diff --git a/spec/datadog/profiling/native_extension_helpers_spec.rb b/spec/datadog/profiling/native_extension_helpers_spec.rb index 062d0e07bc2..0cd8a76edf6 100644 --- a/spec/datadog/profiling/native_extension_helpers_spec.rb +++ b/spec/datadog/profiling/native_extension_helpers_spec.rb @@ -1,36 +1,36 @@ -require 'ext/datadog_profiling_native_extension/native_extension_helpers' -require 'ext/libdatadog_extconf_helpers' -require 'libdatadog' -require 'datadog/profiling/spec_helper' +require "ext/datadog_profiling_native_extension/native_extension_helpers" +require "ext/libdatadog_extconf_helpers" +require "libdatadog" +require "datadog/profiling/spec_helper" # TODO: This should be extracted out once the test suite setup is updated to build libdatadog_api separate from profiling RSpec.describe Datadog::LibdatadogExtconfHelpers do - describe '.libdatadog_folder_relative_to_native_lib_folder' do + describe ".libdatadog_folder_relative_to_native_lib_folder" do let(:extension_folder) { "#{__dir__}/../../../ext/datadog_profiling_native_extension/." } - context 'when libdatadog is available' do + context "when libdatadog is available" do before do skip_if_profiling_not_supported(self) - if PlatformHelpers.mac? && Libdatadog.pkgconfig_folder.nil? && ENV['LIBDATADOG_VENDOR_OVERRIDE'].nil? - raise 'You have a libdatadog setup without macOS support. Did you forget to set LIBDATADOG_VENDOR_OVERRIDE?' + if PlatformHelpers.mac? && Libdatadog.pkgconfig_folder.nil? && ENV["LIBDATADOG_VENDOR_OVERRIDE"].nil? + raise "You have a libdatadog setup without macOS support. Did you forget to set LIBDATADOG_VENDOR_OVERRIDE?" end end - it 'returns a relative path to libdatadog folder from the gem lib folder' do + it "returns a relative path to libdatadog folder from the gem lib folder" do relative_path = described_class.libdatadog_folder_relative_to_native_lib_folder(current_folder: extension_folder) - libdatadog_extension = RbConfig::CONFIG['SOEXT'] || raise('Missing SOEXT for current platform') + libdatadog_extension = RbConfig::CONFIG["SOEXT"] || raise("Missing SOEXT for current platform") gem_lib_folder = "#{Gem.loaded_specs["datadog"].gem_dir}/lib" full_libdatadog_path = "#{gem_lib_folder}/#{relative_path}/libdatadog_profiling.#{libdatadog_extension}" - expect(relative_path).to start_with('../') + expect(relative_path).to start_with("../") expect(File.exist?(full_libdatadog_path)) .to be(true), "Libdatadog not available in expected path: #{full_libdatadog_path.inspect}" end end - context 'when libdatadog is unsupported' do + context "when libdatadog is unsupported" do it do expect( described_class.libdatadog_folder_relative_to_native_lib_folder( @@ -42,24 +42,24 @@ end end - describe '.libdatadog_folder_relative_to_ruby_extensions_folders' do - context 'when libdatadog is available' do + describe ".libdatadog_folder_relative_to_ruby_extensions_folders" do + context "when libdatadog is available" do before do skip_if_profiling_not_supported(self) - if PlatformHelpers.mac? && Libdatadog.pkgconfig_folder.nil? && ENV['LIBDATADOG_VENDOR_OVERRIDE'].nil? - raise 'You have a libdatadog setup without macOS support. Did you forget to set LIBDATADOG_VENDOR_OVERRIDE?' + if PlatformHelpers.mac? && Libdatadog.pkgconfig_folder.nil? && ENV["LIBDATADOG_VENDOR_OVERRIDE"].nil? + raise "You have a libdatadog setup without macOS support. Did you forget to set LIBDATADOG_VENDOR_OVERRIDE?" end end - it 'returns a relative path to libdatadog folder from the ruby extensions folders' do + it "returns a relative path to libdatadog folder from the ruby extensions folders" do extensions_relative, bundler_extensions_relative = described_class.libdatadog_folder_relative_to_ruby_extensions_folders - libdatadog_extension = RbConfig::CONFIG['SOEXT'] || raise('Missing SOEXT for current platform') + libdatadog_extension = RbConfig::CONFIG["SOEXT"] || raise("Missing SOEXT for current platform") libdatadog = "libdatadog_profiling.#{libdatadog_extension}" - expect(extensions_relative).to start_with('../') - expect(bundler_extensions_relative).to start_with('../') + expect(extensions_relative).to start_with("../") + expect(bundler_extensions_relative).to start_with("../") extensions_full = "#{Gem.dir}/extensions/platform/extension_api_version/datadog_version/#{extensions_relative}/#{libdatadog}" @@ -74,7 +74,7 @@ end end - context 'when libdatadog is unsupported' do + context "when libdatadog is unsupported" do it do expect( described_class.libdatadog_folder_relative_to_ruby_extensions_folders(libdatadog_pkgconfig_folder: nil) @@ -83,24 +83,24 @@ end end - describe '::LIBDATADOG_VERSION' do - it 'must match the version restriction set on the gemspec' do + describe "::LIBDATADOG_VERSION" do + it "must match the version restriction set on the gemspec" do # This test is expected to break when the libdatadog version on the .gemspec is updated but we forget to update # the version on the `libdatadog_extconf_helpers.rb` file. Kindly keep them in sync! :) expect(described_class::LIBDATADOG_VERSION).to eq( - Gem.loaded_specs['datadog'].dependencies.find { |dependency| dependency.name == 'libdatadog' }.requirement.to_s + Gem.loaded_specs["datadog"].dependencies.find { |dependency| dependency.name == "libdatadog" }.requirement.to_s ) end end - describe '.pkg_config_missing?' do + describe ".pkg_config_missing?" do subject(:pkg_config_missing) { described_class.pkg_config_missing?(command: command) } before do skip_if_profiling_not_supported(self) end - context 'when command is not available' do + context "when command is not available" do let(:command) { nil } it { is_expected.to be true } @@ -112,7 +112,7 @@ # supported (and thus `skip_if_profiling_not_supported` would've been triggered). # # We could also mock the entire interaction, but this seemed like a simple enough way to go. - context 'when command is available' do + context "when command is available" do before do # This helper is designed to be called from extconf.rb, which requires mkmf, which defines xsystem. # When executed in RSpec, mkmf is not required, so we replace it with the regular system call. @@ -121,14 +121,14 @@ end end - context 'and pkg-config can successfully be called' do - let(:command) { 'pkg-config' } + context "and pkg-config can successfully be called" do + let(:command) { "pkg-config" } it { is_expected.to be false } end - context 'and pkg-config cannot be called' do - let(:command) { 'does-not-exist' } + context "and pkg-config cannot be called" do + let(:command) { "does-not-exist" } it { is_expected.to be true } end @@ -137,23 +137,23 @@ end RSpec.describe Datadog::Profiling::NativeExtensionHelpers::Supported do - describe '.supported?' do + describe ".supported?" do subject(:supported?) { described_class.supported? } - context 'when there is an unsupported_reason' do - before { allow(described_class).to receive(:unsupported_reason).and_return('Unsupported, sorry :(') } + context "when there is an unsupported_reason" do + before { allow(described_class).to receive(:unsupported_reason).and_return("Unsupported, sorry :(") } it { is_expected.to be false } end - context 'when there is no unsupported_reason' do + context "when there is no unsupported_reason" do before { allow(described_class).to receive(:unsupported_reason).and_return(nil) } it { is_expected.to be true } end end - describe '.unsupported_reason' do + describe ".unsupported_reason" do subject(:unsupported_reason) do reason = described_class.unsupported_reason reason&.fetch(:reason)&.join("\n") @@ -163,140 +163,140 @@ allow(RbConfig::CONFIG).to receive(:[]).and_call_original end - context 'when disabled via the DD_PROFILING_NO_EXTENSION environment variable' do - around { |example| ClimateControl.modify('DD_PROFILING_NO_EXTENSION' => 'true') { example.run } } + context "when disabled via the DD_PROFILING_NO_EXTENSION environment variable" do + around { |example| ClimateControl.modify("DD_PROFILING_NO_EXTENSION" => "true") { example.run } } - it { is_expected.to include 'DD_PROFILING_NO_EXTENSION' } + it { is_expected.to include "DD_PROFILING_NO_EXTENSION" } end - context 'when JRuby is used' do - before { stub_const('RUBY_ENGINE', 'jruby') } + context "when JRuby is used" do + before { stub_const("RUBY_ENGINE", "jruby") } - it { is_expected.to include 'JRuby' } + it { is_expected.to include "JRuby" } end - context 'when TruffleRuby is used' do - before { stub_const('RUBY_ENGINE', 'truffleruby') } + context "when TruffleRuby is used" do + before { stub_const("RUBY_ENGINE", "truffleruby") } - it { is_expected.to include 'TruffleRuby' } + it { is_expected.to include "TruffleRuby" } end - context 'when not on JRuby or TruffleRuby' do - before { stub_const('RUBY_ENGINE', 'ruby') } + context "when not on JRuby or TruffleRuby" do + before { stub_const("RUBY_ENGINE", "ruby") } - context 'when on Windows' do + context "when on Windows" do before { expect(Gem).to receive(:win_platform?).and_return(true) } - it { is_expected.to include 'Windows' } + it { is_expected.to include "Windows" } end - context 'when on macOS' do - around { |example| ClimateControl.modify('DD_PROFILING_MACOS_TESTING' => nil) { example.run } } + context "when on macOS" do + around { |example| ClimateControl.modify("DD_PROFILING_MACOS_TESTING" => nil) { example.run } } - before { stub_const('RUBY_PLATFORM', 'x86_64-darwin19') } + before { stub_const("RUBY_PLATFORM", "x86_64-darwin19") } - it { is_expected.to include 'macOS' } + it { is_expected.to include "macOS" } end - context 'when not on Linux' do - before { stub_const('RUBY_PLATFORM', 'sparc-opensolaris') } + context "when not on Linux" do + before { stub_const("RUBY_PLATFORM", "sparc-opensolaris") } - it { is_expected.to include 'operating system is not supported' } + it { is_expected.to include "operating system is not supported" } end - context 'when on Linux or on macOS with testing override enabled' do + context "when on Linux or on macOS with testing override enabled" do before { expect(Gem).to receive(:win_platform?).and_return(false) } - context 'when not on amd64 or arm64' do - before { stub_const('RUBY_PLATFORM', 'mipsel-linux') } + context "when not on amd64 or arm64" do + before { stub_const("RUBY_PLATFORM", "mipsel-linux") } - it { is_expected.to include 'architecture is not supported' } + it { is_expected.to include "architecture is not supported" } end - shared_examples 'supported ruby validation' do - before { stub_const('RUBY_VERSION', '2.5.0') } + shared_examples "supported ruby validation" do + before { stub_const("RUBY_VERSION", "2.5.0") } - shared_examples 'libdatadog available' do - context 'when libdatadog fails to activate' do + shared_examples "libdatadog available" do + context "when libdatadog fails to activate" do before do expect(Datadog::LibdatadogExtconfHelpers) - .to receive(:gem).with('libdatadog', Datadog::LibdatadogExtconfHelpers::LIBDATADOG_VERSION) - .and_raise(LoadError.new('Simulated error activating gem')) + .to receive(:gem).with("libdatadog", Datadog::LibdatadogExtconfHelpers::LIBDATADOG_VERSION) + .and_raise(LoadError.new("Simulated error activating gem")) end - it { is_expected.to include 'exception during loading' } + it { is_expected.to include "exception during loading" } end - context 'when libdatadog successfully activates' do - include_examples 'libdatadog usable' + context "when libdatadog successfully activates" do + include_examples "libdatadog usable" end end - shared_examples 'libdatadog usable' do - context 'when libdatadog DOES NOT HAVE binaries for the current platform' do + shared_examples "libdatadog usable" do + context "when libdatadog DOES NOT HAVE binaries for the current platform" do before do expect(Libdatadog).to receive(:pkgconfig_folder).and_return(nil) expect(Libdatadog).to receive(:available_binaries).and_return(%w[fooarch-linux bararch-linux-musl]) end - it { is_expected.to include 'platform variant' } + it { is_expected.to include "platform variant" } end - context 'when libdatadog HAS BINARIES for the current platform' do - before { expect(Libdatadog).to receive(:pkgconfig_folder).and_return('/simulated/pkgconfig_folder') } + context "when libdatadog HAS BINARIES for the current platform" do + before { expect(Libdatadog).to receive(:pkgconfig_folder).and_return("/simulated/pkgconfig_folder") } - it('marks the native extension as supported') { is_expected.to be nil } + it("marks the native extension as supported") { is_expected.to be nil } end end - context 'on a Ruby version where we CAN NOT use the MJIT header' do - before { stub_const('Datadog::Profiling::NativeExtensionHelpers::CAN_USE_MJIT_HEADER', false) } + context "on a Ruby version where we CAN NOT use the MJIT header" do + before { stub_const("Datadog::Profiling::NativeExtensionHelpers::CAN_USE_MJIT_HEADER", false) } - include_examples 'libdatadog available' + include_examples "libdatadog available" end - context 'on a Ruby version where we CAN use the MJIT header' do - before { stub_const('Datadog::Profiling::NativeExtensionHelpers::CAN_USE_MJIT_HEADER', true) } + context "on a Ruby version where we CAN use the MJIT header" do + before { stub_const("Datadog::Profiling::NativeExtensionHelpers::CAN_USE_MJIT_HEADER", true) } - context 'but DOES NOT have MJIT support' do - before { expect(RbConfig::CONFIG).to receive(:[]).with('MJIT_SUPPORT').and_return('no') } + context "but DOES NOT have MJIT support" do + before { expect(RbConfig::CONFIG).to receive(:[]).with("MJIT_SUPPORT").and_return("no") } - it { is_expected.to include 'without JIT' } + it { is_expected.to include "without JIT" } end - context 'and DOES have MJIT support' do - before { expect(RbConfig::CONFIG).to receive(:[]).with('MJIT_SUPPORT').and_return('yes') } + context "and DOES have MJIT support" do + before { expect(RbConfig::CONFIG).to receive(:[]).with("MJIT_SUPPORT").and_return("yes") } - include_examples 'libdatadog available' + include_examples "libdatadog available" end end end - context 'when on amd64 (x86-64) linux' do - before { stub_const('RUBY_PLATFORM', 'x86_64-linux') } + context "when on amd64 (x86-64) linux" do + before { stub_const("RUBY_PLATFORM", "x86_64-linux") } - include_examples 'supported ruby validation' + include_examples "supported ruby validation" end - context 'when on arm64 (aarch64) linux' do - before { stub_const('RUBY_PLATFORM', 'aarch64-linux') } + context "when on arm64 (aarch64) linux" do + before { stub_const("RUBY_PLATFORM", "aarch64-linux") } - include_examples 'supported ruby validation' + include_examples "supported ruby validation" end - context 'when macOS testing override is enabled' do - around { |example| ClimateControl.modify('DD_PROFILING_MACOS_TESTING' => 'true') { example.run } } + context "when macOS testing override is enabled" do + around { |example| ClimateControl.modify("DD_PROFILING_MACOS_TESTING" => "true") { example.run } } - context 'when on amd64 (x86-64) macOS' do - before { stub_const('RUBY_PLATFORM', 'x86_64-darwin19') } + context "when on amd64 (x86-64) macOS" do + before { stub_const("RUBY_PLATFORM", "x86_64-darwin19") } - include_examples 'supported ruby validation' + include_examples "supported ruby validation" end - context 'when on arm64 macOS' do - before { stub_const('RUBY_PLATFORM', 'arm64-darwin21') } + context "when on arm64 macOS" do + before { stub_const("RUBY_PLATFORM", "arm64-darwin21") } - include_examples 'supported ruby validation' + include_examples "supported ruby validation" end end end diff --git a/spec/datadog/profiling/native_extension_spec.rb b/spec/datadog/profiling/native_extension_spec.rb index b16abdecfec..3a082f624f2 100644 --- a/spec/datadog/profiling/native_extension_spec.rb +++ b/spec/datadog/profiling/native_extension_spec.rb @@ -1,88 +1,88 @@ -require 'datadog/profiling/spec_helper' -require 'datadog/profiling/native_extension' +require "datadog/profiling/spec_helper" +require "datadog/profiling/native_extension" RSpec.describe Datadog::Profiling::NativeExtension do before { skip_if_profiling_not_supported(self) } - describe '.working?' do + describe ".working?" do subject(:working?) { described_class.send(:working?) } it { is_expected.to be true } end - describe 'grab_gvl_and_raise' do - it 'raises the requested exception with the passed in message' do - expect { described_class::Testing._native_grab_gvl_and_raise(ZeroDivisionError, 'this is a test', nil, true) } - .to raise_exception(ZeroDivisionError, 'this is a test') + describe "grab_gvl_and_raise" do + it "raises the requested exception with the passed in message" do + expect { described_class::Testing._native_grab_gvl_and_raise(ZeroDivisionError, "this is a test", nil, true) } + .to raise_exception(ZeroDivisionError, "this is a test") end - it 'accepts printf-style string formatting' do - expect { described_class::Testing._native_grab_gvl_and_raise(ZeroDivisionError, 'divided zero by ', 42, true) } - .to raise_exception(ZeroDivisionError, 'divided zero by 42') + it "accepts printf-style string formatting" do + expect { described_class::Testing._native_grab_gvl_and_raise(ZeroDivisionError, "divided zero by ", 42, true) } + .to raise_exception(ZeroDivisionError, "divided zero by 42") end - it 'limits the exception message to 255 characters' do - big_message = 'a' * 500 + it "limits the exception message to 255 characters" do + big_message = "a" * 500 expect { described_class::Testing._native_grab_gvl_and_raise(ZeroDivisionError, big_message, nil, true) } .to raise_exception(ZeroDivisionError, /a{255}\z/) end - context 'when called without releasing the gvl' do - it 'raises a RuntimeError' do - expect { described_class::Testing._native_grab_gvl_and_raise(ZeroDivisionError, 'this is a test', nil, false) } + context "when called without releasing the gvl" do + it "raises a RuntimeError" do + expect { described_class::Testing._native_grab_gvl_and_raise(ZeroDivisionError, "this is a test", nil, false) } .to raise_exception(RuntimeError, /called by thread holding the global VM lock/) end end end - describe 'grab_gvl_and_raise_syserr' do - it 'raises an exception with the passed in message and errno' do + describe "grab_gvl_and_raise_syserr" do + it "raises an exception with the passed in message and errno" do expect do - described_class::Testing._native_grab_gvl_and_raise_syserr(Errno::EINTR::Errno, 'this is a test', nil, true) + described_class::Testing._native_grab_gvl_and_raise_syserr(Errno::EINTR::Errno, "this is a test", nil, true) end.to raise_exception(Errno::EINTR, "#{Errno::EINTR.exception.message} - this is a test") end - it 'accepts printf-style string formatting' do + it "accepts printf-style string formatting" do expect do - described_class::Testing._native_grab_gvl_and_raise_syserr(Errno::EINTR::Errno, 'divided zero by ', 42, true) + described_class::Testing._native_grab_gvl_and_raise_syserr(Errno::EINTR::Errno, "divided zero by ", 42, true) end.to raise_exception(Errno::EINTR, "#{Errno::EINTR.exception.message} - divided zero by 42") end - it 'limits the caller-provided exception message to 255 characters' do - big_message = 'a' * 500 + it "limits the caller-provided exception message to 255 characters" do + big_message = "a" * 500 expect do described_class::Testing._native_grab_gvl_and_raise_syserr(Errno::EINTR::Errno, big_message, nil, true) end.to raise_exception(Errno::EINTR, /.+a{255}\z/) end - context 'when called without releasing the gvl' do - it 'raises a RuntimeError' do + context "when called without releasing the gvl" do + it "raises a RuntimeError" do expect do - described_class::Testing._native_grab_gvl_and_raise_syserr(Errno::EINTR::Errno, 'this is a test', nil, false) + described_class::Testing._native_grab_gvl_and_raise_syserr(Errno::EINTR::Errno, "this is a test", nil, false) end.to raise_exception(RuntimeError, /called by thread holding the global VM lock/) end end end - describe 'ddtrace_rb_ractor_main_p' do + describe "ddtrace_rb_ractor_main_p" do subject(:ddtrace_rb_ractor_main_p) { described_class::Testing._native_ddtrace_rb_ractor_main_p } - context 'when Ruby has no support for Ractors' do - before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION >= '3' } + context "when Ruby has no support for Ractors" do + before { skip "Behavior does not apply to current Ruby version" if RUBY_VERSION >= "3" } it { is_expected.to be true } end - context 'when Ruby has support for Ractors' do - before { skip 'Behavior does not apply to current Ruby version' if RUBY_VERSION < '3' } + context "when Ruby has support for Ractors" do + before { skip "Behavior does not apply to current Ruby version" if RUBY_VERSION < "3" } - context 'on the main Ractor' do + context "on the main Ractor" do it { is_expected.to be true } end - context 'on a background Ractor', ractors: true do + context "on a background Ractor", ractors: true do # @ivoanjo: When we initially added this test, our test suite kept deadlocking in CI in a later test (not on # this one). # @@ -92,7 +92,7 @@ # I was able to see this even on both Linux with 3.0.3 and macOS with 3.0.4. Thus, I decided to skip this # spec on Ruby 3.0. We can always run it manually if we change something around this helper; and we have # coverage on 3.1+ anyway. - before { skip 'Ruby 3.0 Ractors are too buggy to run this spec' if RUBY_VERSION.start_with?('3.0.') } + before { skip "Ruby 3.0 Ractors are too buggy to run this spec" if RUBY_VERSION.start_with?("3.0.") } subject(:ddtrace_rb_ractor_main_p) do Ractor.new { Datadog::Profiling::NativeExtension::Testing._native_ddtrace_rb_ractor_main_p }.take @@ -103,16 +103,16 @@ end end - describe 'is_current_thread_holding_the_gvl' do + describe "is_current_thread_holding_the_gvl" do subject(:is_current_thread_holding_the_gvl) do Datadog::Profiling::NativeExtension::Testing._native_is_current_thread_holding_the_gvl end - context 'when current thread is holding the global VM lock' do + context "when current thread is holding the global VM lock" do it { is_expected.to be true } end - context 'when current thread is not holding the global VM lock' do + context "when current thread is not holding the global VM lock" do subject(:is_current_thread_holding_the_gvl) do Datadog::Profiling::NativeExtension::Testing._native_release_gvl_and_call_is_current_thread_holding_the_gvl end @@ -120,7 +120,7 @@ it { is_expected.to be false } end - describe 'correctness' do + describe "correctness" do let(:ready_queue) { Queue.new } let(:background_thread) do Thread.new do @@ -148,7 +148,7 @@ # the background thread, we are guaranteed that the background thread does not have the GVL. # # @ivoanjo: It's a bit weird but I wanted test coverage for this. Improvements welcome ;) - it 'returns accurate results when compared to ruby_thread_has_gvl_p' do + it "returns accurate results when compared to ruby_thread_has_gvl_p" do background_thread ready_queue.pop @@ -159,24 +159,24 @@ end end - describe 'enforce_success' do - context 'when there is no error' do - it 'does nothing' do + describe "enforce_success" do + context "when there is no error" do + it "does nothing" do expect { described_class::Testing._native_enforce_success(0, true) }.to_not raise_error end end - context 'when there is an error' do + context "when there is an error" do let(:have_gvl) { true } - it 'raises an exception with the passed in errno' do + it "raises an exception with the passed in errno" do expect { described_class::Testing._native_enforce_success(Errno::EINTR::Errno, have_gvl) } .to raise_exception(Errno::EINTR, /#{Errno::EINTR.exception.message}.+profiling\.c/) end - context 'when called without the gvl' do + context "when called without the gvl" do let(:have_gvl) { false } - it 'raises an exception with the passed in errno' do + it "raises an exception with the passed in errno" do expect { described_class::Testing._native_enforce_success(Errno::EINTR::Errno, have_gvl) } .to raise_exception(Errno::EINTR, /#{Errno::EINTR.exception.message}.+profiling\.c/) end diff --git a/spec/datadog/profiling/pprof/pprof_pb.rb b/spec/datadog/profiling/pprof/pprof_pb.rb index 3c31ac57b3c..4e95e057393 100644 --- a/spec/datadog/profiling/pprof/pprof_pb.rb +++ b/spec/datadog/profiling/pprof/pprof_pb.rb @@ -3,41 +3,41 @@ # Pprof encoding is taken care of by libdatadog, but we use this in our specs to be able to decode profiles and # assert on them -require 'google/protobuf' +require "google/protobuf" Google::Protobuf::DescriptorPool.generated_pool.build do - add_message 'perftools.profiles.Profile' do - repeated :sample_type, :message, 1, 'perftools.profiles.ValueType' - repeated :sample, :message, 2, 'perftools.profiles.Sample' - repeated :mapping, :message, 3, 'perftools.profiles.Mapping' - repeated :location, :message, 4, 'perftools.profiles.Location' - repeated :function, :message, 5, 'perftools.profiles.Function' + add_message "perftools.profiles.Profile" do + repeated :sample_type, :message, 1, "perftools.profiles.ValueType" + repeated :sample, :message, 2, "perftools.profiles.Sample" + repeated :mapping, :message, 3, "perftools.profiles.Mapping" + repeated :location, :message, 4, "perftools.profiles.Location" + repeated :function, :message, 5, "perftools.profiles.Function" repeated :string_table, :string, 6 optional :drop_frames, :int64, 7 optional :keep_frames, :int64, 8 optional :time_nanos, :int64, 9 optional :duration_nanos, :int64, 10 - optional :period_type, :message, 11, 'perftools.profiles.ValueType' + optional :period_type, :message, 11, "perftools.profiles.ValueType" optional :period, :int64, 12 repeated :comment, :int64, 13 optional :default_sample_type, :int64, 14 end - add_message 'perftools.profiles.ValueType' do + add_message "perftools.profiles.ValueType" do optional :type, :int64, 1 optional :unit, :int64, 2 end - add_message 'perftools.profiles.Sample' do + add_message "perftools.profiles.Sample" do repeated :location_id, :uint64, 1 repeated :value, :int64, 2 - repeated :label, :message, 3, 'perftools.profiles.Label' + repeated :label, :message, 3, "perftools.profiles.Label" end - add_message 'perftools.profiles.Label' do + add_message "perftools.profiles.Label" do optional :key, :int64, 1 optional :str, :int64, 2 optional :num, :int64, 3 optional :num_unit, :int64, 4 end - add_message 'perftools.profiles.Mapping' do + add_message "perftools.profiles.Mapping" do optional :id, :uint64, 1 optional :memory_start, :uint64, 2 optional :memory_limit, :uint64, 3 @@ -49,18 +49,18 @@ optional :has_line_numbers, :bool, 9 optional :has_inline_frames, :bool, 10 end - add_message 'perftools.profiles.Location' do + add_message "perftools.profiles.Location" do optional :id, :uint64, 1 optional :mapping_id, :uint64, 2 optional :address, :uint64, 3 - repeated :line, :message, 4, 'perftools.profiles.Line' + repeated :line, :message, 4, "perftools.profiles.Line" optional :is_folded, :bool, 5 end - add_message 'perftools.profiles.Line' do + add_message "perftools.profiles.Line" do optional :function_id, :uint64, 1 optional :line, :int64, 2 end - add_message 'perftools.profiles.Function' do + add_message "perftools.profiles.Function" do optional :id, :uint64, 1 optional :name, :int64, 2 optional :system_name, :int64, 3 @@ -71,13 +71,13 @@ module Perftools module Profiles - Profile = Google::Protobuf::DescriptorPool.generated_pool.lookup('perftools.profiles.Profile').msgclass - ValueType = Google::Protobuf::DescriptorPool.generated_pool.lookup('perftools.profiles.ValueType').msgclass - Sample = Google::Protobuf::DescriptorPool.generated_pool.lookup('perftools.profiles.Sample').msgclass - Label = Google::Protobuf::DescriptorPool.generated_pool.lookup('perftools.profiles.Label').msgclass - Mapping = Google::Protobuf::DescriptorPool.generated_pool.lookup('perftools.profiles.Mapping').msgclass - Location = Google::Protobuf::DescriptorPool.generated_pool.lookup('perftools.profiles.Location').msgclass - Line = Google::Protobuf::DescriptorPool.generated_pool.lookup('perftools.profiles.Line').msgclass - Function = Google::Protobuf::DescriptorPool.generated_pool.lookup('perftools.profiles.Function').msgclass + Profile = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Profile").msgclass + ValueType = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.ValueType").msgclass + Sample = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Sample").msgclass + Label = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Label").msgclass + Mapping = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Mapping").msgclass + Location = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Location").msgclass + Line = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Line").msgclass + Function = Google::Protobuf::DescriptorPool.generated_pool.lookup("perftools.profiles.Function").msgclass end end diff --git a/spec/datadog/profiling/preload_spec.rb b/spec/datadog/profiling/preload_spec.rb index 9cf13240202..be6f448a3e2 100644 --- a/spec/datadog/profiling/preload_spec.rb +++ b/spec/datadog/profiling/preload_spec.rb @@ -1,10 +1,10 @@ -require 'spec_helper' -require 'datadog/profiling' +require "spec_helper" +require "datadog/profiling" -RSpec.describe 'Profiling preloading' do - subject(:preload) { load 'datadog/profiling/preload.rb' } +RSpec.describe "Profiling preloading" do + subject(:preload) { load "datadog/profiling/preload.rb" } - it 'starts the profiler' do + it "starts the profiler" do expect(Datadog::Profiling).to receive(:start_if_enabled) preload diff --git a/spec/datadog/profiling/profiler_spec.rb b/spec/datadog/profiling/profiler_spec.rb index ccdbf7b69ff..f331546ef59 100644 --- a/spec/datadog/profiling/profiler_spec.rb +++ b/spec/datadog/profiling/profiler_spec.rb @@ -1,7 +1,7 @@ -require 'spec_helper' -require 'datadog/profiling/spec_helper' +require "spec_helper" +require "datadog/profiling/spec_helper" -require 'datadog/profiling/profiler' +require "datadog/profiling/profiler" RSpec.describe Datadog::Profiling::Profiler do before { skip_if_profiling_not_supported(self) } @@ -14,20 +14,20 @@ let(:scheduler) { instance_double(Datadog::Profiling::Scheduler) } let(:optional_crashtracker) { nil } - describe '#start' do + describe "#start" do subject(:start) { profiler.start } - it 'signals the worker and scheduler to start' do + it "signals the worker and scheduler to start" do expect(worker).to receive(:start).with(on_failure_proc: an_instance_of(Proc)) expect(scheduler).to receive(:start).with(on_failure_proc: an_instance_of(Proc)) start end - context 'when a crash tracker instance is provided' do + context "when a crash tracker instance is provided" do let(:optional_crashtracker) { instance_double(Datadog::Profiling::Crashtracker) } - it 'signals the crash tracker to start before other components' do + it "signals the crash tracker to start before other components" do expect(optional_crashtracker).to receive(:start).ordered expect(worker).to receive(:start).ordered @@ -37,10 +37,10 @@ end end - context 'when called after a fork' do - before { skip('Spec requires Ruby VM supporting fork') unless PlatformHelpers.supports_fork? } + context "when called after a fork" do + before { skip("Spec requires Ruby VM supporting fork") unless PlatformHelpers.supports_fork? } - it 'resets the worker and the scheduler before starting them' do + it "resets the worker and the scheduler before starting them" do profiler # make sure instance is created in parent, so it detects the forking expect_in_fork do @@ -54,10 +54,10 @@ end end - context 'when a crash tracker instance is provided' do + context "when a crash tracker instance is provided" do let(:optional_crashtracker) { instance_double(Datadog::Profiling::Crashtracker) } - it 'resets the crash tracker before other coponents, as well as restarts it before other components' do + it "resets the crash tracker before other coponents, as well as restarts it before other components" do profiler # make sure instance is created in parent, so it detects the forking expect_in_fork do @@ -76,10 +76,10 @@ end end - describe '#shutdown!' do + describe "#shutdown!" do subject(:shutdown!) { profiler.shutdown! } - it 'signals worker and scheduler to disable and stop' do + it "signals worker and scheduler to disable and stop" do expect(worker).to receive(:stop) expect(scheduler).to receive(:enabled=).with(false) @@ -88,10 +88,10 @@ shutdown! end - context 'when a crash tracker instance is provided' do + context "when a crash tracker instance is provided" do let(:optional_crashtracker) { instance_double(Datadog::Profiling::Crashtracker) } - it 'signals the crash tracker to stop, after other components have stopped' do + it "signals the crash tracker to stop, after other components have stopped" do expect(worker).to receive(:stop).ordered allow(scheduler).to receive(:enabled=) expect(scheduler).to receive(:stop).ordered @@ -103,14 +103,14 @@ end end - describe 'Component failure handling' do + describe "Component failure handling" do let(:worker) { instance_double(Datadog::Profiling::Collectors::CpuAndWallTimeWorker, start: nil) } let(:scheduler) { instance_double(Datadog::Profiling::Scheduler, start: nil) } let(:optional_crashtracker) { instance_double(Datadog::Profiling::Crashtracker, start: nil) } before { allow(Datadog.logger).to receive(:warn) } - context 'when the worker failed' do + context "when the worker failed" do let(:worker_on_failure) do on_failure = nil expect(worker).to receive(:start) { |on_failure_proc:| on_failure = on_failure_proc } @@ -126,33 +126,33 @@ allow(scheduler).to receive(:mark_profiler_failed) end - it 'logs the issue' do + it "logs the issue" do expect(Datadog.logger).to receive(:warn).with(/worker component/) worker_on_failure end - it 'marks the profiler as having failed in the scheduler' do + it "marks the profiler as having failed in the scheduler" do expect(scheduler).to receive(:mark_profiler_failed) worker_on_failure end - it 'stops the scheduler' do + it "stops the scheduler" do expect(scheduler).to receive(:enabled=).with(false) expect(scheduler).to receive(:stop).with(true) worker_on_failure end - it 'does not stop the crashtracker' do + it "does not stop the crashtracker" do expect(optional_crashtracker).to_not receive(:stop) worker_on_failure end end - context 'when the scheduler failed' do + context "when the scheduler failed" do let(:scheduler_on_failure) do on_failure = nil expect(scheduler).to receive(:start) { |on_failure_proc:| on_failure = on_failure_proc } @@ -166,28 +166,28 @@ allow(worker).to receive(:stop) end - it 'logs the issue' do + it "logs the issue" do expect(Datadog.logger).to receive(:warn).with(/scheduler component/) scheduler_on_failure end - it 'stops the worker' do + it "stops the worker" do expect(worker).to receive(:stop) scheduler_on_failure end - it 'does not stop the crashtracker' do + it "does not stop the crashtracker" do expect(optional_crashtracker).to_not receive(:stop) scheduler_on_failure end end - context 'when unknown component failed' do - it 'raises an ArgumentError' do - expect { profiler.send(:component_failed, 'test') }.to raise_error(ArgumentError, /failed_component: "test"/) + context "when unknown component failed" do + it "raises an ArgumentError" do + expect { profiler.send(:component_failed, "test") }.to raise_error(ArgumentError, /failed_component: "test"/) end end end diff --git a/spec/datadog/profiling/scheduler_spec.rb b/spec/datadog/profiling/scheduler_spec.rb index c6c3e18ad08..e1c84d4975f 100644 --- a/spec/datadog/profiling/scheduler_spec.rb +++ b/spec/datadog/profiling/scheduler_spec.rb @@ -1,6 +1,6 @@ -require 'datadog/profiling/spec_helper' +require "datadog/profiling/spec_helper" -require 'datadog/profiling/scheduler' +require "datadog/profiling/scheduler" RSpec.describe Datadog::Profiling::Scheduler do before { skip_if_profiling_not_supported(self) } @@ -12,8 +12,8 @@ subject(:scheduler) { described_class.new(exporter: exporter, transport: transport, interval: interval, **options) } - describe '.new' do - describe 'default settings' do + describe ".new" do + describe "default settings" do it do is_expected.to have_attributes( enabled?: true, @@ -24,16 +24,16 @@ end end - describe '#start' do + describe "#start" do subject(:start) { scheduler.start } - it 'starts the worker' do + it "starts the worker" do expect(scheduler).to receive(:perform) start end end - describe '#perform' do + describe "#perform" do subject(:perform) { scheduler.perform } after do @@ -41,10 +41,10 @@ scheduler.join end - context 'when disabled' do + context "when disabled" do before { scheduler.enabled = false } - it 'does not start a worker thread' do + it "does not start a worker thread" do perform expect(scheduler.send(:worker)).to be nil @@ -60,12 +60,12 @@ end end - context 'when enabled' do + context "when enabled" do before { scheduler.enabled = true } after { scheduler.terminate } - it 'starts a worker thread' do + it "starts a worker thread" do allow(scheduler).to receive(:flush_events) perform @@ -83,12 +83,12 @@ ) end - context 'when perform fails' do + context "when perform fails" do before { Thread.report_on_exception = false if Thread.respond_to?(:report_on_exception=) } after { Thread.report_on_exception = true if Thread.respond_to?(:report_on_exception=) } - it 'calls the on_failure_proc and logs the error' do - expect(scheduler).to receive(:flush_and_wait).and_raise(StandardError.new('Simulated error')) + it "calls the on_failure_proc and logs the error" do + expect(scheduler).to receive(:flush_and_wait).and_raise(StandardError.new("Simulated error")) # This is a bit ugly, but we want the logic in the background thread to be called immediately, and by # default we don't do that @@ -107,8 +107,8 @@ end end - context 'when perform is interrupted' do - it 'logs the interruption' do + context "when perform is interrupted" do + it "logs the interruption" do inside_flush = Queue.new # This is a bit ugly, but we want the logic in the background thread to be called immediately, and by @@ -134,7 +134,7 @@ end end - describe '#flush_and_wait' do + describe "#flush_and_wait" do subject(:flush_and_wait) { scheduler.send(:flush_and_wait) } let(:flush_time) { 0.05 } @@ -145,7 +145,7 @@ end end - it 'changes its wait interval after flushing' do + it "changes its wait interval after flushing" do expect(scheduler).to receive(:loop_wait_time=) do |value| expected_interval = interval - flush_time expect(value).to be <= expected_interval @@ -154,11 +154,11 @@ flush_and_wait end - context 'when the flush takes longer than an interval' do + context "when the flush takes longer than an interval" do let(:options) { {**super(), interval: 0.01} } # Assert that the interval isn't set below the min interval - it 'floors the wait interval to MINIMUM_INTERVAL_SECONDS' do + it "floors the wait interval to MINIMUM_INTERVAL_SECONDS" do expect(scheduler).to receive(:loop_wait_time=) .with(described_class.const_get(:MINIMUM_INTERVAL_SECONDS)) @@ -167,45 +167,45 @@ end end - describe '#flush_events' do + describe "#flush_events" do subject(:flush_events) { scheduler.send(:flush_events) } let(:flush) { instance_double(Datadog::Profiling::Flush) } before { expect(exporter).to receive(:flush).and_return(flush) } - it 'exports the profiling data' do + it "exports the profiling data" do expect(transport).to receive(:export).with(flush) flush_events end - context 'when transport fails' do + context "when transport fails" do before do - expect(transport).to receive(:export) { raise 'Kaboom' } + expect(transport).to receive(:export) { raise "Kaboom" } end - it 'gracefully handles the exception, logging it' do + it "gracefully handles the exception, logging it" do expect(Datadog.logger).to receive(:error).with(/Kaboom/) flush_events end end - context 'when the flush does not contain enough data' do + context "when the flush does not contain enough data" do let(:flush) { nil } - it 'does not try to export the profiling data' do + it "does not try to export the profiling data" do expect(transport).to_not receive(:export) flush_events end end - context 'when being run in a loop' do + context "when being run in a loop" do before { allow(scheduler).to receive(:run_loop?).and_return(true) } - it 'sleeps for up to DEFAULT_FLUSH_JITTER_MAXIMUM_SECONDS seconds before reporting' do + it "sleeps for up to DEFAULT_FLUSH_JITTER_MAXIMUM_SECONDS seconds before reporting" do expect(scheduler).to receive(:sleep) do |sleep_amount| expect(sleep_amount).to be < described_class.const_get(:DEFAULT_FLUSH_JITTER_MAXIMUM_SECONDS) expect(sleep_amount).to be_a_kind_of(Float) @@ -216,10 +216,10 @@ end end - context 'when being run as a one-off' do + context "when being run as a one-off" do before { allow(scheduler).to receive(:run_loop?).and_return(false) } - it 'does not sleep before reporting' do + it "does not sleep before reporting" do expect(scheduler).to_not receive(:sleep) expect(transport).to receive(:export) @@ -229,28 +229,28 @@ end end - describe '#loop_wait_before_first_iteration?' do - it 'enables this feature of IntervalLoop' do + describe "#loop_wait_before_first_iteration?" do + it "enables this feature of IntervalLoop" do expect(scheduler.loop_wait_before_first_iteration?).to be true end end - describe '#work_pending?' do + describe "#work_pending?" do subject(:work_pending?) { scheduler.work_pending? } - context 'when the exporter can flush' do + context "when the exporter can flush" do before { expect(exporter).to receive(:can_flush?).and_return(true) } it { is_expected.to be true } end - context 'when the exporter can not flush' do + context "when the exporter can not flush" do before { expect(exporter).to receive(:can_flush?).and_return(false) } it { is_expected.to be false } end - context 'when the profiler was marked as failed' do + context "when the profiler was marked as failed" do before do scheduler.mark_profiler_failed expect(exporter).to_not receive(:can_flush?) @@ -260,10 +260,10 @@ end end - describe '#reset_after_fork' do + describe "#reset_after_fork" do subject(:reset_after_fork) { scheduler.reset_after_fork } - it 'resets the exporter' do + it "resets the exporter" do expect(exporter).to receive(:reset_after_fork) reset_after_fork diff --git a/spec/datadog/profiling/spec_helper.rb b/spec/datadog/profiling/spec_helper.rb index 23dc9a6f78f..ca7ecdc35e7 100644 --- a/spec/datadog/profiling/spec_helper.rb +++ b/spec/datadog/profiling/spec_helper.rb @@ -1,7 +1,7 @@ -require 'datadog/profiling' +require "datadog/profiling" if Datadog::Profiling.supported? - require 'datadog/profiling/pprof/pprof_pb' - require 'extlz4' + require "datadog/profiling/pprof/pprof_pb" + require "extlz4" end module ProfileHelpers @@ -19,15 +19,15 @@ def has_location?(path:, line:) Frame = Struct.new(:base_label, :path, :lineno) def skip_if_profiling_not_supported(testcase) - testcase.skip('Profiling is not supported on JRuby') if PlatformHelpers.jruby? - testcase.skip('Profiling is not supported on TruffleRuby') if PlatformHelpers.truffleruby? + testcase.skip("Profiling is not supported on JRuby") if PlatformHelpers.jruby? + testcase.skip("Profiling is not supported on TruffleRuby") if PlatformHelpers.truffleruby? # Profiling is not officially supported on macOS due to missing libdatadog binaries, # but it's still useful to allow it to be enabled for development. - if PlatformHelpers.mac? && ENV['DD_PROFILING_MACOS_TESTING'] != 'true' + if PlatformHelpers.mac? && ENV["DD_PROFILING_MACOS_TESTING"] != "true" testcase.skip( - 'Profiling is not supported on macOS. If you still want to run these specs, you can use ' \ - 'DD_PROFILING_MACOS_TESTING=true to override this check.' + "Profiling is not supported on macOS. If you still want to run these specs, you can use " \ + "DD_PROFILING_MACOS_TESTING=true to override this check." ) end @@ -35,7 +35,7 @@ def skip_if_profiling_not_supported(testcase) # Ensure profiling was loaded correctly raise "Profiling does not seem to be available: #{Datadog::Profiling.unsupported_reason}. " \ - 'Try running `bundle exec rake compile` before running this test.' + "Try running `bundle exec rake compile` before running this test." end def decode_profile(pprof_data) @@ -63,7 +63,7 @@ def samples_from_pprof(pprof_data) def decode_frame_from_pprof(decoded_profile, location_id) strings = decoded_profile.string_table location = decoded_profile.location.find { |loc| loc.id == location_id } - raise 'Unexpected: Multiple lines for location' unless location.line.size == 1 + raise "Unexpected: Multiple lines for location" unless location.line.size == 1 line_entry = location.line.first function = decoded_profile.function.find { |func| func.id == line_entry.function_id } @@ -72,7 +72,7 @@ def decode_frame_from_pprof(decoded_profile, location_id) end def object_id_from(thread_id) - if thread_id != 'GC' + if thread_id != "GC" Integer(thread_id.match(/\d+ \((?\d+)\)/)[:object_id]) else -1 diff --git a/spec/datadog/profiling/stack_recorder_spec.rb b/spec/datadog/profiling/stack_recorder_spec.rb index 9894ad8723f..6a102037d56 100644 --- a/spec/datadog/profiling/stack_recorder_spec.rb +++ b/spec/datadog/profiling/stack_recorder_spec.rb @@ -1,5 +1,5 @@ -require 'datadog/profiling/spec_helper' -require 'datadog/profiling/stack_recorder' +require "datadog/profiling/spec_helper" +require "datadog/profiling/stack_recorder" RSpec.describe Datadog::Profiling::StackRecorder do before { skip_if_profiling_not_supported(self) } @@ -40,23 +40,23 @@ def slot_two_mutex_locked? described_class::Testing._native_slot_two_mutex_locked?(stack_recorder) end - describe '#initialize' do - describe 'locking behavior' do - it 'sets slot one as the active slot' do + describe "#initialize" do + describe "locking behavior" do + it "sets slot one as the active slot" do expect(active_slot).to be 1 end - it 'keeps the slot one mutex unlocked' do + it "keeps the slot one mutex unlocked" do expect(slot_one_mutex_locked?).to be false end - it 'keeps the slot two mutex locked' do + it "keeps the slot two mutex locked" do expect(slot_two_mutex_locked?).to be true end end end - describe '#serialize' do + describe "#serialize" do subject(:serialize) { stack_recorder.serialize } let(:start) { serialize[0] } @@ -66,7 +66,7 @@ def slot_two_mutex_locked? let(:decoded_profile) { decode_profile(encoded_pprof) } - it 'debug logs profile information' do + it "debug logs profile information" do message = nil expect(Datadog.logger).to receive(:debug) do |&message_block| @@ -79,43 +79,43 @@ def slot_two_mutex_locked? expect(message).to include finish.iso8601 end - describe 'locking behavior' do - context 'when slot one was the active slot' do - it 'sets slot two as the active slot' do + describe "locking behavior" do + context "when slot one was the active slot" do + it "sets slot two as the active slot" do expect { serialize }.to change { active_slot }.from(1).to(2) end - it 'locks the slot one mutex' do + it "locks the slot one mutex" do expect { serialize }.to change { slot_one_mutex_locked? }.from(false).to(true) end - it 'unlocks the slot two mutex' do + it "unlocks the slot two mutex" do expect { serialize }.to change { slot_two_mutex_locked? }.from(true).to(false) end end - context 'when slot two was the active slot' do + context "when slot two was the active slot" do before do # Trigger serialization once, so that active slots get flipped stack_recorder.serialize end - it 'sets slot one as the active slot' do + it "sets slot one as the active slot" do expect { serialize }.to change { active_slot }.from(2).to(1) end - it 'unlocks the slot one mutex' do + it "unlocks the slot one mutex" do expect { serialize }.to change { slot_one_mutex_locked? }.from(true).to(false) end - it 'locks the slot two mutex' do + it "locks the slot two mutex" do expect { serialize }.to change { slot_two_mutex_locked? }.from(false).to(true) end end end - context 'when the profile is empty' do - it 'uses the current time as the start and finish time' do + context "when the profile is empty" do + it "uses the current time as the start and finish time" do before_serialize = Time.now.utc serialize after_serialize = Time.now.utc @@ -125,7 +125,7 @@ def slot_two_mutex_locked? expect(start).to be <= finish end - describe 'profile types configuration' do + describe "profile types configuration" do let(:cpu_time_enabled) { true } let(:alloc_samples_enabled) { true } let(:heap_samples_enabled) { true } @@ -133,87 +133,87 @@ def slot_two_mutex_locked? let(:timeline_enabled) { true } let(:all_profile_types) do { - 'cpu-time' => 'nanoseconds', - 'cpu-samples' => 'count', - 'wall-time' => 'nanoseconds', - 'alloc-samples' => 'count', - 'alloc-samples-unscaled' => 'count', - 'heap-live-samples' => 'count', - 'heap-live-size' => 'bytes', - 'timeline' => 'nanoseconds', + "cpu-time" => "nanoseconds", + "cpu-samples" => "count", + "wall-time" => "nanoseconds", + "alloc-samples" => "count", + "alloc-samples-unscaled" => "count", + "heap-live-samples" => "count", + "heap-live-size" => "bytes", + "timeline" => "nanoseconds", } end def profile_types_without(*types) result = all_profile_types.dup - types.each { |type| result.delete(type) { raise 'Missing key' } } + types.each { |type| result.delete(type) { raise "Missing key" } } result end - context 'when all profile types are enabled' do - it 'returns a pprof with the configured sample types' do + context "when all profile types are enabled" do + it "returns a pprof with the configured sample types" do expect(sample_types_from(decoded_profile)).to eq(all_profile_types) end end - context 'when cpu-time is disabled' do + context "when cpu-time is disabled" do let(:cpu_time_enabled) { false } - it 'returns a pprof without the cpu-type type' do - expect(sample_types_from(decoded_profile)).to eq(profile_types_without('cpu-time')) + it "returns a pprof without the cpu-type type" do + expect(sample_types_from(decoded_profile)).to eq(profile_types_without("cpu-time")) end end - context 'when alloc-samples is disabled' do + context "when alloc-samples is disabled" do let(:alloc_samples_enabled) { false } - it 'returns a pprof without the alloc-samples type' do + it "returns a pprof without the alloc-samples type" do expect(sample_types_from(decoded_profile)) - .to eq(profile_types_without('alloc-samples', 'alloc-samples-unscaled')) + .to eq(profile_types_without("alloc-samples", "alloc-samples-unscaled")) end end - context 'when heap-live-samples is disabled' do + context "when heap-live-samples is disabled" do let(:heap_samples_enabled) { false } - it 'returns a pprof without the heap-live-samples type' do - expect(sample_types_from(decoded_profile)).to eq(profile_types_without('heap-live-samples')) + it "returns a pprof without the heap-live-samples type" do + expect(sample_types_from(decoded_profile)).to eq(profile_types_without("heap-live-samples")) end end - context 'when heap-live-size is disabled' do + context "when heap-live-size is disabled" do let(:heap_size_enabled) { false } - it 'returns a pprof without the heap-live-size type' do - expect(sample_types_from(decoded_profile)).to eq(profile_types_without('heap-live-size')) + it "returns a pprof without the heap-live-size type" do + expect(sample_types_from(decoded_profile)).to eq(profile_types_without("heap-live-size")) end end - context 'when timeline is disabled' do + context "when timeline is disabled" do let(:timeline_enabled) { false } - it 'returns a pprof without the timeline type' do - expect(sample_types_from(decoded_profile)).to eq(profile_types_without('timeline')) + it "returns a pprof without the timeline type" do + expect(sample_types_from(decoded_profile)).to eq(profile_types_without("timeline")) end end - context 'when all optional types are disabled' do + context "when all optional types are disabled" do let(:cpu_time_enabled) { false } let(:alloc_samples_enabled) { false } let(:heap_samples_enabled) { false } let(:heap_size_enabled) { false } let(:timeline_enabled) { false } - it 'returns a pprof without the optional types' do + it "returns a pprof without the optional types" do expect(sample_types_from(decoded_profile)).to eq( - 'cpu-samples' => 'count', - 'wall-time' => 'nanoseconds', + "cpu-samples" => "count", + "wall-time" => "nanoseconds", ) end end end - it 'returns an empty pprof' do + it "returns an empty pprof" do expect(decoded_profile).to have_attributes( sample: [], mapping: [], @@ -228,7 +228,7 @@ def profile_types_without(*types) ) end - it 'returns stats reporting no recorded samples' do + it "returns stats reporting no recorded samples" do expect(profile_stats).to match( hash_including( recorded_samples: 0, @@ -245,18 +245,18 @@ def sample_types_from(decoded_profile) end end - context 'when profile has a sample' do + context "when profile has a sample" do let(:metric_values) do { - 'cpu-time' => 123, - 'cpu-samples' => 456, - 'wall-time' => 789, - 'alloc-samples' => 4242, - 'alloc-samples-unscaled' => 2222, - 'timeline' => 1111, + "cpu-time" => 123, + "cpu-samples" => 456, + "wall-time" => 789, + "alloc-samples" => 4242, + "alloc-samples-unscaled" => 2222, + "timeline" => 1111, } end - let(:labels) { {'label_a' => 'value_a', 'label_b' => 'value_b', 'state' => 'unknown'}.to_a } + let(:labels) { {"label_a" => "value_a", "label_b" => "value_b", "state" => "unknown"}.to_a } let(:samples) { samples_from_pprof(encoded_pprof) } @@ -266,7 +266,7 @@ def sample_types_from(decoded_profile) expect(samples.size).to be 1 end - it 'encodes the sample with the metrics provided' do + it "encodes the sample with the metrics provided" do expect(samples.first.values) .to eq( "cpu-time": 123, @@ -278,24 +278,24 @@ def sample_types_from(decoded_profile) ) end - context 'when disabling an optional profile sample type' do + context "when disabling an optional profile sample type" do let(:cpu_time_enabled) { false } - it 'encodes the sample with the metrics provided, ignoring the disabled ones' do + it "encodes the sample with the metrics provided, ignoring the disabled ones" do expect(samples.first.values).to eq( "cpu-samples": 456, "wall-time": 789, "alloc-samples": 4242, "alloc-samples-unscaled": 2222, timeline: 1111 ) end end - it 'encodes the sample with the labels provided' do + it "encodes the sample with the labels provided" do labels = samples.first.labels labels.delete(:state) # We test this separately! - expect(labels).to eq(label_a: 'value_a', label_b: 'value_b') + expect(labels).to eq(label_a: "value_a", label_b: "value_b") end - it 'encodes a single empty mapping' do + it "encodes a single empty mapping" do expect(decoded_profile.mapping.size).to be 1 expect(decoded_profile.mapping.first).to have_attributes( @@ -312,7 +312,7 @@ def sample_types_from(decoded_profile) ) end - it 'returns stats reporting one recorded sample' do + it "returns stats reporting one recorded sample" do expect(profile_stats).to match( hash_including( recorded_samples: 1, @@ -324,9 +324,9 @@ def sample_types_from(decoded_profile) end end - context 'when sample is invalid' do - context 'because the local root span id is being defined using a string instead of as a number' do - let(:metric_values) { {'cpu-time' => 123, 'cpu-samples' => 456, 'wall-time' => 789} } + context "when sample is invalid" do + context "because the local root span id is being defined using a string instead of as a number" do + let(:metric_values) { {"cpu-time" => 123, "cpu-samples" => 456, "wall-time" => 789} } it do # We're using `_native_sample` here to test the behavior of `record_sample` in `stack_recorder.c` @@ -335,7 +335,7 @@ def sample_types_from(decoded_profile) Thread.current, stack_recorder, metric_values, - {'local root span id' => 'incorrect', 'state' => 'unknown'}.to_a, + {"local root span id" => "incorrect", "state" => "unknown"}.to_a, [], 400, false, @@ -345,17 +345,17 @@ def sample_types_from(decoded_profile) end end - describe 'trace endpoint behavior' do - let(:metric_values) { {'cpu-time' => 101, 'cpu-samples' => 1, 'wall-time' => 789} } + describe "trace endpoint behavior" do + let(:metric_values) { {"cpu-time" => 101, "cpu-samples" => 1, "wall-time" => 789} } let(:samples) { samples_from_pprof(encoded_pprof) } - it 'includes the endpoint for all matching samples taken before and after recording the endpoint' do - local_root_span_id_with_endpoint = {'local root span id' => 123} - local_root_span_id_without_endpoint = {'local root span id' => 456} + it "includes the endpoint for all matching samples taken before and after recording the endpoint" do + local_root_span_id_with_endpoint = {"local root span id" => 123} + local_root_span_id_without_endpoint = {"local root span id" => 456} sample = proc do |numeric_labels = {}| Datadog::Profiling::Collectors::Stack::Testing._native_sample( - Thread.current, stack_recorder, metric_values, {'state' => 'unknown'}.to_a, numeric_labels.to_a, 400, false + Thread.current, stack_recorder, metric_values, {"state" => "unknown"}.to_a, numeric_labels.to_a, 400, false ) end @@ -363,7 +363,7 @@ def sample_types_from(decoded_profile) sample.call(local_root_span_id_without_endpoint) sample.call(local_root_span_id_with_endpoint) - described_class::Testing._native_record_endpoint(stack_recorder, 123, 'recorded-endpoint') + described_class::Testing._native_record_endpoint(stack_recorder, 123, "recorded-endpoint") sample.call sample.call(local_root_span_id_without_endpoint) @@ -385,29 +385,29 @@ def sample_types_from(decoded_profile) expect( samples.select do |it| labels_without_state.call(it.labels) == - {"local root span id": 123, "trace endpoint": 'recorded-endpoint'} + {"local root span id": 123, "trace endpoint": "recorded-endpoint"} end ).to have(2).items end end - describe 'heap samples and sizes' do + describe "heap samples and sizes" do let(:sample_rate) { 50 } let(:metric_values) do { - 'cpu-time' => 101, - 'cpu-samples' => 1, - 'wall-time' => 789, - 'alloc-samples' => sample_rate, - 'timeline' => 42, - 'heap_sample' => true, + "cpu-time" => 101, + "cpu-samples" => 1, + "wall-time" => 789, + "alloc-samples" => sample_rate, + "timeline" => 42, + "heap_sample" => true, } end - let(:labels) { {'label_a' => 'value_a', 'label_b' => 'value_b', 'state' => 'unknown'}.to_a } + let(:labels) { {"label_a" => "value_a", "label_b" => "value_b", "state" => "unknown"}.to_a } - let(:a_string) { 'a beautiful string' } + let(:a_string) { "a beautiful string" } let(:an_array) { (1..100).to_a.compact } - let(:a_hash) { {'a' => 1, 'b' => '2', 'c' => true, 'd' => Object.new} } + let(:a_hash) { {"a" => 1, "b" => "2", "c" => true, "d" => Object.new} } let(:samples) { samples_from_pprof(encoded_pprof) } @@ -420,7 +420,7 @@ def sample_allocation(obj) before do allocations = [a_string, an_array, "a fearsome interpolated string: #{sample_rate}", (-10..-1).to_a, a_hash, - {'z' => -1, 'y' => '-2', 'x' => false}, Object.new] + {"z" => -1, "y" => "-2", "x" => false}, Object.new] @num_allocations = 0 allocations.each_with_index do |obj, i| # Sample allocations with 2 distinct stacktraces @@ -446,7 +446,7 @@ def sample_allocation(obj) # * Allocate some more stuff and clear again # * Do another GC allocations = ["another fearsome interpolated string: #{sample_rate}", (-20..-10).to_a, - {'a' => 1, 'b' => '2', 'c' => true}, Object.new] + {"a" => 1, "b" => "2", "c" => true}, Object.new] allocations.clear GC.start end @@ -455,24 +455,24 @@ def sample_allocation(obj) # This is here to facilitate troubleshooting when this test fails. Otherwise # it's very hard to understand what may be happening. if example.exception - puts('Heap recorder debugging info:') + puts("Heap recorder debugging info:") puts(described_class::Testing._native_debug_heap_recorder(stack_recorder)) end end - context 'when disabled' do + context "when disabled" do let(:heap_samples_enabled) { false } let(:heap_size_enabled) { false } - it 'are ommitted from the profile' do + it "are ommitted from the profile" do # We sample from 2 distinct locations expect(samples.size).to eq(2) - expect(samples.select { |s| s.values.key?('heap-live-samples') }).to be_empty - expect(samples.select { |s| s.values.key?('heap-live-size') }).to be_empty + expect(samples.select { |s| s.values.key?("heap-live-samples") }).to be_empty + expect(samples.select { |s| s.values.key?("heap-live-size") }).to be_empty end end - context 'when enabled' do + context "when enabled" do let(:heap_samples_enabled) { true } let(:heap_size_enabled) { true } @@ -485,36 +485,36 @@ def sample_allocation(obj) end before do - skip 'Heap profiling is only supported on Ruby >= 2.7' if RUBY_VERSION < '2.7' + skip "Heap profiling is only supported on Ruby >= 2.7" if RUBY_VERSION < "2.7" end - it 'include the stack and sample counts for the objects still left alive' do + it "include the stack and sample counts for the objects still left alive" do # There should be 3 different allocation class labels so we expect 3 different heap samples expect(heap_samples.size).to eq(3) - expect(heap_samples.map { |s| s.labels[:"allocation class"] }).to include('String', 'Array', 'Hash') + expect(heap_samples.map { |s| s.labels[:"allocation class"] }).to include("String", "Array", "Hash") expect(heap_samples.map(&:labels)).to all(match(hash_including("gc gen age": be_a(Integer).and(be >= 0)))) end - it 'include accurate object sizes' do - string_sample = heap_samples.find { |s| s.labels[:"allocation class"] == 'String' } + it "include accurate object sizes" do + string_sample = heap_samples.find { |s| s.labels[:"allocation class"] == "String" } expect(string_sample.values[:"heap-live-size"]).to eq(ObjectSpace.memsize_of(a_string) * sample_rate) - array_sample = heap_samples.find { |s| s.labels[:"allocation class"] == 'Array' } + array_sample = heap_samples.find { |s| s.labels[:"allocation class"] == "Array" } expect(array_sample.values[:"heap-live-size"]).to eq(ObjectSpace.memsize_of(an_array) * sample_rate) - hash_sample = heap_samples.find { |s| s.labels[:"allocation class"] == 'Hash' } + hash_sample = heap_samples.find { |s| s.labels[:"allocation class"] == "Hash" } expect(hash_sample.values[:"heap-live-size"]).to eq(ObjectSpace.memsize_of(a_hash) * sample_rate) end - it 'include accurate object ages' do - string_sample = heap_samples.find { |s| s.labels[:"allocation class"] == 'String' } + it "include accurate object ages" do + string_sample = heap_samples.find { |s| s.labels[:"allocation class"] == "String" } string_age = string_sample.labels[:"gc gen age"] - array_sample = heap_samples.find { |s| s.labels[:"allocation class"] == 'Array' } + array_sample = heap_samples.find { |s| s.labels[:"allocation class"] == "Array" } array_age = array_sample.labels[:"gc gen age"] - hash_sample = heap_samples.find { |s| s.labels[:"allocation class"] == 'Hash' } + hash_sample = heap_samples.find { |s| s.labels[:"allocation class"] == "Hash" } hash_age = hash_sample.labels[:"gc gen age"] unique_sorted_ages = [string_age, array_age, hash_age].uniq.sort @@ -532,7 +532,7 @@ def sample_allocation(obj) expect(hash_age).to be_between(4, 5) end - it 'keeps on reporting accurate samples for other profile types' do + it "keeps on reporting accurate samples for other profile types" do expect(non_heap_samples.size).to eq(2) summed_values = {} @@ -547,7 +547,7 @@ def sample_allocation(obj) # for each profile-type there in. expected_summed_values = {"heap-live-samples": 0, "heap-live-size": 0, "alloc-samples-unscaled": 0} metric_values.each_pair do |k, v| - next if k == 'heap_sample' # This is not a metric, ignore it + next if k == "heap_sample" # This is not a metric, ignore it expected_summed_values[k.to_sym] = v * @num_allocations end @@ -555,7 +555,7 @@ def sample_allocation(obj) expect(summed_values).to eq(expected_summed_values) end - it 'does not include samples with age = 0' do + it "does not include samples with age = 0" do test_num_allocated_objects = 123 test_num_age_bigger_0 = 57 live_objects = Array.new(test_num_allocated_objects) @@ -619,7 +619,7 @@ def sample_allocation(obj) expect(relevant_sample.values[:"heap-live-samples"]).to eq test_num_allocated_object * sample_rate end - it 'contribute to recorded samples stats' do + it "contribute to recorded samples stats" do test_num_allocated_object = 123 live_objects = Array.new(test_num_allocated_object) @@ -644,23 +644,23 @@ def sample_allocation(obj) ) end - context 'with custom heap sample rate configuration' do + context "with custom heap sample rate configuration" do let(:heap_sample_every) { 2 } - it 'only keeps track of some allocations' do + it "only keeps track of some allocations" do # By only sampling every 2nd allocation we only track the odd objects which means our array # should be the only heap sample captured (string is index 0, array is index 1, hash is 4) expect(heap_samples.size).to eq(1) heap_sample = heap_samples.first - expect(heap_sample.labels[:"allocation class"]).to eq('Array') + expect(heap_sample.labels[:"allocation class"]).to eq("Array") expect(heap_sample.values[:"heap-live-samples"]).to eq(sample_rate * heap_sample_every) end end - context 'on Rubies supporting rb_gc_force_recycle' do + context "on Rubies supporting rb_gc_force_recycle" do before do - skip 'rb_gc_force_recycle is a no-op in current Ruby version' if RUBY_VERSION >= '3.1' + skip "rb_gc_force_recycle is a no-op in current Ruby version" if RUBY_VERSION >= "3.1" @recycled_sample_allocation_line = 0 end @@ -725,10 +725,10 @@ def create_obj_in_recycled_slot(should_sample_original: false) rescue RangeError # rubocop:disable Lint/SuppressedException end end - raise 'could not allocate an object in a recycled slot' + raise "could not allocate an object in a recycled slot" end - it 'enforces seen id flag on objects on recycled slots that get sampled' do + it "enforces seen id flag on objects on recycled slots that get sampled" do recycled_obj = create_obj_in_recycled_slot expect(has_seen_id_flag(recycled_obj)).to be false @@ -738,7 +738,7 @@ def create_obj_in_recycled_slot(should_sample_original: false) expect(has_seen_id_flag(recycled_obj)).to be true end - it 'enforces seen id flag on untracked objects that replace tracked recycled objects' do + it "enforces seen id flag on untracked objects that replace tracked recycled objects" do recycled_obj = create_obj_in_recycled_slot(should_sample_original: true) expect(has_seen_id_flag(recycled_obj)).to be false @@ -748,7 +748,7 @@ def create_obj_in_recycled_slot(should_sample_original: false) expect(has_seen_id_flag(recycled_obj)).to be true end - it 'correctly handles lifecycle of objects on recycled slots that get sampled' do + it "correctly handles lifecycle of objects on recycled slots that get sampled" do recycled_obj = create_obj_in_recycled_slot sample_allocation(recycled_obj) @@ -760,13 +760,13 @@ def create_obj_in_recycled_slot(should_sample_original: false) expect(recycled_sample).not_to be nil end - it 'supports allocation samples with duplicate ids due to force recycling' do + it "supports allocation samples with duplicate ids due to force recycling" do recycled_obj = create_obj_in_recycled_slot(should_sample_original: true) expect { sample_allocation(recycled_obj) }.not_to raise_error end - it 'raises on allocation samples with duplicate ids that are not due to force recycling' do + it "raises on allocation samples with duplicate ids that are not due to force recycling" do obj = Object.new sample_allocation(obj) @@ -774,7 +774,7 @@ def create_obj_in_recycled_slot(should_sample_original: false) expect { sample_allocation(obj) }.to raise_error(/supposed to be unique/) end - it 'can detect implicit frees due to slot recycling' do + it "can detect implicit frees due to slot recycling" do live_objects = [] live_objects << create_obj_in_recycled_slot(should_sample_original: true) @@ -790,15 +790,15 @@ def create_obj_in_recycled_slot(should_sample_original: false) # NOTE: This is a regression test that exceptions in end_heap_allocation_recording_with_rb_protect are safely # handled by the stack_recorder. - context 'when the heap sampler raises an exception during _native_sample' do - it 'propagates the exception' do + context "when the heap sampler raises an exception during _native_sample" do + it "propagates the exception" do expect do Datadog::Profiling::Collectors::Stack::Testing ._native_sample(Thread.current, stack_recorder, metric_values, labels, numeric_labels, 400, false) end.to raise_error(RuntimeError, /Ended a heap recording/) end - it 'does not keep the active slot mutex locked' do + it "does not keep the active slot mutex locked" do expect(active_slot).to be 1 expect(slot_one_mutex_locked?).to be false expect(slot_two_mutex_locked?).to be true @@ -817,25 +817,25 @@ def create_obj_in_recycled_slot(should_sample_original: false) end end - context 'when there is a failure during serialization' do + context "when there is a failure during serialization" do before do allow(Datadog.logger).to receive(:error) # Real failures in serialization are hard to trigger, so we're using a mock failure instead - expect(described_class).to receive(:_native_serialize).and_return([:error, 'test error message']) + expect(described_class).to receive(:_native_serialize).and_return([:error, "test error message"]) end it { is_expected.to be nil } - it 'logs an error message' do + it "logs an error message" do expect(Datadog.logger).to receive(:error).with(/test error message/) serialize end end - context 'when serializing multiple times in a row' do - it 'sets the start time of a profile to be >= the finish time of the previous profile' do + context "when serializing multiple times in a row" do + it "sets the start time of a profile to be >= the finish time of the previous profile" do start1, finish1, = stack_recorder.serialize start2, finish2, = stack_recorder.serialize start3, finish3, = stack_recorder.serialize @@ -848,7 +848,7 @@ def create_obj_in_recycled_slot(should_sample_original: false) expect(start4).to be <= finish4 end - it 'sets the start time of the next profile to be >= the previous serialization call' do + it "sets the start time of the next profile to be >= the previous serialization call" do stack_recorder before_serialize = Time.now.utc @@ -861,62 +861,62 @@ def create_obj_in_recycled_slot(should_sample_original: false) end end - describe '#serialize!' do + describe "#serialize!" do subject(:serialize!) { stack_recorder.serialize! } - context 'when serialization succeeds' do + context "when serialization succeeds" do before do expect(described_class).to receive(:_native_serialize).and_return([:ok, %w[start finish serialized-data]]) end - it { is_expected.to eq('serialized-data') } + it { is_expected.to eq("serialized-data") } end - context 'when serialization fails' do - before { expect(described_class).to receive(:_native_serialize).and_return([:error, 'test error message']) } + context "when serialization fails" do + before { expect(described_class).to receive(:_native_serialize).and_return([:error, "test error message"]) } it { expect { serialize! }.to raise_error(RuntimeError, /test error message/) } end end - describe '#reset_after_fork' do + describe "#reset_after_fork" do subject(:reset_after_fork) { stack_recorder.reset_after_fork } - context 'when slot one was the active slot' do - it 'keeps slot one as the active slot' do + context "when slot one was the active slot" do + it "keeps slot one as the active slot" do expect(active_slot).to be 1 end - it 'keeps the slot one mutex unlocked' do + it "keeps the slot one mutex unlocked" do expect(slot_one_mutex_locked?).to be false end - it 'keeps the slot two mutex locked' do + it "keeps the slot two mutex locked" do expect(slot_two_mutex_locked?).to be true end end - context 'when slot two was the active slot' do + context "when slot two was the active slot" do before { stack_recorder.serialize } - it 'sets slot one as the active slot' do + it "sets slot one as the active slot" do expect { reset_after_fork }.to change { active_slot }.from(2).to(1) end - it 'unlocks the slot one mutex' do + it "unlocks the slot one mutex" do expect { reset_after_fork }.to change { slot_one_mutex_locked? }.from(true).to(false) end - it 'locks the slot two mutex' do + it "locks the slot two mutex" do expect { reset_after_fork }.to change { slot_two_mutex_locked? }.from(false).to(true) end end - context 'when profile has a sample' do - let(:metric_values) { {'cpu-time' => 123, 'cpu-samples' => 456, 'wall-time' => 789} } - let(:labels) { {'label_a' => 'value_a', 'label_b' => 'value_b', 'state' => 'unknown'}.to_a } + context "when profile has a sample" do + let(:metric_values) { {"cpu-time" => 123, "cpu-samples" => 456, "wall-time" => 789} } + let(:labels) { {"label_a" => "value_a", "label_b" => "value_b", "state" => "unknown"}.to_a } - it 'makes the next calls to serialize return no data' do + it "makes the next calls to serialize return no data" do # Add some data Datadog::Profiling::Collectors::Stack::Testing ._native_sample(Thread.current, stack_recorder, metric_values, labels, numeric_labels, 400, false) @@ -937,7 +937,7 @@ def create_obj_in_recycled_slot(should_sample_original: false) end end - it 'sets the start_time of the active profile to the time of the reset_after_fork' do + it "sets the start_time of the active profile to the time of the reset_after_fork" do stack_recorder # Initialize instance now = Time.now @@ -947,8 +947,8 @@ def create_obj_in_recycled_slot(should_sample_original: false) end end - describe '#stats' do - it 'returns basic lifetime stats of stack recorder' do + describe "#stats" do + it "returns basic lifetime stats of stack recorder" do num_serializations = 5 num_serializations.times do @@ -984,19 +984,19 @@ def create_obj_in_recycled_slot(should_sample_original: false) expect(serialization_time_total).to be_within(1e-4).of(serialization_time_avg * num_serializations) end - context 'with heap profiling enabled' do + context "with heap profiling enabled" do let(:heap_samples_enabled) { true } let(:heap_size_enabled) { true } before do - skip 'Heap profiling is only supported on Ruby >= 2.7' if RUBY_VERSION < '2.7' + skip "Heap profiling is only supported on Ruby >= 2.7" if RUBY_VERSION < "2.7" end after do |example| # This is here to facilitate troubleshooting when this test fails. Otherwise # it's very hard to understand what may be happening. if example.exception - puts('Heap recorder debugging info:') + puts("Heap recorder debugging info:") puts(described_class::Testing._native_debug_heap_recorder(stack_recorder)) end end @@ -1005,11 +1005,11 @@ def sample_allocation(obj) # Heap sampling currently requires this 2-step process to first pass data about the allocated object... described_class::Testing._native_track_object(stack_recorder, obj, 1, obj.class.name) Datadog::Profiling::Collectors::Stack::Testing._native_sample( - Thread.current, stack_recorder, {'alloc-samples' => 1, 'heap_sample' => true}, [], [], 400, false + Thread.current, stack_recorder, {"alloc-samples" => 1, "heap_sample" => true}, [], [], 400, false ) end - it 'includes heap recorder snapshot' do + it "includes heap recorder snapshot" do live_objects = [] live_heap_samples = 6 live_heap_samples.times do |i| @@ -1072,65 +1072,65 @@ def sample_allocation(obj) end end - describe 'Heap_recorder' do - context 'produces the same hash code for stack-based and location-based keys' do - it 'with empty stacks' do + describe "Heap_recorder" do + context "produces the same hash code for stack-based and location-based keys" do + it "with empty stacks" do described_class::Testing._native_check_heap_hashes([]) end - it 'with single-frame stacks' do + it "with single-frame stacks" do described_class::Testing._native_check_heap_hashes( [ - ['a name', 'a filename', 123] + ["a name", "a filename", 123] ] ) end - it 'with multi-frame stacks' do + it "with multi-frame stacks" do described_class::Testing._native_check_heap_hashes( [ - ['a name', 'a filename', 123], - ['another name', 'anoter filename', 456], + ["a name", "a filename", 123], + ["another name", "anoter filename", 456], ] ) end - it 'with empty names' do + it "with empty names" do described_class::Testing._native_check_heap_hashes( [ - ['', 'a filename', 123], + ["", "a filename", 123], ] ) end - it 'with empty filenames' do + it "with empty filenames" do described_class::Testing._native_check_heap_hashes( [ - ['a name', '', 123], + ["a name", "", 123], ] ) end - it 'with zero lines' do + it "with zero lines" do described_class::Testing._native_check_heap_hashes( [ - ['a name', 'a filename', 0] + ["a name", "a filename", 0] ] ) end - it 'with negative lines' do + it "with negative lines" do described_class::Testing._native_check_heap_hashes( [ - ['a name', 'a filename', -123] + ["a name", "a filename", -123] ] ) end - it 'with biiiiiiig lines' do + it "with biiiiiiig lines" do described_class::Testing._native_check_heap_hashes( [ - ['a name', 'a filename', 4_000_000] + ["a name", "a filename", 4_000_000] ] ) end diff --git a/spec/datadog/profiling/tag_builder_spec.rb b/spec/datadog/profiling/tag_builder_spec.rb index d7f460a4143..bac1fb1933b 100644 --- a/spec/datadog/profiling/tag_builder_spec.rb +++ b/spec/datadog/profiling/tag_builder_spec.rb @@ -1,34 +1,34 @@ -require 'datadog/profiling/tag_builder' +require "datadog/profiling/tag_builder" RSpec.describe Datadog::Profiling::TagBuilder do - describe '.call' do + describe ".call" do let(:settings) { Datadog::Core::Configuration::Settings.new } subject(:call) { described_class.call(settings: settings) } - it 'returns a hash with the tags to be attached to a profile' do + it "returns a hash with the tags to be attached to a profile" do expect(call).to include( - 'host' => Datadog::Core::Environment::Socket.hostname, - 'language' => 'ruby', - 'process_id' => Process.pid.to_s, - 'profiler_version' => start_with('2.'), - 'runtime' => 'ruby', - 'runtime_engine' => RUBY_ENGINE, - 'runtime-id' => Datadog::Core::Environment::Identity.id, - 'runtime_platform' => RUBY_PLATFORM, - 'runtime_version' => RUBY_VERSION, + "host" => Datadog::Core::Environment::Socket.hostname, + "language" => "ruby", + "process_id" => Process.pid.to_s, + "profiler_version" => start_with("2."), + "runtime" => "ruby", + "runtime_engine" => RUBY_ENGINE, + "runtime-id" => Datadog::Core::Environment::Identity.id, + "runtime_platform" => RUBY_PLATFORM, + "runtime_version" => RUBY_VERSION, ) end - describe 'unified service tagging' do + describe "unified service tagging" do [:env, :service, :version].each do |tag| context "when a #{tag} is defined" do before do - settings.send("#{tag}=".to_sym, 'expected_value') + settings.send("#{tag}=".to_sym, "expected_value") end - it 'includes it as a tag' do - expect(call).to include(tag.to_s => 'expected_value') + it "includes it as a tag" do + expect(call).to include(tag.to_s => "expected_value") end end @@ -44,65 +44,65 @@ end end - it 'includes the provided user tags' do - settings.tags = {'foo' => 'bar'} + it "includes the provided user tags" do + settings.tags = {"foo" => "bar"} - expect(call).to include('foo' => 'bar') + expect(call).to include("foo" => "bar") end - context 'when there is a conflict between user and metadata tags' do - it 'overrides the user-provided tags' do - settings.tags = {'foo' => 'bar', 'language' => 'python'} + context "when there is a conflict between user and metadata tags" do + it "overrides the user-provided tags" do + settings.tags = {"foo" => "bar", "language" => "python"} - expect(call).to include('foo' => 'bar', 'language' => 'ruby') + expect(call).to include("foo" => "bar", "language" => "ruby") end end - context 'when user tag keys and values are not strings' do - it 'encodes them as strings' do - settings.tags = {:symbol_key => :symbol_value, nil => 'nil key', 'nil value' => nil, 12 => 34} + context "when user tag keys and values are not strings" do + it "encodes them as strings" do + settings.tags = {:symbol_key => :symbol_value, nil => "nil key", "nil value" => nil, 12 => 34} - expect(call).to include('symbol_key' => 'symbol_value', '' => 'nil key', 'nil value' => '', '12' => '34') + expect(call).to include("symbol_key" => "symbol_value", "" => "nil key", "nil value" => "", "12" => "34") end end - context 'when tagging key or value is not utf-8' do - it 'converts them to utf-8' do - settings.tags = {'ascii-key'.encode(Encoding::ASCII) => 'ascii-value'.encode(Encoding::ASCII)} + context "when tagging key or value is not utf-8" do + it "converts them to utf-8" do + settings.tags = {"ascii-key".encode(Encoding::ASCII) => "ascii-value".encode(Encoding::ASCII)} result = call result.each do |key, value| expect([key, value]).to all(have_attributes(encoding: Encoding::UTF_8)) end - expect(result).to include('ascii-key' => 'ascii-value') + expect(result).to include("ascii-key" => "ascii-value") end end - describe 'source code integration' do - context 'when git environment is available' do + describe "source code integration" do + context "when git environment is available" do before do allow(Datadog::Core::Environment::Git).to receive(:git_repository_url).and_return( - 'git_repository_url' + "git_repository_url" ) - allow(Datadog::Core::Environment::Git).to receive(:git_commit_sha).and_return('git_commit_sha') + allow(Datadog::Core::Environment::Git).to receive(:git_commit_sha).and_return("git_commit_sha") end - it 'includes the git repository URL and commit SHA' do + it "includes the git repository URL and commit SHA" do expect(call).to include( - 'git.repository_url' => 'git_repository_url', 'git.commit.sha' => 'git_commit_sha' + "git.repository_url" => "git_repository_url", "git.commit.sha" => "git_commit_sha" ) end end - context 'when git environment is not available' do + context "when git environment is not available" do before do allow(Datadog::Core::Environment::Git).to receive(:git_repository_url).and_return(nil) allow(Datadog::Core::Environment::Git).to receive(:git_commit_sha).and_return(nil) end - it 'includes the git repository URL and commit SHA' do - expect(call).to_not include('git.repository_url', 'git.commit.sha') + it "includes the git repository URL and commit SHA" do + expect(call).to_not include("git.repository_url", "git.commit.sha") end end end diff --git a/spec/datadog/profiling/tasks/exec_spec.rb b/spec/datadog/profiling/tasks/exec_spec.rb index 15985e72272..5c064ef2630 100644 --- a/spec/datadog/profiling/tasks/exec_spec.rb +++ b/spec/datadog/profiling/tasks/exec_spec.rb @@ -1,28 +1,28 @@ -require 'spec_helper' -require 'datadog/profiling/tasks/exec' +require "spec_helper" +require "datadog/profiling/tasks/exec" RSpec.describe Datadog::Profiling::Tasks::Exec do subject(:task) { described_class.new(args) } - let(:args) { ['ruby', '-e', '"RUBY_VERSION"'] } + let(:args) { ["ruby", "-e", '"RUBY_VERSION"'] } - describe '::new' do + describe "::new" do it { is_expected.to have_attributes(args: args) } end - describe '#run' do + describe "#run" do subject(:run) { task.run } - let(:result) { double('result') } + let(:result) { double("result") } around do |example| # Make sure RUBYOPT is returned to its original state. - original_opts = ENV['RUBYOPT'] + original_opts = ENV["RUBYOPT"] example.run - ENV['RUBYOPT'] = original_opts + ENV["RUBYOPT"] = original_opts end - context 'when RUBYOPT is not defined' do + context "when RUBYOPT is not defined" do before do # Must stub the call out or test will prematurely terminate. expect(Kernel).to receive(:exec) @@ -30,83 +30,83 @@ .and_return(result) end - it 'runs the task with preloads' do + it "runs the task with preloads" do is_expected.to be(result) # Expect preloading to have been attached task.rubyopts.each do |opt| - expect(ENV['RUBYOPT']).to include(opt) + expect(ENV["RUBYOPT"]).to include(opt) end end end - context 'when RUBYOPT is defined' do + context "when RUBYOPT is defined" do before do # Must stub the call out or test will prematurely terminate. expect(Kernel).to receive(:exec) .with(*args) .and_return(result) - ENV['RUBYOPT'] = start_opts + ENV["RUBYOPT"] = start_opts end - let(:start_opts) { 'start_opts' } + let(:start_opts) { "start_opts" } - it 'runs the task with additional preloads' do + it "runs the task with additional preloads" do is_expected.to be(result) # Expect original RUBYOPT to have been preserved - expect(ENV['RUBYOPT']).to include(start_opts) + expect(ENV["RUBYOPT"]).to include(start_opts) # Expect preloading to have been attached task.rubyopts.each do |opt| - expect(ENV['RUBYOPT']).to include(opt) + expect(ENV["RUBYOPT"]).to include(opt) end end end - context 'when exec fails' do + context "when exec fails" do before do allow(Kernel).to receive(:exit) allow(Kernel).to receive(:warn) end - context 'when command does not exist' do + context "when command does not exist" do before do allow(Kernel).to receive(:exec).and_raise(Errno::ENOENT) end - it 'triggers a VM exit with error code 127' do + it "triggers a VM exit with error code 127" do expect(Kernel).to receive(:exit).with(127) run end - it 'logs an error message' do + it "logs an error message" do expect(Kernel).to receive(:warn) do |message| - expect(message).to include('ddprofrb exec failed') + expect(message).to include("ddprofrb exec failed") end run end end - context 'when command is not executable' do + context "when command is not executable" do [Errno::EACCES, Errno::ENOEXEC].each do |error| context "when exec fails with #{error}" do before do allow(Kernel).to receive(:exec).and_raise(error) end - it 'triggers a VM exit with error code 126' do + it "triggers a VM exit with error code 126" do expect(Kernel).to receive(:exit).with(126) run end - it 'logs an error message' do + it "logs an error message" do expect(Kernel).to receive(:warn) do |message| - expect(message).to include('ddprofrb exec failed') + expect(message).to include("ddprofrb exec failed") end run @@ -117,9 +117,9 @@ end end - describe '#rubyopts' do + describe "#rubyopts" do subject(:rubyopts) { task.rubyopts } - it { is_expected.to eq(['-rdatadog/profiling/preload']) } + it { is_expected.to eq(["-rdatadog/profiling/preload"]) } end end diff --git a/spec/datadog/profiling/tasks/help_spec.rb b/spec/datadog/profiling/tasks/help_spec.rb index 6cf88b336ab..b8cb09424f5 100644 --- a/spec/datadog/profiling/tasks/help_spec.rb +++ b/spec/datadog/profiling/tasks/help_spec.rb @@ -1,13 +1,13 @@ -require 'spec_helper' -require 'datadog/profiling/tasks/help' +require "spec_helper" +require "datadog/profiling/tasks/help" RSpec.describe Datadog::Profiling::Tasks::Help do subject(:task) { described_class.new } - describe '#run' do - it 'prints a help message to stdout' do + describe "#run" do + it "prints a help message to stdout" do expect($stdout).to receive(:puts) do |message| - expect(message).to include('Usage: ddprofrb') + expect(message).to include("Usage: ddprofrb") end task.run diff --git a/spec/datadog/profiling/tasks/setup_spec.rb b/spec/datadog/profiling/tasks/setup_spec.rb index 927175ed944..c8ddc729d66 100644 --- a/spec/datadog/profiling/tasks/setup_spec.rb +++ b/spec/datadog/profiling/tasks/setup_spec.rb @@ -1,26 +1,26 @@ -require 'spec_helper' -require 'datadog/profiling/spec_helper' +require "spec_helper" +require "datadog/profiling/spec_helper" -require 'datadog/profiling/tasks/setup' +require "datadog/profiling/tasks/setup" RSpec.describe Datadog::Profiling::Tasks::Setup do subject(:task) { described_class.new } - describe '#run' do + describe "#run" do subject(:run) { task.run } before do described_class::ACTIVATE_EXTENSIONS_ONLY_ONCE.send(:reset_ran_once_state_for_tests) end - it 'actives the forking extension before setting up the at_fork hooks' do + it "actives the forking extension before setting up the at_fork hooks" do expect(Datadog::Core::Utils::AtForkMonkeyPatch).to receive(:apply!).ordered expect(task).to receive(:setup_at_fork_hooks).ordered run end - it 'only sets up the extensions and hooks once, even across different instances' do + it "only sets up the extensions and hooks once, even across different instances" do expect(Datadog::Core::Utils::AtForkMonkeyPatch).to receive(:apply!).once expect_any_instance_of(described_class).to receive(:setup_at_fork_hooks).once @@ -31,7 +31,7 @@ end end - describe '#setup_at_fork_hooks' do + describe "#setup_at_fork_hooks" do subject(:setup_at_fork_hooks) { task.send(:setup_at_fork_hooks) } let(:at_fork_hook) do @@ -50,25 +50,25 @@ the_hook end - it 'sets up an at_fork hook that restarts the profiler' do + it "sets up an at_fork hook that restarts the profiler" do expect(Datadog::Profiling).to receive(:start_if_enabled) at_fork_hook.call end - context 'when there is an issue starting the profiler' do + context "when there is an issue starting the profiler" do before do - expect(Datadog::Profiling).to receive(:start_if_enabled).and_raise('Dummy exception') + expect(Datadog::Profiling).to receive(:start_if_enabled).and_raise("Dummy exception") allow(Datadog.logger).to receive(:warn) # Silence logging during tests end - it 'does not raise any error' do + it "does not raise any error" do at_fork_hook.call end - it 'logs an exception' do + it "logs an exception" do expect(Datadog.logger).to receive(:warn) do |&message| - expect(message.call).to include('Dummy exception') + expect(message.call).to include("Dummy exception") end at_fork_hook.call diff --git a/spec/datadog/profiling/validate_benchmarks_spec.rb b/spec/datadog/profiling/validate_benchmarks_spec.rb index 20d66f54c9e..9172b788b29 100644 --- a/spec/datadog/profiling/validate_benchmarks_spec.rb +++ b/spec/datadog/profiling/validate_benchmarks_spec.rb @@ -1,33 +1,33 @@ -require 'datadog/profiling/spec_helper' +require "datadog/profiling/spec_helper" -RSpec.describe 'Profiling benchmarks' do +RSpec.describe "Profiling benchmarks" do before { skip_if_profiling_not_supported(self) } around do |example| - ClimateControl.modify('VALIDATE_BENCHMARK' => 'true') do + ClimateControl.modify("VALIDATE_BENCHMARK" => "true") do example.run end end benchmarks_to_validate = [ - 'profiler_allocation', - 'profiler_gc', - 'profiler_hold_resume_interruptions', - 'profiler_http_transport', - 'profiler_memory_sample_serialize', - 'profiler_sample_loop_v2', - 'profiler_sample_serialize', + "profiler_allocation", + "profiler_gc", + "profiler_hold_resume_interruptions", + "profiler_http_transport", + "profiler_memory_sample_serialize", + "profiler_sample_loop_v2", + "profiler_sample_serialize", ].freeze benchmarks_to_validate.each do |benchmark| describe benchmark do - it('runs without raising errors') { expect_in_fork { load "./benchmarks/#{benchmark}.rb" } } + it("runs without raising errors") { expect_in_fork { load "./benchmarks/#{benchmark}.rb" } } end end # This test validates that we don't forget to add new benchmarks to benchmarks_to_validate - it 'tests all expected benchmarks in the benchmarks folder' do - all_benchmarks = Dir['./benchmarks/profiler_*'].map { |it| it.gsub('./benchmarks/', '').gsub('.rb', '') } + it "tests all expected benchmarks in the benchmarks folder" do + all_benchmarks = Dir["./benchmarks/profiler_*"].map { |it| it.gsub("./benchmarks/", "").gsub(".rb", "") } expect(benchmarks_to_validate).to contain_exactly(*all_benchmarks) end