diff --git a/src/libexpr/eval-profiler.cc b/src/libexpr/eval-profiler.cc index bf2442c31..c72737f73 100644 --- a/src/libexpr/eval-profiler.cc +++ b/src/libexpr/eval-profiler.cc @@ -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; +using FrameInfo = + std::variant; using FrameStack = std::vector; /** @@ -121,6 +130,8 @@ class SampleStack : public EvalProfiler return Hooks().set(preFunctionCall).set(postFunctionCall); } + FrameInfo getPrimOpFrameInfo(const PrimOp & primOp, std::span 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 now); void saveProfile(); - FrameInfo getFrameInfoFromValueAndPos(const Value & v, PosIdx pos); + FrameInfo getFrameInfoFromValueAndPos(const Value & v, std::span 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 args, PosIdx pos) +{ + auto derivationInfo = [&]() -> std::optional { + /* 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 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 args, const PosIdx pos) +[[gnu::noinline]] void +SampleStack::preFunctionCallHook(EvalState & state, const Value & v, std::span 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(pos.origin)) + os << posCache.lookup(callPos) << ":"; + os << "primop derivationStrict:" << drvName; + return os; +} + void SampleStack::maybeSaveProfile(std::chrono::time_point now) { if (now - lastDump >= profileDumpInterval) diff --git a/tests/functional/flamegraph-profiler.sh b/tests/functional/flamegraph-profiler.sh index 0c35037a8..b074507e8 100755 --- a/tests/functional/flamegraph-profiler.sh +++ b/tests/functional/flamegraph-profiler.sh @@ -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 +"