Showing posts with label ruby. Show all posts
Showing posts with label ruby. Show all posts

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の割合は、スラブ用メモリ領域全体のうちの無駄領域のサイズが占める割合


ソース
#!/usr/bin/ruby
# encoding: UTF-8

def comma(number)
  number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1,').reverse
end

def percent(numerator, denominator)
  (denominator == 0 ? "- " : (100 * numerator / denominator).to_s).rjust(3)
end

alias :org_printf :printf
def printf(fmt, *args)
  org_printf(fmt, *args.map{|a| a.is_a?(Numeric) ? a / $options[:unit_scale] : a})
end

def stats_slabs(host_port)
  host, port = host_port.split(":").first, host_port.split(":").last
  lines = []
  begin
    TCPSocket.open(host, port) do |s|
      s.puts "stats slabs"
      while "END" != (line = s.gets.chop)
        lines << line
      end
    end
  rescue => e
    puts "counld not connect to #{host_port}"
    puts e.message
  end
  lines
end

def parse(lines)
  chunks_stats = []
  lines.each do |line|
    case line
      when /^STAT (\d+).*:chunk_size (\d+)/
        chunks_stats << {no: $1, size: $2.to_i,
          previous_size: chunks_stats.last ? chunks_stats.last[:size].to_i : 0 }
      when /^STAT.*:total_chunks (\d+)/
        chunks_stats.last[:total_chunks] = $1.to_i
      when /^STAT.*:used_chunks (\d+)/
        chunks_stats.last[:used_chunks] = $1.to_i
      when /^STAT.*:free_chunks_end (\d+)/
        chunks_stats.last[:free_chunks_end] = $1.to_i
      when /^STAT.*:free_chunks (\d+)/
        chunks_stats.last[:free_chunks] = $1.to_i
    end
  end
  chunks_stats
end

def calc_and_print(chunks_stats)

  total_malloced = 0 # total size of all chunks.

  total_free_size = 0 # total size of free chunks.
  total_used_size = 0 # total size of used chunks.

  total_items_size_about = 0 # total size of items.
  total_waste_size_about = 0 # total size of wasted spaces by align.

  puts "(unit: #{$options[:unit]})"
  chunks_stats.each_with_index do |stat, i|

    total_malloced += (stat[:size] * stat[:total_chunks])

    # The number of free chunks in a subclass = free_chunks_end + free_chunks
    sum_unused_size = stat[:size] * (stat[:free_chunks_end])   # chunks has not used yet.
    sum_reusable_size = stat[:size] * (stat[:free_chunks])     # deleted chunks.
    sum_free_size = sum_unused_size + sum_reusable_size
    total_free_size += sum_free_size

    used_size = stat[:size] * stat[:used_chunks]
    total_used_size += used_size

    # The average of wasted size(unused space) of a chunk.
    agv_wasted_size_per_chunk = (stat[:size] - stat[:previous_size] ) / 2

    sum_wasted_size = agv_wasted_size_per_chunk * stat[:used_chunks]
    total_waste_size_about += sum_wasted_size

    sum_items_size = (stat[:size] - agv_wasted_size_per_chunk) * stat[:used_chunks]
    total_items_size_about += sum_items_size

    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)
  end

  puts ""
  printf(" total malloced  : %11d #{$options[:unit]}\n", total_malloced)
  printf(" total free size : %11d #{$options[:unit]}(#{percent(total_free_size, total_malloced)}%%)\n", total_free_size)
  printf(" total used size : %11d #{$options[:unit]}(#{percent(total_used_size, total_malloced)}%%)\n", total_used_size)
  printf("     items size ≈ %12d #{$options[:unit]}(#{percent(total_items_size_about, total_malloced)}%%)\n", total_items_size_about)
  printf("    wasted size ≈ %12d #{$options[:unit]}(#{percent(total_waste_size_about, total_malloced)}%%)\n", total_waste_size_about)
end

def parse_calc_print(header, lines)
  chunks_stats = parse(lines)
  puts "-" * 5 + header + "-" * 130
  return if chunks_stats.empty?
  calc_and_print chunks_stats
end

Usage = "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     ./better_stats_slabs.rb -d /path_to_dir
            KiloByte ./better_stats_slabs.rb -d /path_to_dir -k
            MegaByte ./better_stats_slabs.rb -d /path_to_dir -m
            GigaByte ./better_stats_slabs.rb -d /path_to_dir -g
        "
$options = {unit: "Byte", unit_scale: 1}
require 'optparse'
OptionParser.new{|opt|
  Version = "0.1"
  opt.banner = Usage
  opt.on("-k", "kB", "KilloByte") do
    $options[:unit] = "kB"
    $options[:unit_scale] = 1024
  end
  opt.on("-m", "MB", "MegaByte") do
    $options[:unit] = "MB"
    $options[:unit_scale] = 1024 ** 2
  end
  opt.on("-g", "GB", "GigaByte") do
    $options[:unit] = "GB"
    $options[:unit_scale] = 1024 ** 3
  end
  opt.on("-f filepath", "a file which had saved 'stats slabs'.") do |file|
    $options[:file] = file
  end
  opt.on("-d dir_path", "a dir path wihch has 'stats slabs' files.") do |dir|
    $options[:dir] = dir
  end
  opt.on("-h localhost:11211,localhost11222", "Host:Port of memcached process. ex. localhost:11211,localhost:11212") do |hs|
    $options[:hosts_and_ports] = hs
  end
  opt.parse!(ARGV)
}

if file = $options[:file]
    lines = File.readlines(File.expand_path file)
    parse_calc_print(file, lines)
elsif dir = $options[:dir]
    Dir.glob("#{dir}/*") do |path|
      lines= File.readlines(File.expand_path path)
      parse_calc_print(path, lines)
    end
elsif hs = $options[:hosts_and_ports]
    require 'socket'
    host_port_array = hs.split(",").map{|h| h.strip}
    host_port_array.each do |h|
      lines = stats_slabs(h)
      parse_calc_print(h, lines)
    end
else
  puts Usage
end

Thursday, July 29, 2010

Rubyの特異メソッドと特異クラス

プログラミング言語Ruby P269辺りに特異メソッドと特異クラスについての解説がある。
読んだだけじゃ絶対にわすれるので、メモっとく。

特異メソッド
クラスに属するすべてのオブジェクトではなく、単一のオブジェクトだけのために定義されたメソッド
オブジェクトの特異メソッドは、そのオブジェクトのクラスによって定義されない。
そのオブジェクトに対応付けられた無名の特異クラスのインスタンスメソッドである。
無名の特異クラスのことをシングルトンクラス、メタクラスと呼ぶ。

各インスタンスごとに特異クラスを持つ。
classのインスタンスClass1の特異メソッドは、Class1クラスのクラスメソッドであり、
classのインスタンスであるClass1の特異クラスのインスタンスメソッドである。

試してみた。

# ClassクラスのインスタンスであるPointオブジェクトに特異メソッドを定義
# Pointクラスのクラスメソッドになる
class Point; end
def Point.sum x, y; x + y; end
p Point.sum 1,2  #=> 3

# 特異メソッドを複数定義時に便利な構文糖
class << Point
  def minus x, y; x - y; end
  def multi x, y; x * y; end
end
p Point.minus 1,2  #=> -1
p Point.multi 1,2  #=> 2

# クラス定義中にクラスメソッドを複数定義するときに便利な構文糖
# class << self は特異クラス(classのインスタンスであるPointクラスオブジェクト)
class Point
  class << self
    def div x, y; x / y; end
  end
  def self.eignclass 
    class << self; self; end
  end
end

# p1オブジェクトの特異メソッドを定義
p1 = Point.new
def p1.sum x, y, z; x + y + z; end
p p1.sum(1, 2, 3) #=> 6

# p2からはp1の特異メソッドは見えない
p2 = Point.new
#p p2.sum(1, 2, 3) #=> NoMethodError

class << p2
  def sum x, y ,z, a; x + y + z + a; end
  def multi x, y ,z, a; x * y * z * a; end
end
p p2.sum 1, 2, 3, 4 #=> 10
p p2.multi 1, 2, 3, 4 #=> 24

# p2からはP1特異メソッド特異メソッドは見えない
#p p1.sum 1, 2, 3, 4 #=> NoMethodError

# 実行結果
# 3
# -1
# 2
# 6
# 10
# 24


もう一丁。

class Class1
  puts "#{self.__id__} before open singleton class"
  class << self
    puts "#{self.__id__} opening singleton class"
  end
  def self.eignclass 
    class << self; self; end
  end
  puts "<<< " + self.to_s
end

class Class2
  puts ">>> " + self.to_s
  puts "#{self.__id__} before open singleton class"
  class << self
    puts "#{self.__id__} opening singleton class"
  end
  def self.eignclass 
    class << self; self; end
  end
  puts "<<< " + self.to_s
end
puts "------------"
puts "#{Class1.__id__} Class1.__id__"
puts "#{Class1.eignclass.__id__} Class1.eignclass.__id__"

puts "------------"
puts "#{Class2.__id__} Class2.__id__"
puts "#{Class2.eignclass.__id__} Class2.eignclass.__id__"

puts "------------"
c1 = Class1.new
puts "#{c1.__id__} c1's id"
class << c1 
  puts "#{self.__id__} c1's singleton class"
end

puts "------------"
c11 = Class1.new
puts "#{c11.__id__} c11's id"
class << c11
  puts "#{self.__id__} c11's singleton class"
end

puts "------------"
c2 = Class2.new
puts "#{c2.__id__} c2's id"
class << c2 
  puts "#{self.__id__} c2's singleton class"
end

puts "------------"
c21 = Class2.new
puts "#{c21.__id__} c21's id"
class << c21
  puts "#{self.__id__} c21's singleton class"
end

# 実行結果
# >>> Class1
# 70227334526040 before open singleton class
# 70227334526020 opening singleton class
# <<< Class1
# >>> Class2
# 70227334525980 before open singleton class
# 70227334525680 opening singleton class
# <<< Class2
# ------------
# 70227334526040 Class1.__id__
# 70227334526020 Class1.eignclass.__id__
# ------------
# 70227334525980 Class2.__id__
# 70227334525680 Class2.eignclass.__id__
# ------------
# 70227334525100 c1's id
# 70227334525040 c1's singleton class
# ------------
# 70227334524940 c11's id
# 70227334524880 c11's singleton class
# ------------
# 70227334524780 c2's id
# 70227334524720 c2's singleton class
# ------------
# 70227334524620 c21's id
# 70227334524560 c21's singleton class

Thursday, July 22, 2010

Rubyのクラスメソッド

プログラミング言語Ruby(P268)

クラスのクラスメソッドとは、そのクラスを表現するClassクラスのインスタンスの特異メソッドにすぎない。

RubyのModule, Class, include, extend, selfポインタ

プログラミング言語Ruby P258の内容のサンプル。

クラス階層が必要のないグローバル関数を、グローバルな名前空間を汚さないためにModuleとしてまとめる方法。
moduleに定義したインスタンスメソッドをmix-inする方法( include, extend)
includeはインスタンスメソッドとして織りまぜ、
extendは特異メソッドとして織りまぜる。

module SampleModule

  # class method
  def self.hello
    puts "hello"
  end

  # class method
  def self.goodby
    puts "goodby"
  end

  # instance method
  def good_morning
    puts "good_morning"
  end
end

class IncludeMod
  # インスタンスメソッドとしてinclude
  include SampleModule
end

class ExtendMod
  # クラスメソッドとしてextend
  # この時のselfはクラスの中、メソッド定義の外なので ExtendModクラスをサス(P225)
  self.extend SampleModule
  #ExtendMod.extend SampleModule
end

SampleModule.hello
SampleModule.goodby

IncludeMod.new.good_morning
ExtendMod.good_morning


Rubyの継承時のprivateメソッドには注意

プログラミング言語Ruby P248

サブクラスはPrivateメソッドを継承する。
サブクラスは親で定義されたprivateメソッドを呼び出せ、オーバーライドすることが可能。

他人が書いたクラスをサブクラス化するときは気をつけろ。
偶然、同じ名前のprivateメソッドをオーバーライドするとバグる。

Rubyではサブクラス化するのは、スーパークラスの実装を欲知っている時に限るべきだ。
継承ではなく、委譲するべき。

Ruby1.9からSymbolクラスにto_procが追加されたってか

プログラミング言語Rubyを読んでいる。
P217に、Ruby1.9からSynbolクラスにto_procが追加されたので、シンボルに&プレフィックスを付けると、イテレータにブロックとして渡せるようになった、と書いてある。

メモメモ。

def succ x; x + 1; end
p [1,2,3].map{|x| x + 1}
p [1,2,3].map{|x| succ x}
p [1,2,3].map(&:succ)
p [1,2,3].map(&self.method(:succ))
p [1,2,3].map{|x| self.method(:succ).call(x) }
p [1,2,3].map{|x| self.method(:succ).to_proc.call(x) }

Wednesday, July 21, 2010

Rubyで関数を継承?

勉強のためにRubyのライブラリでも見ていくことにした。
とりあえず一発目はtempfile.rbでも見てみるかということで、13行目。

class Tempfile < DelegateClass(File)
ほうほう、DelegateClassを継承しているのね。でも括弧なんだろ? おもむろにCtagsで飛んでみた。(delegate.rb) そしたら、ビックリ。
def DelegateClass(superclass)
関数じゃないですか。

分からん。。
また分かったら追記するってことでメモっておく。

追記
わかった。
def DelegateClass(superclass)
はクラスを返すメソッドなのね。

Delegate(クラス)で指定したクラスのpublic instance methodと
__getobj__, __setobj__をmodule_evalしたクラスを
継承したいからこんなことしてるのか。