test: cover Excache.MemoryTest and overheadKeyBytes
This commit is contained in:
1 parent
61d2aef2ed
commit
16fc47189b
1 file changed
+194
@@ -0,0 +1,194 @@
|
|||||||
|
defmodule Excache.MemoryTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
|
||||||
|
alias Excache.Memory
|
||||||
|
alias Excache.SkipList
|
||||||
|
alias Excache.Store
|
||||||
|
alias Excache.Stream, as: CacheStream
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Store.flushdb()
|
||||||
|
on_exit(&Store.flushdb/0)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "entry overhead matches the real cost of an ETS insert" do
|
||||||
|
entries = for index <- 1..2000, do: {"probe:#{index}", :string, "value-#{index}", nil}
|
||||||
|
|
||||||
|
table = :ets.new(:memoryProbe, [:set, :public])
|
||||||
|
before = :ets.info(table, :memory)
|
||||||
|
Enum.each(entries, &:ets.insert(table, &1))
|
||||||
|
measured = (:ets.info(table, :memory) - before) * Memory.wordSize()
|
||||||
|
:ets.delete(table)
|
||||||
|
|
||||||
|
estimated = Memory.usedMemory(entries)
|
||||||
|
|
||||||
|
assert measured > 0
|
||||||
|
assert_in_delta estimated / measured, 1.0, 0.02
|
||||||
|
end
|
||||||
|
|
||||||
|
test "reference counted binaries are counted beyond the flat term size" do
|
||||||
|
value = :binary.copy("x", 4096)
|
||||||
|
flat = :erts_debug.size(value) * Memory.wordSize()
|
||||||
|
|
||||||
|
assert flat < 200
|
||||||
|
assert Memory.estimateValue(:string, value) == flat + 4096
|
||||||
|
|
||||||
|
nested = [:binary.copy("a", 1024), :binary.copy("b", 1024)]
|
||||||
|
assert Memory.estimateValue(:list, nested) >= 2048
|
||||||
|
|
||||||
|
heapBinary = :binary.copy("y", 64)
|
||||||
|
|
||||||
|
assert Memory.estimateValue(:string, heapBinary) ==
|
||||||
|
:erts_debug.size(heapBinary) * Memory.wordSize()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "estimates grow with the element count of every aggregate type" do
|
||||||
|
pairs = [
|
||||||
|
{:list, listValue(2), listValue(40)},
|
||||||
|
{:hash, hashValue(2), hashValue(40)},
|
||||||
|
{:set, setValue(2), setValue(40)},
|
||||||
|
{:zset, zsetValue(2), zsetValue(40)},
|
||||||
|
{:stream, streamValue(2), streamValue(40)}
|
||||||
|
]
|
||||||
|
|
||||||
|
for {kind, small, large} <- pairs do
|
||||||
|
assert Memory.estimateValue(kind, small) > 0, "#{kind} small estimate is not positive"
|
||||||
|
|
||||||
|
assert Memory.estimateValue(kind, large) > Memory.estimateValue(kind, small),
|
||||||
|
"#{kind} estimate did not grow with the element count"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "element counts and payload bytes are reported per type" do
|
||||||
|
assert Memory.elementCount(:string, "abc") == 1
|
||||||
|
assert Memory.payloadBytes(:string, "abc") == 3
|
||||||
|
|
||||||
|
assert Memory.elementCount(:list, ["a", "bb"]) == 2
|
||||||
|
assert Memory.payloadBytes(:list, ["a", "bb"]) == 3
|
||||||
|
|
||||||
|
hash = %{"f1" => "v1", "f2" => "vvv2"}
|
||||||
|
assert Memory.elementCount(:hash, hash) == 2
|
||||||
|
assert Memory.payloadBytes(:hash, hash) == 2 + 2 + 2 + 4
|
||||||
|
|
||||||
|
set = MapSet.new(["a", "bb", "ccc"])
|
||||||
|
assert Memory.elementCount(:set, set) == 3
|
||||||
|
assert Memory.payloadBytes(:set, set) == 6
|
||||||
|
|
||||||
|
zset = zsetValue(2)
|
||||||
|
assert Memory.elementCount(:zset, zset) == 2
|
||||||
|
assert Memory.payloadBytes(:zset, zset) == 2 * (byte_size("member1") + Memory.wordSize())
|
||||||
|
|
||||||
|
stream = streamValue(2)
|
||||||
|
assert Memory.elementCount(:stream, stream) == 2
|
||||||
|
|
||||||
|
assert Memory.payloadBytes(:stream, stream) ==
|
||||||
|
2 * (2 * Memory.wordSize() + byte_size("field") + byte_size("value1"))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "usedMemory sums every entry and usageByType splits them by kind" do
|
||||||
|
entries = [
|
||||||
|
{"s", :string, "value", nil},
|
||||||
|
{"l", :list, listValue(3), nil},
|
||||||
|
{"h", :hash, hashValue(3), nil},
|
||||||
|
{"st", :set, setValue(3), nil},
|
||||||
|
{"z", :zset, zsetValue(3), nil},
|
||||||
|
{"x", :stream, streamValue(3), nil}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert Memory.usedMemory(entries) ==
|
||||||
|
Enum.reduce(entries, 0, fn entry, total -> total + Memory.entryBytes(entry) end)
|
||||||
|
|
||||||
|
byType = Memory.usageByType(entries)
|
||||||
|
assert Map.keys(byType) |> Enum.sort() == Enum.sort(Memory.kinds())
|
||||||
|
assert Enum.all?(byType, fn {_kind, usage} -> usage.keys == 1 end)
|
||||||
|
|
||||||
|
assert byType.string.bytes == Memory.entryBytes({"s", :string, "value", nil})
|
||||||
|
|
||||||
|
assert Enum.sum(Enum.map(byType, fn {_kind, usage} -> usage.bytes end)) ==
|
||||||
|
Memory.usedMemory(entries)
|
||||||
|
|
||||||
|
withoutStreams = Enum.reject(entries, fn {_key, kind, _value, _ea} -> kind == :stream end)
|
||||||
|
assert Memory.usageByType(withoutStreams).stream == %{keys: 0, bytes: 0}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "stats separate overhead from dataset and point at the largest key" do
|
||||||
|
entries = [
|
||||||
|
{"tiny", :string, "a", nil},
|
||||||
|
{"huge", :string, :binary.copy("z", 8192), 1_700_000_000_000},
|
||||||
|
{"mid", :list, listValue(10), nil}
|
||||||
|
]
|
||||||
|
|
||||||
|
stats = Memory.stats(entries)
|
||||||
|
|
||||||
|
assert stats.keys == 3
|
||||||
|
assert stats.volatileKeys == 1
|
||||||
|
assert stats.usedMemory == Memory.usedMemory(entries)
|
||||||
|
assert stats.datasetBytes == stats.usedMemory - stats.overheadBytes
|
||||||
|
assert stats.overheadBytes == 3 * Memory.entryOverhead() + overheadKeyBytes(entries)
|
||||||
|
assert stats.bytesPerKey == div(stats.usedMemory, 3)
|
||||||
|
assert {"huge", huge} = stats.largestKey
|
||||||
|
assert huge >= 8192
|
||||||
|
assert Memory.stats([]).largestKey == nil
|
||||||
|
assert Memory.stats([]).bytesPerKey == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "describe reports the volatile flag and separates payload from overhead" do
|
||||||
|
value = :binary.copy("p", 512)
|
||||||
|
described = Memory.describe("session:1", {:string, value, 1_700_000_000_000})
|
||||||
|
|
||||||
|
assert described.kind == :string
|
||||||
|
assert described.volatile
|
||||||
|
assert described.payloadBytes == 512
|
||||||
|
assert described.bytes == described.keyBytes + described.valueBytes + Memory.entryOverhead()
|
||||||
|
assert described.overheadBytes == described.bytes - 512
|
||||||
|
assert Memory.describe("k", {:string, "v", nil}).volatile == false
|
||||||
|
end
|
||||||
|
|
||||||
|
test "usage reads a live key and reports nil for a missing one" do
|
||||||
|
:ok = Store.set("alpha", "value", [])
|
||||||
|
{:ok, 3} = Store.rpush("beta", ["a", "b", "c"])
|
||||||
|
|
||||||
|
assert Memory.usage("alpha") == Memory.estimateEntry("alpha", Store.watch_snapshot("alpha"))
|
||||||
|
assert Memory.usage("ghost") == nil
|
||||||
|
assert Memory.describeKey("ghost") == nil
|
||||||
|
assert Memory.describeKey("beta").kind == :list
|
||||||
|
assert Memory.describeKey("beta").elements == 3
|
||||||
|
|
||||||
|
assert Memory.usedMemory() == Memory.usage("alpha") + Memory.usage("beta")
|
||||||
|
assert Memory.usageByType().list.keys == 1
|
||||||
|
assert Memory.stats().keys == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
test "keyBytes grows with the key length" do
|
||||||
|
assert Memory.keyBytes(String.duplicate("k", 200)) > Memory.keyBytes("k")
|
||||||
|
assert Memory.entryOverhead() == 10 * Memory.wordSize()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp overheadKeyBytes(entries) do
|
||||||
|
Enum.reduce(entries, 0, fn {key, _kind, _value, _ea}, total ->
|
||||||
|
total + Memory.keyBytes(key)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp listValue(count), do: Enum.map(1..count, &"item#{&1}")
|
||||||
|
|
||||||
|
defp hashValue(count), do: Map.new(1..count, fn index -> {"field#{index}", "value#{index}"} end)
|
||||||
|
|
||||||
|
defp setValue(count), do: MapSet.new(1..count, &"member#{&1}")
|
||||||
|
|
||||||
|
defp zsetValue(count) do
|
||||||
|
Enum.reduce(1..count, SkipList.new(), fn index, zset ->
|
||||||
|
SkipList.insert(zset, "member#{index}", index * 1.0)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp streamValue(count) do
|
||||||
|
Enum.reduce(1..count, CacheStream.new(), fn index, stream ->
|
||||||
|
{:ok, updated, _id} =
|
||||||
|
CacheStream.add(stream, {:full, index, 0}, ["field", "value#{index}"], 0)
|
||||||
|
|
||||||
|
updated
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user