Files
FESADev/src/fesa/analysis/hht_development_probe.h
T

142 lines
5.8 KiB
C++

#ifndef FESA_ANALYSIS_HHT_DEVELOPMENT_PROBE_H_
#define FESA_ANALYSIS_HHT_DEVELOPMENT_PROBE_H_
// This header is compiled out of normal builds, including environment/file I/O.
#if defined(FESA_HHT_DEVELOPMENT_PROBE)
#include <array>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <string>
#include "fesa/solvers/linear/linear_solver.h"
#include "fesa/solvers/linear/tangent_solver.h"
namespace fesa::hht_development_probe {
enum Stage { kProcedure, kInner, kAssembly, kOuterSolve, kRates,
kFactor, kSolve, kStageCount };
enum Counter { kOuterCorrection, kInnerCorrection, kInternalEvaluation,
kInertiaEvaluation, kFactorCall, kSolveCall, kRhs,
kConstraintInternalEvaluation, kInternalFailure, kCounterCount };
struct Statistics {
std::array<std::atomic<std::uint64_t>, kCounterCount> counts{};
// Timers/stage attribution are serial procedure-thread data. Only the three
// element counters below and internal failures may be touched by workers.
std::array<std::int64_t, kStageCount> inclusive_ns{};
std::array<std::int64_t, kStageCount> exclusive_ns{};
std::array<std::uint64_t, kStageCount> stage_calls{};
std::array<std::array<std::uint64_t, kCounterCount>, kStageCount> by_stage{};
};
inline Statistics* active = nullptr;
class Scope {
public:
explicit Scope(Stage stage) : stage_(stage), parent_(current_),
start_(std::chrono::steady_clock::now()) {
current_ = this;
if (active != nullptr) ++active->stage_calls[stage_];
}
~Scope() {
const auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - start_).count();
if (active != nullptr) {
active->inclusive_ns[stage_] += elapsed;
active->exclusive_ns[stage_] += elapsed - children_;
}
if (parent_ != nullptr) parent_->children_ += elapsed;
current_ = parent_;
}
static Stage Current() { return current_ == nullptr ? kProcedure : current_->stage_; }
private:
Stage stage_;
Scope* parent_;
std::chrono::steady_clock::time_point start_;
std::int64_t children_ = 0;
inline static thread_local Scope* current_ = nullptr;
};
inline void Count(Counter counter, std::uint64_t count = 1U) {
if (active == nullptr) return;
active->counts[counter].fetch_add(count, std::memory_order_relaxed);
// Element callbacks are parallel; their totals use only the atomics above.
if (counter != kInternalEvaluation && counter != kInertiaEvaluation &&
counter != kConstraintInternalEvaluation && counter != kInternalFailure)
active->by_stage[Scope::Current()][counter] += count;
}
class Session {
public:
Session() : previous_(active) { active = &statistics_; }
~Session() {
active = previous_;
#if defined(_WIN32)
char* environment = nullptr;
std::size_t size = 0U;
if (_dupenv_s(&environment, &size, "FESA_HHT_PROBE_OUTPUT") != 0) return;
const std::string path = environment == nullptr ? "" : environment;
std::free(environment);
#else
const char* environment = std::getenv("FESA_HHT_PROBE_OUTPUT");
const std::string path = environment == nullptr ? "" : environment;
#endif
if (path.empty()) return;
std::ofstream output(path, std::ios::out | std::ios::binary);
constexpr const char* counters[] = {"outer_applied_correction", "inner_applied_correction",
"assembly_internal_evaluation", "assembly_inertia_evaluation",
"logical_factor_call", "logical_solve_call", "logical_rhs",
"constraint_internal_evaluation", "assembly_internal_failure"};
constexpr const char* stages[] = {"procedure_other", "inner_graph", "assembly",
"outer_linear", "physical_rates", "factor", "solve"};
for (int c = 0; c < kCounterCount; ++c)
output << counters[c] << '=' << statistics_.counts[c].load() << '\n';
output << "procedure_inclusive_ns=" << statistics_.inclusive_ns[kProcedure] << '\n';
for (int s = 0; s < kStageCount; ++s) {
output << stages[s] << "_calls=" << statistics_.stage_calls[s] << '\n';
output << stages[s] << "_exclusive_ns=" << statistics_.exclusive_ns[s] << '\n';
for (int c = 0; c < kCounterCount; ++c)
if (statistics_.by_stage[s][c] != 0U)
output << stages[s] << '_' << counters[c] << '='
<< statistics_.by_stage[s][c] << '\n';
}
}
private:
Statistics statistics_;
Statistics* previous_;
};
class PositiveSolver final : public LinearSolver {
public:
explicit PositiveSolver(LinearSolver& solver) : solver_(solver) {}
Status Factorize(const SparseMatrix& matrix) override {
Count(kFactorCall); Scope scope{kFactor}; return solver_.Factorize(matrix);
}
Status Solve(const Vector& rhs, Vector& solution) const override {
Count(kSolveCall); Count(kRhs); Scope scope{kSolve};
return solver_.Solve(rhs, solution);
}
private:
LinearSolver& solver_;
};
class CorrectionSolver final : public TangentSolver {
public:
explicit CorrectionSolver(TangentSolver& solver) : solver_(solver) {}
Status FactorizeFresh(const SparseMatrix& matrix) override {
Count(kFactorCall); Scope scope{kFactor}; return solver_.FactorizeFresh(matrix);
}
Status SolveCorrection(const Vector& rhs, Vector& solution) const override {
Count(kSolveCall); Count(kRhs); Scope scope{kSolve};
return solver_.SolveCorrection(rhs, solution);
}
private:
TangentSolver& solver_;
};
} // namespace fesa::hht_development_probe
#define FESA_HHT_PROBE_COUNT(counter) \
::fesa::hht_development_probe::Count(::fesa::hht_development_probe::counter)
#define FESA_HHT_PROBE_SCOPE(stage) \
::fesa::hht_development_probe::Scope probe_scope{ \
::fesa::hht_development_probe::stage}
#else
#define FESA_HHT_PROBE_COUNT(counter) ((void)0)
#define FESA_HHT_PROBE_SCOPE(stage) ((void)0)
#endif
#endif // FESA_ANALYSIS_HHT_DEVELOPMENT_PROBE_H_