mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-10 18:05:44 +02:00
chore: allow slow and precise memory measurement of an object (#4160)
Specifically fixes "MEMORY USAGE" for lists. Signed-off-by: Roman Gershman <roman@dragonflydb.io>
This commit is contained in:
parent
7d6f745636
commit
581cfbf6c5
8 changed files with 51 additions and 18 deletions
2
helio
2
helio
|
@ -1 +1 @@
|
||||||
Subproject commit e6f69f118a1af4d677d0ef8a2da3f0b50fcc7cef
|
Subproject commit 944be564ecd44865a9a057c09b5d4bbf7f6db772
|
|
@ -46,9 +46,15 @@ constexpr XXH64_hash_t kHashSeed = 24061983;
|
||||||
constexpr size_t kAlignSize = 8u;
|
constexpr size_t kAlignSize = 8u;
|
||||||
|
|
||||||
// Approximation since does not account for listpacks.
|
// Approximation since does not account for listpacks.
|
||||||
size_t QlMAllocSize(quicklist* ql) {
|
size_t QlMAllocSize(quicklist* ql, bool slow) {
|
||||||
size_t res = ql->len * sizeof(quicklistNode) + znallocx(sizeof(quicklist));
|
size_t node_size = ql->len * sizeof(quicklistNode) + znallocx(sizeof(quicklist));
|
||||||
return res + ql->count * 16; // we account for each member 16 bytes.
|
if (slow) {
|
||||||
|
for (quicklistNode* node = ql->head; node; node = node->next) {
|
||||||
|
node_size += zmalloc_usable_size(node->entry);
|
||||||
|
}
|
||||||
|
return node_size;
|
||||||
|
}
|
||||||
|
return node_size + ql->count * 16; // we account for each member 16 bytes.
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void FreeObjSet(unsigned encoding, void* ptr, MemoryResource* mr) {
|
inline void FreeObjSet(unsigned encoding, void* ptr, MemoryResource* mr) {
|
||||||
|
@ -291,7 +297,7 @@ static_assert(sizeof(CompactObj) == 18);
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
size_t RobjWrapper::MallocUsed() const {
|
size_t RobjWrapper::MallocUsed(bool slow) const {
|
||||||
if (!inner_obj_)
|
if (!inner_obj_)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -301,8 +307,8 @@ size_t RobjWrapper::MallocUsed() const {
|
||||||
return InnerObjMallocUsed();
|
return InnerObjMallocUsed();
|
||||||
case OBJ_LIST:
|
case OBJ_LIST:
|
||||||
if (encoding_ == OBJ_ENCODING_QUICKLIST)
|
if (encoding_ == OBJ_ENCODING_QUICKLIST)
|
||||||
return QlMAllocSize((quicklist*)inner_obj_);
|
return QlMAllocSize((quicklist*)inner_obj_, slow);
|
||||||
return ((QList*)inner_obj_)->MallocUsed();
|
return ((QList*)inner_obj_)->MallocUsed(slow);
|
||||||
case OBJ_SET:
|
case OBJ_SET:
|
||||||
return MallocUsedSet(encoding_, inner_obj_);
|
return MallocUsedSet(encoding_, inner_obj_);
|
||||||
case OBJ_HASH:
|
case OBJ_HASH:
|
||||||
|
@ -1132,12 +1138,12 @@ void CompactObj::Free() {
|
||||||
memset(u_.inline_str, 0, kInlineLen);
|
memset(u_.inline_str, 0, kInlineLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t CompactObj::MallocUsed() const {
|
size_t CompactObj::MallocUsed(bool slow) const {
|
||||||
if (!HasAllocated())
|
if (!HasAllocated())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (taglen_ == ROBJ_TAG) {
|
if (taglen_ == ROBJ_TAG) {
|
||||||
return u_.r_obj.MallocUsed();
|
return u_.r_obj.MallocUsed(slow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taglen_ == JSON_TAG) {
|
if (taglen_ == JSON_TAG) {
|
||||||
|
|
|
@ -37,7 +37,7 @@ class RobjWrapper {
|
||||||
RobjWrapper() : sz_(0), type_(0), encoding_(0) {
|
RobjWrapper() : sz_(0), type_(0), encoding_(0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t MallocUsed() const;
|
size_t MallocUsed(bool slow) const;
|
||||||
|
|
||||||
uint64_t HashCode() const;
|
uint64_t HashCode() const;
|
||||||
bool Equal(const RobjWrapper& ow) const;
|
bool Equal(const RobjWrapper& ow) const;
|
||||||
|
@ -359,9 +359,9 @@ class CompactObj {
|
||||||
// Postcondition: The object is an in-memory string.
|
// Postcondition: The object is an in-memory string.
|
||||||
void Materialize(std::string_view str, bool is_raw);
|
void Materialize(std::string_view str, bool is_raw);
|
||||||
|
|
||||||
// In case this object a single blob, returns number of bytes allocated on heap
|
// Returns the approximation of memory used by the object.
|
||||||
// for that blob. Otherwise returns 0.
|
// If slow is true, may use more expensive methods to calculate the precise size.
|
||||||
size_t MallocUsed() const;
|
size_t MallocUsed(bool slow = false) const;
|
||||||
|
|
||||||
// Resets the object to empty state (string).
|
// Resets the object to empty state (string).
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
|
@ -431,10 +431,17 @@ bool QList::Replace(long index, std::string_view elem) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t QList::MallocUsed() const {
|
size_t QList::MallocUsed(bool slow) const {
|
||||||
// Approximation since does not account for listpacks.
|
// Approximation since does not account for listpacks.
|
||||||
size_t res = len_ * sizeof(quicklistNode) + znallocx(sizeof(quicklist));
|
size_t node_size = len_ * sizeof(quicklistNode) + znallocx(sizeof(quicklist));
|
||||||
return res + count_ * 16; // we account for each member 16 bytes.
|
if (slow) {
|
||||||
|
for (quicklistNode* node = head_; node; node = node->next) {
|
||||||
|
node_size += zmalloc_usable_size(node->entry);
|
||||||
|
}
|
||||||
|
return node_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node_size + count_ * 16; // we account for each member 16 bytes.
|
||||||
}
|
}
|
||||||
|
|
||||||
void QList::Iterate(IterateFunc cb, long start, long end) const {
|
void QList::Iterate(IterateFunc cb, long start, long end) const {
|
||||||
|
|
|
@ -103,7 +103,7 @@ class QList {
|
||||||
// Returns true if item was replaced, false if index is out of range.
|
// Returns true if item was replaced, false if index is out of range.
|
||||||
bool Replace(long index, std::string_view elem);
|
bool Replace(long index, std::string_view elem);
|
||||||
|
|
||||||
size_t MallocUsed() const;
|
size_t MallocUsed(bool slow) const;
|
||||||
|
|
||||||
void Iterate(IterateFunc cb, long start, long end) const;
|
void Iterate(IterateFunc cb, long start, long end) const;
|
||||||
|
|
||||||
|
|
|
@ -799,5 +799,9 @@ Usage: dragonfly [FLAGS]
|
||||||
unlink(pidfile_path.c_str());
|
unlink(pidfile_path.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns memory to OS.
|
||||||
|
// This is a workaround for a bug in mi_malloc that may cause a crash on exit.
|
||||||
|
mi_collect(true);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -768,6 +768,21 @@ TEST_F(DflyEngineTest, EvalBug2664) {
|
||||||
EXPECT_THAT(resp, DoubleArg(42.9));
|
EXPECT_THAT(resp, DoubleArg(42.9));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DflyEngineTest, MemoryUsage) {
|
||||||
|
for (unsigned i = 0; i < 1000; ++i) {
|
||||||
|
Run({"rpush", "l1", StrCat("val", i)});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < 1000; ++i) {
|
||||||
|
Run({"rpush", "l2", StrCat(string('a', 200), i)});
|
||||||
|
}
|
||||||
|
auto resp = Run({"memory", "usage", "l1"});
|
||||||
|
EXPECT_GT(*resp.GetInt(), 8000);
|
||||||
|
|
||||||
|
resp = Run({"memory", "usage", "l2"});
|
||||||
|
EXPECT_GT(*resp.GetInt(), 100000);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: to test transactions with a single shard since then all transactions become local.
|
// TODO: to test transactions with a single shard since then all transactions become local.
|
||||||
// To consider having a parameter in dragonfly engine controlling number of shards
|
// To consider having a parameter in dragonfly engine controlling number of shards
|
||||||
// unconditionally from number of cpus. TO TEST BLPOP under multi for single/multi argument case.
|
// unconditionally from number of cpus. TO TEST BLPOP under multi for single/multi argument case.
|
||||||
|
|
|
@ -83,7 +83,8 @@ std::string MallocStatsCb(bool backing, unsigned tid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t MemoryUsage(PrimeIterator it) {
|
size_t MemoryUsage(PrimeIterator it) {
|
||||||
return it->first.MallocUsed() + it->second.MallocUsed();
|
size_t key_size = it->first.MallocUsed();
|
||||||
|
return key_size + it->second.MallocUsed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue