Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
profile.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <sysio/vm/exceptions.hpp>
4#include <sysio/vm/utils.hpp>
5#include <sysio/vm/debug_info.hpp>
6#include <vector>
7#include <algorithm>
8#include <atomic>
9#include <thread>
10#include <condition_variable>
11#include <signal.h>
12#include <unistd.h>
13#include <fcntl.h>
14#include <sys/types.h>
15
16namespace sysio::vm {
17
18inline uint32_t profile_interval_us = 10000;
19
21 // buffer_size is the size of the I/O write buffer. The buffer must be at least large enough for one item.
22 // hash_table_size is the maximum number of unique traces that can be stored in memory. It must be a power of 2.
23 template<typename Backend>
24 profile_data(const std::string& file, Backend& bkend, const std::size_t buffer_size = 65536, std::size_t hash_table_size = 1024) :
25 addr_map(bkend.get_debug()),
26 outbuf_storage(buffer_size),
27 items_storage(hash_table_size),
28 table_storage(hash_table_size) // load factor of 1
29 {
31
32 outbuf = outbuf_storage.data();
33 outpos = 0;
34 outsize = outbuf_storage.size();
35
36 list_header* prev = &mru_list;
37 for(auto& item : items_storage) {
38 item.mru.prev = prev;
39 item.mru.next = reinterpret_cast<list_header*>(&item + 1); // Avoid undefined behavior at the end
40 prev = &item.mru;
41 }
42 items_storage.back().mru.next = &mru_list;
43 mru_list.next = &items_storage.front().mru;
44 mru_list.prev = &items_storage.back().mru;
45
46 table = table_storage.data();
48
49 fd = open(file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755);
51 }
53 flush_hash();
55 flush();
56 close(fd);
57 }
58 void write_header() {
59 uint32_t header[] = {
60 0,
61 3,
62 0,
64 0
65 };
66 write(reinterpret_cast<const char*>(header), sizeof(header));
67 }
69 uint32_t trailer[] = { 0, 1, 0 };
70 write(reinterpret_cast<const char*>(trailer), sizeof(trailer));
71 }
72 void flush() {
73 for(std::size_t i = 0; i < outpos;) {
74 auto res = ::write(fd, outbuf + i, outpos - i);
75 if(res == -1) {
76 // report_error
77 break;
78 } else {
79 i += res;
80 }
81 }
82 outpos = 0;
83 }
84
85 void write(const char* data, std::size_t size) {
86 if(size + outpos >= outsize) {
87 flush();
88 }
89 std::memcpy(outbuf + outpos, data, size);
90 outpos += size;
91 }
92
93 static constexpr std::size_t max_frames = 251;
94
95 struct list_header {
98 void unlink() {
99 next->prev = prev;
100 prev->next = next;
101 }
102 };
103
104 struct item {
106 item* next;
107 uint32_t bucket = 0xFFFFFFFF;
108 uint32_t count = 0;
111 // std::hash is not async-signal-safe
112 std::size_t hash() const {
113 // MurmurHash64A
114 // Including len gives a multiple of 2.
115 static_assert(max_frames % 2 == 1);
116 // Not strictly necessary for correctness, but avoids unaligned loads
117 static_assert(offsetof(item, len) % 8 == 0);
118 constexpr std::uint64_t mul = 0xc6a4a7935bd1e995ull;
119 constexpr std::uint64_t seed = 0xbadd00d00ull;
120 constexpr auto shift_mix = [](std::uint64_t v) { return v ^ (v >> 47); };
121 int word_len = len/2+1; // if len is even, add an extra 0 word.
122 uint64_t hash = seed ^ (word_len * 8 * mul);
123 const char* base_addr = reinterpret_cast<const char*>(&len);
124 for(int i = 0; i < word_len; ++i) {
125 std::uint64_t val;
126 memcpy(&val, base_addr + 8*i, 8);
127 hash = (hash ^ shift_mix(val * mul) * mul) * mul;
128 }
129 return shift_mix(shift_mix(hash) * mul);
130 }
131 };
132
133 static bool traces_equal(const item* lhs, const item* rhs) {
134 if(lhs->len != rhs->len) {
135 return false;
136 }
137 for(uint32_t i = 0; i < lhs->len; ++i) {
138 if(lhs->frames[i] != rhs->frames[i]) {
139 return false;
140 }
141 }
142 return true;
143 }
144
145 void write(const item* item) {
146 write(reinterpret_cast<const char*>(&item->count), (2+item->len) * sizeof(std::uint32_t));
147 }
148
150 item* result = reinterpret_cast<item*>(mru_list.prev);
151 if(result->bucket != 0xFFFFFFFFu) {
152 write(result);
153 for(item** entry = &table[result->bucket]; ; entry = &(*entry)->next) {
154 if(*entry == result) {
155 *entry = result->next;
156 break;
157 }
158 }
159 result->bucket = 0xFFFFFFFFu;
160 }
161 return result;
162 }
163
164 void move_to_head(list_header* new_head) {
165 new_head->unlink();
166 new_head->next = mru_list.next;
167 new_head->prev = &mru_list;
168 mru_list.next->prev = new_head;
169 mru_list.next = new_head;
170 }
171
172 // Inserts an item into the hash table OR combines it with an existing entry
173 // The new or modified item will be moved to the head of the MRU list.
174 void insert_hash(item* new_item) {
175 std::size_t hash = new_item->hash();
176 std::size_t idx = hash & (table_size - 1);
177 for(item* bucket_entry = table[idx]; bucket_entry; bucket_entry = bucket_entry->next) {
178 if(traces_equal(new_item, bucket_entry)) {
179 ++bucket_entry->count;
180 move_to_head(&bucket_entry->mru);
181 return;
182 }
183 }
184 new_item->next = table[idx];
185 new_item->bucket = idx;
186 new_item->count = 1;
187 table[idx] = new_item;
188 move_to_head(&new_item->mru);
189 }
190
191 void flush_hash() {
192 for(std::size_t i = 0; i < table_size; ++i) {
193 for(item* entry = table[i]; entry; entry = entry->next) {
194 write(entry);
195 entry->bucket = 0xFFFFFFFF;
196 }
197 table[i] = nullptr;
198 }
199 }
200
201 // The signal handler calling this must not use SA_NODEFER
202 void handle_tick(void** data, int count) {
203 item* entry = evict_oldest();
204
205 std::size_t out = 0;
206 // Translate addresses to wasm addresses; skip any frames that are outside wasm
207 for(int i = 0; i < count && out < max_frames; ++i) {
208 auto addr = addr_map.translate(data[i]);
209 if(addr != 0xFFFFFFFFu) {
210 if(out > 0) {
211 ++addr;
212 }
213 entry->frames[out++] = addr;
214 }
215 }
216 if(out != 0) {
217 entry->len = out;
218 if(out % 2 == 0) {
219 entry->frames[out] = 0; // so hashing can align to a 64-bit boundary
220 }
221 insert_hash(entry);
222 }
223 }
224
225 int fd;
226
227 char* outbuf;
228 std::size_t outpos;
229 std::size_t outsize;
230
231 list_header mru_list;
232 item** table;
233 std::size_t table_size; // must be a power of 2
234
235 // Backend specific backtrace
237 int (*get_backtrace_fn)(const void*, void**, int, void*);
238 const void* exec_context;
239
240 template<typename Context>
241 void init_backtrace(const Context& context) {
242 get_backtrace_fn = [](const void* ctx, void** data, int len, void* uc) {
243 return static_cast<const Context*>(ctx)->backtrace(data, len, uc);
244 };
246 }
247
248 // Unsafe to access from a signal handler
249 std::vector<char> outbuf_storage;
250 std::vector<item> items_storage;
251 std::vector<item*> table_storage;
252};
253
254inline void profile_handler(int sig, siginfo_t *info, void *);
255
257 struct sigaction sa;
258 sa.sa_sigaction = profile_handler;
259 sigemptyset(&sa.sa_mask);
260 sa.sa_flags = SA_SIGINFO | SA_RESTART;
261 sigaction(SIGPROF, &sa, nullptr);
262}
263
265 static int init_helper = (register_profile_signal_handler_impl(), 0);
267}
268
269// Sets the profile interval. Should only be called before starting any profiler.
270// The interval should be between 1 and 999999.
273}
274
275#if USE_POSIX_TIMERS
276
277inline void profile_handler(int sig, siginfo_t *info, void * uc) {
278 static_assert(std::atomic<profile_data*>::is_always_lock_free);
279 auto * ptr = std::atomic_load(static_cast<std::atomic<profile_data*>*>(info->si_value.sival_ptr));
280 if(ptr) {
281 int saved_errno = errno;
282 void* data[profile_data::max_frames*2]; // Includes both wasm and native frames
283 int count = ptr->get_backtrace_fn(ptr->exec_context, data, sizeof(data)/sizeof(data[0]), uc);
284 ptr->handle_tick(data, count);
285 errno = saved_errno;
286 }
287}
288
289struct profile_manager {
292 sigevent event;
293 event.sigev_notify = SIGEV_THREAD_ID;
294 event.sigev_signo = SIGPROF;
295 event.sigev_value.sival_ptr = &current_data;
296 event._sigev_un._tid = gettid();
297 int res = timer_create(CLOCK_MONOTONIC, &event, &timer);
298 SYS_VM_ASSERT(res == 0, profile_exception, "Failed to start timer");
299 struct itimerspec spec;
300 spec.it_interval.tv_sec = 0;
301 spec.it_interval.tv_nsec = profile_interval_us * 1000;
302 spec.it_value.tv_sec = 0;
303 spec.it_value.tv_nsec = profile_interval_us * 1000;
304 res = timer_settime(timer, 0, &spec, nullptr);
305 SYS_VM_ASSERT(res == 0, profile_exception, "Failed to start timer");
306 }
307 void start(profile_data* data) {
308 SYS_VM_ASSERT(!current_data, profile_exception, "Already profiling in the current thread");
309 current_data = data;
310 }
311 void stop() {
312 current_data = nullptr;
313 }
315 current_data = nullptr;
316 timer_delete(timer);
317 }
318 timer_t timer;
319 std::atomic<profile_data*> current_data;
320};
321
322__attribute__((visibility("default")))
323inline thread_local std::unique_ptr<profile_manager> per_thread_profile_manager;
324
325struct scoped_profile {
326 explicit scoped_profile(profile_data* data) {
327 if(data) {
328 if(!per_thread_profile_manager) {
329 per_thread_profile_manager = std::make_unique<profile_manager>();
330 }
331 per_thread_profile_manager->start(data);
332 }
333 }
334 ~scoped_profile() {
335 if(per_thread_profile_manager) {
336 per_thread_profile_manager->stop();
337 }
338 }
339};
340
341#else
342
343__attribute__((visibility("default")))
344inline thread_local std::atomic<profile_data*> per_thread_profile_data = ATOMIC_VAR_INIT(nullptr);
345
346inline void profile_handler(int sig, siginfo_t* info, void* uc) {
347 static_assert(std::atomic<profile_data*>::is_always_lock_free);
348 auto * ptr = std::atomic_load(&per_thread_profile_data);
349 if(ptr) {
350 int saved_errno = errno;
351 void* data[profile_data::max_frames*2]; // Includes both wasm and native frames
352 int count = ptr->get_backtrace_fn(ptr->exec_context, data, sizeof(data)/sizeof(data[0]), uc);
353 ptr->handle_tick(data, count);
354 errno = saved_errno;
355 }
356}
357
361 timer_thread = std::thread([this]{
362 auto lock = std::unique_lock(mutex);
363 while(!timer_cond.wait_for(lock, std::chrono::microseconds(profile_interval_us), [&]{ return done; })) {
364 for(pthread_t notify : threads_to_notify) {
365 pthread_kill(notify, SIGPROF);
366 }
367 }
368 });
369 }
370 void start(profile_data* data) {
371 auto lock = std::lock_guard(mutex);
372
373 per_thread_profile_data = data;
374 threads_to_notify.push_back(pthread_self());
375 }
376 void stop() {
377 per_thread_profile_data = nullptr;
378 auto lock = std::lock_guard(mutex);
379 threads_to_notify.erase(std::find(threads_to_notify.begin(), threads_to_notify.end(), pthread_self()));
380 }
382 {
383 auto lock = std::scoped_lock(mutex);
384 done = true;
385 }
386 timer_cond.notify_one();
387 timer_thread.join();
388 }
390 static profile_manager result;
391 return result;
392 }
393 std::mutex mutex;
394 std::vector<pthread_t> threads_to_notify;
395
396 std::thread timer_thread;
397 bool done = false;
398 std::condition_variable timer_cond;
399};
400
402 public:
403 explicit scoped_profile(profile_data* data) : data(data) {
404 if(data) {
406 }
407 }
409 if(data) {
411 }
412 }
413 private:
414 profile_data* data;
415};
416
417#endif
418
419}
auto & get_context()
Definition backend.hpp:250
std::uint32_t translate(const void *pc) const
scoped_profile(profile_data *data)
Definition profile.hpp:403
int * count
backend_t bkend(hello_wasm, ehm, &wa)
void close(T *e, websocketpp::connection_hdl hdl)
__attribute__((always_inline)) inline uint64_t rotl64(uint64_t x
Definition name.hpp:106
void profile_handler(int sig, siginfo_t *info, void *)
Definition profile.hpp:346
void register_profile_signal_handler()
Definition profile.hpp:264
void ignore_unused_variable_warning(T &...)
Definition utils.hpp:101
void register_profile_signal_handler_impl()
Definition profile.hpp:256
uint32_t profile_interval_us
Definition profile.hpp:18
void set_profile_interval_us(uint32_t value)
Definition profile.hpp:271
#define value
Definition pkcs11.h:157
sysio::client::http::http_context context
Definition main.cpp:200
unsigned int uint32_t
Definition stdint.h:126
unsigned __int64 uint64_t
Definition stdint.h:136
uint32_t frames[max_frames]
Definition profile.hpp:110
std::size_t hash() const
Definition profile.hpp:112
void init_backtrace(const Context &context)
Definition profile.hpp:241
const profile_instr_map & addr_map
Definition profile.hpp:236
profile_data(const std::string &file, Backend &bkend, const std::size_t buffer_size=65536, std::size_t hash_table_size=1024)
Definition profile.hpp:24
std::vector< item > items_storage
Definition profile.hpp:250
int(* get_backtrace_fn)(const void *, void **, int, void *)
Definition profile.hpp:237
std::vector< item * > table_storage
Definition profile.hpp:251
void handle_tick(void **data, int count)
Definition profile.hpp:202
void insert_hash(item *new_item)
Definition profile.hpp:174
const void * exec_context
Definition profile.hpp:238
void write(const char *data, std::size_t size)
Definition profile.hpp:85
void move_to_head(list_header *new_head)
Definition profile.hpp:164
static constexpr std::size_t max_frames
Definition profile.hpp:93
std::vector< char > outbuf_storage
Definition profile.hpp:249
void write(const item *item)
Definition profile.hpp:145
static bool traces_equal(const item *lhs, const item *rhs)
Definition profile.hpp:133
std::condition_variable timer_cond
Definition profile.hpp:398
std::vector< pthread_t > threads_to_notify
Definition profile.hpp:394
static profile_manager & instance()
Definition profile.hpp:389
void start(profile_data *data)
Definition profile.hpp:370
#define SYS_VM_ASSERT(expr, exc_type, msg)
Definition exceptions.hpp:8
void mul(const Operand &op)
size_t len
memcpy((char *) pInfo->slotDescription, s, l)