176 lines
4.0 KiB
Ruby
176 lines
4.0 KiB
Ruby
# 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
|