Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
demon_profiler.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include <algorithm>
15#include <cmath>
16#include <cstddef>
17#include <cstdint>
18#include <string>
19#include <utility>
20#include <vector>
21
22#include "absl/container/flat_hash_map.h"
23#include "absl/status/status.h"
24#include "absl/strings/str_format.h"
25#include "absl/strings/string_view.h"
26#include "absl/time/clock.h"
27#include "absl/time/time.h"
28#include "ortools/base/file.h"
29#include "ortools/base/hash.h"
34#include "ortools/base/types.h"
37#include "ortools/constraint_solver/demon_profiler.pb.h"
38
39namespace operations_research {
40namespace {
41struct Container {
42 Container(const Constraint* ct_, int64_t value_) : ct(ct_), value(value_) {}
43 bool operator<(const Container& c) const { return value > c.value; }
44
45 const Constraint* ct;
46 int64_t value;
47};
48} // namespace
49
50// DemonProfiler manages the profiling of demons and allows access to gathered
51// data. Add this class as a parameter to Solver and access its information
52// after the end of a search.
54 public:
55 explicit DemonProfiler(Solver* const solver)
57 active_constraint_(nullptr),
58 active_demon_(nullptr),
59 start_time_ns_(absl::GetCurrentTimeNanos()) {}
60
61 ~DemonProfiler() override {
62 gtl::STLDeleteContainerPairSecondPointers(constraint_map_.begin(),
63 constraint_map_.end());
64 }
65
66 // In microseconds.
67 // TODO(user): rename and return nanoseconds.
68 int64_t CurrentTime() const {
69 return (absl::GetCurrentTimeNanos() - start_time_ns_) / 1000;
70 }
71
73 Constraint* const constraint) override {
74 if (solver()->state() == Solver::IN_SEARCH) {
75 return;
76 }
77
78 CHECK(active_constraint_ == nullptr);
79 CHECK(active_demon_ == nullptr);
80 CHECK(constraint != nullptr);
81 ConstraintRuns* const ct_run = new ConstraintRuns;
82 ct_run->set_constraint_id(constraint->DebugString());
83 ct_run->add_initial_propagation_start_time(CurrentTime());
84 active_constraint_ = constraint;
85 constraint_map_[constraint] = ct_run;
86 }
87
88 void EndConstraintInitialPropagation(Constraint* const constraint) override {
89 CHECK(active_constraint_ != nullptr);
90 CHECK(active_demon_ == nullptr);
91 CHECK(constraint != nullptr);
92 CHECK_EQ(constraint, active_constraint_);
93 ConstraintRuns* const ct_run = constraint_map_[constraint];
94 if (ct_run != nullptr) {
95 ct_run->add_initial_propagation_end_time(CurrentTime());
96 ct_run->set_failures(0);
97 }
98 active_constraint_ = nullptr;
99 }
100
102 Constraint* const constraint, Constraint* const delayed) override {
103 if (solver()->state() == Solver::IN_SEARCH) {
104 return;
105 }
106
107 CHECK(active_constraint_ == nullptr);
108 CHECK(active_demon_ == nullptr);
109 CHECK(constraint != nullptr);
110 CHECK(delayed != nullptr);
111 ConstraintRuns* const ct_run = constraint_map_[constraint];
112 ct_run->add_initial_propagation_start_time(CurrentTime());
113 active_constraint_ = constraint;
114 }
115
117 Constraint* const constraint, Constraint* const delayed) override {
118 CHECK(active_constraint_ != nullptr);
119 CHECK(active_demon_ == nullptr);
120 CHECK(constraint != nullptr);
121 CHECK(delayed != nullptr);
122 CHECK_EQ(constraint, active_constraint_);
123 ConstraintRuns* const ct_run = constraint_map_[constraint];
124 if (ct_run != nullptr) {
125 ct_run->add_initial_propagation_end_time(CurrentTime());
126 ct_run->set_failures(0);
127 }
128 active_constraint_ = nullptr;
129 }
130
131 void RegisterDemon(Demon* const demon) override {
132 if (solver()->state() == Solver::IN_SEARCH) {
133 return;
134 }
135
136 if (demon_map_.find(demon) == demon_map_.end()) {
137 CHECK(active_constraint_ != nullptr);
138 CHECK(active_demon_ == nullptr);
139 CHECK(demon != nullptr);
140 ConstraintRuns* const ct_run = constraint_map_[active_constraint_];
141 DemonRuns* const demon_run = ct_run->add_demons();
142 demon_run->set_demon_id(demon->DebugString());
143 demon_run->set_failures(0);
144 demon_map_[demon] = demon_run;
145 demons_per_constraint_[active_constraint_].push_back(demon_run);
146 }
147 }
148
149 void BeginDemonRun(Demon* const demon) override {
150 CHECK(demon != nullptr);
151 if (demon->priority() == Solver::VAR_PRIORITY) {
152 return;
153 }
154 CHECK(active_demon_ == nullptr);
155 active_demon_ = demon;
156 DemonRuns* const demon_run = demon_map_[active_demon_];
157 if (demon_run != nullptr) {
158 demon_run->add_start_time(CurrentTime());
159 }
160 }
161
162 void EndDemonRun(Demon* const demon) override {
163 CHECK(demon != nullptr);
164 if (demon->priority() == Solver::VAR_PRIORITY) {
165 return;
166 }
167 CHECK_EQ(active_demon_, demon);
168 DemonRuns* const demon_run = demon_map_[active_demon_];
169 if (demon_run != nullptr) {
170 demon_run->add_end_time(CurrentTime());
171 }
172 active_demon_ = nullptr;
173 }
174
175 void StartProcessingIntegerVariable(IntVar* const var) override {}
176 void EndProcessingIntegerVariable(IntVar* const var) override {}
177 void PushContext(const std::string& context) override {}
178 void PopContext() override {}
179
180 void BeginFail() override {
181 if (active_demon_ != nullptr) {
182 DemonRuns* const demon_run = demon_map_[active_demon_];
183 if (demon_run != nullptr) {
184 demon_run->add_end_time(CurrentTime());
185 demon_run->set_failures(demon_run->failures() + 1);
186 }
187 active_demon_ = nullptr;
188 // active_constraint_ can be non null in case of initial propagation.
189 active_constraint_ = nullptr;
190 } else if (active_constraint_ != nullptr) {
191 ConstraintRuns* const ct_run = constraint_map_[active_constraint_];
192 if (ct_run != nullptr) {
193 ct_run->add_initial_propagation_end_time(CurrentTime());
194 ct_run->set_failures(1);
195 }
196 active_constraint_ = nullptr;
197 }
198 }
199
200 // Restarts a search and clears all previously collected information.
201 void RestartSearch() override {
202 gtl::STLDeleteContainerPairSecondPointers(constraint_map_.begin(),
203 constraint_map_.end());
204 constraint_map_.clear();
205 demon_map_.clear();
206 demons_per_constraint_.clear();
207 }
208
209 // IntExpr modifiers.
210 void SetMin(IntExpr* const expr, int64_t new_min) override {}
211 void SetMax(IntExpr* const expr, int64_t new_max) override {}
212 void SetRange(IntExpr* const expr, int64_t new_min,
213 int64_t new_max) override {}
214 // IntVar modifiers.
215 void SetMin(IntVar* const var, int64_t new_min) override {}
216 void SetMax(IntVar* const var, int64_t new_max) override {}
217 void SetRange(IntVar* const var, int64_t new_min, int64_t new_max) override {}
218 void RemoveValue(IntVar* const var, int64_t value) override {}
219 void SetValue(IntVar* const var, int64_t value) override {}
220 void RemoveInterval(IntVar* const var, int64_t imin, int64_t imax) override {}
221 void SetValues(IntVar* const var,
222 const std::vector<int64_t>& values) override {}
223 void RemoveValues(IntVar* const var,
224 const std::vector<int64_t>& values) override {}
225 // IntervalVar modifiers.
226 void SetStartMin(IntervalVar* const var, int64_t new_min) override {}
227 void SetStartMax(IntervalVar* const var, int64_t new_max) override {}
228 void SetStartRange(IntervalVar* const var, int64_t new_min,
229 int64_t new_max) override {}
230 void SetEndMin(IntervalVar* const var, int64_t new_min) override {}
231 void SetEndMax(IntervalVar* const var, int64_t new_max) override {}
232 void SetEndRange(IntervalVar* const var, int64_t new_min,
233 int64_t new_max) override {}
234 void SetDurationMin(IntervalVar* const var, int64_t new_min) override {}
235 void SetDurationMax(IntervalVar* const var, int64_t new_max) override {}
236 void SetDurationRange(IntervalVar* const var, int64_t new_min,
237 int64_t new_max) override {}
238 void SetPerformed(IntervalVar* const var, bool value) override {}
239 void RankFirst(SequenceVar* const var, int index) override {}
240 void RankNotFirst(SequenceVar* const var, int index) override {}
241 void RankLast(SequenceVar* const var, int index) override {}
242 void RankNotLast(SequenceVar* const var, int index) override {}
243 void RankSequence(SequenceVar* const var, const std::vector<int>& rank_first,
244 const std::vector<int>& rank_last,
245 const std::vector<int>& unperformed) override {}
246
247 // Useful for unit tests.
248 void AddFakeRun(Demon* const demon, int64_t start_time, int64_t end_time,
249 bool is_fail) {
250 CHECK(demon != nullptr);
251 DemonRuns* const demon_run = demon_map_[demon];
252 CHECK(demon_run != nullptr);
253 demon_run->add_start_time(start_time);
254 demon_run->add_end_time(end_time);
255 if (is_fail) {
256 demon_run->set_failures(demon_run->failures() + 1);
257 }
258 }
259
260 // Exports collected data as human-readable text.
261 void PrintOverview(Solver* const solver, absl::string_view filename) {
262 const char* const kConstraintFormat =
263 " - Constraint: %s\n failures=%d, initial propagation "
264 "runtime=%d us, demons=%d, demon invocations=%d, total demon "
265 "runtime=%d us\n";
266 const char* const kDemonFormat =
267 " --- Demon: %s\n invocations=%d, failures=%d, total "
268 "runtime=%d us, [average=%.2lf, median=%.2lf, stddev=%.2lf]\n";
269 File* file;
270 const std::string model =
271 absl::StrFormat("Model %s:\n", solver->model_name());
272 if (file::Open(filename, "w", &file, file::Defaults()).ok()) {
273 file::WriteString(file, model, file::Defaults()).IgnoreError();
274 std::vector<Container> to_sort;
275 for (absl::flat_hash_map<const Constraint*,
276 ConstraintRuns*>::const_iterator it =
277 constraint_map_.begin();
278 it != constraint_map_.end(); ++it) {
279 const Constraint* const ct = it->first;
280 int64_t fails = 0;
281 int64_t demon_invocations = 0;
282 int64_t initial_propagation_runtime = 0;
283 int64_t total_demon_runtime = 0;
284 int demon_count = 0;
285 ExportInformation(ct, &fails, &initial_propagation_runtime,
286 &demon_invocations, &total_demon_runtime,
287 &demon_count);
288 to_sort.push_back(
289 Container(ct, total_demon_runtime + initial_propagation_runtime));
290 }
291 std::sort(to_sort.begin(), to_sort.end());
292
293 for (int i = 0; i < to_sort.size(); ++i) {
294 const Constraint* const ct = to_sort[i].ct;
295 int64_t fails = 0;
296 int64_t demon_invocations = 0;
297 int64_t initial_propagation_runtime = 0;
298 int64_t total_demon_runtime = 0;
299 int demon_count = 0;
300 ExportInformation(ct, &fails, &initial_propagation_runtime,
301 &demon_invocations, &total_demon_runtime,
302 &demon_count);
303 const std::string constraint_message =
304 absl::StrFormat(kConstraintFormat, ct->DebugString(), fails,
305 initial_propagation_runtime, demon_count,
306 demon_invocations, total_demon_runtime);
307 file::WriteString(file, constraint_message, file::Defaults())
308 .IgnoreError();
309 const std::vector<DemonRuns*>& demons = demons_per_constraint_[ct];
310 const int demon_size = demons.size();
311 for (int demon_index = 0; demon_index < demon_size; ++demon_index) {
312 DemonRuns* const demon_runs = demons[demon_index];
313 int64_t invocations = 0;
314 int64_t fails = 0;
315 int64_t runtime = 0;
316 double mean_runtime = 0;
317 double median_runtime = 0;
318 double standard_deviation = 0.0;
319 ExportInformation(demon_runs, &invocations, &fails, &runtime,
320 &mean_runtime, &median_runtime,
321 &standard_deviation);
322 const std::string runs = absl::StrFormat(
323 kDemonFormat, demon_runs->demon_id(), invocations, fails, runtime,
324 mean_runtime, median_runtime, standard_deviation);
325 file::WriteString(file, runs, file::Defaults()).IgnoreError();
326 }
327 }
328 }
329 file->Close(file::Defaults()).IgnoreError();
330 }
331
332 // Export Information
333 void ExportInformation(const Constraint* const constraint,
334 int64_t* const fails,
335 int64_t* const initial_propagation_runtime,
336 int64_t* const demon_invocations,
337 int64_t* const total_demon_runtime, int* demons) {
338 CHECK(constraint != nullptr);
339 ConstraintRuns* const ct_run = constraint_map_[constraint];
340 CHECK(ct_run != nullptr);
341 *demon_invocations = 0;
342 *fails = ct_run->failures();
343 *initial_propagation_runtime = 0;
344 for (int i = 0; i < ct_run->initial_propagation_start_time_size(); ++i) {
345 *initial_propagation_runtime += ct_run->initial_propagation_end_time(i) -
346 ct_run->initial_propagation_start_time(i);
347 }
348 *total_demon_runtime = 0;
349
350 // Gather information.
351 *demons = ct_run->demons_size();
352 CHECK_EQ(*demons, demons_per_constraint_[constraint].size());
353 for (int demon_index = 0; demon_index < *demons; ++demon_index) {
354 const DemonRuns& demon_runs = ct_run->demons(demon_index);
355 *fails += demon_runs.failures();
356 CHECK_EQ(demon_runs.start_time_size(), demon_runs.end_time_size());
357 const int runs = demon_runs.start_time_size();
358 *demon_invocations += runs;
359 for (int run_index = 0; run_index < runs; ++run_index) {
360 const int64_t demon_time =
361 demon_runs.end_time(run_index) - demon_runs.start_time(run_index);
362 *total_demon_runtime += demon_time;
363 }
364 }
365 }
366
367 void ExportInformation(const DemonRuns* const demon_runs,
368 int64_t* const demon_invocations, int64_t* const fails,
369 int64_t* const total_demon_runtime,
370 double* const mean_demon_runtime,
371 double* const median_demon_runtime,
372 double* const stddev_demon_runtime) {
373 CHECK(demon_runs != nullptr);
374 CHECK_EQ(demon_runs->start_time_size(), demon_runs->end_time_size());
375
376 const int runs = demon_runs->start_time_size();
377 *demon_invocations = runs;
378 *fails = demon_runs->failures();
379 *total_demon_runtime = 0;
380 *mean_demon_runtime = 0.0;
381 *median_demon_runtime = 0.0;
382 *stddev_demon_runtime = 0.0;
383 std::vector<double> runtimes;
384 for (int run_index = 0; run_index < runs; ++run_index) {
385 const int64_t demon_time =
386 demon_runs->end_time(run_index) - demon_runs->start_time(run_index);
387 *total_demon_runtime += demon_time;
388 runtimes.push_back(demon_time);
389 }
390 // Compute mean.
391 if (!runtimes.empty()) {
392 *mean_demon_runtime = (1.0L * *total_demon_runtime) / runtimes.size();
393
394 // Compute median.
395 std::sort(runtimes.begin(), runtimes.end());
396 const int pivot = runtimes.size() / 2;
397
398 if (runtimes.size() == 1) {
399 *median_demon_runtime = runtimes[0];
400 } else {
401 *median_demon_runtime =
402 runtimes.size() % 2 == 1
403 ? runtimes[pivot]
404 : (runtimes[pivot - 1] + runtimes[pivot]) / 2.0;
405 }
406
407 // Compute standard deviation.
408 double total_deviation = 0.0f;
409
410 for (int i = 0; i < runtimes.size(); ++i) {
411 total_deviation += pow(runtimes[i] - *mean_demon_runtime, 2);
412 }
413
414 *stddev_demon_runtime = sqrt(total_deviation / runtimes.size());
415 }
416 }
417
418 // The demon_profiler is added by default on the main propagation
419 // monitor. It just needs to be added to the search monitors at the
420 // start of the search.
421 void Install() override { SearchMonitor::Install(); }
422
423 std::string DebugString() const override { return "DemonProfiler"; }
424
425 private:
426 Constraint* active_constraint_;
427 Demon* active_demon_;
428 const int64_t start_time_ns_;
429 absl::flat_hash_map<const Constraint*, ConstraintRuns*> constraint_map_;
430 absl::flat_hash_map<const Demon*, DemonRuns*> demon_map_;
431 absl::flat_hash_map<const Constraint*, std::vector<DemonRuns*> >
432 demons_per_constraint_;
433};
434
435void Solver::ExportProfilingOverview(const std::string& filename) {
436 if (demon_profiler_ != nullptr) {
437 demon_profiler_->PrintOverview(this, filename);
438 }
439}
440
441// ----- Exported Functions -----
442
443void InstallDemonProfiler(DemonProfiler* monitor) { monitor->Install(); }
444
446 if (solver->IsProfilingEnabled()) {
447 return new DemonProfiler(solver);
448 } else {
449 return nullptr;
450 }
451}
452
453void DeleteDemonProfiler(DemonProfiler* monitor) { delete monitor; }
454
456 CHECK(demon != nullptr);
457 if (InstrumentsDemons()) {
458 propagation_monitor_->RegisterDemon(demon);
459 }
460 return demon;
461}
462
463// ----- Exported Methods for Unit Tests -----
464
465void RegisterDemon(Solver* const solver, Demon* const demon,
466 DemonProfiler* const monitor) {
467 monitor->RegisterDemon(demon);
468}
469
470void DemonProfilerAddFakeRun(DemonProfiler* const monitor, Demon* const demon,
471 int64_t start_time, int64_t end_time,
472 bool is_fail) {
473 monitor->AddFakeRun(demon, start_time, end_time, is_fail);
474}
475
477 const Constraint* const constraint,
478 int64_t* const fails,
479 int64_t* const initial_propagation_runtime,
480 int64_t* const demon_invocations,
481 int64_t* const total_demon_runtime,
482 int* const demon_count) {
483 monitor->ExportInformation(constraint, fails, initial_propagation_runtime,
484 demon_invocations, total_demon_runtime,
485 demon_count);
486}
487
489 Constraint* const constraint) {
490 monitor->BeginConstraintInitialPropagation(constraint);
491}
492
494 Constraint* const constraint) {
495 monitor->EndConstraintInitialPropagation(constraint);
496}
497
498} // namespace operations_research
Definition file.h:30
std::string DebugString() const override
--------------— Constraint class ----------------—
void RemoveValues(IntVar *const var, const std::vector< int64_t > &values) override
void PrintOverview(Solver *const solver, absl::string_view filename)
Exports collected data as human-readable text.
void SetEndRange(IntervalVar *const var, int64_t new_min, int64_t new_max) override
void SetMax(IntVar *const var, int64_t new_max) override
void AddFakeRun(Demon *const demon, int64_t start_time, int64_t end_time, bool is_fail)
Useful for unit tests.
void EndProcessingIntegerVariable(IntVar *const var) override
void RankNotLast(SequenceVar *const var, int index) override
DemonProfiler(Solver *const solver)
void SetStartRange(IntervalVar *const var, int64_t new_min, int64_t new_max) override
void RankSequence(SequenceVar *const var, const std::vector< int > &rank_first, const std::vector< int > &rank_last, const std::vector< int > &unperformed) override
void SetEndMax(IntervalVar *const var, int64_t new_max) override
void SetPerformed(IntervalVar *const var, bool value) override
void ExportInformation(const DemonRuns *const demon_runs, int64_t *const demon_invocations, int64_t *const fails, int64_t *const total_demon_runtime, double *const mean_demon_runtime, double *const median_demon_runtime, double *const stddev_demon_runtime)
void SetDurationMin(IntervalVar *const var, int64_t new_min) override
void EndConstraintInitialPropagation(Constraint *const constraint) override
void ExportInformation(const Constraint *const constraint, int64_t *const fails, int64_t *const initial_propagation_runtime, int64_t *const demon_invocations, int64_t *const total_demon_runtime, int *demons)
Export Information.
void SetValue(IntVar *const var, int64_t value) override
void SetMin(IntVar *const var, int64_t new_min) override
IntVar modifiers.
void RankNotFirst(SequenceVar *const var, int index) override
void SetEndMin(IntervalVar *const var, int64_t new_min) override
void SetDurationRange(IntervalVar *const var, int64_t new_min, int64_t new_max) override
void BeginNestedConstraintInitialPropagation(Constraint *const constraint, Constraint *const delayed) override
void SetMax(IntExpr *const expr, int64_t new_max) override
void SetStartMax(IntervalVar *const var, int64_t new_max) override
void BeginFail() override
Just when the failure occurs.
void BeginConstraintInitialPropagation(Constraint *const constraint) override
Propagation events.
void RankLast(SequenceVar *const var, int index) override
void EndDemonRun(Demon *const demon) override
void SetRange(IntVar *const var, int64_t new_min, int64_t new_max) override
void BeginDemonRun(Demon *const demon) override
void StartProcessingIntegerVariable(IntVar *const var) override
void SetDurationMax(IntervalVar *const var, int64_t new_max) override
void SetMin(IntExpr *const expr, int64_t new_min) override
IntExpr modifiers.
std::string DebugString() const override
void RankFirst(SequenceVar *const var, int index) override
SequenceVar modifiers.
void RegisterDemon(Demon *const demon) override
void SetStartMin(IntervalVar *const var, int64_t new_min) override
IntervalVar modifiers.
void SetRange(IntExpr *const expr, int64_t new_min, int64_t new_max) override
void RestartSearch() override
Restarts a search and clears all previously collected information.
void SetValues(IntVar *const var, const std::vector< int64_t > &values) override
void RemoveInterval(IntVar *const var, int64_t imin, int64_t imax) override
void RemoveValue(IntVar *const var, int64_t value) override
void PushContext(const std::string &context) override
void EndNestedConstraintInitialPropagation(Constraint *const constraint, Constraint *const delayed) override
virtual Solver::DemonPriority priority() const
---------------— Demon class -------------—
std::string DebugString() const override
PropagationMonitor(Solver *solver)
-------— Propagation Monitor --------—
virtual void Install()
A search monitors adds itself on the active search.
For the time being, Solver is neither MT_SAFE nor MT_HOT.
Demon * RegisterDemon(Demon *demon)
Adds a new demon and wraps it inside a DemonProfiler if necessary.
void ExportProfilingOverview(const std::string &filename)
@ VAR_PRIORITY
VAR_PRIORITY is between DELAYED_PRIORITY and NORMAL_PRIORITY.
@ IN_SEARCH
Executing the search code.
bool IsProfilingEnabled() const
Returns whether we are profiling the solver.
bool InstrumentsDemons() const
Returns whether we are instrumenting demons.
Definition file.cc:169
absl::Status Open(absl::string_view filename, absl::string_view mode, File **f, Options options)
As of 2016-01, these methods can only be used with flags = file::Defaults().
Definition file.cc:170
absl::Status WriteString(File *file, absl::string_view contents, Options options)
Definition file.cc:231
Options Defaults()
Definition file.h:107
void STLDeleteContainerPairSecondPointers(ForwardIterator begin, ForwardIterator end)
Definition stl_util.h:353
In SWIG mode, we don't want anything besides these top-level includes.
void DeleteDemonProfiler(DemonProfiler *monitor)
void DemonProfilerEndInitialPropagation(DemonProfiler *const monitor, Constraint *const constraint)
DemonProfiler * BuildDemonProfiler(Solver *solver)
void DemonProfilerExportInformation(DemonProfiler *const monitor, const Constraint *const constraint, int64_t *const fails, int64_t *const initial_propagation_runtime, int64_t *const demon_invocations, int64_t *const total_demon_runtime, int *const demon_count)
void RegisterDemon(Solver *const solver, Demon *const demon, DemonProfiler *const monitor)
--— Exported Methods for Unit Tests --—
void DemonProfilerBeginInitialPropagation(DemonProfiler *const monitor, Constraint *const constraint)
void DemonProfilerAddFakeRun(DemonProfiler *const monitor, Demon *const demon, int64_t start_time, int64_t end_time, bool is_fail)
void InstallDemonProfiler(DemonProfiler *monitor)
--— Forward Declarations and Profiling Support --—