Skip to content

Commit 98dfcf2

Browse files
committed
Add --profile flag with performance breakdown
Display profiling summary showing Setup, Dynamics Loop (with Chrono solver and hydrodynamics breakdown), and Export times.
1 parent 1d50653 commit 98dfcf2

5 files changed

Lines changed: 94 additions & 14 deletions

File tree

app/run_hydrochrono.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ struct CLIArgs {
8383
bool h5_verbose = false; // NEW: HDF5 verbose diagnostics
8484
std::string h5_tag; // NEW: Optional tag appended to filename
8585
bool fail_fast = false; // NEW: Stop on first failure during sweep
86+
bool profile = false; // NEW: Enable runtime profiling summary
8687
};
8788

8889
static CLIArgs ParseArguments(int argc, char* argv[]) {
@@ -136,6 +137,8 @@ static CLIArgs ParseArguments(int argc, char* argv[]) {
136137
}
137138
} else if (arg == "--fail-fast") {
138139
args.fail_fast = true;
140+
} else if (arg == "--profile") {
141+
args.profile = true;
139142
} else if (arg.substr(0, 1) != "-") {
140143
// This is a positional argument (input directory)
141144
if (args.input_directory.empty()) {
@@ -286,6 +289,10 @@ int main(int argc, char* argv[]) {
286289
runner_args.push_back("--trace");
287290
}
288291

292+
if (args.profile) {
293+
runner_args.push_back("--profile");
294+
}
295+
289296
if (!args.model_file.empty()) {
290297
runner_args.push_back("--model_file");
291298
runner_args.push_back(args.model_file);

include/hydroc/hydro_forces.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ class ForceFunc6d {
149149

150150
class ChLoadAddedMass;
151151

152+
// Lightweight hydrodynamics profiling stats
153+
struct HydroProfileStats {
154+
double hydrostatics_seconds = 0.0;
155+
double radiation_seconds = 0.0;
156+
double waves_seconds = 0.0;
157+
int hydrostatics_calls = 0;
158+
int radiation_calls = 0;
159+
int waves_calls = 0;
160+
};
161+
152162
// TODO: Rename TestHydro for clarity, perhaps to HydroForces?
153163
// TODO: Split TestHydro class from its helper classes for clearer code structure.
154164
class TestHydro {
@@ -233,6 +243,9 @@ class TestHydro {
233243
*/
234244
double CoordinateFuncForBody(int b, int i);
235245

246+
// Hydrodynamics profiling accessors
247+
HydroProfileStats GetProfileStats() const { return profile_stats_; }
248+
236249
private:
237250
// Class properties related to the body and hydrodynamics
238251
std::vector<std::shared_ptr<ChBody>> bodies_;
@@ -281,6 +294,9 @@ class TestHydro {
281294
* @param index The DOF index, ranging from [0,1,...,5].
282295
*/
283296
double SetVelHistory(double val, int step, int b_num, int index);
297+
298+
// Hydrodynamics profiling data (accumulated over run)
299+
HydroProfileStats profile_stats_;
284300
};
285301

286302
#endif

include/hydroc/utils/profiler.h

Whitespace-only changes.

src/hydro_forces.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <random>
2525
#include <stdexcept>
2626
#include <vector>
27+
#include <chrono>
2728

2829
const int kDofPerBody = 6;
2930
const int kDofLinOrRot = 3;
@@ -251,6 +252,7 @@ void TestHydro::AddWaves(std::shared_ptr<WaveBase> waves) {
251252
}
252253

253254
std::vector<double> TestHydro::ComputeForceHydrostatics() {
255+
auto __t0 = std::chrono::steady_clock::now();
254256
assert(num_bodies_ > 0);
255257

256258
const double rho = file_info_.GetRhoVal();
@@ -305,10 +307,13 @@ std::vector<double> TestHydro::ComputeForceHydrostatics() {
305307
body_force_hydrostatic[5] += buoyancy_torque.z();
306308
}
307309

310+
profile_stats_.hydrostatics_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - __t0).count();
311+
profile_stats_.hydrostatics_calls++;
308312
return force_hydrostatic_;
309313
}
310314

311315
std::vector<double> TestHydro::ComputeForceRadiationDampingConv() {
316+
auto __t0 = std::chrono::steady_clock::now();
312317
const int rirf_steps = file_info_.GetRIRFDims(2);
313318
const int total_dofs = kDofPerBody * num_bodies_;
314319

@@ -348,14 +353,16 @@ std::vector<double> TestHydro::ComputeForceRadiationDampingConv() {
348353
for (int b = 0; b < num_bodies_; ++b) {
349354
auto& velocity_history_body = velocity_history_[b];
350355
if (!velocity_history_body.empty()) {
351-
velocity_history_body.pop_back();
352-
}
356+
velocity_history_body.pop_back();
353357
}
354358
}
359+
}
355360
}
356361

357362
// Nothing to convolve with if we don't yet have at least 2 time points
358363
if (time_history_.size() <= 1) {
364+
profile_stats_.radiation_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - __t0).count();
365+
profile_stats_.radiation_calls++;
359366
return force_radiation_damping_;
360367
}
361368

@@ -414,7 +421,7 @@ std::vector<double> TestHydro::ComputeForceRadiationDampingConv() {
414421
interpolated_velocity_dof[3] = weight_older * older_velocity[3] + weight_newer * newer_velocity[3];
415422
interpolated_velocity_dof[4] = weight_older * older_velocity[4] + weight_newer * newer_velocity[4];
416423
interpolated_velocity_dof[5] = weight_older * older_velocity[5] + weight_newer * newer_velocity[5];
417-
} else {
424+
} else {
418425
throw std::runtime_error(
419426
"Radiation convolution: interpolation error; rirf_query_time not bracketed by adjacent history.");
420427
}
@@ -435,6 +442,8 @@ std::vector<double> TestHydro::ComputeForceRadiationDampingConv() {
435442
}
436443
}
437444

445+
profile_stats_.radiation_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - __t0).count();
446+
profile_stats_.radiation_calls++;
438447
return force_radiation_damping_;
439448
}
440449

@@ -452,19 +461,16 @@ double TestHydro::GetRIRFval(int row, int col, int st) {
452461
}
453462

454463
Eigen::VectorXd TestHydro::ComputeForceWaves() {
464+
auto __t0 = std::chrono::steady_clock::now();
455465
// Ensure bodies_ is not empty
456466
if (bodies_.empty()) {
457467
throw std::runtime_error("bodies_ array is empty in ComputeForceWaves");
458468
}
459469

460470
force_waves_ = user_waves_->GetForceAtTime(bodies_[0]->GetChTime());
461471

462-
// TODO: Add size check for force_waves_ if needed
463-
// Example:
464-
// if (force_waves_.size() != expected_size) {
465-
// throw std::runtime_error("Mismatched size in ComputeForceWaves");
466-
// }
467-
472+
profile_stats_.waves_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - __t0).count();
473+
profile_stats_.waves_calls++;
468474
return force_waves_;
469475
}
470476

src/hydrochrono_runner/run_hydrochrono_from_yaml.cpp

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
292292
bool enable_logging = false; // default: only log when --log is supplied
293293
bool debug_mode = false;
294294
bool trace_mode = false;
295+
bool profile_mode = false;
295296

296297
// Parse command line arguments
297298
for (int i = 1; i < argc; ++i) {
@@ -311,6 +312,8 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
311312
} else if (arg == "--trace") {
312313
trace_mode = true;
313314
debug_mode = true; // trace implies debug
315+
} else if (arg == "--profile") {
316+
profile_mode = true;
314317
} else if (arg == "--nobanner") {
315318
// Optional: could disable banner; currently handled by enable_cli_output
316319
} else if (arg == "--quiet") {
@@ -655,7 +658,14 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
655658
// ---------------------------------------------------------------------
656659
// 7. Run simulation
657660
// ---------------------------------------------------------------------
658-
auto start_time = std::chrono::steady_clock::now();
661+
auto wall_start = std::chrono::steady_clock::now();
662+
// Profiling accumulators
663+
std::chrono::steady_clock::time_point t;
664+
double prof_setup_seconds = 0.0;
665+
double prof_loop_seconds = 0.0;
666+
double prof_export_seconds = 0.0;
667+
double prof_other_seconds = 0.0;
668+
auto prof_section_start = std::chrono::steady_clock::now(); // setup section start
659669

660670

661671
// Log simulation loop entry
@@ -680,10 +690,14 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
680690
while (system->GetChTime() < end_time_bound) {
681691
double current_time = system->GetChTime();
682692
try {
693+
if (profile_mode) { t = std::chrono::steady_clock::now(); }
683694
system->DoStepDynamics(loop_dt);
695+
if (profile_mode) { prof_loop_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - t).count(); }
684696
step_count++;
685697
if (exporter) {
698+
if (profile_mode) { t = std::chrono::steady_clock::now(); }
686699
exporter->RecordStep(system.get());
700+
if (profile_mode) { prof_export_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - t).count(); }
687701
}
688702
previous_time = current_time;
689703
} catch (const std::exception& e) {
@@ -734,10 +748,14 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
734748

735749
try {
736750
// 🧯 Scoped try/catch around DoStepDynamics
751+
if (profile_mode) { t = std::chrono::steady_clock::now(); }
737752
system->DoStepDynamics(loop_dt);
753+
if (profile_mode) { prof_loop_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - t).count(); }
738754
step_count++;
739755
if (exporter) {
756+
if (profile_mode) { t = std::chrono::steady_clock::now(); }
740757
exporter->RecordStep(system.get());
758+
if (profile_mode) { prof_export_seconds += std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - t).count(); }
741759
}
742760

743761
// 🧯 After first step - check if simulation time advanced
@@ -875,21 +893,54 @@ int RunHydroChronoFromYAML(int argc, char* argv[]) {
875893
break; // Exit simulation loop on exception
876894
}
877895
}
878-
}
896+
}
879897
}
880898

881-
auto end_time = std::chrono::steady_clock::now();
882-
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
899+
auto wall_end = std::chrono::steady_clock::now();
900+
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(wall_end - wall_start);
883901

884902
// Final results display (CLI visible)
885903
hydroc::cli::ShowSimulationResults(system->GetChTime(), static_cast<int>(system->GetChTime() / loop_dt), duration.count() / 1000.0);
886904

887905
// Finalize HDF5 output with runtime metadata
888906
if (exporter) {
889-
double wall_s = std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time).count();
907+
double wall_s = std::chrono::duration_cast<std::chrono::duration<double>>(wall_end - wall_start).count();
890908
exporter->SetRunMetadata(std::string(""), std::string(""), wall_s, step_count, loop_dt, system->GetChTime());
891909
exporter->Finalize();
892910
}
911+
912+
// Optional profiling summary
913+
if (profile_mode) {
914+
double wall_seconds = std::chrono::duration_cast<std::chrono::duration<double>>(wall_end - wall_start).count();
915+
prof_other_seconds = std::max(0.0, wall_seconds - (prof_setup_seconds + prof_loop_seconds + prof_export_seconds));
916+
917+
std::vector<std::string> prof_lines;
918+
auto pct = [&](double s){ return hydroc::FormatNumber(100.0 * (s / std::max(1e-12, wall_seconds)), 1) + "%"; };
919+
920+
// Top-level sections
921+
prof_lines.push_back(hydroc::cli::CreateAlignedLine("📦", "Setup", hydroc::FormatNumber(prof_setup_seconds, 3) + " s (" + pct(prof_setup_seconds) + ")"));
922+
prof_lines.push_back(hydroc::cli::CreateAlignedLine("⚙️", "Dynamics Loop", hydroc::FormatNumber(prof_loop_seconds, 3) + " s (" + pct(prof_loop_seconds) + ")"));
923+
924+
// Nested breakdown under Dynamics Loop
925+
if (test_hydro) {
926+
auto hp = test_hydro->GetProfileStats();
927+
double hydro_total = hp.hydrostatics_seconds + hp.radiation_seconds + hp.waves_seconds;
928+
double chrono_solver = std::max(0.0, prof_loop_seconds - hydro_total);
929+
930+
auto loop_pct = [&](double s){ return hydroc::FormatNumber(100.0 * (s / std::max(1e-12, prof_loop_seconds)), 1) + "%"; };
931+
prof_lines.push_back(hydroc::cli::CreateAlignedLine(" 🔧", "Chrono Solver", hydroc::FormatNumber(chrono_solver, 4) + " s (" + loop_pct(chrono_solver) + ")"));
932+
prof_lines.push_back(hydroc::cli::CreateAlignedLine("", "Hydrostatics", hydroc::FormatNumber(hp.hydrostatics_seconds, 4) + " s (" + loop_pct(hp.hydrostatics_seconds) + ") [" + std::to_string(hp.hydrostatics_calls) + " calls]"));
933+
prof_lines.push_back(hydroc::cli::CreateAlignedLine(" 💧", "Radiation Damping", hydroc::FormatNumber(hp.radiation_seconds, 4) + " s (" + loop_pct(hp.radiation_seconds) + ") [" + std::to_string(hp.radiation_calls) + " calls]"));
934+
prof_lines.push_back(hydroc::cli::CreateAlignedLine(" 🌊", "Wave Forces", hydroc::FormatNumber(hp.waves_seconds, 4) + " s (" + loop_pct(hp.waves_seconds) + ") [" + std::to_string(hp.waves_calls) + " calls]"));
935+
}
936+
937+
if (exporter) {
938+
prof_lines.push_back(hydroc::cli::CreateAlignedLine("💾", "Export", hydroc::FormatNumber(prof_export_seconds, 3) + " s (" + pct(prof_export_seconds) + ")"));
939+
}
940+
prof_lines.push_back(hydroc::cli::CreateAlignedLine("━━━", "━━━━━━━━━━━━━━━━━━━━━━", "━━━━━━━━━━━━━━━━━━━━"));
941+
prof_lines.push_back(hydroc::cli::CreateAlignedLine("📈", "Total Runtime", hydroc::FormatNumber(wall_seconds, 3) + " s (100%)"));
942+
hydroc::cli::ShowSectionBox("🔬 Performance Profiling", prof_lines);
943+
}
893944

894945
// Display warnings section if any warnings were collected
895946
hydroc::cli::DisplayWarnings();

0 commit comments

Comments
 (0)