xref: /f-stack/app/redis-5.0.5/utils/lru/test-lru.rb (revision 572c4311)
1require 'rubygems'
2require 'redis'
3
4$runs = []; # Remember the error rate of each run for average purposes.
5$o = {};    # Options set parsing arguments
6
7def testit(filename)
8    r = Redis.new
9    r.config("SET","maxmemory","2000000")
10    if $o[:ttl]
11        r.config("SET","maxmemory-policy","volatile-ttl")
12    else
13        r.config("SET","maxmemory-policy","allkeys-lru")
14    end
15    r.config("SET","maxmemory-samples",5)
16    r.config("RESETSTAT")
17    r.flushall
18
19    html = ""
20    html << <<EOF
21    <html>
22    <body>
23    <style>
24    .box {
25        width:5px;
26        height:5px;
27        float:left;
28        margin: 1px;
29    }
30
31    .old {
32        border: 1px black solid;
33    }
34
35    .new {
36        border: 1px green solid;
37    }
38
39    .otherdb {
40        border: 1px red solid;
41    }
42
43    .ex {
44        background-color: #666;
45    }
46    </style>
47    <pre>
48EOF
49
50    # Fill the DB up to the first eviction.
51    oldsize = r.dbsize
52    id = 0
53    while true
54        id += 1
55        begin
56            r.set(id,"foo")
57        rescue
58            break
59        end
60        newsize = r.dbsize
61        break if newsize == oldsize # A key was evicted? Stop.
62        oldsize = newsize
63    end
64
65    inserted = r.dbsize
66    first_set_max_id = id
67    html << "#{r.dbsize} keys inserted.\n"
68
69    # Access keys sequentially, so that in theory the first part will be expired
70    # and the latter part will not, according to perfect LRU.
71
72    if $o[:ttl]
73        STDERR.puts "Set increasing expire value"
74        (1..first_set_max_id).each{|id|
75            r.expire(id,1000+id)
76            STDERR.print(".") if (id % 150) == 0
77        }
78    else
79        STDERR.puts "Access keys sequentially"
80        (1..first_set_max_id).each{|id|
81            r.get(id)
82            sleep 0.001
83            STDERR.print(".") if (id % 150) == 0
84        }
85    end
86    STDERR.puts
87
88    # Insert more 50% keys. We expect that the new keys will rarely be expired
89    # since their last access time is recent compared to the others.
90    #
91    # Note that we insert the first 100 keys of the new set into DB1 instead
92    # of DB0, so that we can try how cross-DB eviction works.
93    half = inserted/2
94    html << "Insert enough keys to evict half the keys we inserted.\n"
95    add = 0
96
97    otherdb_start_idx = id+1
98    otherdb_end_idx = id+100
99    while true
100        add += 1
101        id += 1
102        if id >= otherdb_start_idx && id <= otherdb_end_idx
103            r.select(1)
104            r.set(id,"foo")
105            r.select(0)
106        else
107            r.set(id,"foo")
108        end
109        break if r.info['evicted_keys'].to_i >= half
110    end
111
112    html << "#{add} additional keys added.\n"
113    html << "#{r.dbsize} keys in DB.\n"
114
115    # Check if evicted keys respect LRU
116    # We consider errors from 1 to N progressively more serious as they violate
117    # more the access pattern.
118
119    errors = 0
120    e = 1
121    error_per_key = 100000.0/first_set_max_id
122    half_set_size = first_set_max_id/2
123    maxerr = 0
124    (1..(first_set_max_id/2)).each{|id|
125        if id >= otherdb_start_idx && id <= otherdb_end_idx
126            r.select(1)
127            exists = r.exists(id)
128            r.select(0)
129        else
130            exists = r.exists(id)
131        end
132        if id < first_set_max_id/2
133            thiserr = error_per_key * ((half_set_size-id).to_f/half_set_size)
134            maxerr += thiserr
135            errors += thiserr if exists
136        elsif id >= first_set_max_id/2
137            thiserr = error_per_key * ((id-half_set_size).to_f/half_set_size)
138            maxerr += thiserr
139            errors += thiserr if !exists
140        end
141    }
142    errors = errors*100/maxerr
143
144    STDERR.puts "Test finished with #{errors}% error! Generating HTML on stdout."
145
146    html << "#{errors}% error!\n"
147    html << "</pre>"
148    $runs << errors
149
150    # Generate the graphical representation
151    (1..id).each{|id|
152        # Mark first set and added items in a different way.
153        c = "box"
154        if id >= otherdb_start_idx && id <= otherdb_end_idx
155            c << " otherdb"
156        elsif id <= first_set_max_id
157            c << " old"
158        else
159            c << " new"
160        end
161
162        # Add class if exists
163        if id >= otherdb_start_idx && id <= otherdb_end_idx
164            r.select(1)
165            exists = r.exists(id)
166            r.select(0)
167        else
168            exists = r.exists(id)
169        end
170
171        c << " ex" if exists
172        html << "<div title=\"#{id}\" class=\"#{c}\"></div>"
173    }
174
175    # Close HTML page
176
177    html << <<EOF
178    </body>
179    </html>
180EOF
181
182    f = File.open(filename,"w")
183    f.write(html)
184    f.close
185end
186
187def print_avg
188    avg = ($runs.reduce {|a,b| a+b}) / $runs.length
189    puts "#{$runs.length} runs, AVG is #{avg}"
190end
191
192if ARGV.length < 1
193    STDERR.puts "Usage: ruby test-lru.rb <html-output-filename> [--runs <count>] [--ttl]"
194    STDERR.puts "Options:"
195    STDERR.puts "  --runs <count>    Execute the test <count> times."
196    STDERR.puts "  --ttl             Set keys with increasing TTL values"
197    STDERR.puts "                    (starting from 1000 seconds) in order to"
198    STDERR.puts "                    test the volatile-lru policy."
199    exit 1
200end
201
202filename = ARGV[0]
203$o[:numruns] = 1
204
205# Options parsing
206i = 1
207while i < ARGV.length
208    if ARGV[i] == '--runs'
209        $o[:numruns] = ARGV[i+1].to_i
210        i+= 1
211    elsif ARGV[i] == '--ttl'
212        $o[:ttl] = true
213    else
214        STDERR.puts "Unknown option #{ARGV[i]}"
215        exit 1
216    end
217    i+= 1
218end
219
220$o[:numruns].times {
221    testit(filename)
222    print_avg if $o[:numruns] != 1
223}
224