class Sentry::Profiler

def to_hash

def to_hash
  return {} unless @sampled
  return {} unless @started
  results = StackProf.results
  return {} unless results
  return {} if results.empty?
  return {} if results[:samples] == 0
  return {} unless results[:raw]
  frame_map = {}
  frames = results[:frames].to_enum.with_index.map do |frame, idx|
    frame_id, frame_data = frame
    # need to map over stackprof frame ids to ours
    frame_map[frame_id] = idx
    file_path = frame_data[:file]
    in_app = in_app?(file_path)
    filename = compute_filename(file_path, in_app)
    function, mod = split_module(frame_data[:name])
    frame_hash = {
      abs_path: file_path,
      function: function,
      filename: filename,
      in_app: in_app
    }
    frame_hash[:module] = mod if mod
    frame_hash[:lineno] = frame_data[:line] if frame_data[:line]
    frame_hash
  end
  idx = 0
  stacks = []
  num_seen = []
  # extract stacks from raw
  # raw is a single array of [.., len_stack, *stack_frames(len_stack), num_stack_seen , ..]
  while (len = results[:raw][idx])
    idx += 1
    # our call graph is reversed
    stack = results[:raw].slice(idx, len).map { |id| frame_map[id] }.compact.reverse
    stacks << stack
    num_seen << results[:raw][idx + len]
    idx += len + 1
    log('Unknown frame in stack') if stack.size != len
  end
  idx = 0
  elapsed_since_start_ns = 0
  samples = []
  num_seen.each_with_index do |n, i|
    n.times do
      # stackprof deltas are in microseconds
      delta = results[:raw_timestamp_deltas][idx]
      elapsed_since_start_ns += (delta * MICRO_TO_NANO_SECONDS).to_i
      idx += 1
      # Not sure why but some deltas are very small like 0/1 values,
      # they pollute our flamegraph so just ignore them for now.
      # Open issue at https://github.com/tmm1/stackprof/issues/201
      next if delta < 10
      samples << {
        stack_id: i,
        # TODO-neel-profiler we need to patch rb_profile_frames and write our own C extension to enable threading info.
        # Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
        # we're profiling is idle/sleeping/waiting for IO etc.
        # https://bugs.ruby-lang.org/issues/10602
        thread_id: '0',
        elapsed_since_start_ns: elapsed_since_start_ns.to_s
      }
    end
  end
  log('Some samples thrown away') if samples.size != results[:samples]
  if samples.size <= 2
    log('Not enough samples, discarding profiler')
    return {}
  end
  profile = {
    frames: frames,
    stacks: stacks,
    samples: samples
  }
  {
    event_id: @event_id,
    platform: PLATFORM,
    version: VERSION,
    profile: profile
  }
end