5
5
6
6
namespace nix {
7
7
8
- void EvalProfiler::preFunctionCallHook (
9
- const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
10
- {
11
- }
8
+ void EvalProfiler::preFunctionCallHook (EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) {}
12
9
13
- void EvalProfiler::postFunctionCallHook (
14
- const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
10
+ void EvalProfiler::postFunctionCallHook (EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
15
11
{
16
12
}
17
13
18
14
void MultiEvalProfiler::preFunctionCallHook (
19
- const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
15
+ EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
20
16
{
21
17
for (auto & profiler : profilers) {
22
18
if (profiler->getNeededHooks ().test (Hook::preFunctionCall))
@@ -25,7 +21,7 @@ void MultiEvalProfiler::preFunctionCallHook(
25
21
}
26
22
27
23
void MultiEvalProfiler::postFunctionCallHook (
28
- const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
24
+ EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
29
25
{
30
26
for (auto & profiler : profilers) {
31
27
if (profiler->getNeededHooks ().test (Hook::postFunctionCall))
@@ -99,6 +95,14 @@ struct FunctorFrameInfo
99
95
auto operator <=>(const FunctorFrameInfo & rhs) const = default ;
100
96
};
101
97
98
+ struct DerivationStrictFrameInfo
99
+ {
100
+ PosIdx callPos = noPos;
101
+ std::string drvName;
102
+ std::ostream & symbolize (const EvalState & state, std::ostream & os, PosCache & posCache) const ;
103
+ auto operator <=>(const DerivationStrictFrameInfo & rhs) const = default ;
104
+ };
105
+
102
106
/* * Fallback frame info. */
103
107
struct GenericFrameInfo
104
108
{
@@ -107,7 +111,8 @@ struct GenericFrameInfo
107
111
auto operator <=>(const GenericFrameInfo & rhs) const = default ;
108
112
};
109
113
110
- using FrameInfo = std::variant<LambdaFrameInfo, PrimOpFrameInfo, FunctorFrameInfo, GenericFrameInfo>;
114
+ using FrameInfo =
115
+ std::variant<LambdaFrameInfo, PrimOpFrameInfo, FunctorFrameInfo, DerivationStrictFrameInfo, GenericFrameInfo>;
111
116
using FrameStack = std::vector<FrameInfo>;
112
117
113
118
/* *
@@ -125,8 +130,10 @@ class SampleStack : public EvalProfiler
125
130
return Hooks ().set (preFunctionCall).set (postFunctionCall);
126
131
}
127
132
133
+ FrameInfo getPrimOpFrameInfo (const PrimOp & primOp, std::span<Value *> args, PosIdx pos);
134
+
128
135
public:
129
- SampleStack (const EvalState & state, std::filesystem::path profileFile, std::chrono::nanoseconds period)
136
+ SampleStack (EvalState & state, std::filesystem::path profileFile, std::chrono::nanoseconds period)
130
137
: state(state)
131
138
, sampleInterval(period)
132
139
, profileFd([&]() {
@@ -140,23 +147,22 @@ class SampleStack : public EvalProfiler
140
147
}
141
148
142
149
[[gnu::noinline]] void
143
- preFunctionCallHook (const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override ;
150
+ preFunctionCallHook (EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override ;
144
151
[[gnu::noinline]] void
145
- postFunctionCallHook (const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override ;
152
+ postFunctionCallHook (EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos) override ;
146
153
147
154
void maybeSaveProfile (std::chrono::time_point<std::chrono::high_resolution_clock> now);
148
155
void saveProfile ();
149
- FrameInfo getFrameInfoFromValueAndPos (const Value & v, PosIdx pos);
156
+ FrameInfo getFrameInfoFromValueAndPos (const Value & v, std::span<Value *> args, PosIdx pos);
150
157
151
158
SampleStack (SampleStack &&) = default ;
152
159
SampleStack & operator =(SampleStack &&) = delete ;
153
160
SampleStack (const SampleStack &) = delete ;
154
161
SampleStack & operator =(const SampleStack &) = delete ;
155
162
~SampleStack ();
156
-
157
163
private:
158
164
/* * Hold on to an instance of EvalState for symbolizing positions. */
159
- const EvalState & state;
165
+ EvalState & state;
160
166
std::chrono::nanoseconds sampleInterval;
161
167
AutoCloseFD profileFd;
162
168
FrameStack stack;
@@ -167,15 +173,41 @@ class SampleStack : public EvalProfiler
167
173
PosCache posCache;
168
174
};
169
175
170
- FrameInfo SampleStack::getFrameInfoFromValueAndPos (const Value & v, PosIdx pos)
176
+ FrameInfo SampleStack::getPrimOpFrameInfo (const PrimOp & primOp, std::span<Value *> args, PosIdx pos)
177
+ {
178
+ auto derivationInfo = [&]() -> std::optional<FrameInfo> {
179
+ /* Here we rely a bit on the implementation details of libexpr/primops/derivation.nix
180
+ and derivationStrict primop. This is not ideal, but is necessary for
181
+ the usefulness of the profiler. This might actually affect the evaluation,
182
+ but the cost shouldn't be that high as to make the traces entirely inaccurate. */
183
+ if (primOp.name == " derivationStrict" ) {
184
+ try {
185
+ /* Error context strings don't actually matter, since we ignore all eval errors. */
186
+ state.forceAttrs (*args[0 ], pos, " " );
187
+ auto attrs = args[0 ]->attrs ();
188
+ auto nameAttr = state.getAttr (state.sName , attrs, " " );
189
+ auto drvName = std::string (state.forceStringNoCtx (*nameAttr->value , pos, " " ));
190
+ return DerivationStrictFrameInfo{.callPos = pos, .drvName = std::move (drvName)};
191
+ } catch (...) {
192
+ /* Ignore all errors, since those will be diagnosed by the evaluator itself. */
193
+ }
194
+ }
195
+
196
+ return std::nullopt;
197
+ }();
198
+
199
+ return derivationInfo.value_or (PrimOpFrameInfo{.expr = &primOp, .callPos = pos});
200
+ }
201
+
202
+ FrameInfo SampleStack::getFrameInfoFromValueAndPos (const Value & v, std::span<Value *> args, PosIdx pos)
171
203
{
172
204
/* NOTE: No actual references to garbage collected values are not held in
173
205
the profiler. */
174
206
if (v.isLambda ())
175
207
return LambdaFrameInfo{.expr = v.payload .lambda .fun , .callPos = pos};
176
- else if (v.isPrimOp ())
177
- return PrimOpFrameInfo{. expr = v.primOp (), . callPos = pos} ;
178
- else if (v.isPrimOpApp ())
208
+ else if (v.isPrimOp ()) {
209
+ return getPrimOpFrameInfo (* v.primOp (), args, pos) ;
210
+ } else if (v.isPrimOpApp ())
179
211
/* Resolve primOp eagerly. Must not hold on to a reference to a Value. */
180
212
return PrimOpFrameInfo{.expr = v.primOpAppPrimOp (), .callPos = pos};
181
213
else if (state.isFunctor (v)) {
@@ -190,10 +222,10 @@ FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, PosIdx pos)
190
222
return GenericFrameInfo{.pos = pos};
191
223
}
192
224
193
- [[gnu::noinline]] void SampleStack::preFunctionCallHook (
194
- const EvalState & state, const Value & v, [[maybe_unused]] std::span<Value *> args, const PosIdx pos)
225
+ [[gnu::noinline]] void
226
+ SampleStack::preFunctionCallHook ( EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
195
227
{
196
- stack.push_back (getFrameInfoFromValueAndPos (v, pos));
228
+ stack.push_back (getFrameInfoFromValueAndPos (v, args, pos));
197
229
198
230
auto now = std::chrono::high_resolution_clock::now ();
199
231
@@ -208,9 +240,8 @@ FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, PosIdx pos)
208
240
}
209
241
210
242
[[gnu::noinline]] void
211
- SampleStack::postFunctionCallHook (const EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
243
+ SampleStack::postFunctionCallHook (EvalState & state, const Value & v, std::span<Value *> args, const PosIdx pos)
212
244
{
213
-
214
245
if (!stack.empty ())
215
246
stack.pop_back ();
216
247
}
@@ -251,6 +282,18 @@ std::ostream & PrimOpFrameInfo::symbolize(const EvalState & state, std::ostream
251
282
return os;
252
283
}
253
284
285
+ std::ostream &
286
+ DerivationStrictFrameInfo::symbolize (const EvalState & state, std::ostream & os, PosCache & posCache) const
287
+ {
288
+ /* Sometimes callsite position can have an unresolved origin, which
289
+ leads to confusing «none»:0 locations in the profile. */
290
+ auto pos = posCache.lookup (callPos);
291
+ if (!std::holds_alternative<std::monostate>(pos.origin ))
292
+ os << posCache.lookup (callPos) << " :" ;
293
+ os << " primop derivationStrict:" << drvName;
294
+ return os;
295
+ }
296
+
254
297
void SampleStack::maybeSaveProfile (std::chrono::time_point<std::chrono::high_resolution_clock> now)
255
298
{
256
299
if (now - lastDump >= profileDumpInterval)
@@ -300,8 +343,7 @@ SampleStack::~SampleStack()
300
343
301
344
} // namespace
302
345
303
- ref<EvalProfiler>
304
- makeSampleStackProfiler (const EvalState & state, std::filesystem::path profileFile, uint64_t frequency)
346
+ ref<EvalProfiler> makeSampleStackProfiler (EvalState & state, std::filesystem::path profileFile, uint64_t frequency)
305
347
{
306
348
/* 0 is a special value for sampling stack after each call. */
307
349
std::chrono::nanoseconds period = frequency == 0
0 commit comments