Method: Sentry::Profiler#to_hash

Defined in:
lib/sentry/profiler.rb

#to_hashObject


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/sentry/profiler.rb', line 83

def to_hash
  unless @sampled
    record_lost_event(:sample_rate)
    return {}
  end

  return {} unless @started

  results = StackProf.results

  if !results || results.empty? || results[:samples] == 0 || !results[:raw]
    record_lost_event(:insufficient_data)
    return {}
  end

  frame_map = {}

  frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
    # need to map over stackprof frame ids to ours
    frame_map[frame_id] = idx

    file_path = frame_data[:file]
    lineno = frame_data[:line]
    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] = lineno if lineno && lineno >= 0

    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 <= MIN_SAMPLES_REQUIRED
    log("Not enough samples, discarding profiler")
    record_lost_event(:insufficient_data)
    return {}
  end

  profile = {
    frames: frames,
    stacks: stacks,
    samples: samples
  }

  {
    event_id: @event_id,
    platform: PLATFORM,
    version: VERSION,
    profile: profile
  }
end