From 5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Wed, 21 Jan 2026 22:40:55 +0100 Subject: Add Redis source code for testing --- examples/redis-unstable/utils/lru/test-lru.rb | 223 ++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 examples/redis-unstable/utils/lru/test-lru.rb (limited to 'examples/redis-unstable/utils/lru/test-lru.rb') diff --git a/examples/redis-unstable/utils/lru/test-lru.rb b/examples/redis-unstable/utils/lru/test-lru.rb new file mode 100644 index 0000000..d511e20 --- /dev/null +++ b/examples/redis-unstable/utils/lru/test-lru.rb @@ -0,0 +1,223 @@ +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
+
+    # 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 << "
" + $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 << "
" + } + + # Close HTML page + + 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 [--runs ] [--ttl]" + STDERR.puts "Options:" + STDERR.puts " --runs Execute the test 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 +} -- cgit v1.2.3