# frozen_string_literal: true

# simple test to check for memory leaks
#
# this performs a trivial operation walking all multisites and grabbing first topic / localizing
# the expectation is that RSS will remain static no matter how many iterations run

if ENV['RAILS_ENV'] != "production"
  exec "RAILS_ENV=production ruby #{__FILE__}"
end

if !ENV['LD_PRELOAD']
  exec "LD_PRELOAD=/usr/lib/libjemalloc.so.1 ruby #{__FILE__}"
end

if ENV['LD_PRELOAD'].include?("jemalloc")
  # for 3.6.0 we need a patch jemal 1.1.0 gem (1.1.1 does not support 3.6.0)
  # however ffi is a problem so we need to patch the gem
  require 'jemal'

  $jemalloc = true
end

if ENV['LD_PRELOAD'].include?("mwrap")
  $mwrap = true
  require 'mwrap'
end

def bin_diff(current)
  $baseline[:arenas].each_with_index do |arena, i|
    next if !arena || !arena[:bins]
    arena[:bins].each do |size, stat|
      allocated = (current.dig(:arenas, i, :bins, size, :allocated) || 0)
      diff = allocated - stat[:allocated]
      puts "bin #{size} delta #{diff}"
    end
  end
end

require File.expand_path("../../config/environment", __FILE__)

Rails.application.routes.recognize_path('abc') rescue nil
I18n.t(:posts)

def rss
  `ps -o rss -p #{$$}`.chomp.split("\n").last.to_i
end

def loop_sites
  RailsMultisite::ConnectionManagement.each_connection do
    yield
  end
end

def biggest_klass(klass)
  ObjectSpace.each_object(klass).max { |a, b| a.length <=> b.length }
end

def iter(warmup: false)
  loop_sites { Topic.first; I18n.t('too_late_to_edit') }
  if !warmup
    GC.start(full_mark: true, immediate_sweep: true)

    if $jemalloc
      jemal_stats = Jemal.stats
      jedelta = "(jdelta #{jemal_stats[:active] - $baseline_jemalloc_active})"
    end

    if $mwrap
      mwrap_delta = (Mwrap.total_bytes_allocated - Mwrap.total_bytes_freed) - $mwrap_baseline
      mwrap_delta = "(mwrap delta #{mwrap_delta})"
    end

    rss_delta = rss - $baseline_rss
    array_delta = biggest_klass(Array).length - $biggest_array_length
    puts "rss: #{rss} (#{rss_delta}) #{mwrap_delta}#{jedelta} heap_delta: #{GC.stat[:heap_live_slots] - $baseline_slots} array_delta: #{array_delta}"

    if $jemalloc
      bin_diff(jemal_stats)
    end
  end

end

iter(warmup: true)
4.times do
  GC.start(full_mark: true, immediate_sweep: true)
end

if $jemalloc
  $baseline = Jemal.stats
  $baseline_jemalloc_active = $baseline[:active]
  4.times do
    GC.start(full_mark: true, immediate_sweep: true)
  end
end

def render_table(array)
  buffer = +""

  width = array[0].map { |k| k.to_s.length }
  cols = array[0].length

  array.each do |row|
    row.each_with_index do |val, i|
      width[i] = [width[i].to_i, val.to_s.length].max
    end
  end

  array[0].each_with_index do |col, i|
    buffer << col.to_s.ljust(width[i], ' ')
    if i == cols - 1
      buffer << "\n"
    else
      buffer << ' | '
    end
  end

  buffer << ("-" * (width.sum + width.length))
  buffer << "\n"

  array.drop(1).each do |row|
    row.each_with_index do |val, i|
      buffer << val.to_s.ljust(width[i], ' ')
      if i == cols - 1
        buffer << "\n"
      else
        buffer << ' | '
      end
    end
  end

  buffer
end

def mwrap_log
  report = +""

  Mwrap.quiet do
    report << "Allocated bytes: #{Mwrap.total_bytes_allocated} Freed bytes: #{Mwrap.total_bytes_freed}\n"
    report << "\n"

    table = []
    Mwrap.each(200000) do |loc, total, allocations, frees, age_sum, max_life|
      table << [total, allocations - frees, frees == 0 ? -1 : (age_sum / frees.to_f).round(2), max_life, loc]
    end

    table.sort! { |a, b| b[1] <=> a[1] }
    table = table[0..50]

    table.prepend(["total", "delta", "mean_life", "max_life", "location"])

    report << render_table(table)
  end

  report
end

Mwrap.clear

if $mwrap
  $mwrap_baseline = Mwrap.total_bytes_allocated - Mwrap.total_bytes_freed
end

$baseline_slots = GC.stat[:heap_live_slots]
$baseline_rss = rss
$biggest_array_length = biggest_klass(Array).length

100000.times do
  iter
  if $mwrap
    puts mwrap_log
    GC.start(full_mark: true, immediate_sweep: true)
  end
end