123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- require 'rubygems'
- require 'redis'
- $runs = []; # Remember the error rate of each run for average purposes.
- $o = {}; # Options set parsing arguments
- def testit(filename)
- r = Redis.new
- r.config("SET","maxmemory","2000000")
- if $o[:ttl]
- r.config("SET","maxmemory-policy","volatile-ttl")
- else
- r.config("SET","maxmemory-policy","allkeys-lru")
- end
- r.config("SET","maxmemory-samples",5)
- r.config("RESETSTAT")
- r.flushall
- html = ""
- html << <<EOF
- <html>
- <body>
- <style>
- .box {
- width:5px;
- height:5px;
- float:left;
- margin: 1px;
- }
- .old {
- border: 1px black solid;
- }
- .new {
- border: 1px green solid;
- }
- .otherdb {
- border: 1px red solid;
- }
- .ex {
- background-color: #666;
- }
- </style>
- <pre>
- EOF
- # Fill the DB up to the first eviction.
- oldsize = r.dbsize
- id = 0
- while true
- id += 1
- begin
- r.set(id,"foo")
- rescue
- break
- end
- newsize = r.dbsize
- break if newsize == oldsize # A key was evicted? Stop.
- oldsize = newsize
- end
- inserted = r.dbsize
- first_set_max_id = id
- html << "#{r.dbsize} keys inserted.\n"
- # Access keys sequentially, so that in theory the first part will be expired
- # and the latter part will not, according to perfect LRU.
- if $o[:ttl]
- STDERR.puts "Set increasing expire value"
- (1..first_set_max_id).each{|id|
- r.expire(id,1000+id)
- STDERR.print(".") if (id % 150) == 0
- }
- else
- STDERR.puts "Access keys sequentially"
- (1..first_set_max_id).each{|id|
- r.get(id)
- sleep 0.001
- STDERR.print(".") if (id % 150) == 0
- }
- end
- STDERR.puts
- # Insert more 50% keys. We expect that the new keys will rarely be expired
- # since their last access time is recent compared to the others.
- #
- # Note that we insert the first 100 keys of the new set into DB1 instead
- # of DB0, so that we can try how cross-DB eviction works.
- half = inserted/2
- html << "Insert enough keys to evict half the keys we inserted.\n"
- add = 0
- otherdb_start_idx = id+1
- otherdb_end_idx = id+100
- while true
- add += 1
- id += 1
- if id >= otherdb_start_idx && id <= otherdb_end_idx
- r.select(1)
- r.set(id,"foo")
- r.select(0)
- else
- r.set(id,"foo")
- end
- break if r.info['evicted_keys'].to_i >= half
- end
- html << "#{add} additional keys added.\n"
- html << "#{r.dbsize} keys in DB.\n"
- # Check if evicted keys respect LRU
- # We consider errors from 1 to N progressively more serious as they violate
- # more the access pattern.
- errors = 0
- e = 1
- error_per_key = 100000.0/first_set_max_id
- half_set_size = first_set_max_id/2
- maxerr = 0
- (1..(first_set_max_id/2)).each{|id|
- if id >= otherdb_start_idx && id <= otherdb_end_idx
- r.select(1)
- exists = r.exists(id)
- r.select(0)
- else
- exists = r.exists(id)
- end
- if id < first_set_max_id/2
- thiserr = error_per_key * ((half_set_size-id).to_f/half_set_size)
- maxerr += thiserr
- errors += thiserr if exists
- elsif id >= first_set_max_id/2
- thiserr = error_per_key * ((id-half_set_size).to_f/half_set_size)
- maxerr += thiserr
- errors += thiserr if !exists
- end
- }
- errors = errors*100/maxerr
- STDERR.puts "Test finished with #{errors}% error! Generating HTML on stdout."
- html << "#{errors}% error!\n"
- html << "</pre>"
- $runs << errors
- # Generate the graphical representation
- (1..id).each{|id|
- # Mark first set and added items in a different way.
- c = "box"
- if id >= otherdb_start_idx && id <= otherdb_end_idx
- c << " otherdb"
- elsif id <= first_set_max_id
- c << " old"
- else
- c << " new"
- end
- # Add class if exists
- if id >= otherdb_start_idx && id <= otherdb_end_idx
- r.select(1)
- exists = r.exists(id)
- r.select(0)
- else
- exists = r.exists(id)
- end
- c << " ex" if exists
- html << "<div title=\"#{id}\" class=\"#{c}\"></div>"
- }
- # Close HTML page
- html << <<EOF
- </body>
- </html>
- EOF
- f = File.open(filename,"w")
- f.write(html)
- f.close
- end
- def print_avg
- avg = ($runs.reduce {|a,b| a+b}) / $runs.length
- puts "#{$runs.length} runs, AVG is #{avg}"
- end
- if ARGV.length < 1
- STDERR.puts "Usage: ruby test-lru.rb <html-output-filename> [--runs <count>] [--ttl]"
- STDERR.puts "Options:"
- STDERR.puts " --runs <count> Execute the test <count> times."
- STDERR.puts " --ttl Set keys with increasing TTL values"
- STDERR.puts " (starting from 1000 seconds) in order to"
- STDERR.puts " test the volatile-lru policy."
- exit 1
- end
- filename = ARGV[0]
- $o[:numruns] = 1
- # Options parsing
- i = 1
- while i < ARGV.length
- if ARGV[i] == '--runs'
- $o[:numruns] = ARGV[i+1].to_i
- i+= 1
- elsif ARGV[i] == '--ttl'
- $o[:ttl] = true
- else
- STDERR.puts "Unknown option #{ARGV[i]}"
- exit 1
- end
- i+= 1
- end
- $o[:numruns].times {
- testit(filename)
- print_avg if $o[:numruns] != 1
- }
|