mirror of
https://github.com/discourse/discourse.git
synced 2024-11-24 09:17:30 +08:00
1d5c2b36f6
This adds mwrap logging to each iteration so we can see how much leaks per iteration and where it is coming from
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
|