mirror of
https://github.com/NixOS/nix
synced 2025-06-24 13:51:16 +02:00
libexpr: Include derivation names in the call stack profile
This makes the profiler much more useful by actually distiguishing different derivations being evaluated. This does make the implementation a bit more convoluted, but I think it's worth it.
This commit is contained in:
parent
a76c76a9d5
commit
9e97ecabb6
2 changed files with 68 additions and 10 deletions
|
@ -95,6 +95,14 @@ struct FunctorFrameInfo
|
|||
auto operator<=>(const FunctorFrameInfo & rhs) const = default;
|
||||
};
|
||||
|
||||
struct DerivationStrictFrameInfo
|
||||
{
|
||||
PosIdx callPos = noPos;
|
||||
std::string drvName;
|
||||
std::ostream & symbolize(const EvalState & state, std::ostream & os, PosCache & posCache) const;
|
||||
auto operator<=>(const DerivationStrictFrameInfo & rhs) const = default;
|
||||
};
|
||||
|
||||
/** Fallback frame info. */
|
||||
struct GenericFrameInfo
|
||||
{
|
||||
|
@ -103,7 +111,8 @@ struct GenericFrameInfo
|
|||
auto operator<=>(const GenericFrameInfo & rhs) const = default;
|
||||
};
|
||||
|
||||
using FrameInfo = std::variant<LambdaFrameInfo, PrimOpFrameInfo, FunctorFrameInfo, GenericFrameInfo>;
|
||||
using FrameInfo =
|
||||
std::variant<LambdaFrameInfo, PrimOpFrameInfo, FunctorFrameInfo, DerivationStrictFrameInfo, GenericFrameInfo>;
|
||||
using FrameStack = std::vector<FrameInfo>;
|
||||
|
||||
/**
|
||||
|
@ -121,6 +130,8 @@ class SampleStack : public EvalProfiler
|
|||
return Hooks().set(preFunctionCall).set(postFunctionCall);
|
||||
}
|
||||
|
||||
FrameInfo getPrimOpFrameInfo(const PrimOp & primOp, std::span<Value *> args, PosIdx pos);
|
||||
|
||||
public:
|
||||
SampleStack(EvalState & state, std::filesystem::path profileFile, std::chrono::nanoseconds period)
|
||||
: state(state)
|
||||
|
@ -142,14 +153,13 @@ public:
|
|||
|
||||
void maybeSaveProfile(std::chrono::time_point<std::chrono::high_resolution_clock> now);
|
||||
void saveProfile();
|
||||
FrameInfo getFrameInfoFromValueAndPos(const Value & v, PosIdx pos);
|
||||
FrameInfo getFrameInfoFromValueAndPos(const Value & v, std::span<Value *> args, PosIdx pos);
|
||||
|
||||
SampleStack(SampleStack &&) = default;
|
||||
SampleStack & operator=(SampleStack &&) = delete;
|
||||
SampleStack(const SampleStack &) = delete;
|
||||
SampleStack & operator=(const SampleStack &) = delete;
|
||||
~SampleStack();
|
||||
|
||||
private:
|
||||
/** Hold on to an instance of EvalState for symbolizing positions. */
|
||||
EvalState & state;
|
||||
|
@ -163,15 +173,41 @@ private:
|
|||
PosCache posCache;
|
||||
};
|
||||
|
||||
FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, PosIdx pos)
|
||||
FrameInfo SampleStack::getPrimOpFrameInfo(const PrimOp & primOp, std::span<Value *> args, PosIdx pos)
|
||||
{
|
||||
auto derivationInfo = [&]() -> std::optional<FrameInfo> {
|
||||
/* Here we rely a bit on the implementation details of libexpr/primops/derivation.nix
|
||||
and derivationStrict primop. This is not ideal, but is necessary for
|
||||
the usefulness of the profiler. This might actually affect the evaluation,
|
||||
but the cost shouldn't be that high as to make the traces entirely inaccurate. */
|
||||
if (primOp.name == "derivationStrict") {
|
||||
try {
|
||||
/* Error context strings don't actually matter, since we ignore all eval errors. */
|
||||
state.forceAttrs(*args[0], pos, "");
|
||||
auto attrs = args[0]->attrs();
|
||||
auto nameAttr = state.getAttr(state.sName, attrs, "");
|
||||
auto drvName = std::string(state.forceStringNoCtx(*nameAttr->value, pos, ""));
|
||||
return DerivationStrictFrameInfo{.callPos = pos, .drvName = std::move(drvName)};
|
||||
} catch (...) {
|
||||
/* Ignore all errors, since those will be diagnosed by the evaluator itself. */
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}();
|
||||
|
||||
return derivationInfo.value_or(PrimOpFrameInfo{.expr = &primOp, .callPos = pos});
|
||||
}
|
||||
|
||||
FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, std::span<Value *> args, PosIdx pos)
|
||||
{
|
||||
/* NOTE: No actual references to garbage collected values are not held in
|
||||
the profiler. */
|
||||
if (v.isLambda())
|
||||
return LambdaFrameInfo{.expr = v.payload.lambda.fun, .callPos = pos};
|
||||
else if (v.isPrimOp())
|
||||
return PrimOpFrameInfo{.expr = v.primOp(), .callPos = pos};
|
||||
else if (v.isPrimOpApp())
|
||||
else if (v.isPrimOp()) {
|
||||
return getPrimOpFrameInfo(*v.primOp(), args, pos);
|
||||
} else if (v.isPrimOpApp())
|
||||
/* Resolve primOp eagerly. Must not hold on to a reference to a Value. */
|
||||
return PrimOpFrameInfo{.expr = v.primOpAppPrimOp(), .callPos = pos};
|
||||
else if (state.isFunctor(v)) {
|
||||
|
@ -186,10 +222,10 @@ FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, PosIdx pos)
|
|||
return GenericFrameInfo{.pos = pos};
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void SampleStack::preFunctionCallHook(
|
||||
EvalState & state, const Value & v, [[maybe_unused]] std::span<Value *> args, const PosIdx pos)
|
||||
[[gnu::noinline]] void
|
||||
SampleStack::preFunctionCallHook(EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
|
||||
{
|
||||
stack.push_back(getFrameInfoFromValueAndPos(v, pos));
|
||||
stack.push_back(getFrameInfoFromValueAndPos(v, args, pos));
|
||||
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
@ -246,6 +282,18 @@ std::ostream & PrimOpFrameInfo::symbolize(const EvalState & state, std::ostream
|
|||
return os;
|
||||
}
|
||||
|
||||
std::ostream &
|
||||
DerivationStrictFrameInfo::symbolize(const EvalState & state, std::ostream & os, PosCache & posCache) const
|
||||
{
|
||||
/* Sometimes callsite position can have an unresolved origin, which
|
||||
leads to confusing «none»:0 locations in the profile. */
|
||||
auto pos = posCache.lookup(callPos);
|
||||
if (!std::holds_alternative<std::monostate>(pos.origin))
|
||||
os << posCache.lookup(callPos) << ":";
|
||||
os << "primop derivationStrict:" << drvName;
|
||||
return os;
|
||||
}
|
||||
|
||||
void SampleStack::maybeSaveProfile(std::chrono::time_point<std::chrono::high_resolution_clock> now)
|
||||
{
|
||||
if (now - lastDump >= profileDumpInterval)
|
||||
|
|
|
@ -89,3 +89,13 @@ expect_trace 'let f2 = (x: x); in f2 1 2' "
|
|||
expect_trace '1 2' "
|
||||
«string»:1:1 1
|
||||
"
|
||||
|
||||
# Derivation
|
||||
expect_trace 'builtins.derivationStrict { name = "somepackage"; }' "
|
||||
«string»:1:1:primop derivationStrict:somepackage 1
|
||||
"
|
||||
|
||||
# Derivation without name attr
|
||||
expect_trace 'builtins.derivationStrict { }' "
|
||||
«string»:1:1:primop derivationStrict 1
|
||||
"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue