You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

255 line
6.9 KiB

  1. // Copyright (c) 2017-Present Pivotal Software, Inc. All rights reserved.
  2. //
  3. // This package, Looking Glass, is double-licensed under the Mozilla
  4. // Public License 1.1 ("MPL") and the Apache License version 2
  5. // ("ASL"). For the MPL, please see LICENSE-MPL-RabbitMQ. For the ASL,
  6. // please see LICENSE-APACHE2.
  7. //
  8. // This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
  9. // either express or implied. See the LICENSE file for specific language governing
  10. // rights and limitations of this software.
  11. //
  12. // If you have any questions regarding licensing, please contact us at
  13. // info@rabbitmq.com.
  14. #define NIF_FUNCTION_NAME(f) lg_ ## f
  15. #include "nif_helpers.h"
  16. // List of atoms used by this NIF.
  17. //
  18. // @todo We don't use threads so perhaps we should make nif_helpers
  19. // better by splitting concerns into threads/not and have nif_helpers
  20. // decide when to create the _nif_thread_ret atom or not.
  21. #define NIF_ATOMS(A) \
  22. A(_nif_thread_ret_) \
  23. A(call) \
  24. A(closed) \
  25. A(cpu_timestamp) \
  26. A(discard) \
  27. A(exception_from) \
  28. A(exit) \
  29. A(extra) \
  30. A(gc_major_end) \
  31. A(gc_major_start) \
  32. A(gc_minor_end) \
  33. A(gc_minor_start) \
  34. A(getting_linked) \
  35. A(getting_unlinked) \
  36. A(in) \
  37. A(in_exiting) \
  38. A(link) \
  39. A(match_spec_result) \
  40. A(mode) \
  41. A(monotonic) \
  42. A(ok) \
  43. A(open) \
  44. A(out) \
  45. A(out_exited) \
  46. A(out_exiting) \
  47. A(percent) \
  48. A(profile) \
  49. A(receive) \
  50. A(register) \
  51. A(remove) \
  52. A(return_from) \
  53. A(return_to) \
  54. A(scheduler_id) \
  55. A(send) \
  56. A(send_to_non_existing_process) \
  57. A(spawn) \
  58. A(spawned) \
  59. A(strict_monotonic) \
  60. A(timestamp) \
  61. A(trace) \
  62. A(trace_status) \
  63. A(tracers) \
  64. A(unlink) \
  65. A(unregister)
  66. NIF_ATOMS(NIF_ATOM_DECL)
  67. // List of functions defined in this NIF.
  68. #define NIF_FUNCTIONS(F) \
  69. F(enabled, 3) \
  70. F(enabled_call, 3) \
  71. F(enabled_procs, 3) \
  72. F(enabled_running_procs, 3) \
  73. F(enabled_send, 3) \
  74. F(trace, 5)
  75. NIF_FUNCTIONS(NIF_FUNCTION_H_DECL)
  76. static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
  77. {
  78. NIF_ATOMS(NIF_ATOM_INIT)
  79. return 0;
  80. }
  81. static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
  82. {
  83. *priv_data = *old_priv_data;
  84. return 0;
  85. }
  86. static void unload(ErlNifEnv* env, void* priv_data)
  87. {
  88. }
  89. // enabled(TraceTag, TracerState, Tracee)
  90. NIF_FUNCTION(enabled)
  91. {
  92. ERL_NIF_TERM tracers, value;
  93. ErlNifPid tracer;
  94. // @todo We can go one step further by having the one pid
  95. // in its own value in the map, skipping a get_map_value step.
  96. // This function will only be called for trace_status.
  97. // We can take a few shortcuts knowing this.
  98. // Disable the trace when the tracers option is missing.
  99. if (!enif_get_map_value(env, argv[1], atom_tracers, &tracers))
  100. return atom_remove;
  101. // Because the tracers supervisor is a one_for_all, we only need
  102. // to check one of the tracer processes to confirm all are alive.
  103. // We know for a fact that this key exists because
  104. // there's at least one tracer process.
  105. enif_get_map_value(env, tracers, enif_make_int(env, 0), &value);
  106. // Disable the trace when one of the tracers is not a local process.
  107. if (!enif_get_local_pid(env, value, &tracer))
  108. return atom_remove;
  109. // Disable the trace when one of the tracers is not alive.
  110. if (!enif_is_process_alive(env, &tracer))
  111. return atom_remove;
  112. return atom_discard;
  113. }
  114. NIF_FUNCTION(enabled_call)
  115. {
  116. // We always want both call and return_to.
  117. return atom_trace;
  118. }
  119. NIF_FUNCTION(enabled_procs)
  120. {
  121. ERL_NIF_TERM mode;
  122. // We only want the spawn and exit events when 'profile' mode
  123. // is enabled. Technically we only care about exits for callgrind,
  124. // but spawn is cheap to keep and useful for message profilers.
  125. if (enif_get_map_value(env, argv[1], atom_mode, &mode)
  126. && enif_is_identical(atom_profile, mode)
  127. && !(enif_is_identical(atom_spawn, argv[0])
  128. || enif_is_identical(atom_exit, argv[0]))) {
  129. return atom_discard;
  130. }
  131. return atom_trace;
  132. }
  133. NIF_FUNCTION(enabled_running_procs)
  134. {
  135. // We always want both in and out.
  136. return atom_trace;
  137. }
  138. NIF_FUNCTION(enabled_send)
  139. {
  140. // We always want both send and send_to_non_existing_process.
  141. return atom_trace;
  142. }
  143. // trace(TraceTag, TracerState, Tracee, TraceTerm, Opts)
  144. NIF_FUNCTION(trace)
  145. {
  146. ERL_NIF_TERM tracers, head, ts, extra, mspec, msg;
  147. ErlNifPid tracer;
  148. unsigned int nth;
  149. size_t len;
  150. int has_extra, has_mspec;
  151. if (!enif_get_map_value(env, argv[1], atom_tracers, &tracers))
  152. return atom_ok;
  153. // We know for a fact that the argument is a map. And if not,
  154. // no problem because we will return when trying to get a value from it.
  155. enif_get_map_size(env, tracers, &len);
  156. #if (ERL_NIF_MAJOR_VERSION >= 2) && (ERL_NIF_MINOR_VERSION >= 12)
  157. nth = enif_hash(ERL_NIF_INTERNAL_HASH, argv[2], 0) % len;
  158. #else
  159. // Select the correct tracer for this process.
  160. //
  161. // The pid value is detailed in:
  162. // 5b6dd0e84cf0f1dc19ddd05f86cf04b2695d8a9e/erts/emulator/beam/erl_term.h#L498
  163. //
  164. // As can be seen there, the first four bits of the pid value
  165. // are always the same. We therefore shift them out.
  166. ErlNifPid tracee;
  167. if (!enif_get_local_pid(env, argv[2], &tracee))
  168. return atom_ok;
  169. nth = (tracee.pid >> 4) % len;
  170. #endif
  171. if (!enif_get_map_value(env, tracers, enif_make_int(env, nth), &head))
  172. return atom_ok;
  173. if (!enif_get_local_pid(env, head, &tracer))
  174. return atom_ok;
  175. // Everything good. Generate a timestamp to include in the message.
  176. ts = enif_make_int64(env, enif_monotonic_time(ERL_NIF_USEC));
  177. // Build the message. There can be two different messages
  178. // depending on whether the extra option was set:
  179. //
  180. // - {Tag, Tracee, Ts, Term}
  181. // - {Tag, Tracee, Ts, Term, Extra}
  182. //
  183. // On top of that when match specs are enabled we may have
  184. // one additional term at the end of the tuple containing
  185. // the result of the match spec function.
  186. //
  187. // - {Tag, Tracee, Ts, Term, Result}
  188. // - {Tag, Tracee, Ts, Term, Extra, Result}
  189. has_extra = enif_get_map_value(env, argv[4], atom_extra, &extra);
  190. has_mspec = enif_get_map_value(env, argv[4], atom_match_spec_result, &mspec);
  191. if (has_extra && has_mspec)
  192. msg = enif_make_tuple6(env, argv[0], argv[2], ts, argv[3], extra, mspec);
  193. else if (has_extra)
  194. msg = enif_make_tuple5(env, argv[0], argv[2], ts, argv[3], extra);
  195. else if (has_mspec)
  196. msg = enif_make_tuple5(env, argv[0], argv[2], ts, argv[3], mspec);
  197. else
  198. msg = enif_make_tuple4(env, argv[0], argv[2], ts, argv[3]);
  199. // Send the message to the selected tracer.
  200. enif_send(env, &tracer, NULL, msg);
  201. return atom_ok;
  202. }
  203. static ErlNifFunc nif_funcs[] = {
  204. NIF_FUNCTIONS(NIF_FUNCTION_ARRAY)
  205. };
  206. ERL_NIF_INIT(tpTracerNif, nif_funcs, load, NULL, upgrade, unload)