ruby_memprofiler_pprof 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. checksums.yaml +4 -4
  2. data/ext/ruby_memprofiler_pprof_ext/collector.c +803 -0
  3. data/ext/ruby_memprofiler_pprof_ext/compat.c +184 -0
  4. data/ext/ruby_memprofiler_pprof_ext/compile_commands.json +1 -0
  5. data/ext/ruby_memprofiler_pprof_ext/extconf.rb +152 -0
  6. data/ext/ruby_memprofiler_pprof_ext/pprof.upb.c +199 -0
  7. data/ext/ruby_memprofiler_pprof_ext/pprof.upb.h +924 -0
  8. data/ext/ruby_memprofiler_pprof_ext/pprof_out.c +430 -0
  9. data/ext/ruby_memprofiler_pprof_ext/ruby_hacks.c +118 -0
  10. data/ext/ruby_memprofiler_pprof_ext/ruby_memprofiler_pprof.c +10 -0
  11. data/ext/ruby_memprofiler_pprof_ext/ruby_memprofiler_pprof.h +183 -0
  12. data/ext/ruby_memprofiler_pprof_ext/ruby_private/ruby26/gc_private.h +324 -0
  13. data/ext/ruby_memprofiler_pprof_ext/ruby_private/ruby27/gc_private.h +339 -0
  14. data/ext/ruby_memprofiler_pprof_ext/ruby_private/ruby30/gc_private.h +361 -0
  15. data/ext/ruby_memprofiler_pprof_ext/ruby_private/ruby31/gc_private.h +374 -0
  16. data/ext/ruby_memprofiler_pprof_ext/ruby_private.h +31 -0
  17. data/ext/ruby_memprofiler_pprof_ext/sample.c +43 -0
  18. data/ext/ruby_memprofiler_pprof_ext/vendor/backtracie/backtracie.h +268 -0
  19. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/BUILD +0 -0
  20. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/CONTRIBUTING.md +0 -0
  21. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/DESIGN.md +0 -0
  22. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/LICENSE +0 -0
  23. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/README.md +0 -0
  24. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/WORKSPACE +0 -0
  25. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/BUILD +0 -0
  26. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/amalgamate.py +0 -0
  27. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/build_defs.bzl +0 -0
  28. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/lua.BUILD +0 -0
  29. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/protobuf.patch +0 -0
  30. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/py_proto_library.bzl +0 -0
  31. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/python_downloads.bzl +0 -0
  32. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/system_python.bzl +0 -0
  33. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/upb_proto_library.bzl +0 -0
  34. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/bazel/workspace_deps.bzl +0 -0
  35. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/BUILD +0 -0
  36. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/BUILD.googleapis +0 -0
  37. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/benchmark.cc +0 -0
  38. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/build_defs.bzl +0 -0
  39. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/compare.py +0 -0
  40. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/descriptor.proto +0 -0
  41. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/descriptor_sv.proto +0 -0
  42. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/empty.proto +0 -0
  43. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/gen_protobuf_binary_cc.py +0 -0
  44. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/gen_synthetic_protos.py +0 -0
  45. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/benchmarks/gen_upb_binary_c.py +0 -0
  46. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/cmake/BUILD.bazel +0 -0
  47. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/cmake/README.md +0 -0
  48. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/cmake/build_defs.bzl +0 -0
  49. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/cmake/make_cmakelists.py +0 -0
  50. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/cmake/staleness_test.py +0 -0
  51. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/cmake/staleness_test_lib.py +0 -0
  52. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/docs/render.py +0 -0
  53. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/docs/style-guide.md +0 -0
  54. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/docs/vs-cpp-protos.md +0 -0
  55. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/docs/wrapping-upb.md +0 -0
  56. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/BUILD +0 -0
  57. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/convert.c +0 -0
  58. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/convert.h +0 -0
  59. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/descriptor.c +0 -0
  60. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/descriptor.h +0 -0
  61. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/descriptor_containers.c +0 -0
  62. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/descriptor_containers.h +0 -0
  63. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/descriptor_pool.c +0 -0
  64. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/descriptor_pool.h +0 -0
  65. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/dist/BUILD.bazel +0 -0
  66. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/dist/dist.bzl +0 -0
  67. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/extension_dict.c +0 -0
  68. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/extension_dict.h +0 -0
  69. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/map.c +0 -0
  70. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/map.h +0 -0
  71. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/message.c +0 -0
  72. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/message.h +0 -0
  73. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/minimal_test.py +0 -0
  74. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/BUILD +0 -0
  75. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/README.md +0 -0
  76. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/descriptor_database_test_wrapper.py +0 -0
  77. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/descriptor_pool_test_wrapper.py +0 -0
  78. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/descriptor_test_wrapper.py +0 -0
  79. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/generator_test_wrapper.py +0 -0
  80. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/json_format_test_wrapper.py +0 -0
  81. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/keywords_test_wrapper.py +0 -0
  82. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/message_factory_test_wrapper.py +0 -0
  83. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/message_test_wrapper.py +0 -0
  84. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/proto_builder_test_wrapper.py +0 -0
  85. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/pyproto_test_wrapper.bzl +0 -0
  86. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/reflection_test_wrapper.py +0 -0
  87. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/service_reflection_test_wrapper.py +0 -0
  88. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/symbol_database_test_wrapper.py +0 -0
  89. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/text_encoding_test_wrapper.py +0 -0
  90. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/text_format_test_wrapper.py +0 -0
  91. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/unknown_fields_test_wrapper.py +0 -0
  92. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/well_known_types_test_wrapper.py +0 -0
  93. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/pb_unit_tests/wire_format_test_wrapper.py +0 -0
  94. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/protobuf.c +0 -0
  95. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/protobuf.h +0 -0
  96. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/py_extension.bzl +0 -0
  97. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/python_api.h +0 -0
  98. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/repeated.c +0 -0
  99. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/repeated.h +0 -0
  100. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/unknown_fields.c +0 -0
  101. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/unknown_fields.h +0 -0
  102. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/python/version_script.lds +0 -0
  103. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/lunit/LICENSE +0 -0
  104. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/lunit/README.google +0 -0
  105. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/lunit/console.lua +0 -0
  106. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/lunit/lunit.lua +0 -0
  107. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/utf8_range/BUILD +0 -0
  108. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/utf8_range/LICENSE +0 -0
  109. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/utf8_range/naive.c +0 -0
  110. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/utf8_range/range2-neon.c +0 -0
  111. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/utf8_range/range2-sse.c +0 -0
  112. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/third_party/utf8_range/utf8_range.h +0 -0
  113. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/BUILD.bazel +0 -0
  114. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/README.md +0 -0
  115. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/def.c +0 -0
  116. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/lua_proto_library.bzl +0 -0
  117. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/main.c +0 -0
  118. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/msg.c +0 -0
  119. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/test.proto +0 -0
  120. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/test_upb.lua +0 -0
  121. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/upb.c +0 -0
  122. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/upb.h +0 -0
  123. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/upb.lua +0 -0
  124. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/bindings/lua/upbc.cc +0 -0
  125. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/collections.c +0 -0
  126. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/collections.h +0 -0
  127. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/conformance_upb.c +0 -0
  128. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/conformance_upb_failures.txt +0 -0
  129. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/decode.c +0 -0
  130. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/decode.h +0 -0
  131. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/decode_fast.c +0 -0
  132. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/decode_fast.h +0 -0
  133. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/decode_internal.h +0 -0
  134. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/def.c +0 -0
  135. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/def.h +0 -0
  136. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/def.hpp +0 -0
  137. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/empty.proto +0 -0
  138. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/encode.c +0 -0
  139. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/encode.h +0 -0
  140. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/fuzz/BUILD +0 -0
  141. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/fuzz/file_descriptor_parsenew_fuzzer.cc +0 -0
  142. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/json_decode.c +0 -0
  143. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/json_decode.h +0 -0
  144. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/json_encode.c +0 -0
  145. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/json_encode.h +0 -0
  146. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/mini_table.c +0 -0
  147. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/mini_table.h +0 -0
  148. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/mini_table.hpp +0 -0
  149. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/mini_table_accessors.c +0 -0
  150. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/mini_table_accessors.h +0 -0
  151. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/mini_table_accessors_internal.h +0 -0
  152. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/mini_table_accessors_test.cc +0 -0
  153. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/mini_table_test.cc +0 -0
  154. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/msg.c +0 -0
  155. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/msg.h +0 -0
  156. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/msg_internal.h +0 -0
  157. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/msg_test.cc +0 -0
  158. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/msg_test.proto +0 -0
  159. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/port_def.inc +0 -0
  160. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/port_undef.inc +0 -0
  161. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/reflection.c +0 -0
  162. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/reflection.h +0 -0
  163. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/reflection.hpp +0 -0
  164. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/table.c +0 -0
  165. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/table_internal.h +0 -0
  166. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/test.proto +0 -0
  167. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/test_cpp.cc +0 -0
  168. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/test_cpp.proto +0 -0
  169. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/test_generated_code.cc +0 -0
  170. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/test_table.cc +0 -0
  171. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/text_encode.c +0 -0
  172. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/text_encode.h +0 -0
  173. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/upb.c +0 -0
  174. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/upb.h +0 -0
  175. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/upb.hpp +0 -0
  176. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/upb_internal.h +0 -0
  177. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/BUILD +0 -0
  178. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/README.md +0 -0
  179. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/compare.c +0 -0
  180. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/compare.h +0 -0
  181. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/compare_test.cc +0 -0
  182. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/def_to_proto.c +0 -0
  183. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/def_to_proto.h +0 -0
  184. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/def_to_proto_public_import_test.proto +0 -0
  185. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/def_to_proto_regular_import_test.proto +0 -0
  186. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/def_to_proto_test.cc +0 -0
  187. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/def_to_proto_test.proto +0 -0
  188. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/def_to_proto_weak_import_test.proto +0 -0
  189. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/def_to_proto_wweak_import_test.proto +0 -0
  190. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/required_fields.c +0 -0
  191. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/required_fields.h +0 -0
  192. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/required_fields_test.cc +0 -0
  193. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upb/util/required_fields_test.proto +0 -0
  194. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upbc/BUILD +0 -0
  195. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upbc/common.cc +0 -0
  196. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upbc/common.h +0 -0
  197. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upbc/protoc-gen-upb.cc +0 -0
  198. data/ext/{ruby_memprofiler_pprof → ruby_memprofiler_pprof_ext}/vendor/upb/upbc/protoc-gen-upbdefs.cc +0 -0
  199. data/lib/ruby_memprofiler_pprof/atfork.rb +1 -1
  200. data/lib/ruby_memprofiler_pprof/block_flusher.rb +48 -4
  201. data/lib/ruby_memprofiler_pprof/file_flusher.rb +13 -6
  202. data/lib/ruby_memprofiler_pprof/profile_app.rb +8 -12
  203. data/lib/ruby_memprofiler_pprof/profile_data.rb +7 -8
  204. data/lib/ruby_memprofiler_pprof/version.rb +1 -1
  205. data/lib/ruby_memprofiler_pprof.rb +5 -4
  206. data/libexec/ruby_memprofiler_pprof_profile +6 -6
  207. metadata +207 -200
  208. data/ext/ruby_memprofiler_pprof/backtrace.c +0 -429
  209. data/ext/ruby_memprofiler_pprof/collector.c +0 -1055
  210. data/ext/ruby_memprofiler_pprof/compat.c +0 -182
  211. data/ext/ruby_memprofiler_pprof/extconf.rb +0 -72
  212. data/ext/ruby_memprofiler_pprof/pprof.upb.c +0 -170
  213. data/ext/ruby_memprofiler_pprof/pprof.upb.h +0 -848
  214. data/ext/ruby_memprofiler_pprof/pprof_out.c +0 -285
  215. data/ext/ruby_memprofiler_pprof/ruby_memprofiler_pprof.c +0 -11
  216. data/ext/ruby_memprofiler_pprof/ruby_memprofiler_pprof.h +0 -301
  217. data/ext/ruby_memprofiler_pprof/strtab.c +0 -391
@@ -0,0 +1,803 @@
1
+ #include <pthread.h>
2
+ #include <stdbool.h>
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+ #include <time.h>
6
+
7
+ #include <ruby.h>
8
+ #include <ruby/debug.h>
9
+ #include <ruby/thread.h>
10
+
11
+ #include <backtracie.h>
12
+
13
+ #include "ruby/st.h"
14
+ #include "ruby_memprofiler_pprof.h"
15
+
16
+ struct collector_cdata {
17
+ // Global variables we need to keep a hold of
18
+ VALUE cCollector;
19
+ VALUE cProfileData;
20
+ VALUE mMemprofilerPprof;
21
+
22
+ // Ruby Tracepoint objects for our hooks
23
+ VALUE newobj_trace;
24
+ VALUE freeobj_trace;
25
+
26
+ // How often (as a fraction of UINT32_MAX) we should sample allocations
27
+ uint32_t u32_sample_rate;
28
+ // This flag is used to make sure we detach our tracepoints as we're getting GC'd.
29
+ bool is_tracing;
30
+ // If we're flushing, this contains the thread that's doing the flushing. This is used
31
+ // to exclude allocations from that thread from heap profiling.
32
+ VALUE flush_thread;
33
+ // Whether or not to use pretty backtraces (true) or fast ones (false)
34
+ bool pretty_backtraces;
35
+
36
+ // ======== Heap samples ========
37
+ // A hash-table keying live VALUEs to their struct mpp_sample. This is _not_ cleared
38
+ // when #flush is called; instead, elements are deleted when they are free'd. This is
39
+ // used for building heap profiles.
40
+ st_table *heap_samples;
41
+ // Number of elements currently in the heap profile hash
42
+ size_t heap_samples_count;
43
+ // How big the sample table can grow
44
+ size_t max_heap_samples;
45
+ // This number goes up by one every time #flush is called, and is used to keep _new_ samples from winding up in a
46
+ // profile we're in the process of flushing.
47
+ unsigned int current_flush_epoch;
48
+
49
+ // ======== Sample drop counters ========
50
+ // Number of samples dropped for want of space in the heap allocation table.
51
+ size_t dropped_samples_heap_bufsize;
52
+
53
+ // Table of (VALUE) -> (refcount) which is used to make sure we only mark the parts of our samples once, since many of
54
+ // the samples will hold references to the same iseq's etc.
55
+ st_table *mark_table;
56
+
57
+ // Debugging counters
58
+ int64_t last_gc_mark_ns;
59
+ };
60
+
61
+ static struct collector_cdata *collector_cdata_get(VALUE self);
62
+ static VALUE collector_alloc(VALUE klass);
63
+ static VALUE collector_initialize(int argc, VALUE *argv, VALUE self);
64
+ static void collector_cdata_gc_mark(void *ptr);
65
+ static int collector_gc_mark_each_table_entry(st_data_t key, st_data_t value, st_data_t ctxarg);
66
+ static void collector_gc_free(void *ptr);
67
+ static void collector_gc_free_heap_samples(struct collector_cdata *cd);
68
+ static int collector_gc_free_each_heap_sample(st_data_t key, st_data_t value, st_data_t ctxarg);
69
+ static size_t collector_gc_memsize(const void *ptr);
70
+ static int collector_gc_memsize_each_heap_sample(st_data_t key, st_data_t value, st_data_t arg);
71
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
72
+ static void collector_cdata_gc_compact(void *ptr);
73
+ static int collector_compact_each_table_entry(st_data_t key, st_data_t value, st_data_t ctxarg);
74
+ static int collector_compact_each_heap_sample(st_data_t key, st_data_t value, st_data_t ctxarg);
75
+ #endif
76
+ static void collector_mark_sample_value_as_freed(struct collector_cdata *cd, VALUE freed_obj);
77
+ static void collector_tphook_newobj(VALUE tpval, void *data);
78
+ static void collector_tphook_freeobj(VALUE tpval, void *data);
79
+ static VALUE collector_start(VALUE self);
80
+ static VALUE collector_stop(VALUE self);
81
+ static VALUE collector_is_running(VALUE self);
82
+ static VALUE collector_flush(int argc, VALUE *argv, VALUE self);
83
+ struct flush_protected_ctx {
84
+ struct collector_cdata *cd;
85
+ struct mpp_pprof_serctx *serctx;
86
+ bool yield_gvl;
87
+ bool proactively_yield_gvl;
88
+ };
89
+ static VALUE flush_protected(VALUE ctxarg);
90
+ struct flush_each_sample_ctx {
91
+ struct collector_cdata *cd;
92
+ struct mpp_pprof_serctx *serctx;
93
+ bool proactively_yield_gvl;
94
+ char *errbuf;
95
+ size_t sizeof_errbuf;
96
+ int r;
97
+ int i;
98
+ size_t actual_sample_count;
99
+ int64_t nogvl_duration;
100
+ int64_t gvl_yield_count;
101
+ int64_t gvl_check_yield_count;
102
+ unsigned int flush_epoch;
103
+ };
104
+ int flush_each_sample(st_data_t key, st_data_t value, st_data_t ctxarg);
105
+ struct flush_nogvl_ctx {
106
+ struct collector_cdata *cd;
107
+ struct mpp_pprof_serctx *serctx;
108
+ char *pprof_outbuf;
109
+ size_t pprof_outbuf_len;
110
+ char *errbuf;
111
+ size_t sizeof_errbuf;
112
+ int r;
113
+ };
114
+ static void *flush_nogvl(void *ctx);
115
+ static void flush_nogvl_unblock(void *ctx);
116
+ static VALUE collector_profile(VALUE self);
117
+ static VALUE collector_live_heap_samples_count(VALUE self);
118
+ static VALUE collector_get_sample_rate(VALUE self);
119
+ static VALUE collector_set_sample_rate(VALUE self, VALUE newval);
120
+ static VALUE collector_get_max_heap_samples(VALUE self);
121
+ static VALUE collector_set_max_heap_samples(VALUE self, VALUE newval);
122
+ static VALUE collector_get_pretty_backtraces(VALUE self);
123
+ static VALUE collector_set_pretty_backtraces(VALUE self, VALUE newval);
124
+ static VALUE collector_get_last_mark_nsecs(VALUE self);
125
+ static VALUE collector_get_mark_table_size(VALUE self);
126
+ static void mark_table_refcount_inc(st_table *mark_table, VALUE key);
127
+ static void mark_table_refcount_dec(st_table *mark_table, VALUE key);
128
+
129
+ static const rb_data_type_t collector_cdata_type = {"collector_cdata",
130
+ {
131
+ collector_cdata_gc_mark,
132
+ collector_gc_free,
133
+ collector_gc_memsize,
134
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
135
+ collector_cdata_gc_compact,
136
+ #endif
137
+ {0}, /* reserved */
138
+ },
139
+ /* parent, data, [ flags ] */
140
+ NULL,
141
+ NULL,
142
+ 0};
143
+
144
+ void mpp_setup_collector_class() {
145
+ VALUE mMemprofilerPprof = rb_const_get(rb_cObject, rb_intern("MemprofilerPprof"));
146
+ VALUE cCollector = rb_define_class_under(mMemprofilerPprof, "Collector", rb_cObject);
147
+ rb_define_alloc_func(cCollector, collector_alloc);
148
+
149
+ rb_define_method(cCollector, "initialize", collector_initialize, -1);
150
+ rb_define_method(cCollector, "sample_rate", collector_get_sample_rate, 0);
151
+ rb_define_method(cCollector, "sample_rate=", collector_set_sample_rate, 1);
152
+ rb_define_method(cCollector, "max_heap_samples", collector_get_max_heap_samples, 0);
153
+ rb_define_method(cCollector, "max_heap_samples=", collector_set_max_heap_samples, 1);
154
+ rb_define_method(cCollector, "pretty_backtraces", collector_get_pretty_backtraces, 0);
155
+ rb_define_method(cCollector, "pretty_backtraces=", collector_set_pretty_backtraces, 1);
156
+ rb_define_method(cCollector, "running?", collector_is_running, 0);
157
+ rb_define_method(cCollector, "start!", collector_start, 0);
158
+ rb_define_method(cCollector, "stop!", collector_stop, 0);
159
+ rb_define_method(cCollector, "flush", collector_flush, -1);
160
+ rb_define_method(cCollector, "profile", collector_profile, 0);
161
+ rb_define_method(cCollector, "live_heap_samples_count", collector_live_heap_samples_count, 0);
162
+ rb_define_method(cCollector, "last_mark_nsecs", collector_get_last_mark_nsecs, 0);
163
+ rb_define_method(cCollector, "mark_table_size", collector_get_mark_table_size, 0);
164
+ }
165
+
166
+ static struct collector_cdata *collector_cdata_get(VALUE self) {
167
+ struct collector_cdata *a;
168
+ TypedData_Get_Struct(self, struct collector_cdata, &collector_cdata_type, a);
169
+ return a;
170
+ }
171
+
172
+ static VALUE collector_alloc(VALUE klass) {
173
+ struct collector_cdata *cd;
174
+ VALUE v = TypedData_Make_Struct(klass, struct collector_cdata, &collector_cdata_type, cd);
175
+
176
+ cd->newobj_trace = Qnil;
177
+ cd->freeobj_trace = Qnil;
178
+ cd->flush_thread = Qnil;
179
+
180
+ cd->u32_sample_rate = 0;
181
+ cd->is_tracing = false;
182
+ cd->heap_samples = NULL;
183
+ cd->heap_samples_count = 0;
184
+ cd->max_heap_samples = 0;
185
+ cd->dropped_samples_heap_bufsize = 0;
186
+ cd->current_flush_epoch = 0;
187
+ cd->mark_table = NULL;
188
+ cd->last_gc_mark_ns = 0;
189
+ return v;
190
+ }
191
+
192
+ static VALUE collector_initialize(int argc, VALUE *argv, VALUE self) {
193
+ struct collector_cdata *cd = collector_cdata_get(self);
194
+
195
+ // Save constants
196
+ cd->mMemprofilerPprof = rb_const_get(rb_cObject, rb_intern("MemprofilerPprof"));
197
+ cd->cCollector = rb_const_get(cd->mMemprofilerPprof, rb_intern("Collector"));
198
+ cd->cProfileData = rb_const_get(cd->mMemprofilerPprof, rb_intern("ProfileData"));
199
+
200
+ // Argument parsing
201
+ VALUE kwargs_hash = Qnil;
202
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "00:", &kwargs_hash);
203
+ VALUE kwarg_values[3];
204
+ ID kwarg_ids[3];
205
+ kwarg_ids[0] = rb_intern("sample_rate");
206
+ kwarg_ids[1] = rb_intern("max_heap_samples");
207
+ kwarg_ids[2] = rb_intern("pretty_backtraces");
208
+ rb_get_kwargs(kwargs_hash, kwarg_ids, 0, 3, kwarg_values);
209
+
210
+ // Default values...
211
+ if (kwarg_values[0] == Qundef)
212
+ kwarg_values[0] = DBL2NUM(0.01);
213
+ if (kwarg_values[1] == Qundef)
214
+ kwarg_values[1] = LONG2NUM(50000);
215
+ if (kwarg_values[2] == Qundef)
216
+ kwarg_values[2] = Qtrue;
217
+
218
+ rb_funcall(self, rb_intern("sample_rate="), 1, kwarg_values[0]);
219
+ rb_funcall(self, rb_intern("max_heap_samples="), 1, kwarg_values[1]);
220
+ rb_funcall(self, rb_intern("pretty_backtraces="), 1, kwarg_values[2]);
221
+
222
+ cd->heap_samples = st_init_numtable();
223
+ cd->heap_samples_count = 0;
224
+
225
+ cd->mark_table = st_init_numtable();
226
+
227
+ return Qnil;
228
+ }
229
+
230
+ static void collector_cdata_gc_mark(void *ptr) {
231
+ struct timespec t1 = mpp_gettime_monotonic();
232
+
233
+ struct collector_cdata *cd = (struct collector_cdata *)ptr;
234
+ rb_gc_mark_movable(cd->newobj_trace);
235
+ rb_gc_mark_movable(cd->freeobj_trace);
236
+ rb_gc_mark_movable(cd->mMemprofilerPprof);
237
+ rb_gc_mark_movable(cd->cCollector);
238
+ rb_gc_mark_movable(cd->cProfileData);
239
+ rb_gc_mark_movable(cd->flush_thread);
240
+ st_foreach(cd->mark_table, collector_gc_mark_each_table_entry, 0);
241
+
242
+ struct timespec t2 = mpp_gettime_monotonic();
243
+ cd->last_gc_mark_ns = mpp_time_delta_nsec(t1, t2);
244
+ }
245
+
246
+ static int collector_gc_mark_each_table_entry(st_data_t key, st_data_t value, st_data_t ctxarg) {
247
+ rb_gc_mark_movable((VALUE)key);
248
+ return ST_CONTINUE;
249
+ }
250
+
251
+ static void collector_gc_free(void *ptr) {
252
+ struct collector_cdata *cd = (struct collector_cdata *)ptr;
253
+ if (cd->is_tracing) {
254
+ if (cd->newobj_trace) {
255
+ rb_tracepoint_disable(cd->newobj_trace);
256
+ }
257
+ if (cd->freeobj_trace) {
258
+ rb_tracepoint_disable(cd->freeobj_trace);
259
+ }
260
+ }
261
+
262
+ collector_gc_free_heap_samples(cd);
263
+ ruby_xfree(ptr);
264
+ }
265
+
266
+ static void collector_gc_free_heap_samples(struct collector_cdata *cd) {
267
+ if (cd->heap_samples) {
268
+ st_foreach(cd->heap_samples, collector_gc_free_each_heap_sample, (st_data_t)cd);
269
+ st_free_table(cd->heap_samples);
270
+ }
271
+ cd->heap_samples = NULL;
272
+ }
273
+
274
+ static int collector_gc_free_each_heap_sample(st_data_t key, st_data_t value, st_data_t ctxarg) {
275
+ struct mpp_sample *sample = (struct mpp_sample *)value;
276
+ mpp_sample_free(sample);
277
+ return ST_DELETE;
278
+ }
279
+
280
+ static size_t collector_gc_memsize(const void *ptr) {
281
+ struct collector_cdata *cd = (struct collector_cdata *)ptr;
282
+ size_t sz = sizeof(*cd);
283
+ if (cd->heap_samples) {
284
+ st_foreach(cd->heap_samples, collector_gc_memsize_each_heap_sample, (st_data_t)&sz);
285
+ sz += st_memsize(cd->heap_samples);
286
+ }
287
+
288
+ return sz;
289
+ }
290
+
291
+ static int collector_gc_memsize_each_heap_sample(st_data_t key, st_data_t value, st_data_t arg) {
292
+ size_t *acc_ptr = (size_t *)arg;
293
+ struct mpp_sample *sample = (struct mpp_sample *)value;
294
+ *acc_ptr += mpp_sample_memsize(sample);
295
+ return ST_CONTINUE;
296
+ }
297
+
298
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
299
+ // Support VALUES we're tracking being moved away in Ruby 2.7+ with GC.compact
300
+ static void collector_cdata_gc_compact(void *ptr) {
301
+ struct collector_cdata *cd = (struct collector_cdata *)ptr;
302
+ cd->newobj_trace = rb_gc_location(cd->newobj_trace);
303
+ cd->freeobj_trace = rb_gc_location(cd->freeobj_trace);
304
+ cd->mMemprofilerPprof = rb_gc_location(cd->mMemprofilerPprof);
305
+ cd->cCollector = rb_gc_location(cd->cCollector);
306
+ cd->cProfileData = rb_gc_location(cd->cProfileData);
307
+ cd->flush_thread = rb_gc_location(cd->flush_thread);
308
+
309
+ // Keep track of allocated objects we sampled that might move.
310
+ st_foreach(cd->heap_samples, collector_compact_each_heap_sample, (st_data_t)cd);
311
+ st_foreach(cd->mark_table, collector_compact_each_table_entry, (st_data_t)cd);
312
+ }
313
+
314
+ static int mark_table_refcount_update(st_data_t *key, st_data_t *value, st_data_t ctxarg, int existing) {
315
+ if (existing) {
316
+ *value += ((int)ctxarg);
317
+ } else {
318
+ *value = ((int)ctxarg);
319
+ }
320
+ return *value == 0 ? ST_DELETE : ST_CONTINUE;
321
+ }
322
+
323
+ static int collector_compact_each_table_entry(st_data_t key, st_data_t value, st_data_t ctxarg) {
324
+ struct collector_cdata *cd = (struct collector_cdata *)ctxarg;
325
+ VALUE key_value = (VALUE)key;
326
+ VALUE new_value = rb_gc_location(key_value);
327
+ if (new_value == key_value) {
328
+ return ST_CONTINUE;
329
+ } else {
330
+ // Insert a new netry for the moved value, or add this items refcount to the existing entry.
331
+ st_update(cd->mark_table, new_value, mark_table_refcount_update, value);
332
+ return ST_DELETE;
333
+ }
334
+ }
335
+
336
+ static int collector_compact_each_heap_sample(st_data_t key, st_data_t value, st_data_t ctxarg) {
337
+ struct collector_cdata *cd = (struct collector_cdata *)ctxarg;
338
+ struct mpp_sample *sample = (struct mpp_sample *)value;
339
+
340
+ for (size_t i = 0; i < sample->frames_count; i++) {
341
+ minimal_location_t *frame = &sample->frames[i];
342
+ if (frame->method_name_contents == BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL) {
343
+ frame->method_name.base_label = rb_gc_location(frame->method_name.base_label);
344
+ }
345
+ switch (frame->method_qualifier_contents) {
346
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF:
347
+ frame->method_qualifier.self = rb_gc_location(frame->method_qualifier.self);
348
+ break;
349
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS:
350
+ frame->method_qualifier.self_class = rb_gc_location(frame->method_qualifier.self_class);
351
+ break;
352
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS:
353
+ frame->method_qualifier.cme_defined_class = rb_gc_location(frame->method_qualifier.cme_defined_class);
354
+ break;
355
+ }
356
+ frame->filename = rb_gc_location(frame->filename);
357
+ }
358
+
359
+ // Handle compaction of our weak reference to the heap sample.
360
+ if (rb_gc_location(sample->allocated_value_weak) == sample->allocated_value_weak) {
361
+ return ST_CONTINUE;
362
+ } else {
363
+ sample->allocated_value_weak = rb_gc_location(sample->allocated_value_weak);
364
+ st_insert(cd->heap_samples, sample->allocated_value_weak, (st_data_t)sample);
365
+ return ST_DELETE;
366
+ }
367
+ }
368
+
369
+ #endif
370
+
371
+ static void collector_mark_sample_value_as_freed(struct collector_cdata *cd, VALUE freed_obj) {
372
+ struct mpp_sample *sample;
373
+ if (st_delete(cd->heap_samples, (st_data_t *)&freed_obj, (st_data_t *)&sample)) {
374
+ for (size_t i = 0; i < sample->frames_count; i++) {
375
+ minimal_location_t *frame = &sample->frames[i];
376
+ if (frame->method_name_contents == BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL) {
377
+ mark_table_refcount_dec(cd->mark_table, frame->method_name.base_label);
378
+ }
379
+ switch (frame->method_qualifier_contents) {
380
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF:
381
+ mark_table_refcount_dec(cd->mark_table, frame->method_qualifier.self);
382
+ break;
383
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS:
384
+ mark_table_refcount_dec(cd->mark_table, frame->method_qualifier.self_class);
385
+ break;
386
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS:
387
+ mark_table_refcount_dec(cd->mark_table, frame->method_qualifier.cme_defined_class);
388
+ break;
389
+ }
390
+ mark_table_refcount_dec(cd->mark_table, frame->filename);
391
+ }
392
+
393
+ // We deleted it out of live objects; free the sample
394
+ mpp_sample_free(sample);
395
+ cd->heap_samples_count--;
396
+ }
397
+ }
398
+
399
+ static void mark_table_refcount_inc(st_table *mark_table, VALUE key) {
400
+ if (key == Qnil || key == Qundef || key == 0) {
401
+ return;
402
+ }
403
+ st_update(mark_table, key, mark_table_refcount_update, 1);
404
+ }
405
+ static void mark_table_refcount_dec(st_table *mark_table, VALUE key) {
406
+ if (key == Qnil || key == Qundef || key == 0) {
407
+ return;
408
+ }
409
+ st_update(mark_table, key, mark_table_refcount_update, (st_data_t)-1);
410
+ }
411
+
412
+ static void collector_tphook_newobj(VALUE tpval, void *data) {
413
+ // If an object is created or freed during our newobj hook, Ruby refuses to recursively run
414
+ // the newobj/freeobj hook! It's just silently skipped. Thus, we can wind up missing
415
+ // some objects (not good), or we can wind up missing the fact that some objects
416
+ // were freed (even worse!).
417
+ // Thus, we have to make sure that
418
+ // 1) No Ruby objects are created in this method,
419
+ // 2) No Ruby objects are freed in this method,
420
+ // We achieve 1 by, well, not creating any Ruby objects. 2 would happen if the GC runs; one
421
+ // of the things that _can_ trigger the GC to run, unfortunately, is ruby_xmalloc() and
422
+ // friends (used internally by backtracie, and by st_hash, and also by this gem's malloc
423
+ // wrapper). So, we need to disable the GC, and re-enable it at the end of our hook.
424
+ // The normal "disable the GC" function, rb_gc_disable(), doesn't quite do it, because _that_
425
+ // calls rb_gc_rest() to finish off any in-progress collection! If we called that, we'd free
426
+ // objects, and miss removing them from our sample map. So, instead, we twiddle the dont_gc
427
+ // flag on the objspace directly with this compat wrapper.
428
+ VALUE gc_was_already_disabled = mpp_rb_gc_disable_no_rest();
429
+
430
+ rb_trace_arg_t *tparg;
431
+ VALUE newobj;
432
+ struct collector_cdata *cd = (struct collector_cdata *)data;
433
+
434
+ #ifdef HAVE_WORKING_RB_GC_FORCE_RECYCLE
435
+ tparg = rb_tracearg_from_tracepoint(tpval);
436
+ newobj = rb_tracearg_object(tparg);
437
+
438
+ // Normally, any object allocated that calls the newobj hook will be freed during GC,
439
+ // and the freeobj tracepoint will then be called. Thus, any object added to the heap sample
440
+ // map will be removed before a different object in the same slot is creeated.
441
+ // Unfortunately, the one place that _isn't_ true is if an object is freed manually with
442
+ // rb_gc_force_recycle(). This is deprecated in Ruby >= 3.1, but before that the only time
443
+ // we find out that such an object is freed is when a new object is created in the same
444
+ // slot. Handle that by marking an existing object in the sample map as "free'd".
445
+ collector_mark_sample_value_as_freed(cd, newobj);
446
+ #endif
447
+ // Skip the rest of this method if we're not sampling.
448
+ if (mpp_rand() > cd->u32_sample_rate) {
449
+ goto out;
450
+ }
451
+ // Don't profile allocations that were caused by the flusher; these allocations are
452
+ // 1) numerous,
453
+ // 2) probably not of interest,
454
+ // 3) guaranteed not to actually make it into a heap usage profile anyway, since
455
+ // they get freed at the end of the flushing routine.
456
+ if (rb_thread_current() == cd->flush_thread) {
457
+ goto out;
458
+ }
459
+ // Make sure there's enough space in our buffer
460
+ if (cd->heap_samples_count >= cd->max_heap_samples) {
461
+ cd->dropped_samples_heap_bufsize++;
462
+ goto out;
463
+ }
464
+
465
+ #ifndef HAVE_WORKING_RB_GC_FORCE_RECYCLE
466
+ // If we don't need to do the "check every object to see if it was freed" check,
467
+ // we can defer actually calling these functions until we've decided whether or not
468
+ // to sample.
469
+ tparg = rb_tracearg_from_tracepoint(tpval);
470
+ newobj = rb_tracearg_object(tparg);
471
+ #endif
472
+
473
+ // OK, now it's time to add to our sample buffer.
474
+ struct mpp_sample *sample = mpp_sample_capture(newobj);
475
+ sample->flush_epoch = cd->current_flush_epoch;
476
+ // insert into live sample map
477
+ int alread_existed = st_insert(cd->heap_samples, newobj, (st_data_t)sample);
478
+ MPP_ASSERT_MSG(alread_existed == 0, "st_insert did an update in the newobj hook");
479
+ cd->heap_samples_count++;
480
+
481
+ // Add them to the list of things we will GC mark
482
+ for (size_t i = 0; i < sample->frames_count; i++) {
483
+ minimal_location_t *frame = &sample->frames[i];
484
+ if (frame->method_name_contents == BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL) {
485
+ mark_table_refcount_inc(cd->mark_table, frame->method_name.base_label);
486
+ }
487
+ switch (frame->method_qualifier_contents) {
488
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF:
489
+ mark_table_refcount_inc(cd->mark_table, frame->method_qualifier.self);
490
+ break;
491
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS:
492
+ mark_table_refcount_inc(cd->mark_table, frame->method_qualifier.self_class);
493
+ break;
494
+ case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS:
495
+ mark_table_refcount_inc(cd->mark_table, frame->method_qualifier.cme_defined_class);
496
+ break;
497
+ }
498
+ mark_table_refcount_inc(cd->mark_table, frame->filename);
499
+ }
500
+ out:
501
+ if (!RTEST(gc_was_already_disabled)) {
502
+ rb_gc_enable();
503
+ }
504
+ }
505
+
506
+ static void collector_tphook_freeobj(VALUE tpval, void *data) {
507
+ // See discussion about disabling GC in newobj tracepoint, it all applies here too.
508
+ VALUE gc_was_already_disabled = mpp_rb_gc_disable_no_rest();
509
+
510
+ struct collector_cdata *cd = (struct collector_cdata *)data;
511
+
512
+ // Definitely do _NOT_ try and run any Ruby code in here. Any allocation will crash
513
+ // the process.
514
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
515
+ VALUE freed_obj = rb_tracearg_object(tparg);
516
+ collector_mark_sample_value_as_freed(cd, freed_obj);
517
+
518
+ if (!RTEST(gc_was_already_disabled)) {
519
+ rb_gc_enable();
520
+ }
521
+ }
522
+
523
+ static VALUE collector_start(VALUE self) {
524
+ struct collector_cdata *cd = collector_cdata_get(self);
525
+ if (cd->is_tracing)
526
+ return Qnil;
527
+
528
+ // Don't needlessly double-initialize everything
529
+ if (cd->heap_samples_count > 0) {
530
+ collector_gc_free_heap_samples(cd);
531
+ cd->heap_samples = st_init_numtable();
532
+ cd->heap_samples_count = 0;
533
+ }
534
+ cd->dropped_samples_heap_bufsize = 0;
535
+
536
+ if (cd->newobj_trace == Qnil) {
537
+ cd->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, collector_tphook_newobj, cd);
538
+ }
539
+ if (cd->freeobj_trace == Qnil) {
540
+ cd->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, collector_tphook_freeobj, cd);
541
+ }
542
+
543
+ rb_tracepoint_enable(cd->newobj_trace);
544
+ rb_tracepoint_enable(cd->freeobj_trace);
545
+
546
+ cd->is_tracing = true;
547
+ return Qnil;
548
+ }
549
+
550
+ static VALUE collector_stop(VALUE self) {
551
+ struct collector_cdata *cd = collector_cdata_get(self);
552
+ if (!cd->is_tracing)
553
+ return Qnil;
554
+ rb_tracepoint_disable(cd->newobj_trace);
555
+ rb_tracepoint_disable(cd->freeobj_trace);
556
+ cd->is_tracing = false;
557
+ // Don't clear any of our buffers - it's OK to access the profiling info after calling stop!
558
+ return Qnil;
559
+ }
560
+
561
+ static VALUE collector_is_running(VALUE self) {
562
+ struct collector_cdata *cd = collector_cdata_get(self);
563
+ return cd->is_tracing ? Qtrue : Qfalse;
564
+ }
565
+
566
+ static VALUE collector_flush(int argc, VALUE *argv, VALUE self) {
567
+ struct collector_cdata *cd = collector_cdata_get(self);
568
+
569
+ // kwarg handling
570
+ VALUE kwargs_hash = Qnil;
571
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "00:", &kwargs_hash);
572
+ VALUE kwarg_values[2];
573
+ ID kwarg_ids[2];
574
+ kwarg_ids[0] = rb_intern("yield_gvl");
575
+ kwarg_ids[1] = rb_intern("proactively_yield_gvl");
576
+ rb_get_kwargs(kwargs_hash, kwarg_ids, 0, 2, kwarg_values);
577
+
578
+ bool yield_gvl = false;
579
+ bool proactively_yield_gvl = false;
580
+
581
+ if (kwarg_values[0] != Qundef) {
582
+ yield_gvl = RTEST(kwarg_values[0]);
583
+ }
584
+ if (kwarg_values[1] != Qundef) {
585
+ proactively_yield_gvl = RTEST(kwarg_values[1]);
586
+ }
587
+
588
+ struct flush_protected_ctx ctx;
589
+ ctx.cd = cd;
590
+ ctx.proactively_yield_gvl = proactively_yield_gvl;
591
+ ctx.yield_gvl = yield_gvl;
592
+ ctx.serctx = NULL;
593
+ int jump_tag = 0;
594
+ VALUE retval = rb_protect(flush_protected, (VALUE)&ctx, &jump_tag);
595
+
596
+ if (ctx.serctx)
597
+ mpp_pprof_serctx_destroy(ctx.serctx);
598
+ cd->flush_thread = Qnil;
599
+
600
+ // Now return-or-raise back to ruby.
601
+ if (jump_tag) {
602
+ rb_jump_tag(jump_tag);
603
+ }
604
+ return retval;
605
+ }
606
+
607
+ int flush_each_sample(st_data_t key, st_data_t value, st_data_t ctxarg) {
608
+ struct flush_each_sample_ctx *ctx = (struct flush_each_sample_ctx *)ctxarg;
609
+ struct collector_cdata *cd = ctx->cd;
610
+ struct mpp_sample *sample = (struct mpp_sample *)value;
611
+ int ret;
612
+
613
+ if (ctx->proactively_yield_gvl && (ctx->i % 25 == 0)) {
614
+ ctx->gvl_check_yield_count++;
615
+ if (mpp_is_someone_else_waiting_for_gvl()) {
616
+ ctx->gvl_yield_count++;
617
+ struct timespec t1 = mpp_gettime_monotonic();
618
+ rb_thread_schedule();
619
+ struct timespec t2 = mpp_gettime_monotonic();
620
+ ctx->nogvl_duration += mpp_time_delta_nsec(t1, t2);
621
+ }
622
+ }
623
+ ctx->i++;
624
+
625
+ if (sample->flush_epoch > ctx->flush_epoch) {
626
+ // This is a new sample captured since we started calling #flush; skip it.
627
+ return ST_CONTINUE;
628
+ }
629
+
630
+ // Need to disable GC so that our freeobj tracepoint hook can't delete the sample out of the map
631
+ // after we've decided we're _also_ going to delete the sample out of the map.
632
+ VALUE gc_was_already_disabled = mpp_rb_gc_disable_no_rest();
633
+
634
+ if (!mpp_is_value_still_validish(sample->allocated_value_weak)) {
635
+ mpp_sample_free(sample);
636
+ cd->heap_samples_count--;
637
+ ret = ST_DELETE;
638
+ } else {
639
+ sample->allocated_value_objsize = mpp_rb_obj_memsize_of(sample->allocated_value_weak);
640
+ ctx->r = mpp_pprof_serctx_add_sample(ctx->serctx, sample, ctx->errbuf, ctx->sizeof_errbuf);
641
+ if (ctx->r == -1) {
642
+ ret = ST_STOP;
643
+ } else {
644
+ ctx->actual_sample_count++;
645
+ ret = ST_CONTINUE;
646
+ }
647
+ }
648
+
649
+ if (!RTEST(gc_was_already_disabled)) {
650
+ rb_gc_enable();
651
+ }
652
+
653
+ return ret;
654
+ }
655
+
656
+ static VALUE flush_protected(VALUE ctxarg) {
657
+ struct timespec t_start = mpp_gettime_monotonic();
658
+
659
+ struct flush_protected_ctx *ctx = (struct flush_protected_ctx *)ctxarg;
660
+ struct collector_cdata *cd = ctx->cd;
661
+ bool proactively_yield_gvl = ctx->proactively_yield_gvl;
662
+ cd->flush_thread = rb_thread_current();
663
+ unsigned int flush_epoch = cd->current_flush_epoch++;
664
+
665
+ size_t dropped_samples_bufsize = cd->dropped_samples_heap_bufsize;
666
+ cd->dropped_samples_heap_bufsize = 0;
667
+
668
+ // Begin setting up pprof serialisation.
669
+ char errbuf[256];
670
+ ctx->serctx = mpp_pprof_serctx_new(errbuf, sizeof(errbuf));
671
+ if (!ctx->serctx) {
672
+ rb_raise(rb_eRuntimeError, "ruby_memprofiler_pprof: setting up serialisation: %s", errbuf);
673
+ }
674
+ struct mpp_pprof_serctx *serctx = ctx->serctx;
675
+ struct flush_each_sample_ctx sample_ctx;
676
+ sample_ctx.r = 0;
677
+ sample_ctx.i = 0;
678
+ sample_ctx.actual_sample_count = 0;
679
+ sample_ctx.errbuf = errbuf;
680
+ sample_ctx.sizeof_errbuf = sizeof(errbuf);
681
+ sample_ctx.serctx = serctx;
682
+ sample_ctx.cd = cd;
683
+ sample_ctx.proactively_yield_gvl = proactively_yield_gvl;
684
+ sample_ctx.nogvl_duration = 0;
685
+ sample_ctx.gvl_yield_count = 0;
686
+ sample_ctx.gvl_check_yield_count = 0;
687
+ sample_ctx.flush_epoch = flush_epoch;
688
+ st_foreach(cd->heap_samples, flush_each_sample, (st_data_t)&sample_ctx);
689
+ if (sample_ctx.r == -1) {
690
+ rb_raise(rb_eRuntimeError, "ruby_memprofiler_pprof: failed preparing samples for serialisation: %s",
691
+ sample_ctx.errbuf);
692
+ }
693
+
694
+ struct flush_nogvl_ctx nogvl_ctx;
695
+ nogvl_ctx.errbuf = errbuf;
696
+ nogvl_ctx.sizeof_errbuf = sizeof(errbuf);
697
+ nogvl_ctx.serctx = serctx;
698
+ nogvl_ctx.cd = cd;
699
+
700
+ struct timespec t_serialize_start = mpp_gettime_monotonic();
701
+
702
+ if (ctx->yield_gvl) {
703
+ rb_thread_call_without_gvl(flush_nogvl, &nogvl_ctx, flush_nogvl_unblock, &nogvl_ctx);
704
+ } else {
705
+ flush_nogvl(&nogvl_ctx);
706
+ }
707
+
708
+ if (nogvl_ctx.r == -1) {
709
+ rb_raise(rb_eRuntimeError, "ruby_memprofiler_pprof: failed serialising samples: %s", nogvl_ctx.errbuf);
710
+ }
711
+
712
+ VALUE pprof_data = rb_str_new(nogvl_ctx.pprof_outbuf, nogvl_ctx.pprof_outbuf_len);
713
+
714
+ struct timespec t_end = mpp_gettime_monotonic();
715
+
716
+ VALUE profile_data = rb_class_new_instance(0, NULL, cd->cProfileData);
717
+ rb_funcall(profile_data, rb_intern("pprof_data="), 1, pprof_data);
718
+ rb_funcall(profile_data, rb_intern("heap_samples_count="), 1, SIZET2NUM(sample_ctx.actual_sample_count));
719
+ rb_funcall(profile_data, rb_intern("dropped_samples_heap_bufsize="), 1, SIZET2NUM(dropped_samples_bufsize));
720
+ rb_funcall(profile_data, rb_intern("flush_duration_nsecs="), 1, INT2NUM(mpp_time_delta_nsec(t_start, t_end)));
721
+ rb_funcall(profile_data, rb_intern("pprof_serialization_nsecs="), 1,
722
+ INT2NUM(mpp_time_delta_nsec(t_serialize_start, t_end)));
723
+ rb_funcall(profile_data, rb_intern("sample_add_nsecs="), 1, INT2NUM(mpp_time_delta_nsec(t_start, t_serialize_start)));
724
+ rb_funcall(profile_data, rb_intern("sample_add_without_gvl_nsecs="), 1, INT2NUM(sample_ctx.nogvl_duration));
725
+ rb_funcall(profile_data, rb_intern("gvl_proactive_yield_count="), 1, INT2NUM(sample_ctx.gvl_yield_count));
726
+ rb_funcall(profile_data, rb_intern("gvl_proactive_check_yield_count="), 1, INT2NUM(sample_ctx.gvl_check_yield_count));
727
+
728
+ return profile_data;
729
+ }
730
+
731
+ static void *flush_nogvl(void *ctxarg) {
732
+ struct flush_nogvl_ctx *ctx = (struct flush_nogvl_ctx *)ctxarg;
733
+
734
+ ctx->r = mpp_pprof_serctx_serialize(ctx->serctx, &ctx->pprof_outbuf, &ctx->pprof_outbuf_len, ctx->errbuf,
735
+ ctx->sizeof_errbuf);
736
+ return NULL;
737
+ }
738
+
739
+ static void flush_nogvl_unblock(void *ctxarg) {
740
+ struct flush_nogvl_ctx *ctx = (struct flush_nogvl_ctx *)ctxarg;
741
+ uint8_t one = 1;
742
+ __atomic_store(&ctx->serctx->interrupt, &one, __ATOMIC_SEQ_CST);
743
+ }
744
+
745
+ static VALUE collector_profile(VALUE self) {
746
+ rb_need_block();
747
+
748
+ rb_funcall(self, rb_intern("start!"), 0);
749
+ rb_yield_values(0);
750
+ VALUE profile_output = rb_funcall(self, rb_intern("flush"), 0);
751
+ rb_funcall(self, rb_intern("stop!"), 0);
752
+
753
+ return profile_output;
754
+ }
755
+
756
+ static VALUE collector_live_heap_samples_count(VALUE self) {
757
+ struct collector_cdata *cd = collector_cdata_get(self);
758
+ return SIZET2NUM(cd->heap_samples_count);
759
+ }
760
+
761
+ static VALUE collector_get_sample_rate(VALUE self) {
762
+ struct collector_cdata *cd = collector_cdata_get(self);
763
+ return DBL2NUM(((double)cd->u32_sample_rate) / UINT32_MAX);
764
+ }
765
+
766
+ static VALUE collector_set_sample_rate(VALUE self, VALUE newval) {
767
+ struct collector_cdata *cd = collector_cdata_get(self);
768
+ // Convert the double sample rate (between 0 and 1) to a value between 0 and UINT32_MAX
769
+ cd->u32_sample_rate = UINT32_MAX * NUM2DBL(newval);
770
+ return newval;
771
+ }
772
+
773
+ static VALUE collector_get_max_heap_samples(VALUE self) {
774
+ struct collector_cdata *cd = collector_cdata_get(self);
775
+ return SIZET2NUM(cd->max_heap_samples);
776
+ }
777
+
778
+ static VALUE collector_set_max_heap_samples(VALUE self, VALUE newval) {
779
+ struct collector_cdata *cd = collector_cdata_get(self);
780
+ cd->max_heap_samples = NUM2SIZET(newval);
781
+ return newval;
782
+ }
783
+
784
+ static VALUE collector_get_pretty_backtraces(VALUE self) {
785
+ struct collector_cdata *cd = collector_cdata_get(self);
786
+ return cd->pretty_backtraces ? Qtrue : Qfalse;
787
+ }
788
+
789
+ static VALUE collector_set_pretty_backtraces(VALUE self, VALUE newval) {
790
+ struct collector_cdata *cd = collector_cdata_get(self);
791
+ cd->pretty_backtraces = RTEST(newval);
792
+ return newval;
793
+ }
794
+
795
+ static VALUE collector_get_last_mark_nsecs(VALUE self) {
796
+ struct collector_cdata *cd = collector_cdata_get(self);
797
+ return INT2NUM(cd->last_gc_mark_ns);
798
+ }
799
+
800
+ static VALUE collector_get_mark_table_size(VALUE self) {
801
+ struct collector_cdata *cd = collector_cdata_get(self);
802
+ return UINT2NUM(cd->mark_table->num_entries);
803
+ }