-
Notifications
You must be signed in to change notification settings - Fork 16
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
Adding arbitrary information to execution graph #166
Conversation
b4e83f4
to
a626d66
Compare
bc3f886
to
7290d36
Compare
This is good, as any info from the message will automatically be put onto the execution graph result. However, I have a couple of points/ questions:
Then adding info would be done through some utility functions:
Then for now, for simple MPI tracing we'd be interested in the following, however, I'm not sure if it's possible:
The execution graph currently relies on Redis, which we'd like to remove one day, but I think we can switch this to use Faabric state under the hood instead (eventually). |
I agree with most of your comments, some observations.
|
ed6b366
to
f0065ee
Compare
I think this is unnecessary complexity, it means we have to maintain another map of message IDs and values, when it seems like we will always have a reference to the target message when doing the tracing (so could just set it directly). However, perhaps I don't understand the risks/ difficulties. When would we modify the wrong message? If we're passing a reference to the message object itself into the functions that add info (and modifying it in place), then by definition it's the right one; the place where I can see this going wrong is if we're passing a copy of the original message at some point, and therefore edits don't persist on the original. However, we should only ever be passing messages around by reference and never copying, so I'm not sure this would be a problem. I don't see race conditions being a problem either, as each message is only ever handled by a single executor (or single scheduler thread) IIRC.
I'm not sure I understand the points about no-opping and efficiency. What I'm saying is that we won't do any execution graph stuff by default in either Release or Debug builds (i.e. less than we do now, where we record the graph for every request), unless someone sets the
Yes, good point, although i'm not sure how straightforward it will be to map ranks to hosts, especially if we ever do migration. I would say recording counts of messages to hosts as well as to ranks would be great if possible (each would just be a different key/ value). |
src/scheduler/MpiWorld.cpp
Outdated
@@ -33,6 +34,9 @@ static thread_local std::unordered_map< | |||
std::unique_ptr<faabric::transport::AsyncSendMessageEndpoint>> | |||
ranksSendEndpoints; | |||
|
|||
// Id of the message that created this thread-local instance | |||
static thread_local int thisMsgId; |
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.
Having thread-local state makes me nervous and I'd like to avoid it if at all possible. If we edit the message objects directly then perhaps we could avoid this.
src/util/exec_graph.cpp
Outdated
|
||
checkMessageNotLinked(); | ||
|
||
linkedMsg = std::make_shared<faabric::Message>(msg); |
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.
Does this not copy of the message?
src/util/exec_graph.cpp
Outdated
linkedMsg = std::make_shared<faabric::Message>(msg); | ||
|
||
// If message flag is not set, no-op the increment functions for minimal | ||
// overhead |
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.
This is quite a lot of black magic and potentially premature optimisation. Could we avoid this complexity by editing messages directly, then putting in an if(!msg.recordexecgraph()) { return; }
in those methods (or the same check but wrapping the call to those methods)?
src/proto/faabric.proto
Outdated
@@ -148,6 +153,10 @@ message Message { | |||
string sgxTag = 35; | |||
bytes sgxPolicy = 36; | |||
bytes sgxResult = 37; | |||
|
|||
// Exec-graph utils | |||
bool recordExecGraph = 38; |
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.
This flag needs to be added to the message JSON serialisation/ deserialisation to allow clients to pass it in. I.e. the request JSON would look something like:
{
"user": "mpi",
"func": "some_mpi_func",
...
"exec_graph": true
}
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.
As mentioned in the PR description, I think we should go for #167 . The amount of complexity reduced is vast, and definately worth the 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.
Agreed, although for now we should still add it to what we have. The change in complexity may be good, but it's a bit of code that currently requires very little maintenance, and making the change would inevitably take at least a few hours (especially as it would require testing the operations from the two language clients and all the experiments).
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.
However, I take the point around complexity of a map. Unfortunately this work isn't usable until we pass it back to the client, and using it to debug an issue with an experiment is our top priority. Is it possible to use the protobuf serialisation to serialise just this map to JSON, put that as a string into the JSON sent back to the client, then have the client deserialise it?
@@ -9,9 +9,9 @@ class MpiContext | |||
public: | |||
MpiContext(); | |||
|
|||
int createWorld(const faabric::Message& msg); | |||
int createWorld(faabric::Message& msg); |
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.
Need to de-constify this so that we can actually edit the message when tracing.
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.
Nice, this is looking good, just a couple of tweaks and it's good to go.
const std::string& key, | ||
const int valueToIncrement = 1); | ||
|
||
static inline std::string const mpiMsgCountPrefix = "mpi-msgcount-torank-"; |
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.
This constant is MPI-specific and only used by MPI stuff, therefore should live in an MPI header (and I would also use a #define
to fit with the style of the other constants we define, but an inline std::string
is probably equivalent).
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 know this is not how we define constants elsewhere, and I thought about the #define
option, but I liked the idea of having the string constant sit inside a namespace, thus why I went for the inline
option.
Orthogonally, the constant is MPI-specific but also exec-graph specific, i.e. it is only used to record this exec graph details, and is not needed in MPI headers. Thus why I placed it here, I can see us having some of this "prefix" strings together here; happy to move elsewhere though.
const auto& map = msg.execgraphdetails(); | ||
for (const auto& it : map) { | ||
out = fmt::format("{},{}:{}", out, it.first, it.second); | ||
} |
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.
Won't this result in a leading ,
?
Could instead do this with a sstream
, appending a comma and skipping the comma on the last element (a bit like here: https://github.com/faasm/faabric/blob/master/src/util/bytes.cpp#L77)
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.
👍
auto& map = *msg.mutable_execgraphdetails(); | ||
map["foo"] = "bar"; | ||
auto& intMap = *msg.mutable_intexecgraphdetails(); | ||
intMap["foo"] = 0; |
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.
Is this test checking the serialisation of the maps? I can't see a string that looks like "foo:bar,qux:blah".
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.
We don't have to actually hardcode the strings. The way we check for correctness is:
- Set message fields ( =: msgA)
- Serialise message
- De-serialise message ( =: msgB)
- Check
msgA == msgB
I was missing the de-serialise and equality check bits.
@@ -266,6 +307,54 @@ std::string getStringFromJson(Document& doc, | |||
return std::string(valuePtr, valuePtr + it->value.GetStringLength()); | |||
} | |||
|
|||
std::map<std::string, std::string> getStringStringMapFromJson( |
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 actually completely overlooked the string to json conversion, and updating the checkMessageEquality
function.
…yout and rename to ExecGraphDetail
2048391
to
80e5521
Compare
In this PR I introduce a feature to count arbitrary events in a per-message fashion. The motivation for this PR is to be able to shed light into the system's behaviour in a low-intrusion manner. Given that protobuf messages are first-class citizens in the system, it feels natural to include this tracing information in the message objects.
To start the recording, we must set the
recordexecgraph
flag in the message.At the moment, the fields populated are not serialised to and from JSON. The reason being that serialising an arbitrary map will add a lot of complexity to the already convoluted methods.
After some local testing, I think it is a better option to go for #167 first.