Move profiler logic into a separate class.

PiperOrigin-RevId: 834200782
Change-Id: Ida050a5c055237c4e0a0c725a1277c9239a89538
This commit is contained in:
Haroon Qureshi
2025-11-19 02:34:24 -08:00
committed by Copybara-Service
parent e18e35f967
commit a2fd5fdd4a
6 changed files with 232 additions and 163 deletions
+6 -146
View File
@@ -54,8 +54,6 @@ namespace mujoco::studio {
// - sensor graph
// - convergence profiler
// - solver iteration profiler
// - "passive" mode
// - async physics
static constexpr toolbox::Window::Config kWindowConfig = {
#ifdef EMSCRIPTEN
@@ -150,7 +148,7 @@ App::App(int width, int height, std::string ini_path,
mjv_defaultOption(&vis_options_);
LoadSettings();
ClearProfilerData();
profiler_.Clear();
#ifdef USE_CLASSIC_OPENGL
ImGui_ImplOpenGL3_Init();
@@ -166,7 +164,7 @@ void App::OnModelLoaded(std::string_view model_file) {
renderer_->Init(Model());
tmp_ = UiTempState();
mjv_defaultOption(&vis_options_);
ClearProfilerData();
profiler_.Clear();
std::string base_path = "/";
std::string model_name = "model";
@@ -210,7 +208,7 @@ bool App::Update() {
// simulation itself will only update if it is not paused.
if (!tmp_.modal_open) {
if (physics_->Update(&perturb_)) {
UpdateProfilerData();
profiler_.Update(Model(), Data());
}
}
@@ -589,99 +587,6 @@ void App::SetCamera(int idx) {
}
}
void App::ClearProfilerData() {
constexpr int kProfilerMaxFrames = 200;
cpu_total_.clear();
cpu_collision_.clear();
cpu_prepare_.clear();
cpu_solve_.clear();
cpu_other_.clear();
dim_dof_.clear();
dim_body_.clear();
dim_constraint_.clear();
dim_sqrt_nnz_.clear();
dim_contact_.clear();
dim_iteration_.clear();
cpu_total_.resize(kProfilerMaxFrames, 0);
cpu_collision_.resize(kProfilerMaxFrames, 0);
cpu_prepare_.resize(kProfilerMaxFrames, 0);
cpu_solve_.resize(kProfilerMaxFrames, 0);
cpu_other_.resize(kProfilerMaxFrames, 0);
dim_dof_.resize(kProfilerMaxFrames, 0);
dim_body_.resize(kProfilerMaxFrames, 0);
dim_constraint_.resize(kProfilerMaxFrames, 0);
dim_sqrt_nnz_.resize(kProfilerMaxFrames, 0);
dim_contact_.resize(kProfilerMaxFrames, 0);
dim_iteration_.resize(kProfilerMaxFrames, 0);
}
void App::UpdateProfilerData() {
// CPU timers.
mjtNum total = Data()->timer[mjTIMER_STEP].duration;
mjtNum number = static_cast<mjtNum>(Data()->timer[mjTIMER_STEP].number);
if (number == 0.0) {
total = Data()->timer[mjTIMER_FORWARD].duration;
number = static_cast<mjtNum>(Data()->timer[mjTIMER_FORWARD].number);
}
if (number == 0.0) {
// This can happen if the simulation is paused.
return;
}
cpu_total_.erase(cpu_total_.begin());
cpu_total_.push_back(total / number);
mjtNum collision = Data()->timer[mjTIMER_POS_COLLISION].duration / number;
cpu_collision_.erase(cpu_collision_.begin());
cpu_collision_.push_back(collision);
mjtNum prepare = (Data()->timer[mjTIMER_POS_MAKE].duration / number) +
(Data()->timer[mjTIMER_POS_PROJECT].duration / number);
cpu_prepare_.erase(cpu_prepare_.begin());
cpu_prepare_.push_back(prepare);
mjtNum solve = Data()->timer[mjTIMER_CONSTRAINT].duration / number;
cpu_solve_.erase(cpu_solve_.begin());
cpu_solve_.push_back(solve);
mjtNum other = total - collision - prepare - solve;
cpu_other_.erase(cpu_other_.begin());
cpu_other_.push_back(other);
// Solver diagnostics.
mjtNum sqrt_nnz = 0;
int solver_niter = 0;
const int nisland = Data()->nefc ? mjMAX(1, mjMIN(Data()->nisland, mjNISLAND)) : 0;
for (int island=0; island < nisland; island++) {
sqrt_nnz += Data()->solver_nnz[island];
solver_niter += Data()->solver_niter[island];
}
sqrt_nnz = mju_sqrt(sqrt_nnz);
dim_dof_.erase(dim_dof_.begin());
int nv = (Model()->opt.enableflags & mjENBL_SLEEP) ? Data()->nv_awake
: Model()->nv;
dim_dof_.push_back(nv);
dim_body_.erase(dim_body_.begin());
int nbody = (Model()->opt.enableflags & mjENBL_SLEEP) ? Data()->nbody_awake
: Model()->nbody;
dim_body_.push_back(nbody);
dim_constraint_.erase(dim_constraint_.begin());
dim_constraint_.push_back(Data()->nefc);
dim_sqrt_nnz_.erase(dim_sqrt_nnz_.begin());
dim_sqrt_nnz_.push_back(sqrt_nnz);
dim_contact_.erase(dim_contact_.begin());
dim_contact_.push_back(Data()->ncon);
dim_iteration_.erase(dim_iteration_.begin());
dim_iteration_.push_back(static_cast<float>(solver_niter) / nisland);
}
void App::BuildGuiWithWindows() {
if (!tmp_.show_ui_lhs) {
return;
@@ -749,7 +654,7 @@ void App::BuildGuiWithWindows() {
}
if (ui_.profiler) {
if (ImGui::Begin("Profiler", &ui_.profiler)) {
ProfilerGui();
profiler_.Gui();
}
ImGui::End();
}
@@ -838,7 +743,7 @@ void App::BuildGuiWithSections() {
Section("Joints", section_flags, [this] { JointsGui(); });
Section("Controls", section_flags, [this] { ControlsGui(); });
Section("Sensor", section_flags, [this] { SensorGui(); });
Section("Profiler", section_flags, [this] { ProfilerGui(); });
Section("Profiler", section_flags, [this] { profiler_.Gui(); });
Section("State", section_flags, [this] { StateGui(); }, .5f);
// Cache the size and position of the window before we end it
@@ -1156,7 +1061,7 @@ void App::MainMenuGui() {
}
if (ImGui::MenuItem("Profiler", "", ui_.profiler)) {
ToggleWindow(ui_.profiler);
ClearProfilerData();
profiler_.Clear();
}
if (ImGui::MenuItem("Sensor", "", ui_.sensor)) {
ToggleWindow(ui_.sensor);
@@ -1599,51 +1504,6 @@ void App::StateGui() {
}
}
void App::ProfilerGui() {
const int plot_flags = 0;
if (ImPlot::BeginPlot("CPU Time", ImVec2(-1, 0), plot_flags)) {
ImPlot::SetupAxis(ImAxis_X1, "frame", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxis(ImAxis_Y1, "msec", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.2f");
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
ImPlot::SetupFinish();
ImPlot::PlotLine("total", cpu_total_.data(), cpu_total_.size(), 1,
-(int)cpu_total_.size());
ImPlot::PlotLine("prepare", cpu_prepare_.data(), cpu_prepare_.size(), 1,
-(int)cpu_prepare_.size());
ImPlot::PlotLine("solve", cpu_solve_.data(), cpu_solve_.size(), 1,
-(int)cpu_solve_.size());
ImPlot::PlotLine("collision", cpu_collision_.data(), cpu_collision_.size(),
1, -(int)cpu_collision_.size());
ImPlot::PlotLine("other", cpu_other_.data(), cpu_other_.size(), 1,
-(int)cpu_other_.size());
ImPlot::EndPlot();
}
if (ImPlot::BeginPlot("Dimensions", ImVec2(-1, 0), plot_flags)) {
ImPlot::SetupAxis(ImAxis_X1, "frame", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxis(ImAxis_Y1, "count", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.0f");
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
ImPlot::SetupFinish();
ImPlot::PlotLine("dof", dim_dof_.data(), dim_dof_.size(), 1,
-(int)dim_dof_.size());
ImPlot::PlotLine("body", dim_body_.data(), dim_body_.size(), 1,
-(int)dim_body_.size());
ImPlot::PlotLine("constraint", dim_constraint_.data(),
dim_constraint_.size(), 1, -(int)dim_constraint_.size());
ImPlot::PlotLine("sqrt(nnz)", dim_sqrt_nnz_.data(), dim_sqrt_nnz_.size(), 1,
-(int)dim_sqrt_nnz_.size());
ImPlot::PlotLine("contact", dim_contact_.data(), dim_contact_.size(), 1,
-(int)dim_contact_.size());
ImPlot::PlotLine("iteration", dim_iteration_.data(), dim_iteration_.size(),
1, -(int)dim_iteration_.size());
ImPlot::EndPlot();
}
}
void App::InfoGui() {
const int num_islands = std::clamp(Data()->nisland, 1, mjNISLAND);
+3 -16
View File
@@ -28,6 +28,7 @@
#include "experimental/toolbox/helpers.h"
#include "experimental/toolbox/physics.h"
#include "experimental/toolbox/renderer.h"
#include "experimental/toolbox/sim_profiler.h"
#include "experimental/toolbox/window.h"
namespace mujoco::studio {
@@ -141,9 +142,6 @@ class App {
void HandleMouseEvents();
void HandleKeyboardEvents();
void ClearProfilerData();
void UpdateProfilerData();
void BuildGuiWithWindows();
void BuildGuiWithSections();
@@ -174,23 +172,12 @@ class App {
std::unique_ptr<toolbox::Physics> physics_;
toolbox::LoadAssetFn load_asset_fn_;
toolbox::SimProfiler profiler_;
mjvCamera camera_;
mjvPerturb perturb_;
mjvOption vis_options_;
// profiler data
std::vector<float> cpu_total_;
std::vector<float> cpu_collision_;
std::vector<float> cpu_prepare_;
std::vector<float> cpu_solve_;
std::vector<float> cpu_other_;
std::vector<float> dim_dof_;
std::vector<float> dim_body_;
std::vector<float> dim_constraint_;
std::vector<float> dim_sqrt_nnz_;
std::vector<float> dim_contact_;
std::vector<float> dim_iteration_;
UiState ui_;
UiTempState tmp_;
};
+2
View File
@@ -30,6 +30,8 @@ target_sources(${MUJOCO_TOOLBOX_TARGET_NAME}
physics.h
renderer.cc
renderer.h
sim_profiler.cc
sim_profiler.h
step_control.cc
step_control.h
window.cc
+166
View File
@@ -0,0 +1,166 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "experimental/toolbox/sim_profiler.h"
#include <mujoco/mujoco.h>
#include <imgui.h>
#include <implot.h>
namespace mujoco::toolbox {
SimProfiler::SimProfiler() {
Clear();
}
void SimProfiler::Clear() {
constexpr int kProfilerMaxFrames = 200;
cpu_total_.clear();
cpu_collision_.clear();
cpu_prepare_.clear();
cpu_solve_.clear();
cpu_other_.clear();
dim_dof_.clear();
dim_body_.clear();
dim_constraint_.clear();
dim_sqrt_nnz_.clear();
dim_contact_.clear();
dim_iteration_.clear();
cpu_total_.resize(kProfilerMaxFrames, 0);
cpu_collision_.resize(kProfilerMaxFrames, 0);
cpu_prepare_.resize(kProfilerMaxFrames, 0);
cpu_solve_.resize(kProfilerMaxFrames, 0);
cpu_other_.resize(kProfilerMaxFrames, 0);
dim_dof_.resize(kProfilerMaxFrames, 0);
dim_body_.resize(kProfilerMaxFrames, 0);
dim_constraint_.resize(kProfilerMaxFrames, 0);
dim_sqrt_nnz_.resize(kProfilerMaxFrames, 0);
dim_contact_.resize(kProfilerMaxFrames, 0);
dim_iteration_.resize(kProfilerMaxFrames, 0);
}
void SimProfiler::Update(const mjModel* model, const mjData* data) {
// CPU timers.
mjtNum total = data->timer[mjTIMER_STEP].duration;
mjtNum number = static_cast<mjtNum>(data->timer[mjTIMER_STEP].number);
if (number == 0.0) {
total = data->timer[mjTIMER_FORWARD].duration;
number = static_cast<mjtNum>(data->timer[mjTIMER_FORWARD].number);
}
if (number == 0.0) {
// This can happen if the simulation is paused.
return;
}
cpu_total_.erase(cpu_total_.begin());
cpu_total_.push_back(total / number);
mjtNum collision = data->timer[mjTIMER_POS_COLLISION].duration / number;
cpu_collision_.erase(cpu_collision_.begin());
cpu_collision_.push_back(collision);
mjtNum prepare = (data->timer[mjTIMER_POS_MAKE].duration / number) +
(data->timer[mjTIMER_POS_PROJECT].duration / number);
cpu_prepare_.erase(cpu_prepare_.begin());
cpu_prepare_.push_back(prepare);
mjtNum solve = data->timer[mjTIMER_CONSTRAINT].duration / number;
cpu_solve_.erase(cpu_solve_.begin());
cpu_solve_.push_back(solve);
mjtNum other = total - collision - prepare - solve;
cpu_other_.erase(cpu_other_.begin());
cpu_other_.push_back(other);
// Solver diagnostics.
mjtNum sqrt_nnz = 0;
int solver_niter = 0;
const int nisland = data->nefc ? mjMAX(1, mjMIN(data->nisland, mjNISLAND)) : 0;
for (int island=0; island < nisland; island++) {
sqrt_nnz += data->solver_nnz[island];
solver_niter += data->solver_niter[island];
}
sqrt_nnz = mju_sqrt(sqrt_nnz);
dim_dof_.erase(dim_dof_.begin());
int nv = (model->opt.enableflags & mjENBL_SLEEP) ? data->nv_awake
: model->nv;
dim_dof_.push_back(nv);
dim_body_.erase(dim_body_.begin());
int nbody = (model->opt.enableflags & mjENBL_SLEEP) ? data->nbody_awake
: model->nbody;
dim_body_.push_back(nbody);
dim_constraint_.erase(dim_constraint_.begin());
dim_constraint_.push_back(data->nefc);
dim_sqrt_nnz_.erase(dim_sqrt_nnz_.begin());
dim_sqrt_nnz_.push_back(sqrt_nnz);
dim_contact_.erase(dim_contact_.begin());
dim_contact_.push_back(data->ncon);
dim_iteration_.erase(dim_iteration_.begin());
dim_iteration_.push_back(static_cast<float>(solver_niter) / nisland);
}
void SimProfiler::Gui() {
const int plot_flags = 0;
if (ImPlot::BeginPlot("CPU Time", ImVec2(-1, 0), plot_flags)) {
ImPlot::SetupAxis(ImAxis_X1, "frame", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxis(ImAxis_Y1, "msec", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.2f");
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
ImPlot::SetupFinish();
ImPlot::PlotLine("total", cpu_total_.data(), cpu_total_.size(), 1,
-(int)cpu_total_.size());
ImPlot::PlotLine("prepare", cpu_prepare_.data(), cpu_prepare_.size(), 1,
-(int)cpu_prepare_.size());
ImPlot::PlotLine("solve", cpu_solve_.data(), cpu_solve_.size(), 1,
-(int)cpu_solve_.size());
ImPlot::PlotLine("collision", cpu_collision_.data(), cpu_collision_.size(),
1, -(int)cpu_collision_.size());
ImPlot::PlotLine("other", cpu_other_.data(), cpu_other_.size(), 1,
-(int)cpu_other_.size());
ImPlot::EndPlot();
}
if (ImPlot::BeginPlot("Dimensions", ImVec2(-1, 0), plot_flags)) {
ImPlot::SetupAxis(ImAxis_X1, "frame", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxis(ImAxis_Y1, "count", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.0f");
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
ImPlot::SetupFinish();
ImPlot::PlotLine("dof", dim_dof_.data(), dim_dof_.size(), 1,
-(int)dim_dof_.size());
ImPlot::PlotLine("body", dim_body_.data(), dim_body_.size(), 1,
-(int)dim_body_.size());
ImPlot::PlotLine("constraint", dim_constraint_.data(),
dim_constraint_.size(), 1, -(int)dim_constraint_.size());
ImPlot::PlotLine("sqrt(nnz)", dim_sqrt_nnz_.data(), dim_sqrt_nnz_.size(), 1,
-(int)dim_sqrt_nnz_.size());
ImPlot::PlotLine("contact", dim_contact_.data(), dim_contact_.size(), 1,
-(int)dim_contact_.size());
ImPlot::PlotLine("iteration", dim_iteration_.data(), dim_iteration_.size(),
1, -(int)dim_iteration_.size());
ImPlot::EndPlot();
}
}
} // namespace mujoco::toolbox
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_TOOLBOX_SIM_PROFILER_H_
#define MUJOCO_SRC_EXPERIMENTAL_TOOLBOX_SIM_PROFILER_H_
#include <vector>
#include <mujoco/mujoco.h>
namespace mujoco::toolbox {
// Collects and displays profiling data for MuJoCo simulations.
class SimProfiler {
public:
SimProfiler();
// Clears all captured profiling data.
void Clear();
// Updates the profiling data with the latest simulation data.
void Update(const mjModel* model, const mjData* data);
// Displays the profiling data using ImPlot.
void Gui();
private:
std::vector<float> cpu_total_;
std::vector<float> cpu_collision_;
std::vector<float> cpu_prepare_;
std::vector<float> cpu_solve_;
std::vector<float> cpu_other_;
std::vector<float> dim_dof_;
std::vector<float> dim_body_;
std::vector<float> dim_constraint_;
std::vector<float> dim_sqrt_nnz_;
std::vector<float> dim_contact_;
std::vector<float> dim_iteration_;
};
} // namespace mujoco::toolbox
#endif // MUJOCO_SRC_EXPERIMENTAL_TOOLBOX_SIM_PROFILER_H_
+1 -1
View File
@@ -53,7 +53,7 @@ static void InitImGui(SDL_Window* window, const LoadAssetFn& load_asset_fn) {
main_cfg.GlyphExtraSpacing.x = 0.3f;
auto main_font = load_asset_fn("OpenSans-Regular.ttf");
io.Fonts->AddFontFromMemoryTTF(main_font.data(), main_font.size(), 18.f,
io.Fonts->AddFontFromMemoryTTF(main_font.data(), main_font.size(), 20.f,
&main_cfg);
ImFontConfig icon_cfg;