@@ -95,6 +95,14 @@ struct FunctorFrameInfo
95
95
auto operator <=>(const FunctorFrameInfo & rhs) const = default ;
96
96
};
97
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
+
98
106
/* * Fallback frame info. */
99
107
struct GenericFrameInfo
100
108
{
@@ -103,7 +111,8 @@ struct GenericFrameInfo
103
111
auto operator <=>(const GenericFrameInfo & rhs) const = default ;
104
112
};
105
113
106
- using FrameInfo = std::variant<LambdaFrameInfo, PrimOpFrameInfo, FunctorFrameInfo, GenericFrameInfo>;
114
+ using FrameInfo =
115
+ std::variant<LambdaFrameInfo, PrimOpFrameInfo, FunctorFrameInfo, DerivationStrictFrameInfo, GenericFrameInfo>;
107
116
using FrameStack = std::vector<FrameInfo>;
108
117
109
118
/* *
@@ -121,6 +130,8 @@ class SampleStack : public EvalProfiler
121
130
return Hooks ().set (preFunctionCall).set (postFunctionCall);
122
131
}
123
132
133
+ FrameInfo getPrimOpFrameInfo (const PrimOp & primOp, std::span<Value *> args, PosIdx pos);
134
+
124
135
public:
125
136
SampleStack (EvalState & state, std::filesystem::path profileFile, std::chrono::nanoseconds period)
126
137
: state(state)
@@ -142,14 +153,13 @@ class SampleStack : public EvalProfiler
142
153
143
154
void maybeSaveProfile (std::chrono::time_point<std::chrono::high_resolution_clock> now);
144
155
void saveProfile ();
145
- FrameInfo getFrameInfoFromValueAndPos (const Value & v, PosIdx pos);
156
+ FrameInfo getFrameInfoFromValueAndPos (const Value & v, std::span<Value *> args, PosIdx pos);
146
157
147
158
SampleStack (SampleStack &&) = default ;
148
159
SampleStack & operator =(SampleStack &&) = delete ;
149
160
SampleStack (const SampleStack &) = delete ;
150
161
SampleStack & operator =(const SampleStack &) = delete ;
151
162
~SampleStack ();
152
-
153
163
private:
154
164
/* * Hold on to an instance of EvalState for symbolizing positions. */
155
165
EvalState & state;
@@ -163,15 +173,41 @@ class SampleStack : public EvalProfiler
163
173
PosCache posCache;
164
174
};
165
175
166
- 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)
167
203
{
168
204
/* NOTE: No actual references to garbage collected values are not held in
169
205
the profiler. */
170
206
if (v.isLambda ())
171
207
return LambdaFrameInfo{.expr = v.payload .lambda .fun , .callPos = pos};
172
- else if (v.isPrimOp ())
173
- return PrimOpFrameInfo{. expr = v.primOp (), . callPos = pos} ;
174
- else if (v.isPrimOpApp ())
208
+ else if (v.isPrimOp ()) {
209
+ return getPrimOpFrameInfo (* v.primOp (), args, pos) ;
210
+ } else if (v.isPrimOpApp ())
175
211
/* Resolve primOp eagerly. Must not hold on to a reference to a Value. */
176
212
return PrimOpFrameInfo{.expr = v.primOpAppPrimOp (), .callPos = pos};
177
213
else if (state.isFunctor (v)) {
@@ -186,10 +222,10 @@ FrameInfo SampleStack::getFrameInfoFromValueAndPos(const Value & v, PosIdx pos)
186
222
return GenericFrameInfo{.pos = pos};
187
223
}
188
224
189
- [[gnu::noinline]] void SampleStack::preFunctionCallHook (
190
- 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)
191
227
{
192
- stack.push_back (getFrameInfoFromValueAndPos (v, pos));
228
+ stack.push_back (getFrameInfoFromValueAndPos (v, args, pos));
193
229
194
230
auto now = std::chrono::high_resolution_clock::now ();
195
231
@@ -246,6 +282,18 @@ std::ostream & PrimOpFrameInfo::symbolize(const EvalState & state, std::ostream
246
282
return os;
247
283
}
248
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
+
249
297
void SampleStack::maybeSaveProfile (std::chrono::time_point<std::chrono::high_resolution_clock> now)
250
298
{
251
299
if (now - lastDump >= profileDumpInterval)
0 commit comments