Phosphor
|
Date: 2016-04-19
Author: Will Gardner
Recommended Reading:
The purpose of this spec is to specify the user API for tooling a C++ application for tracing. The user API will consist of preprocessor macros. The reasoning behind this is twofold, firstly it allows for for trivially compiling out the tracing (e.g. for production or testing performance overhead), secondly it makes it easier to start instrumenting code while the tracing implementation is under development.
This document does not detail the API for initialising tracing or triggering some kind of tracing dump.
There will be initially three event types:
Every event has common parameters of category, event_name and, optionally, arguments. Both category and event_name should be fixed cstrings (ie. won’t go out of scope) - this allows for potential optimisations (e.g. just store the pointer). The category might be just a single category in an initial implementation with a comma separated list in future.
TRACE_EVENT(category, event_name, args...)
In this example a start event event_name
is logged to category
with a variadic number of args
. Thread ID and the time are automatically collected. This will also place a variable in the current scope that will log a corresponding end event when it goes out of scope. This might be useful for logging the start and end of an expensive function e.g.
fdb_status fdb_get_kv(fdb_kvs_handle *handle, const void *key, void **value_out) { /* Assuming the disk category is enabled, log the fdb_get_kv start event with the handle and the key requested. */ TRACE_EVENT("disk", "fdb_get_kv", handle, key); /* Do something expensive like fetch from disk */ ... /* Automatically add end event for the function call */ return status; }
TRACE_EVENT_START(category, event_name, args...) TRACE_EVENT_END(category, event_name)
This example is more or less identical to the previous, except the end of the trace event is explicit. This must be called from a single thread as the start and end events will be matched up even when nested. This can be useful for tracing an event that occurs across functions or in the middle of a function, e.g. acquiring an expensive/global lock:
DcpResponse* DcpProducer::getNextItem() { TRACE_EVENT_START("dcp", "DcpProducer::getNextItem()/get-expensive-lock"); /* Acquire an expensive lock */ TRACE_EVENT_END("dcp", "DcpProducer::getNextItem()/get-expensive-lock"); /* Do stuff with the lock */ }
TRACE_EVENT_INSTANT(category, event_name, args...)
In this example a single event will be logged that is effectively equivalent to the synchronous start event, except without an expectation of a corresponding end event.
void HashTable::remove(Item* itm) { /* Inexpensive function call but worth logging */ TRACE_EVENT_INSTANT("hashtable", "HashTable::remove", itm->key); }
TRACE_ASYNC_EVENT_START(category, event_name, event_id, args...) TRACE_ASYNC_EVENT_END(category, event_name, event_id)
The primary difference with asynchronous events is that they can occur across threads and out of order which means matching them cannot be inferred, therefore they have an event_id for tracking this. The event_id would be some kind of opaque integer/pointer. Examples include EWOULDBLOCK handling:
/* On a front-end memcached thread */ ENGINE_ERROR_CODE EvpStore(ENGINE_HANDLE* handle, const void *cookie, item* itm) { TRACE_ASYNC_EVENT_START('persist', 'EvpStore/persistTo', cookie, itm->key); /* Add to disk-write queue */ } /* On a writer thread */ void PersistenceCallback::callback(mutation_result &value) { TRACE_ASYNC_EVENT_END("persist", "EvpStore/persistTo", queued_value->cookie); /* Notify memcached */ }