Skip to content

Commit a4ee2d5

Browse files
authored
feat: json debug memory command added (#5884)
Fixed: #5480
1 parent 1fe20da commit a4ee2d5

File tree

2 files changed

+125
-16
lines changed

2 files changed

+125
-16
lines changed

src/server/json_family.cc

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,6 +1484,22 @@ auto OpFields(const OpArgs& op_args, string_view key, const WrappedJsonPath& jso
14841484
return JsonReadOnlyOperation<std::optional<std::size_t>>(op_args, key, json_path, std::move(cb));
14851485
}
14861486

1487+
// Returns numeric vector that represents the memory size in bytes of JSON value at each path.
1488+
auto OpMemory(const OpArgs& op_args, string_view key, const WrappedJsonPath& json_path) {
1489+
auto cb = [](const string_view&, const JsonType& val) -> std::optional<std::size_t> {
1490+
auto mem_cb = [](const void* ptr) -> std::size_t {
1491+
if (!ptr) {
1492+
return 0;
1493+
}
1494+
return mi_usable_size(const_cast<void*>(ptr));
1495+
};
1496+
return val.compute_memory_size(mem_cb);
1497+
};
1498+
return JsonReadOnlyOperation<std::optional<std::size_t>>(
1499+
op_args, key, json_path, std::move(cb),
1500+
ReadOnlyOperationOptions{false, CallbackResultOptions::DefaultReadOnlyOptions()});
1501+
}
1502+
14871503
// Returns json vector that represents the result of the json query.
14881504
auto OpResp(const OpArgs& op_args, string_view key, const WrappedJsonPath& json_path) {
14891505
auto cb = [](const string_view&, const JsonType& val) { return val; };
@@ -1702,35 +1718,54 @@ void JsonFamily::Debug(CmdArgList args, const CommandContext& cmd_cntx) {
17021718
string_view command = parser.Next();
17031719

17041720
auto* builder = static_cast<RedisReplyBuilder*>(cmd_cntx.rb);
1705-
// The 'MEMORY' sub-command is not supported yet, calling to operation function should be added
1706-
// here.
1721+
17071722
if (absl::EqualsIgnoreCase(command, "help")) {
1708-
builder->StartArray(2);
1723+
builder->StartArray(3);
1724+
builder->SendBulkString(
1725+
"JSON.DEBUG MEMORY <key> [path] - report memory size (bytes) of the JSON element. "
1726+
"Path defaults to root if not provided.");
17091727
builder->SendBulkString(
1710-
"JSON.DEBUG FIELDS <key> <path> - report number of fields in the JSON element.");
1728+
"JSON.DEBUG FIELDS <key> [path] - report number of fields in the JSON element. "
1729+
"Path defaults to root if not provided.");
17111730
builder->SendBulkString("JSON.DEBUG HELP - print help message.");
17121731
return;
17131732
}
17141733

1715-
if (!absl::EqualsIgnoreCase(command, "fields")) {
1716-
builder->SendError(facade::UnknownSubCmd(command, "JSON.DEBUG"), facade::kSyntaxErrType);
1734+
if (absl::EqualsIgnoreCase(command, "memory")) {
1735+
// JSON.DEBUG MEMORY
1736+
string_view key = parser.Next();
1737+
string_view path = parser.NextOrDefault();
1738+
1739+
WrappedJsonPath json_path = GET_OR_SEND_UNEXPECTED(ParseJsonPath(path));
1740+
1741+
auto cb = [&](Transaction* t, EngineShard* shard) {
1742+
return OpMemory(t->GetOpArgs(shard), key, json_path);
1743+
};
1744+
1745+
auto result = cmd_cntx.tx->ScheduleSingleHopT(std::move(cb));
1746+
auto* rb = static_cast<RedisReplyBuilder*>(builder);
1747+
reply_generic::Send(result, rb);
17171748
return;
17181749
}
17191750

1720-
// JSON.DEBUG FIELDS
1751+
if (absl::EqualsIgnoreCase(command, "fields")) {
1752+
// JSON.DEBUG FIELDS
1753+
string_view key = parser.Next();
1754+
string_view path = parser.NextOrDefault();
17211755

1722-
string_view key = parser.Next();
1723-
string_view path = parser.NextOrDefault();
1756+
WrappedJsonPath json_path = GET_OR_SEND_UNEXPECTED(ParseJsonPath(path));
17241757

1725-
WrappedJsonPath json_path = GET_OR_SEND_UNEXPECTED(ParseJsonPath(path));
1758+
auto cb = [&](Transaction* t, EngineShard* shard) {
1759+
return OpFields(t->GetOpArgs(shard), key, json_path);
1760+
};
17261761

1727-
auto cb = [&](Transaction* t, EngineShard* shard) {
1728-
return OpFields(t->GetOpArgs(shard), key, json_path);
1729-
};
1762+
auto result = cmd_cntx.tx->ScheduleSingleHopT(std::move(cb));
1763+
auto* rb = static_cast<RedisReplyBuilder*>(builder);
1764+
reply_generic::Send(result, rb);
1765+
return;
1766+
}
17301767

1731-
auto result = cmd_cntx.tx->ScheduleSingleHopT(std::move(cb));
1732-
auto* rb = static_cast<RedisReplyBuilder*>(builder);
1733-
reply_generic::Send(result, rb);
1768+
builder->SendError(facade::UnknownSubCmd(command, "JSON.DEBUG"), facade::kSyntaxErrType);
17341769
}
17351770

17361771
void JsonFamily::MGet(CmdArgList args, const CommandContext& cmd_cntx) {

src/server/json_family_test.cc

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2716,6 +2716,80 @@ TEST_F(JsonFamilyTest, DebugFieldsLegacy) {
27162716
EXPECT_THAT(resp, IntArg(1));
27172717
}
27182718

2719+
TEST_F(JsonFamilyTest, DebugMemory) {
2720+
auto resp = Run({"JSON.SET", "json1", "$",
2721+
R"([1, 2.3, "foo", true, null, {}, [], {"a":1, "b":2}, [1,2,3]])"});
2722+
EXPECT_EQ(resp, "OK");
2723+
2724+
resp = Run({"JSON.DEBUG", "memory", "json1", "$[*]"});
2725+
EXPECT_EQ(resp.type, RespExpr::ARRAY);
2726+
EXPECT_EQ(resp.GetVec().size(), 9);
2727+
EXPECT_EQ(resp.GetVec()[0].GetInt(), 0);
2728+
EXPECT_EQ(resp.GetVec()[1].GetInt(), 0);
2729+
EXPECT_EQ(resp.GetVec()[2].GetInt(), 0);
2730+
EXPECT_EQ(resp.GetVec()[3].GetInt(), 0);
2731+
EXPECT_EQ(resp.GetVec()[4].GetInt(), 0);
2732+
EXPECT_GE(resp.GetVec()[5].GetInt(), 0);
2733+
EXPECT_GE(resp.GetVec()[6].GetInt(), 0);
2734+
EXPECT_GT(resp.GetVec()[7].GetInt(), 0);
2735+
EXPECT_GT(resp.GetVec()[8].GetInt(), 0);
2736+
2737+
resp = Run({"JSON.DEBUG", "memory", "json1", "$"});
2738+
EXPECT_GT(resp.GetInt(), 0);
2739+
2740+
resp = Run({"JSON.SET", "bigstr", "$",
2741+
R"({"text":"This is a longer string that should definitely exceed SSO buffer"})"});
2742+
EXPECT_EQ(resp, "OK");
2743+
resp = Run({"JSON.DEBUG", "memory", "bigstr", "$.text"});
2744+
EXPECT_GT(resp.GetInt(), 0);
2745+
2746+
resp = Run({"JSON.SET", "obj_doc", "$", R"({"num":42, "obj":{"k1":1,"k2":2}})"});
2747+
EXPECT_EQ(resp, "OK");
2748+
resp = Run({"JSON.DEBUG", "MEMORY", "obj_doc", "$.num"});
2749+
EXPECT_EQ(resp.GetInt(), 0);
2750+
resp = Run({"JSON.DEBUG", "memory", "obj_doc", "$.obj"});
2751+
EXPECT_GT(resp.GetInt(), 0);
2752+
}
2753+
2754+
TEST_F(JsonFamilyTest, DebugMemoryLegacy) {
2755+
auto resp = Run({"JSON.SET", "json1", "$",
2756+
R"([1, 2.3, "foo", true, null, {}, [], {"a":1, "b":2}, [1,2,3]])"});
2757+
EXPECT_EQ(resp, "OK");
2758+
2759+
resp = Run({"JSON.DEBUG", "memory", "json1", "."});
2760+
EXPECT_EQ(resp.type, RespExpr::INT64);
2761+
EXPECT_GT(resp.GetInt(), 0);
2762+
2763+
resp = Run({"JSON.DEBUG", "memory", "json1"});
2764+
EXPECT_EQ(resp.type, RespExpr::INT64);
2765+
EXPECT_GT(resp.GetInt(), 0);
2766+
2767+
resp = Run({"JSON.SET", "primitives", "$", R"({"num":42, "bool":true, "null":null})"});
2768+
EXPECT_EQ(resp, "OK");
2769+
resp = Run({"JSON.DEBUG", "memory", "primitives", ".num"});
2770+
EXPECT_EQ(resp.GetInt(), 0);
2771+
resp = Run({"JSON.DEBUG", "memory", "primitives", ".bool"});
2772+
EXPECT_EQ(resp.GetInt(), 0);
2773+
resp = Run({"JSON.DEBUG", "memory", "primitives", ".null"});
2774+
EXPECT_EQ(resp.GetInt(), 0);
2775+
2776+
resp = Run({"JSON.SET", "obj_doc", "$",
2777+
R"({"longstring":"This is a very long string that definitely exceeds SSO buffer"})"});
2778+
EXPECT_EQ(resp, "OK");
2779+
resp = Run({"JSON.DEBUG", "MEMORY", "obj_doc", ".longstring"});
2780+
EXPECT_GT(resp.GetInt(), 0);
2781+
2782+
resp = Run({"JSON.SET", "arr", "$", R"([1,2,3,4,5,6,7,8,9,10])"});
2783+
EXPECT_EQ(resp, "OK");
2784+
resp = Run({"JSON.DEBUG", "memory", "arr", "."});
2785+
EXPECT_GT(resp.GetInt(), 0);
2786+
2787+
resp = Run({"JSON.SET", "obj", "$", R"({"a":1, "b":2, "c":3})"});
2788+
EXPECT_EQ(resp, "OK");
2789+
resp = Run({"JSON.DEBUG", "memory", "obj", "."});
2790+
EXPECT_GT(resp.GetInt(), 0);
2791+
}
2792+
27192793
TEST_F(JsonFamilyTest, Resp) {
27202794
auto resp = Run({"JSON.SET", "json", ".", PhonebookJson});
27212795
ASSERT_THAT(resp, "OK");

0 commit comments

Comments
 (0)