-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prototype JSON serializer for CallProfiler
#323
Changes from 3 commits
2a8b6dd
56b91e5
03d939a
e36c42e
b754b43
67df4d9
6d8d20b
8d95caa
31f0b8c
1ef1eef
caf93e3
ba561cb
4c3efc7
974ad73
3f939b8
a7180ca
5f1eb66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,6 +72,60 @@ void CallProfiler::print_profiling_result(const RadixTreeNode<CallerProfile> & n | |
} | ||
} | ||
|
||
void CallProfiler::serialize(std::ostream & outstream) const | ||
{ | ||
outstream << R"({)"; | ||
outstream << R"("radix_tree": )"; | ||
serialize_radix_tree(outstream); | ||
outstream << R"(})"; | ||
} | ||
|
||
void CallProfiler::serialize_radix_tree(std::ostream & outstream) const | ||
{ | ||
outstream << R"({)"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should not do the JSON serialization yourself. Consider using a library, such as RapidJson, to help you wrap the object into JSON format. Here is what you can do:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Does it mean that I need to install RapidJson library in .github/workflows ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you need to install RadpidJson in CMake, so you can use it in cpp files. (of course, also update the CI files) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like @ThreeMonth03 's current approach. Developing the JSON writing code provides more freedom than using a thirdparty. A thirdparty introduces additional dependency and increases the complexity of the building system. JSON is easy, and writing a parser should be fun. I do not see a good reason to use a thirdparty JSON library. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See more discussion here: https://discord.com/channels/730297880140578906/1234815972809117778 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per the discussion in Discord, I don't think we need a library for JSON, but if we need one, the current implementation is not general, we should have a JSON parser class instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Does it mean that I should survey and implement a general json parser class? |
||
outstream << R"("nodes": [)"; | ||
serialize_radix_tree_nodes(*(m_radix_tree.get_current_node()), true, outstream); | ||
outstream << R"(],)"; | ||
outstream << R"("current_node": )" << m_radix_tree.get_current_node()->key() << R"(,)"; | ||
outstream << R"("unique_id": )" << m_radix_tree.get_unique_node(); | ||
outstream << R"(})"; | ||
} | ||
|
||
// NOLINTNEXTLINE(misc-no-recursion) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid recursion from the implementation |
||
void CallProfiler::serialize_radix_tree_nodes(const RadixTreeNode<CallerProfile> & node, bool is_first_node, std::ostream & outstream) const | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's more readable to use some comment showing examples of the serialized results. |
||
{ | ||
// Avoid the trailing comma. | ||
if (!is_first_node) | ||
{ | ||
outstream << R"(,)"; | ||
} | ||
outstream << R"({)"; | ||
outstream << R"("key": )" << node.key() << R"(,)"; | ||
outstream << R"("name": ")" << node.name() << R"(",)"; | ||
outstream << R"("data": )"; | ||
node.data().serialize(outstream); | ||
outstream << R"(,)"; | ||
outstream << R"("children": [)"; | ||
bool is_first_child = true; | ||
for (const auto & child : node.children()) | ||
{ | ||
// Avoid the trailing comma. | ||
if (!is_first_child) | ||
{ | ||
outstream << R"(,)"; | ||
} | ||
is_first_child = false; | ||
outstream << child->key(); | ||
} | ||
outstream << R"(])"; | ||
outstream << R"(})"; | ||
for (const auto & child : node.children()) | ||
{ | ||
// NOLINTNEXTLINE(misc-no-recursion) | ||
serialize_radix_tree_nodes(*child, false, outstream); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to avoid the multiprogramming, I implement these three functions under the CallProfiler. |
||
|
||
} /* end namespace modmesh */ | ||
|
||
// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -191,6 +191,45 @@ struct CallerProfile | |
call_count++; | ||
} | ||
|
||
// It returns the json format. | ||
void serialize(std::ostream & outstream) const | ||
{ | ||
outstream << R"({)"; | ||
outstream << R"("start_time": )" << start_time.time_since_epoch().count() << R"(,)"; | ||
outstream << R"("caller_name": ")" << caller_name << R"(",)"; | ||
outstream << R"("total_time": )" << total_time.count() << R"(,)"; | ||
outstream << R"("call_count": )" << call_count << R"(,)"; | ||
outstream << R"("is_running": )" << is_running; | ||
outstream << R"(})"; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The serialization of the CallerProfile. |
||
|
||
// It deserialize the json format. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment block is unnecessary. |
||
void deserialize(const std::string & json) | ||
{ | ||
std::stringstream ss(json); | ||
std::string info; | ||
// start_time | ||
std::getline(ss, info, ':'); | ||
std::getline(ss, info, ','); | ||
start_time = std::chrono::high_resolution_clock::time_point(std::chrono::nanoseconds(std::stoll(info))); | ||
// caller_name | ||
std::getline(ss, info, ':'); | ||
std::getline(ss, info, ','); | ||
caller_name = info; | ||
// total_time | ||
std::getline(ss, info, ':'); | ||
std::getline(ss, info, ','); | ||
total_time = std::chrono::nanoseconds(std::stoll(info)); | ||
// call_count | ||
std::getline(ss, info, ':'); | ||
std::getline(ss, info, ','); | ||
call_count = std::stoi(info); | ||
// is_running | ||
std::getline(ss, info, ':'); | ||
std::getline(ss, info, ','); | ||
is_running = std::stoi(info); | ||
} | ||
|
||
std::chrono::high_resolution_clock::time_point start_time; | ||
std::string caller_name; | ||
std::chrono::nanoseconds total_time = std::chrono::nanoseconds(0); /// use nanoseconds to have higher precision | ||
|
@@ -261,8 +300,12 @@ class CallProfiler | |
reset(); | ||
} | ||
|
||
void serialize(std::ostream & outstream) const; | ||
|
||
private: | ||
void print_profiling_result(const RadixTreeNode<CallerProfile> & node, const int depth, std::ostream & outstream) const; | ||
void serialize_radix_tree(std::ostream & outstream) const; | ||
void serialize_radix_tree_nodes(const RadixTreeNode<CallerProfile> & node, bool is_first_node, std::ostream & outstream) const; | ||
|
||
private: | ||
RadixTree<CallerProfile> m_radix_tree; /// the data structure of the callers | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -212,5 +212,30 @@ TEST_F(CallProfilerTest, cancel) | |
EXPECT_EQ(radix_tree().get_unique_node(), 0); | ||
} | ||
|
||
TEST_F(CallProfilerTest, test_serialization) | ||
{ | ||
pProfiler->reset(); | ||
|
||
foo1(); | ||
|
||
// Example: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please use a fixture (which can be just a string in this test file) to compare with the serialization results? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this example comment block have the same content as your fixture string? If yes, this comment block should be removed. And as mentioned above, the fixture strings should use good indentation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is. |
||
// { | ||
// "radix_tree": | ||
// { | ||
// "nodes":[ | ||
// {"key":-1,"name":"","data":{"start_time": "0","caller_name": "","total_time": 0,"call_count": 0,"is_running": 0},"children":[0]}, | ||
// {"key":0,"name":"void modmesh::detail::foo1()","data":{"start_time": "17745276708555250","caller_name": "void modmesh::detail::foo1()","total_time": 61002916,"call_count": 1,"is_running": 1},"children":[1]}, | ||
// {"key":1,"name":"void modmesh::detail::foo2()","data":{"start_time": "17745276708555458","caller_name": "void modmesh::detail::foo2()","total_time": 54002250,"call_count": 1,"is_running": 1},"children":[2]}, | ||
// {"key":2,"name":"void modmesh::detail::foo3()","data":{"start_time": "17745276743555833","caller_name": "void modmesh::detail::foo3()","total_time": 19001833,"call_count": 1,"is_running": 1},"children":[]} | ||
// ], | ||
// "current_node":-1, | ||
// "unique_id":3 | ||
// } | ||
// } | ||
|
||
std::stringstream ss; | ||
pProfiler->serialize(ss); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is the simple example of the serialization of the CallProfiler. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you only ran the function, but didn't verify the output. here is what you can do:
|
||
} // namespace detail | ||
} // namespace modmesh | ||
} // namespace modmesh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to keep the serialization code outside the object to be serialized. Perhaps try to make a
CallProfilerSerializer
. What do you think?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good to me.
It can make
CallProfiler
compact.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ThreeMonth03 I think it's better to make the json parser as a separate class, so later other classes can utilize the json parser as well. Let me know if you would like to implement the generic json parser. I can help implement the parser if you don't have time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm unable to implement the generic json parser.
@tigercosmos Could you give me a hand?