mirror of
https://github.com/matrix-construct/construct
synced 2024-11-17 23:40:57 +01:00
ircd::ctx: Add preliminary profiling system to mitigate silent degradation.
This system alerts the developer when something blocks a context from yielding for too long or exceeds stack usage limits. More profiling can be added for further optimization.
This commit is contained in:
parent
acd5593aac
commit
2251595551
4 changed files with 212 additions and 30 deletions
|
@ -55,10 +55,6 @@ struct ctx
|
||||||
ctx(const ctx &) = delete;
|
ctx(const ctx &) = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t stack_usage_here(const ctx &) __attribute__((noinline));
|
|
||||||
bool stack_warning(const ctx &, const double &pct = 0.80);
|
|
||||||
void stack_assertion(const ctx &, const double &pct = 0.80);
|
|
||||||
|
|
||||||
struct continuation
|
struct continuation
|
||||||
{
|
{
|
||||||
ctx *self;
|
ctx *self;
|
||||||
|
@ -74,9 +70,9 @@ inline
|
||||||
continuation::continuation(ctx *const &self)
|
continuation::continuation(ctx *const &self)
|
||||||
:self{self}
|
:self{self}
|
||||||
{
|
{
|
||||||
|
mark(prof::event::CUR_YIELD);
|
||||||
assert(self != nullptr);
|
assert(self != nullptr);
|
||||||
assert(self->notes <= 1);
|
assert(self->notes <= 1);
|
||||||
stack_assertion(*self);
|
|
||||||
ircd::ctx::current = nullptr;
|
ircd::ctx::current = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +81,7 @@ continuation::~continuation()
|
||||||
noexcept
|
noexcept
|
||||||
{
|
{
|
||||||
ircd::ctx::current = self;
|
ircd::ctx::current = self;
|
||||||
|
mark(prof::event::CUR_CONTINUE);
|
||||||
self->notes = 1;
|
self->notes = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,20 +98,6 @@ const
|
||||||
return *self->yc;
|
return *self->yc;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
|
||||||
stack_assertion(const ctx &ctx,
|
|
||||||
const double &pct)
|
|
||||||
{
|
|
||||||
assert(!stack_warning(ctx, pct));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool
|
|
||||||
stack_warning(const ctx &ctx,
|
|
||||||
const double &pct)
|
|
||||||
{
|
|
||||||
return stack_usage_here(ctx) > double(ctx.stack_max) * pct;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
ctx::note()
|
ctx::note()
|
||||||
{
|
{
|
||||||
|
@ -151,7 +134,10 @@ ctx::wait()
|
||||||
// Interruption shouldn't be used for normal operation,
|
// Interruption shouldn't be used for normal operation,
|
||||||
// so please eat this branch misprediction.
|
// so please eat this branch misprediction.
|
||||||
if(unlikely(flags & INTERRUPTED))
|
if(unlikely(flags & INTERRUPTED))
|
||||||
|
{
|
||||||
|
mark(prof::event::CUR_INTERRUPT);
|
||||||
throw interrupted("ctx(%p)::wait()", (const void *)this);
|
throw interrupted("ctx(%p)::wait()", (const void *)this);
|
||||||
|
}
|
||||||
|
|
||||||
// notes = 1; set by continuation dtor on wakeup
|
// notes = 1; set by continuation dtor on wakeup
|
||||||
return true;
|
return true;
|
||||||
|
|
70
include/ircd/ctx_prof.h
Normal file
70
include/ircd/ctx_prof.h
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* charybdis: oh just a little chat server
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Charybdis Development Team
|
||||||
|
* Copyright (C) 2016 Jason Volk <jason@zemos.net>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice is present in all copies.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
||||||
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#define HAVE_IRCD_CTX_PROF_H
|
||||||
|
|
||||||
|
/* Profiling for the context system. These facilities provide tools and statistics.
|
||||||
|
* The primary purpose here is to alert developers of unwanted context behavior, in
|
||||||
|
* addition to optimizing the overall performance of the context system.
|
||||||
|
*
|
||||||
|
* The original use case is for the embedded database backend. Function calls are made which may
|
||||||
|
* conduct blocking I/O before returning. This will hang the current userspace context while it
|
||||||
|
* is running and thus BLOCK EVERY CONTEXT in the entire IRCd. Since this is still an asynchronous
|
||||||
|
* system, it just doesn't have callbacks: we do not do I/O without a cooperative yield.
|
||||||
|
* Fortunately there are mechanisms to mitigate this -- but we have to know for sure. A database
|
||||||
|
* call which has been passed over for mitigation may start doing some blocking flush under load,
|
||||||
|
* etc. The profiler will alert us of this so it doesn't silently degrade performance.
|
||||||
|
*/
|
||||||
|
namespace ircd {
|
||||||
|
namespace ctx {
|
||||||
|
namespace prof {
|
||||||
|
|
||||||
|
struct settings
|
||||||
|
{
|
||||||
|
double stack_usage_warning; // percentage
|
||||||
|
double stack_usage_assertion; // percentage
|
||||||
|
|
||||||
|
microseconds slice_warning; // Warn when the yield-to-yield time exceeds
|
||||||
|
microseconds slice_interrupt; // Interrupt exception when exceeded (not a signal)
|
||||||
|
microseconds slice_assertion; // abort() when exceeded (not a signal, must yield)
|
||||||
|
}
|
||||||
|
extern settings;
|
||||||
|
|
||||||
|
enum class event
|
||||||
|
{
|
||||||
|
SPAWN, // Context spawn requested
|
||||||
|
JOIN, // Context join requested
|
||||||
|
JOINED, // Context join completed
|
||||||
|
CUR_ENTER, // Current context entered
|
||||||
|
CUR_LEAVE, // Current context leaving
|
||||||
|
CUR_YIELD, // Current context yielding
|
||||||
|
CUR_CONTINUE, // Current context continuing
|
||||||
|
CUR_INTERRUPT, // Current context detects interruption
|
||||||
|
};
|
||||||
|
|
||||||
|
void mark(const event &);
|
||||||
|
|
||||||
|
} // namespace prof
|
||||||
|
} // namespace ctx
|
||||||
|
} // namespace ircd
|
|
@ -47,6 +47,7 @@ namespace ircd
|
||||||
using std::chrono::seconds;
|
using std::chrono::seconds;
|
||||||
using std::chrono::milliseconds;
|
using std::chrono::milliseconds;
|
||||||
using std::chrono::microseconds;
|
using std::chrono::microseconds;
|
||||||
|
using std::chrono::duration_cast;
|
||||||
using ostream = std::ostream;
|
using ostream = std::ostream;
|
||||||
namespace ph = std::placeholders;
|
namespace ph = std::placeholders;
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
@ -89,6 +90,7 @@ namespace ircd
|
||||||
#include "mode_lease.h"
|
#include "mode_lease.h"
|
||||||
#include "snomask.h"
|
#include "snomask.h"
|
||||||
#include "ctx.h"
|
#include "ctx.h"
|
||||||
|
#include "ctx_prof.h"
|
||||||
#include "ctx_dock.h"
|
#include "ctx_dock.h"
|
||||||
#include "ctx_mutex.h"
|
#include "ctx_mutex.h"
|
||||||
#include "ctx_shared_state.h"
|
#include "ctx_shared_state.h"
|
||||||
|
|
146
ircd/ctx.cc
146
ircd/ctx.cc
|
@ -92,6 +92,10 @@ ircd::ctx::context::context(const size_t &stack_sz,
|
||||||
ircd::ctx::current = current;
|
ircd::ctx::current = current;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The profiler is told about the spawn request here, not inside the closure
|
||||||
|
// which is probably the same event-slice as event::CUR_ENTER and not as useful.
|
||||||
|
mark(prof::event::SPAWN);
|
||||||
|
|
||||||
if(flags & DEFER_POST)
|
if(flags & DEFER_POST)
|
||||||
ios->post(std::move(spawn));
|
ios->post(std::move(spawn));
|
||||||
else if(flags & DEFER_DISPATCH)
|
else if(flags & DEFER_DISPATCH)
|
||||||
|
@ -134,10 +138,11 @@ ircd::ctx::context::join()
|
||||||
if(joined())
|
if(joined())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Set the target context to notify this context when it finishes
|
mark(prof::event::JOIN);
|
||||||
assert(!c->adjoindre);
|
assert(!c->adjoindre);
|
||||||
c->adjoindre = &cur();
|
c->adjoindre = &cur(); // Set the target context to notify this context when it finishes
|
||||||
wait();
|
wait();
|
||||||
|
mark(prof::event::JOINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
ircd::ctx::ctx *
|
ircd::ctx::ctx *
|
||||||
|
@ -210,15 +215,17 @@ noexcept
|
||||||
notes = 1;
|
notes = 1;
|
||||||
stack_base = uintptr_t(__builtin_frame_address(0));
|
stack_base = uintptr_t(__builtin_frame_address(0));
|
||||||
ircd::ctx::current = this;
|
ircd::ctx::current = this;
|
||||||
|
mark(prof::event::CUR_ENTER);
|
||||||
|
|
||||||
const scope atexit([this]
|
const scope atexit([this]
|
||||||
{
|
{
|
||||||
ircd::ctx::current = nullptr;
|
|
||||||
this->yc = nullptr;
|
|
||||||
|
|
||||||
if(adjoindre)
|
if(adjoindre)
|
||||||
notify(*adjoindre);
|
notify(*adjoindre);
|
||||||
|
|
||||||
|
mark(prof::event::CUR_LEAVE);
|
||||||
|
ircd::ctx::current = nullptr;
|
||||||
|
this->yc = nullptr;
|
||||||
|
|
||||||
if(flags & SELF_DESTRUCT)
|
if(flags & SELF_DESTRUCT)
|
||||||
delete this;
|
delete this;
|
||||||
});
|
});
|
||||||
|
@ -227,12 +234,6 @@ noexcept
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
|
||||||
ctx::stack_usage_here(const ctx &ctx)
|
|
||||||
{
|
|
||||||
return ctx.stack_base - uintptr_t(__builtin_frame_address(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// ctx_pool.h
|
// ctx_pool.h
|
||||||
|
@ -326,3 +327,126 @@ catch(const std::exception &e)
|
||||||
&cur(),
|
&cur(),
|
||||||
e.what());
|
e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// ctx_prof.h
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace ircd {
|
||||||
|
namespace ctx {
|
||||||
|
namespace prof {
|
||||||
|
|
||||||
|
struct settings settings
|
||||||
|
{
|
||||||
|
0.66, // stack_usage_warning
|
||||||
|
0.87, // stack_usage_assertion
|
||||||
|
|
||||||
|
5000us, // slice_warning
|
||||||
|
0us, // slice_interrupt
|
||||||
|
0us, // slice_assertion
|
||||||
|
};
|
||||||
|
|
||||||
|
time_point cur_slice_start; // Time slice state
|
||||||
|
|
||||||
|
size_t stack_usage_here(const ctx &) __attribute__((noinline));
|
||||||
|
void check_stack();
|
||||||
|
void check_slice();
|
||||||
|
void slice_start();
|
||||||
|
|
||||||
|
void handle_cur_continue();
|
||||||
|
void handle_cur_yield();
|
||||||
|
void handle_cur_leave();
|
||||||
|
void handle_cur_enter();
|
||||||
|
|
||||||
|
} // namespace prof
|
||||||
|
} // namespace ctx
|
||||||
|
} // namespace ircd
|
||||||
|
|
||||||
|
void
|
||||||
|
ctx::prof::mark(const event &e)
|
||||||
|
{
|
||||||
|
switch(e)
|
||||||
|
{
|
||||||
|
case event::CUR_ENTER: handle_cur_enter(); break;
|
||||||
|
case event::CUR_LEAVE: handle_cur_leave(); break;
|
||||||
|
case event::CUR_YIELD: handle_cur_yield(); break;
|
||||||
|
case event::CUR_CONTINUE: handle_cur_continue(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ctx::prof::handle_cur_enter()
|
||||||
|
{
|
||||||
|
slice_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ctx::prof::handle_cur_leave()
|
||||||
|
{
|
||||||
|
check_slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ctx::prof::handle_cur_yield()
|
||||||
|
{
|
||||||
|
check_stack();
|
||||||
|
check_slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ctx::prof::handle_cur_continue()
|
||||||
|
{
|
||||||
|
slice_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ctx::prof::slice_start()
|
||||||
|
{
|
||||||
|
cur_slice_start = steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ctx::prof::check_slice()
|
||||||
|
{
|
||||||
|
auto &c(cur());
|
||||||
|
const auto time_usage(steady_clock::now() - cur_slice_start);
|
||||||
|
if(unlikely(settings.slice_warning > 0us && time_usage >= settings.slice_warning))
|
||||||
|
{
|
||||||
|
log::warning("CONTEXT TIMESLICE EXCEEDED ctx(%p) last: %06ld microseconds",
|
||||||
|
(const void *)&c,
|
||||||
|
duration_cast<microseconds>(time_usage).count());
|
||||||
|
|
||||||
|
assert(settings.slice_assertion == 0us || time_usage < settings.slice_assertion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(unlikely(settings.slice_interrupt > 0us && time_usage >= settings.slice_interrupt))
|
||||||
|
throw interrupted("ctx(%p): Time slice exceeded (last: %06ld microseconds)",
|
||||||
|
(const void *)&c,
|
||||||
|
duration_cast<microseconds>(time_usage).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ctx::prof::check_stack()
|
||||||
|
{
|
||||||
|
auto &c(cur());
|
||||||
|
const double &stack_max(c.stack_max);
|
||||||
|
const auto stack_usage(stack_usage_here(c));
|
||||||
|
|
||||||
|
if(unlikely(stack_usage > stack_max * settings.stack_usage_warning))
|
||||||
|
{
|
||||||
|
log::warning("CONTEXT STACK USAGE ctx(%p) used %zu of %zu bytes",
|
||||||
|
(const void *)&c,
|
||||||
|
stack_usage,
|
||||||
|
c.stack_max);
|
||||||
|
|
||||||
|
assert(stack_usage < c.stack_max * settings.stack_usage_assertion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ctx::prof::stack_usage_here(const ctx &ctx)
|
||||||
|
{
|
||||||
|
return ctx.stack_base - uintptr_t(__builtin_frame_address(0));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue