Skip to content

Instantly share code, notes, and snippets.

@ibrahima
Created May 18, 2026 18:22
Show Gist options
  • Select an option

  • Save ibrahima/a507c97838b1fbe41c5d45ed8ce07cdf to your computer and use it in GitHub Desktop.

Select an option

Save ibrahima/a507c97838b1fbe41c5d45ed8ce07cdf to your computer and use it in GitHub Desktop.
Benchmark script for TailwindMerge::Config merging memory leak
# frozen_string_literal: true
# To run this script from the tailwind_merge repository, use:
# `ruby -Ilib bench_memory_leak.rb`
require "objspace"
require "tailwind_merge"
ITERATIONS = Integer(ENV.fetch("ITERATIONS", "1000"))
PAYLOAD_SIZE = Integer(ENV.fetch("PAYLOAD_SIZE", "1024"))
def rss_kb
status = File.read("/proc/self/status")
status[/^VmRSS:\s+(\d+)\s+kB$/, 1].to_i
rescue Errno::ENOENT
`ps -o rss= -p #{Process.pid}`.to_i
end
def format_kb_from_bytes(bytes)
format("%.1f KB", bytes / 1024.0)
end
def format_mb_from_kb(kb)
format("%.1f MB", kb / 1024.0)
end
def snapshot(label)
GC.start(full_mark: true, immediate_sweep: true)
{
label: label,
rss_kb: rss_kb,
heap_live_slots: GC.stat[:heap_live_slots],
total_allocated_objects: GC.stat[:total_allocated_objects],
merger_instances: ObjectSpace.each_object(TailwindMerge::Merger).count,
class_utils_instances: ObjectSpace.each_object(TailwindMerge::ClassGroupUtils).count,
default_spacing_entries: TailwindMerge::Config::DEFAULTS[:theme]["spacing"].length,
memsize_all: ObjectSpace.memsize_of_all,
}
end
def print_snapshot(before, after)
puts "#{before[:label]} -> #{after[:label]}"
puts "RSS: #{before[:rss_kb]} KB (#{format_mb_from_kb(before[:rss_kb])}) -> " \
"#{after[:rss_kb]} KB (#{format_mb_from_kb(after[:rss_kb])}) " \
"delta=#{after[:rss_kb] - before[:rss_kb]} KB (#{format_mb_from_kb(after[:rss_kb] - before[:rss_kb])})"
puts "heap_live_slots: #{before[:heap_live_slots]} -> #{after[:heap_live_slots]} " \
"delta=#{after[:heap_live_slots] - before[:heap_live_slots]}"
puts "memsize_of_all: #{format_kb_from_bytes(before[:memsize_all])} -> " \
"#{format_kb_from_bytes(after[:memsize_all])} " \
"delta=#{format_kb_from_bytes(after[:memsize_all] - before[:memsize_all])}"
puts "allocated objects: delta=#{after[:total_allocated_objects] - before[:total_allocated_objects]}"
puts "Merger instances: #{before[:merger_instances]} -> #{after[:merger_instances]}"
puts "ClassGroupUtils: #{before[:class_utils_instances]} -> #{after[:class_utils_instances]}"
puts "DEFAULT spacing length: #{before[:default_spacing_entries]} -> #{after[:default_spacing_entries]}"
end
def build_mergers
ITERATIONS.times do |i|
custom_scale = "custom-space-#{i}-#{"x" * PAYLOAD_SIZE}"
merger = TailwindMerge::Merger.new(
config: {
theme: {
"spacing" => [custom_scale],
},
},
)
# Exercise the custom theme path, then drop the local reference.
merger.merge("p-1 p-#{custom_scale}")
end
end
before = snapshot("before")
build_mergers
after = snapshot("after")
print_snapshot(before, after)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment