lib/sentry/vernier/output.rb



# frozen_string_literal: true

require "json"
require "rbconfig"

module Sentry
  module Vernier
    class Output
      include Profiler::Helpers

      attr_reader :profile

      def initialize(profile, project_root:, in_app_pattern:, app_dirs_pattern:)
        @profile = profile
        @project_root = project_root
        @in_app_pattern = in_app_pattern
        @app_dirs_pattern = app_dirs_pattern
      end

      def to_h
        @to_h ||= {
          frames: frames,
          stacks: stacks,
          samples: samples,
          thread_metadata: thread_metadata
        }
      end

      private

      def thread_metadata
        profile.threads.map { |thread_id, thread_info|
          [thread_id, { name: thread_info[:name] }]
        }.to_h
      end

      def samples
        profile.threads.flat_map { |thread_id, thread_info|
          started_at = thread_info[:started_at]
          samples, timestamps = thread_info.values_at(:samples, :timestamps)

          samples.zip(timestamps).map { |stack_id, timestamp|
            elapsed_since_start_ns = timestamp - started_at

            next if elapsed_since_start_ns < 0

            {
              thread_id: thread_id.to_s,
              stack_id: stack_id,
              elapsed_since_start_ns: elapsed_since_start_ns.to_s
            }
          }.compact
        }
      end

      def frames
        funcs = stack_table_hash[:frame_table].fetch(:func)
        lines = stack_table_hash[:func_table].fetch(:first_line)

        funcs.map do |idx|
          function, mod = split_module(stack_table_hash[:func_table][:name][idx])

          abs_path = stack_table_hash[:func_table][:filename][idx]
          in_app = in_app?(abs_path)
          filename = compute_filename(abs_path, in_app)

          {
            function: function,
            module: mod,
            filename: filename,
            abs_path: abs_path,
            lineno: (lineno = lines[idx]) > 0 ? lineno : nil,
            in_app: in_app
          }.compact
        end
      end

      def stacks
        profile._stack_table.stack_count.times.map do |stack_id|
          profile.stack(stack_id).frames.map(&:idx)
        end
      end

      def stack_table_hash
        @stack_table_hash ||= profile._stack_table.to_h
      end
    end
  end
end