Monday, February 21, 2011

memcachedのstats slabsを解析するRubyスクリプト

memcachedのstats slabsの項目名は直感的でない。

例えば、
free_chunksは、delete等されて再利用可能なチャンク数
free_chunks_endが最後にアロケートされたページで一度もsetされていないチャンク数
とかは、memcachedのソースを読んでやっと意味がわかった。

また、チューニングの際に必要な、アラインメントによる無駄領域の合計がぱっと見でわからなかったりする。

とういことで、
memcachedのstats slabs統計情報を解析するRubyスクリプトを張り付けておく。

# テキストプロトコルにしか対応してません。
# バイナリプロトコル専用のmemachedへは、rubyのmemcachedライブラリを使うように改造すりゃできる。


起動オプション
Usage:
./better_stats_slabs.rb -d /path_to_dir
or ./better_stats_slabs.rb -f /path_to_file
or ./better_stats_slabs.rb -h localhost:11211,localhost11222

Byte表示  オプションなし(デフォルト)
KiloByte表示 -k
MegaByte表示 -m
GigaByte表示 -g

出力結果

出力説明

STAT1..の行はスラブクラスごとの情報、下のtotalは全体の情報。
数字は全部サイズ。(byte, kB, MB GBの切り替えはオプションで可能)

空き領域が0のスラブクラスの先頭にはアスタリスクを表示している。
どのスラブでout of memoryが発生しているか、が分かる。

never_usedは、free_chunks_endのサイズ合計であり、最後に割り当てられたスラブ(ページ)の空き領域
reusableは、free_chunksのサイズ合計であり、delete済みのチャンク数合計
free_totalは、never_usedと、reusableの合計。つまり、そのスラブの空き領域合計サイズ。
free_totalが0だと、次にそのスラブクラスにsetした際にmallocされるってこと。(-Lオプションつけない場合)

item_sizeは、アラインメントを意識したアイテムの平均サイズの合計
wastedは、アラインメントを意識したアイテムの平均無駄サイズの合計
item_size + wastedが使用中チャンクサイズの合計です。
チューニング時にgrowth_factorを決める際に参考にできる。

total mallocは、スラブ用領域に割り当てられたサイズの合計、
total free sizeは空き領域合計
total ued sizeは使用中チャンクサイズ合計
items sizeの割合は、スラブ用メモリ領域全体のうちのアイテム本体のサイズが占める割合
wasted sizeの割合は、スラブ用メモリ領域全体のうちの無駄領域のサイズが占める割合


ソース
  1. #!/usr/bin/ruby  
  2. # encoding: UTF-8  
  3.   
  4. def comma(number)  
  5.   number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1,').reverse  
  6. end  
  7.   
  8. def percent(numerator, denominator)  
  9.   (denominator == 0 ? "- " : (100 * numerator / denominator).to_s).rjust(3)  
  10. end  
  11.   
  12. alias :org_printf :printf  
  13. def printf(fmt, *args)  
  14.   org_printf(fmt, *args.map{|a| a.is_a?(Numeric) ? a / $options[:unit_scale] : a})  
  15. end  
  16.   
  17. def stats_slabs(host_port)  
  18.   host, port = host_port.split(":").first, host_port.split(":").last  
  19.   lines = []  
  20.   begin  
  21.     TCPSocket.open(host, port) do |s|  
  22.       s.puts "stats slabs"  
  23.       while "END" != (line = s.gets.chop)  
  24.         lines << line  
  25.       end  
  26.     end  
  27.   rescue => e  
  28.     puts "counld not connect to #{host_port}"  
  29.     puts e.message  
  30.   end  
  31.   lines  
  32. end  
  33.   
  34. def parse(lines)  
  35.   chunks_stats = []  
  36.   lines.each do |line|  
  37.     case line  
  38.       when /^STAT (\d+).*:chunk_size (\d+)/  
  39.         chunks_stats << {no: $1, size: $2.to_i,  
  40.           previous_size: chunks_stats.last ? chunks_stats.last[:size].to_i : 0 }  
  41.       when /^STAT.*:total_chunks (\d+)/  
  42.         chunks_stats.last[:total_chunks] = $1.to_i  
  43.       when /^STAT.*:used_chunks (\d+)/  
  44.         chunks_stats.last[:used_chunks] = $1.to_i  
  45.       when /^STAT.*:free_chunks_end (\d+)/  
  46.         chunks_stats.last[:free_chunks_end] = $1.to_i  
  47.       when /^STAT.*:free_chunks (\d+)/  
  48.         chunks_stats.last[:free_chunks] = $1.to_i  
  49.     end  
  50.   end  
  51.   chunks_stats  
  52. end  
  53.   
  54. def calc_and_print(chunks_stats)  
  55.   
  56.   total_malloced = 0 # total size of all chunks.  
  57.   
  58.   total_free_size = 0 # total size of free chunks.  
  59.   total_used_size = 0 # total size of used chunks.  
  60.   
  61.   total_items_size_about = 0 # total size of items.  
  62.   total_waste_size_about = 0 # total size of wasted spaces by align.  
  63.   
  64.   puts "(unit: #{$options[:unit]})"  
  65.   chunks_stats.each_with_index do |stat, i|  
  66.   
  67.     total_malloced += (stat[:size] * stat[:total_chunks])  
  68.   
  69.     # The number of free chunks in a subclass = free_chunks_end + free_chunks  
  70.     sum_unused_size = stat[:size] * (stat[:free_chunks_end])   # chunks has not used yet.  
  71.     sum_reusable_size = stat[:size] * (stat[:free_chunks])     # deleted chunks.  
  72.     sum_free_size = sum_unused_size + sum_reusable_size  
  73.     total_free_size += sum_free_size  
  74.   
  75.     used_size = stat[:size] * stat[:used_chunks]  
  76.     total_used_size += used_size  
  77.   
  78.     # The average of wasted size(unused space) of a chunk.  
  79.     agv_wasted_size_per_chunk = (stat[:size] - stat[:previous_size] ) / 2  
  80.   
  81.     sum_wasted_size = agv_wasted_size_per_chunk * stat[:used_chunks]  
  82.     total_waste_size_about += sum_wasted_size  
  83.   
  84.     sum_items_size = (stat[:size] - agv_wasted_size_per_chunk) * stat[:used_chunks]  
  85.     total_items_size_about += sum_items_size  
  86.   
  87.     printf(" #{sum_free_size == 0 ? "*" : " "} STAT %2d(#{stat[:size].to_s.rjust(4)}) | never_used: %10d reusable: %10d free_total: %10d | item_size : %10d (#{percent(sum_items_size, used_size)}%%) wasted: %10d (#{percent(sum_wasted_size, used_size)}%%)\n", stat[:no],  sum_unused_size, sum_reusable_size, sum_free_size, sum_items_size, sum_wasted_size)  
  88.   end  
  89.   
  90.   puts ""  
  91.   printf(" total malloced  : %11d #{$options[:unit]}\n", total_malloced)  
  92.   printf(" total free size : %11d #{$options[:unit]}(#{percent(total_free_size, total_malloced)}%%)\n", total_free_size)  
  93.   printf(" total used size : %11d #{$options[:unit]}(#{percent(total_used_size, total_malloced)}%%)\n", total_used_size)  
  94.   printf("     items size ≈ %12d #{$options[:unit]}(#{percent(total_items_size_about, total_malloced)}%%)\n", total_items_size_about)  
  95.   printf("    wasted size ≈ %12d #{$options[:unit]}(#{percent(total_waste_size_about, total_malloced)}%%)\n", total_waste_size_about)  
  96. end  
  97.   
  98. def parse_calc_print(header, lines)  
  99.   chunks_stats = parse(lines)  
  100.   puts "-" * 5 + header + "-" * 130  
  101.   return if chunks_stats.empty?  
  102.   calc_and_print chunks_stats  
  103. end  
  104.   
  105. Usage = "Usage:  
  106.                ./better_stats_slabs.rb -d /path_to_dir  
  107.             or ./better_stats_slabs.rb -f /path_to_file  
  108.             or ./better_stats_slabs.rb -h localhost:11211,localhost11222  
  109.   
  110.             Byte     ./better_stats_slabs.rb -d /path_to_dir  
  111.             KiloByte ./better_stats_slabs.rb -d /path_to_dir -k  
  112.             MegaByte ./better_stats_slabs.rb -d /path_to_dir -m  
  113.             GigaByte ./better_stats_slabs.rb -d /path_to_dir -g  
  114.         "  
  115. $options = {unit: "Byte", unit_scale: 1}  
  116. require 'optparse'  
  117. OptionParser.new{|opt|  
  118.   Version = "0.1"  
  119.   opt.banner = Usage  
  120.   opt.on("-k""kB""KilloByte"do  
  121.     $options[:unit] = "kB"  
  122.     $options[:unit_scale] = 1024  
  123.   end  
  124.   opt.on("-m""MB""MegaByte"do  
  125.     $options[:unit] = "MB"  
  126.     $options[:unit_scale] = 1024 ** 2  
  127.   end  
  128.   opt.on("-g""GB""GigaByte"do  
  129.     $options[:unit] = "GB"  
  130.     $options[:unit_scale] = 1024 ** 3  
  131.   end  
  132.   opt.on("-f filepath""a file which had saved 'stats slabs'."do |file|  
  133.     $options[:file] = file  
  134.   end  
  135.   opt.on("-d dir_path""a dir path wihch has 'stats slabs' files."do |dir|  
  136.     $options[:dir] = dir  
  137.   end  
  138.   opt.on("-h localhost:11211,localhost11222""Host:Port of memcached process. ex. localhost:11211,localhost:11212"do |hs|  
  139.     $options[:hosts_and_ports] = hs  
  140.   end  
  141.   opt.parse!(ARGV)  
  142. }  
  143.   
  144. if file = $options[:file]  
  145.     lines = File.readlines(File.expand_path file)  
  146.     parse_calc_print(file, lines)  
  147. elsif dir = $options[:dir]  
  148.     Dir.glob("#{dir}/*"do |path|  
  149.       lines= File.readlines(File.expand_path path)  
  150.       parse_calc_print(path, lines)  
  151.     end  
  152. elsif hs = $options[:hosts_and_ports]  
  153.     require 'socket'  
  154.     host_port_array = hs.split(",").map{|h| h.strip}  
  155.     host_port_array.each do |h|  
  156.       lines = stats_slabs(h)  
  157.       parse_calc_print(h, lines)  
  158.     end  
  159. else  
  160.   puts Usage  
  161. end  

No comments:

Post a Comment