ruby_memprofiler_pprof 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. checksums.yaml +7 -0
  2. data/ext/ruby_memprofiler_pprof/backtrace.c +429 -0
  3. data/ext/ruby_memprofiler_pprof/collector.c +1055 -0
  4. data/ext/ruby_memprofiler_pprof/compat.c +182 -0
  5. data/ext/ruby_memprofiler_pprof/extconf.rb +72 -0
  6. data/ext/ruby_memprofiler_pprof/pprof.upb.c +170 -0
  7. data/ext/ruby_memprofiler_pprof/pprof.upb.h +848 -0
  8. data/ext/ruby_memprofiler_pprof/pprof_out.c +285 -0
  9. data/ext/ruby_memprofiler_pprof/ruby_memprofiler_pprof.c +11 -0
  10. data/ext/ruby_memprofiler_pprof/ruby_memprofiler_pprof.h +301 -0
  11. data/ext/ruby_memprofiler_pprof/strtab.c +391 -0
  12. data/ext/ruby_memprofiler_pprof/vendor/upb/BUILD +719 -0
  13. data/ext/ruby_memprofiler_pprof/vendor/upb/CONTRIBUTING.md +37 -0
  14. data/ext/ruby_memprofiler_pprof/vendor/upb/DESIGN.md +201 -0
  15. data/ext/ruby_memprofiler_pprof/vendor/upb/LICENSE +26 -0
  16. data/ext/ruby_memprofiler_pprof/vendor/upb/README.md +78 -0
  17. data/ext/ruby_memprofiler_pprof/vendor/upb/WORKSPACE +58 -0
  18. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/BUILD +53 -0
  19. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/amalgamate.py +129 -0
  20. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/build_defs.bzl +160 -0
  21. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/lua.BUILD +127 -0
  22. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/protobuf.patch +54 -0
  23. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/py_proto_library.bzl +137 -0
  24. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/python_downloads.bzl +84 -0
  25. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/system_python.bzl +101 -0
  26. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/upb_proto_library.bzl +388 -0
  27. data/ext/ruby_memprofiler_pprof/vendor/upb/bazel/workspace_deps.bzl +89 -0
  28. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/BUILD +252 -0
  29. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/BUILD.googleapis +54 -0
  30. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/benchmark.cc +333 -0
  31. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/build_defs.bzl +88 -0
  32. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/compare.py +118 -0
  33. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/descriptor.proto +888 -0
  34. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/descriptor_sv.proto +890 -0
  35. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/empty.proto +6 -0
  36. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/gen_protobuf_binary_cc.py +64 -0
  37. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/gen_synthetic_protos.py +118 -0
  38. data/ext/ruby_memprofiler_pprof/vendor/upb/benchmarks/gen_upb_binary_c.py +65 -0
  39. data/ext/ruby_memprofiler_pprof/vendor/upb/cmake/BUILD.bazel +102 -0
  40. data/ext/ruby_memprofiler_pprof/vendor/upb/cmake/README.md +23 -0
  41. data/ext/ruby_memprofiler_pprof/vendor/upb/cmake/build_defs.bzl +73 -0
  42. data/ext/ruby_memprofiler_pprof/vendor/upb/cmake/make_cmakelists.py +340 -0
  43. data/ext/ruby_memprofiler_pprof/vendor/upb/cmake/staleness_test.py +57 -0
  44. data/ext/ruby_memprofiler_pprof/vendor/upb/cmake/staleness_test_lib.py +186 -0
  45. data/ext/ruby_memprofiler_pprof/vendor/upb/docs/render.py +43 -0
  46. data/ext/ruby_memprofiler_pprof/vendor/upb/docs/style-guide.md +65 -0
  47. data/ext/ruby_memprofiler_pprof/vendor/upb/docs/vs-cpp-protos.md +255 -0
  48. data/ext/ruby_memprofiler_pprof/vendor/upb/docs/wrapping-upb.md +444 -0
  49. data/ext/ruby_memprofiler_pprof/vendor/upb/python/BUILD +216 -0
  50. data/ext/ruby_memprofiler_pprof/vendor/upb/python/convert.c +394 -0
  51. data/ext/ruby_memprofiler_pprof/vendor/upb/python/convert.h +63 -0
  52. data/ext/ruby_memprofiler_pprof/vendor/upb/python/descriptor.c +1694 -0
  53. data/ext/ruby_memprofiler_pprof/vendor/upb/python/descriptor.h +80 -0
  54. data/ext/ruby_memprofiler_pprof/vendor/upb/python/descriptor_containers.c +704 -0
  55. data/ext/ruby_memprofiler_pprof/vendor/upb/python/descriptor_containers.h +114 -0
  56. data/ext/ruby_memprofiler_pprof/vendor/upb/python/descriptor_pool.c +650 -0
  57. data/ext/ruby_memprofiler_pprof/vendor/upb/python/descriptor_pool.h +48 -0
  58. data/ext/ruby_memprofiler_pprof/vendor/upb/python/dist/BUILD.bazel +193 -0
  59. data/ext/ruby_memprofiler_pprof/vendor/upb/python/dist/dist.bzl +190 -0
  60. data/ext/ruby_memprofiler_pprof/vendor/upb/python/extension_dict.c +247 -0
  61. data/ext/ruby_memprofiler_pprof/vendor/upb/python/extension_dict.h +39 -0
  62. data/ext/ruby_memprofiler_pprof/vendor/upb/python/map.c +522 -0
  63. data/ext/ruby_memprofiler_pprof/vendor/upb/python/map.h +66 -0
  64. data/ext/ruby_memprofiler_pprof/vendor/upb/python/message.c +1909 -0
  65. data/ext/ruby_memprofiler_pprof/vendor/upb/python/message.h +101 -0
  66. data/ext/ruby_memprofiler_pprof/vendor/upb/python/minimal_test.py +183 -0
  67. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/BUILD +70 -0
  68. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/README.md +11 -0
  69. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/descriptor_database_test_wrapper.py +30 -0
  70. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/descriptor_pool_test_wrapper.py +45 -0
  71. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/descriptor_test_wrapper.py +46 -0
  72. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/generator_test_wrapper.py +30 -0
  73. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/json_format_test_wrapper.py +30 -0
  74. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/keywords_test_wrapper.py +30 -0
  75. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/message_factory_test_wrapper.py +37 -0
  76. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/message_test_wrapper.py +52 -0
  77. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/proto_builder_test_wrapper.py +32 -0
  78. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/pyproto_test_wrapper.bzl +36 -0
  79. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/reflection_test_wrapper.py +45 -0
  80. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/service_reflection_test_wrapper.py +30 -0
  81. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/symbol_database_test_wrapper.py +30 -0
  82. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/text_encoding_test_wrapper.py +30 -0
  83. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/text_format_test_wrapper.py +30 -0
  84. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/unknown_fields_test_wrapper.py +30 -0
  85. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/well_known_types_test_wrapper.py +36 -0
  86. data/ext/ruby_memprofiler_pprof/vendor/upb/python/pb_unit_tests/wire_format_test_wrapper.py +30 -0
  87. data/ext/ruby_memprofiler_pprof/vendor/upb/python/protobuf.c +350 -0
  88. data/ext/ruby_memprofiler_pprof/vendor/upb/python/protobuf.h +230 -0
  89. data/ext/ruby_memprofiler_pprof/vendor/upb/python/py_extension.bzl +55 -0
  90. data/ext/ruby_memprofiler_pprof/vendor/upb/python/python_api.h +61 -0
  91. data/ext/ruby_memprofiler_pprof/vendor/upb/python/repeated.c +828 -0
  92. data/ext/ruby_memprofiler_pprof/vendor/upb/python/repeated.h +69 -0
  93. data/ext/ruby_memprofiler_pprof/vendor/upb/python/unknown_fields.c +404 -0
  94. data/ext/ruby_memprofiler_pprof/vendor/upb/python/unknown_fields.h +39 -0
  95. data/ext/ruby_memprofiler_pprof/vendor/upb/python/version_script.lds +6 -0
  96. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/lunit/LICENSE +32 -0
  97. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/lunit/README.google +9 -0
  98. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/lunit/console.lua +156 -0
  99. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/lunit/lunit.lua +725 -0
  100. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/utf8_range/BUILD +19 -0
  101. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/utf8_range/LICENSE +21 -0
  102. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/utf8_range/naive.c +92 -0
  103. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/utf8_range/range2-neon.c +157 -0
  104. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/utf8_range/range2-sse.c +170 -0
  105. data/ext/ruby_memprofiler_pprof/vendor/upb/third_party/utf8_range/utf8_range.h +9 -0
  106. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/BUILD.bazel +129 -0
  107. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/README.md +8 -0
  108. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/def.c +939 -0
  109. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/lua_proto_library.bzl +138 -0
  110. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/main.c +83 -0
  111. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/msg.c +1118 -0
  112. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/test.proto +69 -0
  113. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/test_upb.lua +846 -0
  114. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/upb.c +258 -0
  115. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/upb.h +132 -0
  116. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/upb.lua +58 -0
  117. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/bindings/lua/upbc.cc +134 -0
  118. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/collections.c +192 -0
  119. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/collections.h +174 -0
  120. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/conformance_upb.c +346 -0
  121. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/conformance_upb_failures.txt +1 -0
  122. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/decode.c +1221 -0
  123. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/decode.h +94 -0
  124. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/decode_fast.c +1055 -0
  125. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/decode_fast.h +153 -0
  126. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/decode_internal.h +211 -0
  127. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/def.c +3262 -0
  128. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/def.h +414 -0
  129. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/def.hpp +438 -0
  130. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/empty.proto +1 -0
  131. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/encode.c +604 -0
  132. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/encode.h +71 -0
  133. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/fuzz/BUILD +13 -0
  134. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/fuzz/file_descriptor_parsenew_fuzzer.cc +43 -0
  135. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/json_decode.c +1509 -0
  136. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/json_decode.h +47 -0
  137. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/json_encode.c +776 -0
  138. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/json_encode.h +62 -0
  139. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/mini_table.c +1147 -0
  140. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/mini_table.h +189 -0
  141. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/mini_table.hpp +112 -0
  142. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/mini_table_accessors.c +363 -0
  143. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/mini_table_accessors.h +263 -0
  144. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/mini_table_accessors_internal.h +59 -0
  145. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/mini_table_accessors_test.cc +425 -0
  146. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/mini_table_test.cc +230 -0
  147. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/msg.c +428 -0
  148. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/msg.h +114 -0
  149. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/msg_internal.h +836 -0
  150. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/msg_test.cc +491 -0
  151. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/msg_test.proto +195 -0
  152. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/port_def.inc +261 -0
  153. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/port_undef.inc +62 -0
  154. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/reflection.c +323 -0
  155. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/reflection.h +109 -0
  156. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/reflection.hpp +37 -0
  157. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/table.c +926 -0
  158. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/table_internal.h +385 -0
  159. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/test.proto +74 -0
  160. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/test_cpp.cc +186 -0
  161. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/test_cpp.proto +12 -0
  162. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/test_generated_code.cc +977 -0
  163. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/test_table.cc +580 -0
  164. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/text_encode.c +472 -0
  165. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/text_encode.h +64 -0
  166. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/upb.c +362 -0
  167. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/upb.h +378 -0
  168. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/upb.hpp +115 -0
  169. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/upb_internal.h +68 -0
  170. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/BUILD +121 -0
  171. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/README.md +7 -0
  172. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/compare.c +300 -0
  173. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/compare.h +66 -0
  174. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/compare_test.cc +236 -0
  175. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/def_to_proto.c +572 -0
  176. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/def_to_proto.h +62 -0
  177. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/def_to_proto_public_import_test.proto +32 -0
  178. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/def_to_proto_regular_import_test.proto +36 -0
  179. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/def_to_proto_test.cc +143 -0
  180. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/def_to_proto_test.proto +119 -0
  181. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/def_to_proto_weak_import_test.proto +28 -0
  182. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/def_to_proto_wweak_import_test.proto +28 -0
  183. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/required_fields.c +311 -0
  184. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/required_fields.h +94 -0
  185. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/required_fields_test.cc +202 -0
  186. data/ext/ruby_memprofiler_pprof/vendor/upb/upb/util/required_fields_test.proto +48 -0
  187. data/ext/ruby_memprofiler_pprof/vendor/upb/upbc/BUILD +78 -0
  188. data/ext/ruby_memprofiler_pprof/vendor/upb/upbc/common.cc +77 -0
  189. data/ext/ruby_memprofiler_pprof/vendor/upb/upbc/common.h +112 -0
  190. data/ext/ruby_memprofiler_pprof/vendor/upb/upbc/protoc-gen-upb.cc +1997 -0
  191. data/ext/ruby_memprofiler_pprof/vendor/upb/upbc/protoc-gen-upbdefs.cc +193 -0
  192. data/lib/ruby_memprofiler_pprof/atfork.rb +77 -0
  193. data/lib/ruby_memprofiler_pprof/block_flusher.rb +61 -0
  194. data/lib/ruby_memprofiler_pprof/file_flusher.rb +45 -0
  195. data/lib/ruby_memprofiler_pprof/profile_app.rb +30 -0
  196. data/lib/ruby_memprofiler_pprof/profile_data.rb +18 -0
  197. data/lib/ruby_memprofiler_pprof/version.rb +5 -0
  198. data/lib/ruby_memprofiler_pprof.rb +8 -0
  199. data/libexec/ruby_memprofiler_pprof_profile +16 -0
  200. metadata +257 -0
@@ -0,0 +1,1055 @@
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 <vm_core.h>
10
+ #include <iseq.h>
11
+
12
+ #include "ruby_memprofiler_pprof.h"
13
+
14
+ struct collector_cdata {
15
+ // Internal, cross-ractor lock for this data
16
+ pthread_mutex_t lock;
17
+
18
+ // Global variables we need to keep a hold of
19
+ VALUE cCollector;
20
+ VALUE cProfileData;
21
+ VALUE mMemprofilerPprof;
22
+
23
+ // Ruby Tracepoint objects for our hooks
24
+ VALUE newobj_trace;
25
+ VALUE freeobj_trace;
26
+ VALUE creturn_trace;
27
+
28
+ // How often (as a fraction of UINT32_MAX) we should sample allocations;
29
+ // Must be accessed through atomics
30
+ uint32_t u32_sample_rate;
31
+ // How often (as a fraction of UINT32_MAX) we should retain allocations, to profile allocations
32
+ // as well as just heap usage.
33
+ // Does _NOT_ need to be accessed through atomics.
34
+ uint32_t u32_allocation_retain_rate;
35
+ // This flag is used to make sure we detach our tracepoints as we're getting GC'd.
36
+ bool is_tracing;
37
+
38
+ // ======== Allocation samples ========
39
+ // A linked list of samples, added to each time memory is allocated and cleared when
40
+ // #flush is called.
41
+ struct mpp_sample *allocation_samples;
42
+ // Number of elements currently in the list
43
+ int64_t allocation_samples_count;
44
+ // How big the linked list can grow
45
+ int64_t max_allocation_samples;
46
+ // When objects are first allocated, we won't actually know their _real_ size; the object is not
47
+ // in a state where calling rb_obj_memsize_of() on it is well-defined. Among other things, if the
48
+ // object is T_CLASS, the ivar table won't be initialized yet, and trying to get its size will crash.
49
+ // Even if it _did_ work (which it did, in versions of Ruby before variable-sized RValues), calling
50
+ // rb_obj_memsize_of() will return sizeof(RVALUE). If e.g. a T_STRING is being allocated,
51
+ // the heap memory for that is actually only allocated _after_ the newobj tracepoint fires.
52
+ //
53
+ // To make sure we can see the "real" size of these, we add a tracepoint on CRETURN. When that hook
54
+ // fires, we check the size of all (still-live) objects recently allocated, and store _that_ as
55
+ // the allocation size. This works well for T_STRING, T_DATA, T_STRUCT's etc that are allocated
56
+ // inside C and then immediately filled; the correct memsize will be recorded on them before the
57
+ // Ruby backtrace even changes.
58
+ // This counter therefore keeps track of how many elements of *allocation_samples have yet to have
59
+ // this hook called on them.
60
+ int64_t pending_size_count;
61
+
62
+ // ======== Heap samples ========
63
+ // A hash-table keying live VALUEs to their allocation sample. This is _not_ cleared
64
+ // when #flush is called; instead, elements are deleted when they are free'd. This is
65
+ // used for building heap profiles.
66
+ st_table *heap_samples;
67
+ // Number of elements currently in the heap profile hash
68
+ int64_t heap_samples_count;
69
+ // How big the sample table can grow
70
+ int64_t max_heap_samples;
71
+
72
+ // ======== Sample drop counters ========
73
+ // These are all accessed via atomics; how else would we have a counter for how often we failed
74
+ // to acquire the lock?
75
+ //
76
+ // Number of samples dropped for want of obtaining the lock.
77
+ int64_t dropped_samples_nolock;
78
+ // Number of samples dropped for want of space in the allocation buffer.
79
+ int64_t dropped_samples_allocation_bufsize;
80
+ // Number of samples dropped for want of space in the heap allocation table.
81
+ int64_t dropped_samples_heap_bufsize;
82
+
83
+ // String interning table used to keep constant pointers to every string; this saves memory
84
+ // used in backtraces, and also helps us efficiently build up the pprof protobuf format (since that
85
+ // _requires_ that strings are interned in a string table).
86
+ struct mpp_strtab *string_tab;
87
+ // Same thing, but for backtrace locations.
88
+ struct mpp_rb_loctab *loctab;
89
+
90
+ // Which method to use for getting backtraces
91
+ int bt_method;
92
+
93
+ // This we need to know so we can at least give a non-zero size for new objects.
94
+ size_t rvalue_size;
95
+ };
96
+
97
+ // We need a global list of all collectors, so that, in our atfork handler, we can correctly lock/unlock
98
+ // all of their mutexes and guarantee correctness across forks.
99
+ static st_table *global_collectors;
100
+ static pthread_mutex_t global_collectors_lock;
101
+
102
+ static void internal_sample_decrement_refcount(struct collector_cdata *cd, struct mpp_sample *s) {
103
+ s->refcount--;
104
+ if (!s->refcount) {
105
+ mpp_rb_backtrace_destroy(cd->loctab, s->bt);
106
+ mpp_free(s);
107
+ }
108
+ }
109
+
110
+ static int collector_cdata_gc_decrement_live_object_refcounts(st_data_t key, st_data_t value, st_data_t arg) {
111
+ struct mpp_sample *s = (struct mpp_sample *)value;
112
+ struct collector_cdata *cd = (struct collector_cdata *)arg;
113
+ internal_sample_decrement_refcount(cd, s);
114
+ return ST_CONTINUE;
115
+ }
116
+
117
+ static void collector_cdata_gc_free_heap_samples(struct collector_cdata *cd) {
118
+ if (cd->heap_samples) {
119
+ st_foreach(cd->heap_samples, collector_cdata_gc_decrement_live_object_refcounts, (st_data_t)cd);
120
+ st_free_table(cd->heap_samples);
121
+ }
122
+ cd->heap_samples = NULL;
123
+ }
124
+
125
+ static void internal_sample_list_decrement_refcount(struct collector_cdata *cd, struct mpp_sample *s) {
126
+ while (s) {
127
+ struct mpp_sample *next_s = s->next_alloc;
128
+ internal_sample_decrement_refcount(cd, s);
129
+ s = next_s;
130
+ }
131
+ }
132
+
133
+ static void collector_cdata_gc_free_allocation_samples(struct collector_cdata *cd) {
134
+ internal_sample_list_decrement_refcount(cd, cd->allocation_samples);
135
+ cd->allocation_samples = NULL;
136
+ }
137
+
138
+ static int collector_cdata_gc_memsize_live_objects(st_data_t key, st_data_t value, st_data_t arg) {
139
+ size_t *acc_ptr = (size_t *)arg;
140
+ struct mpp_sample *s = (struct mpp_sample *)value;
141
+
142
+ // Only consider the live object list to be holding the backtrace, for accounting purposes, if it's
143
+ // not also in the allocation sample list.
144
+ if (s->refcount == 1) {
145
+ *acc_ptr += sizeof(*s);
146
+ *acc_ptr += mpp_rb_backtrace_memsize(s->bt);
147
+ }
148
+ return ST_CONTINUE;
149
+ }
150
+
151
+ static void collector_cdata_gc_free_loctab(struct collector_cdata *cd) {
152
+ if (cd->loctab) {
153
+ mpp_rb_loctab_destroy(cd->loctab);
154
+ }
155
+ }
156
+
157
+ static void collector_cdata_gc_free_strtab(struct collector_cdata *cd) {
158
+ if (cd->string_tab) {
159
+ mpp_strtab_destroy(cd->string_tab);
160
+ }
161
+ }
162
+
163
+ static void collector_cdata_gc_mark(void *ptr) {
164
+ struct collector_cdata *cd = (struct collector_cdata *)ptr;
165
+ rb_gc_mark_movable(cd->newobj_trace);
166
+ rb_gc_mark_movable(cd->freeobj_trace);
167
+ rb_gc_mark_movable(cd->creturn_trace);
168
+ rb_gc_mark_movable(cd->mMemprofilerPprof);
169
+ rb_gc_mark_movable(cd->cCollector);
170
+ rb_gc_mark_movable(cd->cProfileData);
171
+ }
172
+
173
+ static void collector_cdata_gc_free(void *ptr) {
174
+ struct collector_cdata *cd = (struct collector_cdata *)ptr;
175
+ if (cd->is_tracing) {
176
+ if (cd->newobj_trace) {
177
+ rb_tracepoint_disable(cd->newobj_trace);
178
+ }
179
+ if (cd->freeobj_trace) {
180
+ rb_tracepoint_disable(cd->freeobj_trace);
181
+ }
182
+ }
183
+
184
+ // Needed in case there are any in-flight tracepoints we just disabled above.
185
+ mpp_pthread_mutex_lock(&cd->lock);
186
+
187
+ collector_cdata_gc_free_heap_samples(cd);
188
+ collector_cdata_gc_free_allocation_samples(cd);
189
+ collector_cdata_gc_free_loctab(cd);
190
+ collector_cdata_gc_free_strtab(cd);
191
+
192
+ // Remove from global collectors list.
193
+ mpp_pthread_mutex_lock(&global_collectors_lock);
194
+ st_data_t cd_key = (st_data_t)cd;
195
+ st_delete(global_collectors, &cd_key, NULL);
196
+ mpp_pthread_mutex_unlock(&global_collectors_lock);
197
+
198
+ mpp_pthread_mutex_unlock(&cd->lock);
199
+ mpp_pthread_mutex_destroy(&cd->lock);
200
+
201
+ ruby_xfree(ptr);
202
+ }
203
+
204
+ static size_t collector_cdata_memsize(const void *ptr) {
205
+ struct collector_cdata *cd = (struct collector_cdata *)ptr;
206
+ size_t sz = sizeof(*cd);
207
+ if (cd->heap_samples) {
208
+ st_foreach(cd->heap_samples, collector_cdata_gc_memsize_live_objects, (st_data_t)&sz);
209
+ sz += st_memsize(cd->heap_samples);
210
+ }
211
+ if (cd->string_tab) {
212
+ sz += mpp_strtab_memsize(cd->string_tab);
213
+ }
214
+ if (cd->loctab) {
215
+ sz += mpp_rb_loctab_memsize(cd->loctab);
216
+ }
217
+ struct mpp_sample *s = cd->allocation_samples;
218
+ while (s) {
219
+ sz += sizeof(*s);
220
+ sz += mpp_rb_backtrace_memsize(s->bt);
221
+ s = s->next_alloc;
222
+ }
223
+
224
+ return sz;
225
+ }
226
+
227
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
228
+ // Support VALUES we're tracking being moved away in Ruby 2.7+ with GC.compact
229
+ static int collector_move_each_live_object(st_data_t key, st_data_t value, st_data_t arg) {
230
+ struct collector_cdata *cd = (struct collector_cdata *)arg;
231
+ struct mpp_sample *sample = (struct mpp_sample *)value;
232
+
233
+ if (rb_gc_location(sample->allocated_value_weak) == sample->allocated_value_weak) {
234
+ return ST_CONTINUE;
235
+ } else {
236
+ sample->allocated_value_weak = rb_gc_location(sample->allocated_value_weak);
237
+ st_insert(cd->heap_samples, sample->allocated_value_weak, (st_data_t)sample);
238
+ return ST_DELETE;
239
+ }
240
+ }
241
+
242
+ static void collector_cdata_gc_compact(void *ptr) {
243
+ struct collector_cdata *cd = (struct collector_cdata *)ptr;
244
+ cd->newobj_trace = rb_gc_location(cd->newobj_trace);
245
+ cd->freeobj_trace = rb_gc_location(cd->freeobj_trace);
246
+ cd->creturn_trace = rb_gc_location(cd->creturn_trace);
247
+ cd->mMemprofilerPprof = rb_gc_location(cd->mMemprofilerPprof);
248
+ cd->cCollector = rb_gc_location(cd->cCollector);
249
+ cd->cProfileData = rb_gc_location(cd->cProfileData);
250
+ st_foreach(cd->heap_samples, collector_move_each_live_object, (st_data_t)cd);
251
+ }
252
+ #endif
253
+
254
+ static const rb_data_type_t collector_cdata_type = {
255
+ "collector_cdata",
256
+ {
257
+ collector_cdata_gc_mark, collector_cdata_gc_free, collector_cdata_memsize,
258
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
259
+ collector_cdata_gc_compact,
260
+ #endif
261
+ { 0 }, /* reserved */
262
+ },
263
+ /* parent, data, [ flags ] */
264
+ NULL, NULL, 0
265
+ };
266
+
267
+ static struct collector_cdata *collector_cdata_get(VALUE self) {
268
+ struct collector_cdata *a;
269
+ TypedData_Get_Struct(self, struct collector_cdata, &collector_cdata_type, a);
270
+ return a;
271
+ }
272
+
273
+ static VALUE collector_alloc(VALUE klass) {
274
+ struct collector_cdata *cd;
275
+ VALUE v = TypedData_Make_Struct(klass, struct collector_cdata, &collector_cdata_type, cd);
276
+
277
+ cd->newobj_trace = Qnil;
278
+ cd->freeobj_trace = Qnil;
279
+ cd->creturn_trace = Qnil;
280
+
281
+ __atomic_store_n(&cd->u32_sample_rate, 0, __ATOMIC_SEQ_CST);
282
+ cd->is_tracing = false;
283
+
284
+ cd->allocation_samples = NULL;
285
+ cd->allocation_samples_count = 0;
286
+ cd->max_allocation_samples = 0;
287
+ cd->pending_size_count = 0;
288
+
289
+ cd->heap_samples = NULL;
290
+ cd->heap_samples_count = 0;
291
+ cd->max_heap_samples = 0;
292
+
293
+ __atomic_store_n(&cd->dropped_samples_allocation_bufsize, 0, __ATOMIC_SEQ_CST);
294
+ __atomic_store_n(&cd->dropped_samples_heap_bufsize, 0, __ATOMIC_SEQ_CST);
295
+ __atomic_store_n(&cd->dropped_samples_nolock, 0, __ATOMIC_SEQ_CST);
296
+
297
+ cd->string_tab = NULL;
298
+ cd->loctab = NULL;
299
+
300
+ // Initialize the mutex.
301
+ // It really does need to be recursive - if we call a rb_* function while holding
302
+ // the lock, that could trigger the GC to run and call our freeobj tracepoint,
303
+ // which _also_ needs the lock.
304
+ pthread_mutexattr_t mutex_attr;
305
+ mpp_pthread_mutexattr_init(&mutex_attr);
306
+ mpp_pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
307
+ mpp_pthread_mutex_init(&cd->lock, &mutex_attr);
308
+ mpp_pthread_mutexattr_destroy(&mutex_attr);
309
+
310
+ // Add us to the global list of collectors, to handle pthread_atfork.
311
+ mpp_pthread_mutex_lock(&global_collectors_lock);
312
+ st_insert(global_collectors, (st_data_t)cd, (st_data_t)cd);
313
+ mpp_pthread_mutex_unlock(&global_collectors_lock);
314
+ return v;
315
+ }
316
+
317
+ struct initialize_protected_args {
318
+ int argc;
319
+ VALUE *argv;
320
+ VALUE self;
321
+ struct collector_cdata *cd;
322
+ };
323
+
324
+ static VALUE collector_initialize_protected(VALUE vargs) {
325
+ struct initialize_protected_args *args = (struct initialize_protected_args *)vargs;
326
+ struct collector_cdata *cd = args->cd;
327
+
328
+ // Save constants
329
+ cd->mMemprofilerPprof = rb_const_get(rb_cObject, rb_intern("MemprofilerPprof"));
330
+ cd->cCollector = rb_const_get(cd->mMemprofilerPprof, rb_intern("Collector"));
331
+ cd->cProfileData = rb_const_get(cd->mMemprofilerPprof, rb_intern("ProfileData"));
332
+
333
+ // Argument parsing
334
+ VALUE kwargs_hash = Qnil;
335
+ rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, args->argc, args->argv, "00:", &kwargs_hash);
336
+ VALUE kwarg_values[5];
337
+ ID kwarg_ids[5];
338
+ kwarg_ids[0] = rb_intern("sample_rate");
339
+ kwarg_ids[1] = rb_intern("max_allocation_samples");
340
+ kwarg_ids[2] = rb_intern("max_heap_samples");
341
+ kwarg_ids[3] = rb_intern("bt_method");
342
+ kwarg_ids[4] = rb_intern("allocation_retain_rate");
343
+ rb_get_kwargs(kwargs_hash, kwarg_ids, 0, 5, kwarg_values);
344
+
345
+ // Default values...
346
+ if (kwarg_values[0] == Qundef) kwarg_values[0] = DBL2NUM(0.01);
347
+ if (kwarg_values[1] == Qundef) kwarg_values[1] = LONG2NUM(10000);
348
+ if (kwarg_values[2] == Qundef) kwarg_values[2] = LONG2NUM(50000);
349
+ if (kwarg_values[3] == Qundef) kwarg_values[3] = rb_id2sym(rb_intern("cfp"));
350
+ if (kwarg_values[4] == Qundef) kwarg_values[4] = DBL2NUM(1);
351
+
352
+ rb_funcall(args->self, rb_intern("sample_rate="), 1, kwarg_values[0]);
353
+ rb_funcall(args->self, rb_intern("max_allocation_samples="), 1, kwarg_values[1]);
354
+ rb_funcall(args->self, rb_intern("max_heap_samples="), 1, kwarg_values[2]);
355
+ rb_funcall(args->self, rb_intern("bt_method="), 1, kwarg_values[3]);
356
+ rb_funcall(args->self, rb_intern("allocation_retain_rate="), 1, kwarg_values[4]);
357
+
358
+ cd->string_tab = mpp_strtab_new();
359
+ cd->loctab = mpp_rb_loctab_new(cd->string_tab);
360
+ cd->allocation_samples = NULL;
361
+ cd->allocation_samples_count = 0;
362
+ cd->pending_size_count = 0;
363
+ cd->heap_samples = st_init_numtable();
364
+ cd->heap_samples_count = 0;
365
+
366
+ VALUE internal_constants = rb_const_get(rb_mGC, rb_intern("INTERNAL_CONSTANTS"));
367
+ cd->rvalue_size = NUM2LONG(rb_hash_aref(internal_constants, rb_id2sym(rb_intern("RVALUE_SIZE"))));
368
+
369
+ return Qnil;
370
+ }
371
+
372
+ static VALUE collector_initialize(int argc, VALUE *argv, VALUE self) {
373
+ // Need to do this rb_protect dance to ensure that all access to collector_cdata is through the mutex.
374
+ struct initialize_protected_args args;
375
+ args.argc = argc;
376
+ args.argv = argv;
377
+ args.self = self;
378
+ args.cd = collector_cdata_get(self);
379
+
380
+ mpp_pthread_mutex_lock(&args.cd->lock);
381
+ int jump_tag = 0;
382
+ VALUE r = rb_protect(collector_initialize_protected, (VALUE)&args, &jump_tag);
383
+ mpp_pthread_mutex_unlock(&args.cd->lock);
384
+ if (jump_tag) rb_jump_tag(jump_tag);
385
+ return r;
386
+ }
387
+
388
+ static VALUE collector_get_sample_rate(VALUE self) {
389
+ struct collector_cdata *cd = collector_cdata_get(self);
390
+ uint32_t sample_rate = __atomic_load_n(&cd->u32_sample_rate, __ATOMIC_SEQ_CST);
391
+ return DBL2NUM(((double)sample_rate)/UINT32_MAX);
392
+ }
393
+
394
+ static VALUE collector_set_sample_rate(VALUE self, VALUE newval) {
395
+ struct collector_cdata *cd = collector_cdata_get(self);
396
+ double dbl_sample_rate = NUM2DBL(newval);
397
+ // Convert the double sample rate (between 0 and 1) to a value between 0 and UINT32_MAX
398
+ uint32_t new_sample_rate_uint = UINT32_MAX * dbl_sample_rate;
399
+ __atomic_store_n(&cd->u32_sample_rate, new_sample_rate_uint, __ATOMIC_SEQ_CST);
400
+ return newval;
401
+ }
402
+
403
+ static VALUE collector_get_allocation_retain_rate(VALUE self) {
404
+ struct collector_cdata *cd = collector_cdata_get(self);
405
+ mpp_pthread_mutex_lock(&cd->lock);
406
+ uint32_t retain_rate_u32 = cd->u32_allocation_retain_rate;
407
+ mpp_pthread_mutex_unlock(&cd->lock);
408
+ return DBL2NUM(((double)retain_rate_u32)/UINT32_MAX);
409
+ }
410
+
411
+ static VALUE collector_set_allocation_retain_rate(VALUE self, VALUE newval) {
412
+ struct collector_cdata *cd = collector_cdata_get(self);
413
+ uint32_t retain_rate_u32 = UINT32_MAX * NUM2DBL(newval);
414
+ mpp_pthread_mutex_lock(&cd->lock);
415
+ cd->u32_allocation_retain_rate = retain_rate_u32;
416
+ mpp_pthread_mutex_unlock(&cd->lock);
417
+ return newval;
418
+ }
419
+
420
+ static VALUE collector_get_max_allocation_samples(VALUE self) {
421
+ struct collector_cdata *cd = collector_cdata_get(self);
422
+ mpp_pthread_mutex_lock(&cd->lock);
423
+ int64_t v = cd->max_allocation_samples;
424
+ mpp_pthread_mutex_unlock(&cd->lock);
425
+ return LONG2NUM(v);
426
+ }
427
+
428
+ static VALUE collector_set_max_allocation_samples(VALUE self, VALUE newval) {
429
+ struct collector_cdata *cd = collector_cdata_get(self);
430
+ int64_t v = NUM2LONG(newval);
431
+ mpp_pthread_mutex_lock(&cd->lock);
432
+ cd->max_allocation_samples = v;
433
+ mpp_pthread_mutex_unlock(&cd->lock);
434
+ return newval;
435
+ }
436
+
437
+ static VALUE collector_get_max_heap_samples(VALUE self) {
438
+ struct collector_cdata *cd = collector_cdata_get(self);
439
+ mpp_pthread_mutex_lock(&cd->lock);
440
+ int64_t v = cd->max_heap_samples;
441
+ mpp_pthread_mutex_unlock(&cd->lock);
442
+ return LONG2NUM(v);
443
+ }
444
+
445
+ static VALUE collector_set_max_heap_samples(VALUE self, VALUE newval) {
446
+ struct collector_cdata *cd = collector_cdata_get(self);
447
+ int64_t v = NUM2LONG(newval);
448
+ mpp_pthread_mutex_lock(&cd->lock);
449
+ cd->max_heap_samples = v;
450
+ mpp_pthread_mutex_unlock(&cd->lock);
451
+ return newval;
452
+ }
453
+
454
+ static void collector_mark_sample_as_freed(struct collector_cdata *cd, VALUE freed_obj) {
455
+ struct mpp_sample *sample;
456
+ if (st_delete(cd->heap_samples, (st_data_t *)&freed_obj, (st_data_t *)&sample)) {
457
+ // Clear out the reference to it
458
+ sample->allocated_value_weak = Qundef;
459
+ // We deleted it out of live objects; decrement its refcount.
460
+ internal_sample_decrement_refcount(cd, sample);
461
+ cd->heap_samples_count--;
462
+ }
463
+ }
464
+
465
+
466
+ struct newobj_impl_args {
467
+ struct collector_cdata *cd;
468
+ struct mpp_rb_backtrace *bt;
469
+ VALUE tpval;
470
+ VALUE newobj;
471
+ size_t allocation_size;
472
+ };
473
+
474
+ // Collects all the parts of collector_tphook_newobj that could throw.
475
+ static VALUE collector_tphook_newobj_protected(VALUE args_as_uintptr) {
476
+ struct newobj_impl_args *args = (struct newobj_impl_args*)args_as_uintptr;
477
+ struct collector_cdata *cd = args->cd;
478
+ if (cd->bt_method == MPP_BT_METHOD_CFP) {
479
+ mpp_rb_backtrace_capture(cd->loctab, &args->bt);
480
+ } else if (cd->bt_method == MPP_BT_METHOD_SLOWRB) {
481
+ mpp_rb_backtrace_capture_slowrb(cd->loctab, &args->bt);
482
+ } else {
483
+ MPP_ASSERT_FAIL("unknown bt_method");
484
+ }
485
+ args->allocation_size = cd->rvalue_size;
486
+ return Qnil;
487
+ }
488
+
489
+ static void collector_tphook_newobj(VALUE tpval, void *data) {
490
+ struct collector_cdata *cd = (struct collector_cdata *)data;
491
+ struct newobj_impl_args args;
492
+ args.cd = cd;
493
+ args.tpval = tpval;
494
+ args.bt = NULL;
495
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
496
+ args.newobj = rb_tracearg_object(tparg);
497
+ int jump_tag = 0;
498
+ VALUE original_errinfo = Qundef;
499
+
500
+ mpp_pthread_mutex_lock(&cd->lock);
501
+
502
+ // For every new object that is created, we _MUST_ check if there is already another VALUE with the same,
503
+ // well, value, in our heap profiling table of live objects. This is because Ruby reserves the right to
504
+ // simply free some kinds of internal objects (such as T_IMEMOs) by simply setting the flags value on it
505
+ // to zero, without invoking the GC and without calling any kind of hook. So, we need to detect when such
506
+ // an object is freed and then the RVALUE is re-used for a new object to track it appropriately.
507
+ collector_mark_sample_as_freed(cd, args.newobj);
508
+
509
+ // Skip the rest of this method if we're not sampling.
510
+ uint32_t sample_rate = __atomic_load_n(&cd->u32_sample_rate, __ATOMIC_SEQ_CST);
511
+ if (mpp_rand() > sample_rate) {
512
+ goto out;
513
+ }
514
+
515
+ // Make sure there's enough space in our buffers.
516
+ if (cd->allocation_samples_count >= cd->max_allocation_samples) {
517
+ __atomic_add_fetch(&cd->dropped_samples_allocation_bufsize, 1, __ATOMIC_SEQ_CST);
518
+ goto out;
519
+ }
520
+ if (cd->heap_samples_count >= cd->max_heap_samples) {
521
+ __atomic_add_fetch(&cd->dropped_samples_heap_bufsize, 1, __ATOMIC_SEQ_CST);
522
+ goto out;
523
+ }
524
+
525
+ // OK - run our code in here under rb_protect now so that it cannot longjmp out
526
+ original_errinfo = rb_errinfo();
527
+ rb_protect(collector_tphook_newobj_protected, (VALUE)&args, &jump_tag);
528
+ if (jump_tag) goto out;
529
+
530
+ // This looks super redundant, _BUT_ there is a narrow possibility that some of the code we invoke
531
+ // inside the rb_protect actually does RVALUE allocations itself, and so recursively runs this hook
532
+ // (which will work, because the &cd->lock mutex is recursive). So, we need to actually check
533
+ // our buffer sizes _again_.
534
+ if (cd->allocation_samples_count >= cd->max_allocation_samples) {
535
+ __atomic_add_fetch(&cd->dropped_samples_allocation_bufsize, 1, __ATOMIC_SEQ_CST);
536
+ goto out;
537
+ }
538
+ if (cd->heap_samples_count >= cd->max_heap_samples) {
539
+ __atomic_add_fetch(&cd->dropped_samples_heap_bufsize, 1, __ATOMIC_SEQ_CST);
540
+ goto out;
541
+ }
542
+
543
+ // OK, now it's time to add to our sample buffers.
544
+ struct mpp_sample *sample = mpp_xmalloc(sizeof(struct mpp_sample));
545
+ // Set the sample refcount to two. Once because it's going in the allocation sampling buffer,
546
+ // and once because it's going in the heap profiling set.
547
+ sample->refcount = 2;
548
+ sample->bt = args.bt;
549
+ sample->allocation_size = args.allocation_size;
550
+ sample->current_size = args.allocation_size;
551
+ sample->allocated_value_weak = args.newobj;
552
+
553
+ // Insert into allocation profiling list.
554
+ sample->next_alloc = cd->allocation_samples;
555
+ cd->allocation_samples = sample;
556
+ cd->allocation_samples_count++;
557
+ cd->pending_size_count++;
558
+
559
+ // Also insert into live object list
560
+ st_insert(cd->heap_samples, args.newobj, (st_data_t)sample);
561
+ cd->heap_samples_count++;
562
+
563
+ // Clear args.bt so it doesn't get free'd below.
564
+ args.bt = NULL;
565
+
566
+ out:
567
+ // If this wasn't cleared, we need to free it.
568
+ if (args.bt) mpp_rb_backtrace_destroy(cd->loctab, args.bt);
569
+ // If there was an exception, ignore it and restore the original errinfo.
570
+ if (jump_tag && original_errinfo != Qundef) rb_set_errinfo(original_errinfo);
571
+
572
+ mpp_pthread_mutex_unlock(&cd->lock);
573
+ }
574
+
575
+ static void collector_tphook_freeobj(VALUE tpval, void *data) {
576
+ struct collector_cdata *cd = (struct collector_cdata *)data;
577
+
578
+ // We unfortunately do really need the mutex here, because if we don't handle this, we might
579
+ // leave an allocation kicking around in live_objects that has been freed.
580
+ mpp_pthread_mutex_lock(&cd->lock);
581
+
582
+ // Definitely do _NOT_ try and run any Ruby code in here. Any allocation will crash
583
+ // the process.
584
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
585
+ VALUE freed_obj = rb_tracearg_object(tparg);
586
+ collector_mark_sample_as_freed(cd, freed_obj);
587
+
588
+ mpp_pthread_mutex_unlock(&cd->lock);
589
+ }
590
+
591
+ static VALUE collector_tphook_creturn_protected(VALUE cdataptr) {
592
+ struct collector_cdata *cd = (struct collector_cdata *)cdataptr;
593
+
594
+ struct mpp_sample *s = cd->allocation_samples;
595
+ struct mpp_sample **prev_slot = &cd->allocation_samples;
596
+ for (int64_t i = 0; i < cd->pending_size_count; i++) {
597
+ MPP_ASSERT_MSG(s, "More pending size samples than samples in linked list??");
598
+ // Ruby apparently has the right to free stuff that's used internally (like T_IMEMOs)
599
+ // _without_ invoking the garbage collector (and thus, _without_ invoking our hook). When
600
+ // it does that, it will set flags of the RVALUE to zero, which indicates that the object
601
+ // is now free.
602
+ // Detect this and consider it the same as free'ing an object. Otherwise, we might try and
603
+ // memsize() it, which will cause an rb_bug to trigger
604
+ if (RB_TYPE_P(s->allocated_value_weak, T_NONE)) {
605
+ collector_mark_sample_as_freed(cd, s->allocated_value_weak);
606
+ s->allocated_value_weak = Qundef;
607
+ }
608
+ if (s->allocated_value_weak != Qundef) {
609
+ s->allocation_size = rb_obj_memsize_of(s->allocated_value_weak);
610
+ s->current_size = s->allocation_size;
611
+ }
612
+
613
+ if (mpp_rand() > cd->u32_allocation_retain_rate) {
614
+ // Drop this sample out of the allocation sample list. We've been asked to drop a certain
615
+ // percentage of things out of this list, so we don't OOM with piles of short-lived objects.
616
+ *prev_slot = s->next_alloc;
617
+
618
+ // Annoying little dance here so we don't read s->next_alloc after freeing s.
619
+ struct mpp_sample *next_s = s->next_alloc;
620
+ internal_sample_decrement_refcount(cd, s);
621
+ s = next_s;
622
+
623
+ cd->allocation_samples_count--;
624
+ } else {
625
+ prev_slot = &s->next_alloc;
626
+ s = s->next_alloc;
627
+ }
628
+ }
629
+ return Qnil;
630
+ }
631
+
632
+ static void collector_tphook_creturn(VALUE tpval, void *data) {
633
+ struct collector_cdata *cd = (struct collector_cdata *)data;
634
+ int jump_tag = 0;
635
+ VALUE original_errinfo;
636
+ // If we can't get the lock this time round, we can just do it later.
637
+ if (mpp_pthread_mutex_trylock(&cd->lock) != 0) {
638
+ return;
639
+ }
640
+ if (cd->pending_size_count == 0) goto out;
641
+
642
+ original_errinfo = rb_errinfo();
643
+ rb_protect(collector_tphook_creturn_protected, (VALUE)cd, &jump_tag);
644
+ cd->pending_size_count = 0;
645
+ if (jump_tag) {
646
+ rb_set_errinfo(original_errinfo);
647
+ }
648
+
649
+ out:
650
+ mpp_pthread_mutex_unlock(&cd->lock);
651
+ }
652
+
653
+ static VALUE collector_start_protected(VALUE self) {
654
+ struct collector_cdata *cd = collector_cdata_get(self);
655
+
656
+ if (cd->newobj_trace == Qnil) {
657
+ cd->newobj_trace = rb_tracepoint_new(
658
+ 0, RUBY_INTERNAL_EVENT_NEWOBJ, collector_tphook_newobj, cd
659
+ );
660
+ }
661
+ if (cd->freeobj_trace == Qnil) {
662
+ cd->freeobj_trace = rb_tracepoint_new(
663
+ 0, RUBY_INTERNAL_EVENT_FREEOBJ, collector_tphook_freeobj, cd
664
+ );
665
+ }
666
+ if (cd->creturn_trace == Qnil) {
667
+ cd->creturn_trace = rb_tracepoint_new(
668
+ 0, RUBY_EVENT_C_RETURN, collector_tphook_creturn, cd
669
+ );
670
+ }
671
+
672
+ rb_tracepoint_enable(cd->newobj_trace);
673
+ rb_tracepoint_enable(cd->freeobj_trace);
674
+ rb_tracepoint_enable(cd->creturn_trace);
675
+ return Qnil;
676
+ }
677
+
678
+ static VALUE collector_start(VALUE self) {
679
+ int jump_tag = 0;
680
+ struct collector_cdata *cd = collector_cdata_get(self);
681
+ mpp_pthread_mutex_lock(&cd->lock);
682
+ if (cd->is_tracing) goto out;
683
+
684
+ // Don't needlessly double-initialize everything
685
+ if (cd->heap_samples_count > 0) {
686
+ collector_cdata_gc_free_heap_samples(cd);
687
+ cd->heap_samples = st_init_numtable();
688
+ cd->heap_samples_count = 0;
689
+ }
690
+ if (cd->allocation_samples_count > 0) {
691
+ collector_cdata_gc_free_allocation_samples(cd);
692
+ cd->allocation_samples = NULL;
693
+ cd->allocation_samples_count = 0;
694
+ cd->pending_size_count = 0;
695
+ }
696
+ cd->is_tracing = true;
697
+ __atomic_store_n(&cd->dropped_samples_allocation_bufsize, 0, __ATOMIC_SEQ_CST);
698
+ __atomic_store_n(&cd->dropped_samples_heap_bufsize, 0, __ATOMIC_SEQ_CST);
699
+ __atomic_store_n(&cd->dropped_samples_nolock, 0, __ATOMIC_SEQ_CST);
700
+
701
+ // Now do the things that might throw
702
+ rb_protect(collector_start_protected, self, &jump_tag);
703
+
704
+ out:
705
+ mpp_pthread_mutex_unlock(&cd->lock);
706
+ if (jump_tag) {
707
+ rb_jump_tag(jump_tag);
708
+ }
709
+ return Qnil;
710
+ }
711
+
712
+ static VALUE collector_stop_protected(VALUE self) {
713
+ struct collector_cdata *cd = collector_cdata_get(self);
714
+ rb_tracepoint_disable(cd->newobj_trace);
715
+ rb_tracepoint_disable(cd->freeobj_trace);
716
+ rb_tracepoint_disable(cd->creturn_trace);
717
+ return Qnil;
718
+ }
719
+
720
+ static VALUE collector_stop(VALUE self) {
721
+ int jump_tag = 0;
722
+ struct collector_cdata *cd = collector_cdata_get(self);
723
+ mpp_pthread_mutex_lock(&cd->lock);
724
+ if (!cd->is_tracing) goto out;
725
+
726
+ rb_protect(collector_stop_protected, self, &jump_tag);
727
+ if (jump_tag) goto out;
728
+
729
+ cd->is_tracing = false;
730
+ // Don't clear any of our buffers - it's OK to access the profiling info after calling stop!
731
+ out:
732
+ mpp_pthread_mutex_unlock(&cd->lock);
733
+ if (jump_tag) {
734
+ rb_jump_tag(jump_tag);
735
+ }
736
+ return Qnil;
737
+ }
738
+
739
+ static VALUE collector_is_running(VALUE self) {
740
+ struct collector_cdata *cd = collector_cdata_get(self);
741
+ mpp_pthread_mutex_lock(&cd->lock);
742
+ bool running = cd->is_tracing;
743
+ mpp_pthread_mutex_unlock(&cd->lock);
744
+ return running ? Qtrue : Qfalse;
745
+ }
746
+
747
+ static int collector_heap_samples_each_calc_size(st_data_t key, st_data_t val, st_data_t arg) {
748
+ struct mpp_sample *sample = (struct mpp_sample *)val;
749
+ struct collector_cdata *cd = (struct collector_cdata *)arg;
750
+ MPP_ASSERT_MSG(sample->allocated_value_weak != Qundef, "undef was in heap sample map");
751
+
752
+ // Check that the sample is, in fact, still live. This can happen if an object is freed internally
753
+ // by Ruby without firing our freeobj hook (which Ruby is allowed to do for some kinds of objects).
754
+ // In that case, flags will be zero and so type will be T_NONE.
755
+ // Note that if an object is freed and then the slot is subsequently re-used for a different object,
756
+ // our newobj hook will fire in that case and do this too. So this method captures the sequence
757
+ // allocate -> free -> flush, but the newobj hook handles the allocate -> free -> reuse -> flush case.
758
+ if (RB_TYPE_P(sample->allocated_value_weak, T_NONE)) {
759
+ sample->allocated_value_weak = Qundef;
760
+ internal_sample_decrement_refcount(cd, sample);
761
+ cd->heap_samples_count--;
762
+ return ST_DELETE;
763
+ }
764
+
765
+ sample->current_size = rb_obj_memsize_of(sample->allocated_value_weak);
766
+ return ST_CONTINUE;
767
+ }
768
+
769
+ struct collector_heap_samples_each_add_args {
770
+ struct mpp_pprof_serctx *serctx;
771
+ char *errbuf;
772
+ size_t errbuf_len;
773
+ int r;
774
+ };
775
+
776
+ static int collector_heap_samples_each_add(st_data_t key, st_data_t val, st_data_t arg) {
777
+ struct mpp_sample *sample = (struct mpp_sample *)val;
778
+ struct collector_heap_samples_each_add_args *args = (struct collector_heap_samples_each_add_args *)arg;
779
+
780
+ int r = mpp_pprof_serctx_add_sample(args->serctx, sample, MPP_SAMPLE_TYPE_HEAP, args->errbuf, args->errbuf_len);
781
+ if (r != 0) {
782
+ args->r = r;
783
+ return ST_STOP;
784
+ }
785
+ return ST_CONTINUE;
786
+ }
787
+
788
+ struct collector_flush_prepresult_args {
789
+ const char *pprofbuf;
790
+ size_t pprofbuf_len;
791
+ VALUE cProfileData;
792
+
793
+ // Extra struff that needs to go onto the struct.
794
+ int64_t allocation_samples_count;
795
+ int64_t heap_samples_count;
796
+ int64_t dropped_samples_nolock;
797
+ int64_t dropped_samples_allocation_bufsize;
798
+ int64_t dropped_samples_heap_bufsize;
799
+ };
800
+
801
+ static VALUE collector_flush_protected_heap_sample_size(VALUE self) {
802
+ struct collector_cdata *cd = collector_cdata_get(self);
803
+ st_foreach(cd->heap_samples, collector_heap_samples_each_calc_size, (st_data_t)cd);
804
+ return Qnil;
805
+ }
806
+
807
+ static VALUE collector_flush_prepresult(VALUE vargs) {
808
+ struct collector_flush_prepresult_args *args =
809
+ (struct collector_flush_prepresult_args *)vargs;
810
+
811
+ VALUE pprof_data = rb_str_new(args->pprofbuf, args->pprofbuf_len);
812
+ VALUE profile_data = rb_class_new_instance(0, NULL, args->cProfileData);
813
+ rb_funcall(profile_data, rb_intern("pprof_data="), 1, pprof_data);
814
+ rb_funcall(profile_data, rb_intern("allocation_samples_count="), 1, LONG2NUM(args->allocation_samples_count));
815
+ rb_funcall(profile_data, rb_intern("heap_samples_count="), 1, LONG2NUM(args->heap_samples_count));
816
+ rb_funcall(profile_data, rb_intern("dropped_samples_nolock="), 1, LONG2NUM(args->dropped_samples_nolock));
817
+ rb_funcall(
818
+ profile_data, rb_intern("dropped_samples_allocation_bufsize="),
819
+ 1, LONG2NUM(args->dropped_samples_allocation_bufsize)
820
+ );
821
+ rb_funcall(
822
+ profile_data, rb_intern("dropped_samples_heap_bufsize="),
823
+ 1, LONG2NUM(args->dropped_samples_heap_bufsize)
824
+ );
825
+ return profile_data;
826
+ }
827
+
828
+ static VALUE collector_flush(VALUE self) {
829
+ struct collector_cdata *cd = collector_cdata_get(self);
830
+ struct mpp_pprof_serctx *serctx = NULL;
831
+ char *buf_out;
832
+ size_t buflen_out;
833
+ char errbuf[256];
834
+ int jump_tag = 0;
835
+ int r = 0;
836
+ VALUE retval = Qundef;
837
+ struct mpp_sample *sample_list = NULL;
838
+ struct collector_flush_prepresult_args prepresult_args;
839
+ int lock_held = 0;
840
+
841
+ // Whilst under the GVL, we need to get the collector lock
842
+ mpp_pthread_mutex_lock(&cd->lock);
843
+ lock_held = 1;
844
+
845
+ sample_list = cd->allocation_samples;
846
+ cd->allocation_samples = NULL;
847
+ prepresult_args.allocation_samples_count = cd->allocation_samples_count;
848
+ prepresult_args.heap_samples_count = cd->heap_samples_count;
849
+ cd->allocation_samples_count = 0;
850
+ cd->pending_size_count = 0;
851
+
852
+ prepresult_args.dropped_samples_nolock =
853
+ __atomic_exchange_n(&cd->dropped_samples_nolock, 0, __ATOMIC_SEQ_CST);
854
+ prepresult_args.dropped_samples_allocation_bufsize =
855
+ __atomic_exchange_n(&cd->dropped_samples_allocation_bufsize, 0, __ATOMIC_SEQ_CST);
856
+ prepresult_args.dropped_samples_heap_bufsize =
857
+ __atomic_exchange_n(&cd->dropped_samples_heap_bufsize, 0, __ATOMIC_SEQ_CST);
858
+
859
+ // Get the current size for everything in the live allocations table.
860
+ rb_protect(collector_flush_protected_heap_sample_size, self, &jump_tag);
861
+ if (jump_tag) goto out;
862
+
863
+ serctx = mpp_pprof_serctx_new();
864
+ MPP_ASSERT_MSG(serctx, "mpp_pprof_serctx_new failed??");
865
+ r = mpp_pprof_serctx_set_loctab(serctx, cd->loctab, errbuf, sizeof(errbuf));
866
+ if (r == -1) {
867
+ goto out;
868
+ }
869
+
870
+ // Now that we have the samples (and have processed the stringtab) we can
871
+ // yield the lock.
872
+ mpp_pthread_mutex_unlock(&cd->lock);
873
+ lock_held = 0;
874
+
875
+ // Add the allocation samples
876
+ struct mpp_sample *s = sample_list;
877
+ while (s) {
878
+ r = mpp_pprof_serctx_add_sample(serctx, s, MPP_SAMPLE_TYPE_ALLOCATION, errbuf, sizeof(errbuf));
879
+ if (r == -1) {
880
+ goto out;
881
+ }
882
+ s = s->next_alloc;
883
+ }
884
+
885
+ // Add the heap samples
886
+ struct collector_heap_samples_each_add_args heap_add_args;
887
+ heap_add_args.serctx = serctx;
888
+ heap_add_args.errbuf = errbuf;
889
+ heap_add_args.errbuf_len = sizeof(errbuf);
890
+ heap_add_args.r = 0;
891
+ st_foreach(cd->heap_samples, collector_heap_samples_each_add, (st_data_t)&heap_add_args);
892
+ if (heap_add_args.r != 0) goto out;
893
+
894
+ r = mpp_pprof_serctx_serialize(serctx, &buf_out, &buflen_out, errbuf, sizeof(errbuf));
895
+ if ( r == -1) {
896
+ goto out;
897
+ }
898
+ // Annoyingly, since rb_str_new could (in theory) throw, we have to rb_protect the whole construction
899
+ // of our return value to ensure we don't leak serctx.
900
+ prepresult_args.pprofbuf = buf_out;
901
+ prepresult_args.pprofbuf_len = buflen_out;
902
+ prepresult_args.cProfileData = cd->cProfileData;
903
+ retval = rb_protect(collector_flush_prepresult, (VALUE)&prepresult_args, &jump_tag);
904
+
905
+ // Do cleanup here now.
906
+ out:
907
+ if (serctx) mpp_pprof_serctx_destroy(serctx);
908
+ if (lock_held) mpp_pthread_mutex_unlock(&cd->lock);
909
+ if (sample_list) internal_sample_decrement_refcount(cd, sample_list);
910
+
911
+ // Now return-or-raise back to ruby.
912
+ if (jump_tag) {
913
+ rb_jump_tag(jump_tag);
914
+ }
915
+ if (retval == Qundef) {
916
+ // Means we have an error to construct and throw
917
+ rb_raise(rb_eRuntimeError, "ruby_memprofiler_pprof failed serializing pprof protobuf: %s", errbuf);
918
+ }
919
+ return retval;
920
+
921
+ RB_GC_GUARD(self);
922
+ }
923
+
924
+ static VALUE collector_profile(VALUE self) {
925
+ rb_need_block();
926
+
927
+ rb_funcall(self, rb_intern("start!"), 0);
928
+ rb_yield_values(0);
929
+ VALUE profile_output = rb_funcall(self, rb_intern("flush"), 0);
930
+ rb_funcall(self, rb_intern("stop!"), 0);
931
+
932
+ return profile_output;
933
+ }
934
+
935
+ static VALUE collector_live_heap_samples_count(VALUE self) {
936
+ struct collector_cdata *cd = collector_cdata_get(self);
937
+
938
+ mpp_pthread_mutex_lock(&cd->lock);
939
+ int64_t counter = cd->heap_samples_count;
940
+ mpp_pthread_mutex_unlock(&cd->lock);
941
+ return LONG2NUM(counter);
942
+ }
943
+
944
+ static VALUE collector_bt_method_get(VALUE self) {
945
+ struct collector_cdata *cd = collector_cdata_get(self);
946
+
947
+ mpp_pthread_mutex_lock(&cd->lock);
948
+ int method = cd->bt_method;
949
+ mpp_pthread_mutex_unlock(&cd->lock);
950
+
951
+ if (method == MPP_BT_METHOD_CFP) {
952
+ return rb_id2sym(rb_intern("cfp"));
953
+ } else if (method == MPP_BT_METHOD_SLOWRB) {
954
+ return rb_id2sym(rb_intern("slowrb"));
955
+ } else {
956
+ MPP_ASSERT_FAIL("unknown bt_method");
957
+ return Qundef;
958
+ }
959
+ }
960
+
961
+ static VALUE collector_bt_method_set(VALUE self, VALUE newval) {
962
+ struct collector_cdata *cd = collector_cdata_get(self);
963
+
964
+ ID bt_method = rb_sym2id(newval);
965
+ int method;
966
+ if (bt_method == rb_intern("cfp")) {
967
+ method = MPP_BT_METHOD_CFP;
968
+ } else if (bt_method == rb_intern("slowrb")) {
969
+ method = MPP_BT_METHOD_SLOWRB;
970
+ } else {
971
+ rb_raise(rb_eArgError, "passed value for bt_method was not recognised");
972
+ }
973
+
974
+ mpp_pthread_mutex_lock(&cd->lock);
975
+ cd->bt_method = method;
976
+ mpp_pthread_mutex_unlock(&cd->lock);
977
+
978
+ return newval;
979
+ }
980
+
981
+ static int mpp_collector_atfork_lock_el(st_data_t key, st_data_t value, st_data_t arg) {
982
+ struct collector_cdata *cd = (struct collector_cdata *)key;
983
+ mpp_pthread_mutex_lock(&cd->lock);
984
+ return ST_CONTINUE;
985
+ }
986
+
987
+ static int mpp_collector_atfork_unlock_el(st_data_t key, st_data_t value, st_data_t arg) {
988
+ struct collector_cdata *cd = (struct collector_cdata *)key;
989
+ mpp_pthread_mutex_unlock(&cd->lock);
990
+ return ST_CONTINUE;
991
+ }
992
+
993
+ static int mpp_collector_atfork_replace_el(st_data_t key, st_data_t value, st_data_t arg) {
994
+ struct collector_cdata *cd = (struct collector_cdata *)key;
995
+
996
+ // In the parent process, we simply release the mutexes, but in the child process, we have
997
+ // to _RECREATE_ them. This is because they're recursive mutexes, and must hold some kind of
998
+ // thread ID in them somehow; unlocking them post-fork simply doesn't work it seems.
999
+ // It's safe to re-create the mutex at this point, because no other thread can possibly be
1000
+ // holding it since we took it pre-fork
1001
+ mpp_pthread_mutex_destroy(&cd->lock);
1002
+ pthread_mutexattr_t mutex_attr;
1003
+ mpp_pthread_mutexattr_init(&mutex_attr);
1004
+ mpp_pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
1005
+ memset(&cd->lock, 0, sizeof(cd->lock));
1006
+ mpp_pthread_mutex_init(&cd->lock, &mutex_attr);
1007
+ mpp_pthread_mutexattr_destroy(&mutex_attr);
1008
+
1009
+ return ST_CONTINUE;
1010
+ }
1011
+
1012
+ static void mpp_collector_atfork_prepare() {
1013
+ mpp_pthread_mutex_lock(&global_collectors_lock);
1014
+ st_foreach(global_collectors, mpp_collector_atfork_lock_el, 0);
1015
+ }
1016
+
1017
+ static void mpp_collector_atfork_release_parent() {
1018
+ st_foreach(global_collectors, mpp_collector_atfork_unlock_el, 0);
1019
+ mpp_pthread_mutex_unlock(&global_collectors_lock);
1020
+ }
1021
+
1022
+ static void mpp_collector_atfork_release_child() {
1023
+ st_foreach(global_collectors, mpp_collector_atfork_replace_el, 0);
1024
+ mpp_pthread_mutex_unlock(&global_collectors_lock);
1025
+ }
1026
+
1027
+
1028
+ void mpp_setup_collector_class() {
1029
+ VALUE mMemprofilerPprof = rb_const_get(rb_cObject, rb_intern("MemprofilerPprof"));
1030
+ VALUE cCollector = rb_define_class_under(mMemprofilerPprof, "Collector", rb_cObject);
1031
+ rb_define_alloc_func(cCollector, collector_alloc);
1032
+
1033
+
1034
+ rb_define_method(cCollector, "initialize", collector_initialize, -1);
1035
+ rb_define_method(cCollector, "sample_rate", collector_get_sample_rate, 0);
1036
+ rb_define_method(cCollector, "sample_rate=", collector_set_sample_rate, 1);
1037
+ rb_define_method(cCollector, "max_allocation_samples", collector_get_max_allocation_samples, 0);
1038
+ rb_define_method(cCollector, "max_allocation_samples=", collector_set_max_allocation_samples, 1);
1039
+ rb_define_method(cCollector, "max_heap_samples", collector_get_max_heap_samples, 0);
1040
+ rb_define_method(cCollector, "max_heap_samples=", collector_set_max_heap_samples, 1);
1041
+ rb_define_method(cCollector, "bt_method", collector_bt_method_get, 0);
1042
+ rb_define_method(cCollector, "bt_method=", collector_bt_method_set, 1);
1043
+ rb_define_method(cCollector, "allocation_retain_rate", collector_get_allocation_retain_rate, 0);
1044
+ rb_define_method(cCollector, "allocation_retain_rate=", collector_set_allocation_retain_rate, 1);
1045
+ rb_define_method(cCollector, "running?", collector_is_running, 0);
1046
+ rb_define_method(cCollector, "start!", collector_start, 0);
1047
+ rb_define_method(cCollector, "stop!", collector_stop, 0);
1048
+ rb_define_method(cCollector, "flush", collector_flush, 0);
1049
+ rb_define_method(cCollector, "profile", collector_profile, 0);
1050
+ rb_define_method(cCollector, "live_heap_samples_count", collector_live_heap_samples_count, 0);
1051
+
1052
+ global_collectors = st_init_numtable();
1053
+ mpp_pthread_mutex_init(&global_collectors_lock, NULL);
1054
+ mpp_pthread_atfork(mpp_collector_atfork_prepare, mpp_collector_atfork_release_parent, mpp_collector_atfork_release_child);
1055
+ }