1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-24 22:11:15 +02:00

libexpr: Add EvalProfiler

This patch adds an EvalProfiler and MultiEvalProfiler that can be used
to insert hooks into the evaluation for the purposes of function tracing
(what function-trace currently does) or for flamegraph/tracy profilers.

See the following commits for how this is supposed to be integrated into
the evaluator and performance considerations.
This commit is contained in:
Sergei Zimmerman 2025-05-16 21:53:06 +00:00
parent e5e5c819dd
commit 6b4a86a9e3
No known key found for this signature in database
GPG key ID: A9B0B557CA632325
5 changed files with 164 additions and 0 deletions

View file

@ -0,0 +1,48 @@
#include "nix/expr/eval-profiler.hh"
#include "nix/expr/nixexpr.hh"
namespace nix {
void EvalProfiler::preFunctionCallHook(
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{
}
void EvalProfiler::postFunctionCallHook(
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{
}
void MultiEvalProfiler::preFunctionCallHook(
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{
for (auto & profiler : profilers) {
if (profiler->getNeededHooks().test(Hook::preFunctionCall))
profiler->preFunctionCallHook(state, v, args, pos);
}
}
void MultiEvalProfiler::postFunctionCallHook(
const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
{
for (auto & profiler : profilers) {
if (profiler->getNeededHooks().test(Hook::postFunctionCall))
profiler->postFunctionCallHook(state, v, args, pos);
}
}
EvalProfiler::Hooks MultiEvalProfiler::getNeededHooksImpl() const
{
Hooks hooks;
for (auto & p : profilers)
hooks |= p->getNeededHooks();
return hooks;
}
void MultiEvalProfiler::addProfiler(ref<EvalProfiler> profiler)
{
profilers.push_back(profiler);
invalidateNeededHooks();
}
}

View file

@ -0,0 +1,113 @@
#pragma once
/**
* @file
*
* Evaluation profiler interface definitions and builtin implementations.
*/
#include "nix/util/ref.hh"
#include <vector>
#include <span>
#include <bitset>
#include <optional>
namespace nix {
class EvalState;
class PosIdx;
struct Value;
class EvalProfiler
{
public:
enum Hook {
preFunctionCall,
postFunctionCall,
};
static constexpr std::size_t numHooks = Hook::postFunctionCall + 1;
using Hooks = std::bitset<numHooks>;
private:
std::optional<Hooks> neededHooks;
protected:
/** Invalidate the cached neededHooks. */
void invalidateNeededHooks()
{
neededHooks = std::nullopt;
}
/**
* Get which hooks need to be called.
*
* This is the actual implementation which has to be defined by subclasses.
* Public API goes through the needsHooks, which is a
* non-virtual interface (NVI) which caches the return value.
*/
virtual Hooks getNeededHooksImpl() const
{
return Hooks{};
}
public:
/**
* Hook called in the EvalState::callFunction preamble.
* Gets called only if (getNeededHooks().test(Hook::preFunctionCall)) is true.
*
* @param state Evaluator state.
* @param v Function being invoked.
* @param args Function arguments.
* @param pos Function position.
*/
virtual void
preFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos);
/**
* Hook called on EvalState::callFunction exit.
* Gets called only if (getNeededHooks().test(Hook::postFunctionCall)) is true.
*
* @param state Evaluator state.
* @param v Function being invoked.
* @param args Function arguments.
* @param pos Function position.
*/
virtual void
postFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos);
virtual ~EvalProfiler() = default;
/**
* Get which hooks need to be invoked for this EvalProfiler instance.
*/
Hooks getNeededHooks()
{
if (neededHooks.has_value())
return *neededHooks;
return *(neededHooks = getNeededHooksImpl());
}
};
/**
* Profiler that invokes multiple profilers at once.
*/
class MultiEvalProfiler : public EvalProfiler
{
std::vector<ref<EvalProfiler>> profilers;
[[gnu::noinline]] Hooks getNeededHooksImpl() const override;
public:
MultiEvalProfiler() = default;
/** Register a profiler instance. */
void addProfiler(ref<EvalProfiler> profiler);
[[gnu::noinline]] void
preFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override;
[[gnu::noinline]] void
postFunctionCallHook(const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override;
};
}

View file

@ -2,6 +2,7 @@
///@file
#include "nix/expr/eval.hh"
#include "nix/expr/eval-profiler.hh"
#include <chrono>

View file

@ -14,6 +14,7 @@ headers = [config_pub_h] + files(
'eval-error.hh',
'eval-gc.hh',
'eval-inline.hh',
'eval-profiler.hh',
'eval-settings.hh',
'eval.hh',
'function-trace.hh',

View file

@ -140,6 +140,7 @@ sources = files(
'eval-cache.cc',
'eval-error.cc',
'eval-gc.cc',
'eval-profiler.cc',
'eval-settings.cc',
'eval.cc',
'function-trace.cc',