-
Notifications
You must be signed in to change notification settings - Fork 5
Draft: Associate label sets with wall profiles #98
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
Conversation
Overall package sizeSelf size: 3.2 MB Dependency sizes
🤖 This report was automatically generated by heaviest-objects-in-the-universe |
88a883f
to
6cb5345
Compare
7ff3d57
to
832a591
Compare
d7f74a4
to
97a6d61
Compare
|
||
const T& front() const { return buffer[front_index_]; } | ||
|
||
template <typename T2> |
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.
❔ question: Why are push_back/push_front templated ?
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.
To make their arguments universal references, so we can use std::forward
bindings/profilers/wall.cc
Outdated
auto newProfilers = new ProfilerMap(*currProfilers); | ||
updateFn(newProfilers); | ||
if (profilers.compare_exchange_weak(currProfilers, newProfilers)) { | ||
delete currProfilers; |
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 that at this point another thread might still reference currProfilers (eg. if it was interrupted just after line 360).
If I understand correctly, you are trying to do some kind of RCU, but I am not sure if it's feasible in userspace.
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.
Made some changes, using atomic sets to nullptr
for the sighandler to temporarily take ownership of the map while it's using it
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.
Not sure about this, it seems that it does not solve the issue that when deleting currProfilers
on line 403 another thread might be copying it on 399 because it loaded its value before CAS operation succeeded on line 401.
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.
yep, you're right. I added a mutex to updateProfilers
. It still uses the same logic to coordinate with the signal handler though. With the latest change I also accounted for the fact we can now have at most one mutator thread, so I simplified the logic around compare-and-exchange, as the once constructed new map is always valid, so we just need to try to busily install it, and don't have to loop through the whole method one more time and construct the new map again (as it'll still be based on the previous map.) Basically, there's no need for delete newProfilers
anymore.
bindings/profilers/wall.hh
Outdated
SampleContext() {} | ||
|
||
SampleContext(std::shared_ptr<v8::Global<v8::Value>> l, uint64_t t) | ||
: labels(l), timestamp(t) {} |
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.
: labels(l), timestamp(t) {} | |
: labels(std::move(l)), timestamp(t) {} |
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.
Hm… might it actually be even better to declare the argument as a shared_ptr&
? Then it wouldn't be copied on the call site but would otherwise be fed correctly to the labels
copy constructor here.
56abe94
to
29e6175
Compare
8e2f906
to
911ff76
Compare
911ff76
to
547a14e
Compare
bindings/profilers/wall.cc
Outdated
static void sighandler(int sig, siginfo_t* info, void* context) { | ||
auto isolate = Isolate::GetCurrent(); | ||
WallProfiler* prof = nullptr; | ||
auto prof_map = profilers.exchange(nullptr); |
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.
Very elegant solution !
This way sighandler will always be able to "lock" the map without using a mutex, nor using a CAS loop.
All the complexity / latency is moved to updateProfilers, but this is not a problem since it is rarely called.
5a19209
to
4a97423
Compare
bindings/profilers/wall.cc
Outdated
// to it in time, and stop as soon as it sees a context that's too recent | ||
// for this sample. | ||
for (;;) { | ||
auto contextTimestamp = sampleContext.timestamp - zeroContextTime; |
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.
contextTimestamp
is an uint64_t
(because sampleContext.timestamp
and zeroContextTime
are both uint64_t
), but zeroContextTime
might be bigger than sampleContext.timestamp
(if a context from a previous period was not matched for example), leading to a contextTimestamp
holding a very large value and test (contextTimestamp < earliest)
failing whereas is should.
I find it best to avoid unsigned arithmetic types in c++, because they lead to surprising behaviours in the face of integer promotion rules (eg. -1 > 1u
being 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.
Our only source of unsigned ints was uv_hrtime()
but fortunately we could now completely ditch it in favor of v8::base::TimeTicks
so this'll no longer be an issue 🎉
1a1e721
to
630d700
Compare
7a66f02
to
2bddc62
Compare
3bf3471
to
8b06d8c
Compare
Make ring buffer operations interrupt safe.
8b06d8c
to
42a55ad
Compare
Superseded by #105 |
Adds ability to associate custom label sets to wall samples. This can then be used by dd-trace-js to associate span ids with said samples.
Implementation notes
The implementation adds JS API to set and clear labels, as well as API to query whether labels were used when capturing a sample. In practice, the
WallProfiler
JS class will gain getters and setters for propertieslabels
, a method namedunsetLabels
and a getter for read-only propertylabelsCaptured
.Internally, the implementation uses a PPROF signal handler executing before V8's signal handler used for profiling. It will associate the current label set with a timestamp, and put it in a preallocated ring buffer (so allocations are avoided in the signal handler.) This happens once every 10ms while profiling. The ring buffer is sized generously, for 2x the number of samples for a single profile. This allows capturing the next profile's labels while the previous profile is post-processed.
When the profile is post-processed once every minute, the labels are matched to samples based on the timestamp, and emitted into the serialized profile.
Timestamp matching
Timestamp translation is necessary between V8's timestamps and libuv's high-resolution timer. The timestamps don't need to be exactly matched (and most likely won't be.) V8 uses microsecond timestamps, libuv uses nanosecond timestamps, and the bases are different. After matching the bases and scaling, the labels with closest timestamp in the ring buffer within a ±5ms range of a sample are associated with the sample.