modes = ["data", "pipe"]
methods = ["GET", "INCR", "LPOP", "LPUSH", "LPUSH (needed to benchmark LRANGE)",
"PING_BULK", "PING_INLINE", "SADD", "SET", "SPOP",
"LRANGE_100 (first 100 elements)"
"LRANGE_300 (first 300 elements)"
"LRANGE_500 (first 450 elements)"
"LRANGE_600 (first 600 elements)"]
suffix = "-one"
base = "/files/redis-benchmark-json"
# We sort version-specific compiler comparisons from low to high score
sortValue = (pa,pb) ->
[_, a] = pa
[_, b] = pb
a - b
# Sort version strings properly, considering rc, patch level, and beta versions
sortVersion = (pa,pb) ->
[a, _] = pa
[b, _] = pb
[aversion, _] = a.split '_'
[bversion, _] = b.split '_'
[a1, a2, a3] = aversion.split '.'
[b1, b2, b3] = bversion.split '.'
if a1 == b1 and a2 == b2
a3s = a3.split '-'
b3s = b3.split '-'
subd = a3s[0] - b3s[0]
# If we know they won't be equal, then return subtracted:
if subd != 0 then return subd
# Here, a3s[0] == b3s[0], so we need to
# compare sub-versions of either -N, -rcN, or -betaN
a3sl = a3s.length > 1
b3sl = b3s.length > 1
if a3sl and b3sl
a3s1 = a3s[1][0]
b3s1 = b3s[1][0]
# If they match rc tags or beta tags, compare at tag level
# Beta sorts before RC sorts before Regular before Patch
if (a3s1 == 'r' and b3s1 == 'r') or (a3s1 == 'b' and b3s1 == 'b')
if a3s[1] > b3s[1] then return 1
if a3s[1] < b3s[1] then return -1
return 0
else if (a3s1 == 'b' and b3s1 == 'r') then return -1
else if (a3s1 == 'r' and b3s1 == 'b') then return 1
else if (a3s1 == 'r' or a3s1 == 'b') then return -1
else if (b3s1 == 'b' or b3s1 == 'r') then return 1
return a3s1 - b3s1
if a3sl and not b3sl
# If a has a dash but b doesn't
if a3s[1][0] == 'r' or a3s[1][0] == 'b' then return -1
# Else, we have a patch release, so sort above the normal version
return 1
else if b3sl and not a3sl
if b3s[1][0] == 'r' or b3s[1][0] == 'b' then return 1
return -1
# By here, we have covered every possible case for the third level
else if a1 == b1 and a2 > b2 then return 1
else if a1 == b1 and a2 < b2 then return -1
else if a1 > b1 then return 1
else if a1 < b1 then return -1
else return 0
toCompilerStr = (version, mode, method) ->
"COMPILER-#{version}-#{mode}-#{method}"
toCompiler = (version, mode, method, totalMethod) ->
fileBase = toCompilerStr version, mode, method
c = $.get "#{base}/#{fileBase}.json"
c.done (j) ->
datas[fileBase] = j.sort(sortValue)
if not extremes[mode]
extremes[mode] = {}
# here, "method" is just the version number, so we don't
# conflict with proper "methods" in the methods array.
extremes[mode][totalMethod] =
min: j[0][1]
max: j[j.length-1][1]
fetchData = ->
for mode in modes
for method in methods
x = $.get "#{base}/#{mode}-#{method}#{suffix}.json"
# Have to properly capture mode/method. A normal
# closure won't work because JS is capturing the references,
# not the values, so at the end, all the variables point
# to the same value, which isn't what we want at all.
c = (m1, m2) ->
x.done (j) ->
sorted = false
if not datas[m1]
datas[m1] = {}
if options.sortValue
datas[m1][m2] = j.sort(sortValue)
sorted = true
else
datas[m1][m2] = j.sort(sortVersion)
if not extremes[m1]
extremes[m1] = {}
minMax = []
if sorted
minMax = j
else
minMax = j.slice(0)
minMax.sort(sortValue)
extremes[m1][m2] =
min: minMax[0][1]
max: minMax[minMax.length-1][1]
c mode, method
# Split dashes, add spaces, then replace "gcc -4.9" with "gcc-4.9"
fixFlags = (compilerflags) ->
compilerflags.split('-').join(' -').replace(' -4.9', '-4.9')
versionOptions = (vercompiler) ->
s = vercompiler.split '_'
if s.length > 1
# We have a Redis Version and Compiler Flags
[version, compilerflags] = s
"#{version} (#{fixFlags compilerflags})"
else
# We only have Compiler Flags
fixFlags vercompiler
maxGraphs = 64
createGraphDivs = ->
s = $("style")
for num in [maxGraphs..0]
s.after("<div id='graph_#{num}' class='graph'/>")
originals = {}
createdAxes = {}
createXAxis = (data) ->
xaxis = []
if createdAxes[data]
return createdAxes[data]
for [version, score], idx in data
# Replace version information with index
if not originals[idx]
originals[idx] = []
originals[idx].push(data[idx][0]) # store original unbroken versioncompilerflags
data[idx][0] = idx
# Add x-axis idx->version lookup
xaxis.push([idx, versionOptions version])
createdAxes[data] = xaxis
origSettings = []
origZoom = null
drawGraph = (container, idx, method, mode, data, secondary, label, opts) ->
if not secondary
# Save original settings so we can toggle back from compiler view
# "not secondary" because we don't want to cache the compiler view args
origSettings[idx] = [container, idx, method, mode, data, secondary, label, opts]
origZoom = options.scaleFactor
Flotr.draw(container, [
mouse:
track: true
relative: true
trackFormatter: hoverShow
data: data
label: label
lines:
show:false
], opts)
Flotr.EventAdapter.observe container, "flotr:click", (e) ->
# e.hit is only populated if we click on a data point. Other graph
# clicks also trigger here, but we only want info clicks.
if not e.hit then return
# :sadface: hacky toggle() function
if secondary
# if secondary, switch back to primary graph using original settings
options.scaleFactor = origZoom
drawGraph.apply(this, origSettings[idx])
else
dataPoint = e.hit.series.xaxis.ticks[e.hit.index].label
ver = dataPoint.split(' ')
compilerStr = toCompilerStr ver[0], mode, method
totalMethod = "#{ver[0]} #{method}"
localDraw = ->
data = datas[compilerStr]
options.scaleFactor = null
draw data, container, idx, mode, totalMethod, true
if datas[compilerStr]
localDraw()
else
originalTerm = originals[idx][e.hit.index]
# console.log("original: ", dataPoint, originalTerm, method, mode)
x = toCompiler ver[0], mode, method, totalMethod
$.when(x).done -> localDraw()
hoverShow = (obj) ->
tick = obj.nearest.xaxis.ticks[parseInt(obj.x)]
if tick then tick.label + " = " + obj.y
draw = (data, selector, idx, mode, method, secondary) ->
xaxis = createXAxis data
console.log mode, method, extremes[mode][method]
ymin = ymax = null
autoscale = true
if not (options.scaleFactor is null)
min = extremes[mode][method].min
max = extremes[mode][method].max
ymin = min / options.scaleFactor
ymax = max * ((options.scaleFactor - 1)/2 + 1)
autoscale = false
drawGraph $(selector)[0], idx, method, mode, data, secondary, null,
title: method
subtitle: mode
fontSize: 15
HtmlText: false # required to use labelsAngle
bars:
show: true
horizontal: false
shadowSize: 3
barWidth: 0.7
fill: true
align: "center"
xaxis:
ticks: xaxis
noticks: xaxis.length
labelsAngle: -45
autoscale: true
title: "Redis Version with Compiler and Options"
yaxis:
autoscale: autoscale
# autoscaleMargin: 0
min: ymin
max: ymax
title: "Operations per second"
y2axis:
autoscale: true
titleAngle: 270
autoscaleMargin: 0
grid:
verticalLines: false
minorVerticalLines: false
options =
scaleFactor: 1.01
sortValue: false
drawEverything = () ->
i = 0
$(".graph").css('display', 'block')
for mode in modes
for method in methods
console.log("Using", mode, method)
draw datas[mode][method], "#graph_#{i}", i, mode, method
i++
# datas = cache of all retrieved data
datas = {}
extremes = {}
initGraphs = (hide) ->
waitForThese = fetchData()
# Merge all the wait lists together so we can single-done deferred wait
waitFor = $.map waitForThese, (e) -> e
$.when.apply($, waitFor).done ->
drawEverything()
# We hide the generated graphs at first so people
# don't have a tiny scroll bar before they reach the graphs.
$(".graph").css('display', 'none') if hide
$ ->
createGraphDivs()
initGraphs true
$('#useful').click (e) ->
options.scaleFactor = 1.09
drawEverything()
$('#maxchange').click (e) ->
options.scaleFactor = null
drawEverything()
$('#sortall').click (e) ->
options.sortValue = not options.sortValue
options.scaleFactor = null
if options.sortValue
$(this).text 'Sort by Version Number'
else
$(this).text 'Sort from Low to High'
# Yes, it's inefficient to re-fetch every graph JSON to re-sort the data
# but at this point the data storage is pretty baked into the API and
# it's easier to re-fetch instead of re-sort our cached data (and the
# re-un-sort it) depending on user state.
# Spoiler: The JSON URLs never expire, so the re-fetch should be
# entirely from your local cache.
initGraphs()