Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds maliput profiler #538

Merged
merged 10 commits into from Jan 27, 2023
12 changes: 12 additions & 0 deletions CMakeLists.txt
Expand Up @@ -47,6 +47,18 @@ else()
message(STATUS "Doxygen generation - Disabled")
endif()

##############################################################################
# Profiling
##############################################################################

if(MALIPUT_PROFILER_ENABLE)
message(STATUS "Maliput Profiler - Enabled")
find_package(ignition-common3 REQUIRED COMPONENTS profiler)
add_definitions(-DMALIPUT_PROFILER_ENABLE)
else()
message(STATUS "Maliput Profiler - Disabled")
endif()

##############################################################################
# Sources
##############################################################################
Expand Down
29 changes: 29 additions & 0 deletions README.md
Expand Up @@ -78,6 +78,35 @@ For further info refer to [Source Installation on Ubuntu](https://maliput.readth

It is recommended to follow the guidelines for setting up a development workspace as described [here](https://maliput.readthedocs.io/en/latest/developer_setup.html).

## Profiling maliput

`maliput` is able to run a profiler for evaluating performance.
It is implemented via `ign-common3`'s `profiler` component.

In order to avoid any performance drop and to keep maliput dependency chain clean, this is disabled by default and the binaries aren't distributed with the profiler enabled.

For having the profiler enabled when using `maliput`, it is mandatory to install `maliput` from source.

### Steps

1. Install prerequisites. `ign-common3` is not installed via `rosdep` as the profiler is only run on demand:

```
sudo apt install libignition-common3-profiler-dev
```

2. Build `maliput` package using `MALIPUT_PROFILER_ENABLE` cmake argument:
Continue the [Source-Installation-on-Ubuntu](#Source-Installation-on-Ubuntu) instructions. The only difference is:
```
colcon build --packages-select maliput --cmake-args " -DMALIPUT_PROFILER_ENABLE=On"
```

3. Run an application with your preferred `maliput` backend. In another terminal, open the visualizer for the profiler:
```
ign_remotery_vis
```
_Note: As it opens a browser using `xdg-open`, it is recommended to have installed `xdg-utils` and a browser: (e.g: `sudo apt install -y xdg-utils firefox`)_.

## Contributing

Please see [CONTRIBUTING](https://maliput.readthedocs.io/en/latest/contributing.html) page.
Expand Down
46 changes: 18 additions & 28 deletions include/maliput/api/lane.h
Expand Up @@ -64,43 +64,43 @@ class Lane {
virtual ~Lane() = default;

/// Returns the persistent identifier.
LaneId id() const { return do_id(); }
LaneId id() const;

/// Returns the Segment to which this Lane belongs.
const Segment* segment() const { return do_segment(); }
const Segment* segment() const;

/// Returns the index of this Lane within the Segment which owns it.
int index() const { return do_index(); }
int index() const;

/// Returns a pointer to the adjacent Lane to the left of this Lane.
///
/// Left is considered the +r direction with regards to the (s,r,h) frame,
/// e.g., "to the left along the +s direction".
///
/// @returns nullptr iff parent Segment has no Lane to the left.
const Lane* to_left() const { return do_to_left(); }
const Lane* to_left() const;

/// Returns a pointer to the adjacent Lane to the right of this Lane.
///
/// Right is considered the -r direction with regards to the (s,r,h) frame,
/// e.g., "to the right along the +s direction".
///
/// @returns nullptr iff parent Segment has no Lane to the right.
const Lane* to_right() const { return do_to_right(); }
const Lane* to_right() const;

/// Returns the arc-length of the Lane along its reference curve.
///
/// The value of length() is also the maximum s-coordinate for this Lane;
/// i.e., the domain of s is [0, length()].
double length() const { return do_length(); }
double length() const;

/// Returns the nominal lateral (r) bounds for the lane as a function of s.
///
/// These are the lateral bounds for a position that is considered to be
/// "staying in the lane".
///
/// @see segment_bounds(double s) that defines the whole surface.
RBounds lane_bounds(double s) const { return do_lane_bounds(s); }
RBounds lane_bounds(double s) const;

/// Returns the lateral segment (r) bounds of the lane as a function of s.
///
Expand All @@ -110,7 +110,7 @@ class Lane {
///
/// @see lane_bounds(double s) that defines what's considered to be "staying
/// in the lane".
RBounds segment_bounds(double s) const { return do_segment_bounds(s); }
RBounds segment_bounds(double s) const;

/// Returns the elevation (`h`) bounds of the lane as a function of `(s, r)`.
///
Expand All @@ -119,14 +119,14 @@ class Lane {
///
/// `s` is within [0, `length()`] of this Lane and `r` is within
/// `lane_bounds(s)`.
HBounds elevation_bounds(double s, double r) const { return do_elevation_bounds(s, r); }
HBounds elevation_bounds(double s, double r) const;

/// Returns the InertialPosition corresponding to the given LanePosition.
///
/// @pre The s component of @p lane_pos must be in domain [0, Lane::length()].
/// @pre The r component of @p lane_pos must be in domain [Rmin, Rmax]
/// derived from Lane::segment_bounds().
InertialPosition ToInertialPosition(const LanePosition& lane_pos) const { return DoToInertialPosition(lane_pos); }
InertialPosition ToInertialPosition(const LanePosition& lane_pos) const;

/// Determines the LanePosition corresponding to InertialPosition @p inertial_pos.
/// The LanePosition is expected to be contained within the lane's boundaries.
Expand All @@ -135,9 +135,7 @@ class Lane {
/// This method guarantees that its result satisfies the condition that
/// `ToInertialPosition(result.lane_position)` is within `linear_tolerance()`
/// of `result.nearest_position`.
LanePositionResult ToLanePosition(const InertialPosition& inertial_pos) const {
return DoToLanePosition(inertial_pos);
}
LanePositionResult ToLanePosition(const InertialPosition& inertial_pos) const;

/// Determines the LanePosition corresponding to InertialPosition @p inertial_pos.
/// The LanePosition is expected to be contained within the segment's boundaries.
Expand All @@ -146,9 +144,7 @@ class Lane {
/// This method guarantees that its result satisfies the condition that
/// `ToInertialPosition(result.lane_position)` is within `linear_tolerance()`
/// of `result.nearest_position`.
LanePositionResult ToSegmentPosition(const InertialPosition& inertial_pos) const {
return DoToSegmentPosition(inertial_pos);
}
LanePositionResult ToSegmentPosition(const InertialPosition& inertial_pos) const;

// TODO(maddog@tri.global) Method to convert LanePosition to that of
// another Lane. (Should assert that both
Expand All @@ -160,39 +156,33 @@ class Lane {
/// Returns the rotation which expresses the orientation of the
/// `Lane`-frame basis at @p lane_pos with respect to the
/// `Inertial`-frame basis.
Rotation GetOrientation(const LanePosition& lane_pos) const { return DoGetOrientation(lane_pos); }
Rotation GetOrientation(const LanePosition& lane_pos) const;

/// Computes derivatives of LanePosition given a velocity vector @p velocity.
/// @p velocity is a isometric velocity vector oriented in the `Lane`-frame
/// at @p position.
///
/// @returns `Lane`-frame derivatives packed into a LanePosition struct.
LanePosition EvalMotionDerivatives(const LanePosition& position, const IsoLaneVelocity& velocity) const {
return DoEvalMotionDerivatives(position, velocity);
}
LanePosition EvalMotionDerivatives(const LanePosition& position, const IsoLaneVelocity& velocity) const;

// TODO(maddog@tri.global) Design/implement this.
// void EvalSurfaceDerivatives(...) const { return do_(); }

/// Returns the lane's BranchPoint for the end specified by @p which_end.
const BranchPoint* GetBranchPoint(const LaneEnd::Which which_end) const { return DoGetBranchPoint(which_end); }
const BranchPoint* GetBranchPoint(const LaneEnd::Which which_end) const;

/// Returns the set of LaneEnd's which connect with this lane on the
/// same side of the BranchPoint at @p which_end. At a minimum,
/// this set will include this Lane.
const LaneEndSet* GetConfluentBranches(const LaneEnd::Which which_end) const {
return DoGetConfluentBranches(which_end);
}
const LaneEndSet* GetConfluentBranches(const LaneEnd::Which which_end) const;

/// Returns the set of LaneEnd's which continue onward from this lane at the
/// BranchPoint at @p which_end.
const LaneEndSet* GetOngoingBranches(const LaneEnd::Which which_end) const { return DoGetOngoingBranches(which_end); }
const LaneEndSet* GetOngoingBranches(const LaneEnd::Which which_end) const;

/// Returns the default ongoing LaneEnd connected at @p which_end,
/// or std::nullopt if no default branch has been established at @p which_end.
std::optional<LaneEnd> GetDefaultBranch(const LaneEnd::Which which_end) const {
return DoGetDefaultBranch(which_end);
}
std::optional<LaneEnd> GetDefaultBranch(const LaneEnd::Which which_end) const;

/// Returns if this lane contains @p lane_position.
bool Contains(const LanePosition& lane_position) const;
Expand Down
25 changes: 9 additions & 16 deletions include/maliput/api/road_geometry.h
Expand Up @@ -138,9 +138,7 @@ class RoadGeometry {
// might expect an updated RoadPosition which is
// nearby (e.g., on the same Lane).
RoadPositionResult ToRoadPosition(const InertialPosition& inertial_position,
const std::optional<RoadPosition>& hint = std::nullopt) const {
return DoToRoadPosition(inertial_position, hint);
}
const std::optional<RoadPosition>& hint = std::nullopt) const;

/// Obtains all RoadPositions within @p radius of @p inertial_position. Only Lanes
/// whose segment regions include points that are within @p radius of
Expand All @@ -163,10 +161,7 @@ class RoadGeometry {
/// Note that derivative implementations may choose to violate the above
/// semantics for performance reasons. See docstrings of derivative
/// implementations for details.
std::vector<RoadPositionResult> FindRoadPositions(const InertialPosition& inertial_position, double radius) const {
MALIPUT_THROW_UNLESS(radius >= 0.);
return DoFindRoadPositions(inertial_position, radius);
}
std::vector<RoadPositionResult> FindRoadPositions(const InertialPosition& inertial_position, double radius) const;

/// Returns the tolerance guaranteed for linear measurements (positions).
double linear_tolerance() const { return do_linear_tolerance(); }
Expand Down Expand Up @@ -202,9 +197,7 @@ class RoadGeometry {
/// @throws maliput::assertion_error When any LaneSRange in `lane_s_route.ranges()` refers to
/// an unknown Lane.
std::vector<InertialPosition> SampleAheadWaypoints(const LaneSRoute& lane_s_route,
double path_length_sampling_rate) const {
return DoSampleAheadWaypoints(lane_s_route, path_length_sampling_rate);
}
double path_length_sampling_rate) const;

/// The Backend Frame is an inertial frame similar to the Inertial Frame that
/// differ one from another by an isometric transformation. This method
Expand All @@ -214,7 +207,7 @@ class RoadGeometry {
/// maliput_design.h.
///
/// @return maliput's Inertial Frame to Backend Frame translation vector.
math::Vector3 inertial_to_backend_frame_translation() const { return do_inertial_to_backend_frame_translation(); }
math::Vector3 inertial_to_backend_frame_translation() const;

// TODO(#400): Add RollPitchYaw inertial_to_backend_frame_rotation() const.

Expand Down Expand Up @@ -266,22 +259,22 @@ class RoadGeometry::IdIndex {
virtual ~IdIndex() = default;

/// Returns the Lane identified by @p id, or `nullptr` if @p id is unknown.
const Lane* GetLane(const LaneId& id) const { return DoGetLane(id); }
const Lane* GetLane(const LaneId& id) const;

// Returns all of the Lane instances.
const std::unordered_map<LaneId, const Lane*>& GetLanes() const { return DoGetLanes(); }
const std::unordered_map<LaneId, const Lane*>& GetLanes() const;

/// Returns the Segment identified by @p id, or `nullptr` if @p id is
/// unknown.
const Segment* GetSegment(const SegmentId& id) const { return DoGetSegment(id); }
const Segment* GetSegment(const SegmentId& id) const;

/// Returns the Junction identified by @p id, or `nullptr` if @p id is
/// unknown.
const Junction* GetJunction(const JunctionId& id) const { return DoGetJunction(id); }
const Junction* GetJunction(const JunctionId& id) const;

/// Returns the BranchPoint identified by @p id, or `nullptr` if @p id is
/// unknown.
const BranchPoint* GetBranchPoint(const BranchPointId& id) const { return DoGetBranchPoint(id); }
const BranchPoint* GetBranchPoint(const BranchPointId& id) const;

protected:
IdIndex() = default;
Expand Down
79 changes: 79 additions & 0 deletions include/maliput/common/profiler.h
@@ -0,0 +1,79 @@
// BSD 3-Clause License
//
// Copyright (c) 2023, Woven Planet.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once

/// @file Wraps ignition common's profiler behind maliput macros.
/// This allows us to enable/disable profiling at compile time and avoid adding a dependency on ignition common.

#ifndef MALIPUT_PROFILER_ENABLE
/// Always set this variable to some value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use @def MALIPUT_PROFILER_ENABLE ....
Which value should we use?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This states that if the MALIPUT_PROFILER_ENABLE is not defined then set it to zero. (as it is done in the very line below)

And the define is expected to bet set via preprocessor : cmake-ars --> compile definition

#define MALIPUT_PROFILER_ENABLE 0
#endif

#if MALIPUT_PROFILER_ENABLE

#define IGN_PROFILER_ENABLE 1
#include <ignition/common/Profiler.hh>

/// \brief Set name of profiled thread
#define MALIPUT_PROFILE_THREAD_NAME(name) IGN_PROFILE_THREAD_NAME(name)
/// \brief Log profiling text, if supported by implementation
#define MALIPUT_PROFILE_LOG_TEXT(name) IGN_PROFILE_LOG_TEXT(name)
/// \brief Being profiling sample
#define MALIPUT_PROFILE_BEGIN(name) IGN_PROFILE_BEGIN(name)
/// \brief End profiling sample
#define MALIPUT_PROFILE_END() IGN_PROFILE_END()

/// \brief Convenience wrapper for scoped profiling sample. Use MALIPUT_PROFILE
#define MALIPUT_PROFILE_L(name, line) IGN_PROFILE_L(name, line)
/// \brief Scoped profiling sample. Sample will stop at end of scope.
#define MALIPUT_PROFILE(name) IGN_PROFILE(name)
Comment on lines +54 to +57
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use __FUNC__ / __PRETTY_FUNCTION__ (even better but not sure if it is available in clang ) ?

That will allow to reduce the cost to implement the profiling everywhere, meaning that we don't need to review spelling mistakes and so on and the compiler will do it for us.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could create a new macro like

#define MALIPUT_PROFILE_FUNC() IGN_PROFILE(__FUNCTION__)
  • Using FUNCTION only gives you the name of the method, without carrying about namespacing. So I wouldn't use it
  • Using PRETTY_FUNCTION is possible, I tried it using compiler explorer for both gcc and clang. The firm of the method is quite complete: namespaces + return type + argument. The only issue here is that when using the profiler UI in order to analyze data it isn't that user friendly with long names, it is basically a mess. (I can try this out and post a picture here using the profiler)

Finally, we are using the profiler for analyzing entire method execution, however, it could be used por different part of a method. For example, check this usecase in gz-sim::simulationrunner

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the macro #define MALIPUT_PROFILE_FUNC() IGN_PROFILE(__FUNCTION__)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about also adding the option with pretty_function? Check 22ff1d8

In the api I am still using the MALIPUT_PROFILE macro as it is better described.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is OK, but I would use it everywhere we are using the name of the function only to avoid spelling mistakes. Also, the macro could be (I hope the compiler shakes my right hand):

#define MALIPUT_PROFILE_FUNC()  IGN_PROFILE(__FUNCTION__)
#define MALIPUT_PROFILE_FUNC_MSG(msg) IGN_PROFILE(__FUNCTION_": "#msg)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, are these __FUNCTION__ and __PRETTY_FUNCTION in any of the standards? Never saw them before. If they are not, not sure guys how you deal with this platform constraints decisions in maliput.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, are these __FUNCTION__ and __PRETTY_FUNCTION in any of the standards? Never saw them before. If they are not, not sure guys how you deal with this platform constraints decisions in maliput.

Good question, __FUNCTION__ is part of the C++ standard already. Regarding __PRETTY_FUNCTION__ apparently it is not in the standard (at least yet) however gcc and clang have support for this. Given that we state to support both gcc and clang compilers, I think that there is no harm in using them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#define MALIPUT_PROFILE_FUNC_MSG(msg) IGN_PROFILE(__FUNCTION_": "#msg)

The issue with #define MALIPUT_PROFILE_FUNC_MSG(msg) IGN_PROFILE(__FUNCTION_": "#msg) is the argument:
IGN_PROFILER is expecting a char * and :

  • __FUNCTION__": "#msg
  • __FUNCTION__ + ": " + #msg
    aren't correct.

There is an alternative of concatenating as a string and converting to const char * but making this conversion/operation every time that the method is called isn't ideal.
#define MALIPUT_PROFILE_FUNC_MSG(msg) IGN_PROFILE((static_cast<std::string>(__FUNCTION__) + std::string(": ") + static_cast<std::string>(msg)).c_str())


I replaced all the occurrences with MALIPUT_PROFILE_FUNC() Check 209cc9d
image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Let's keep it as is then.

/// \brief Scoped profiling sample as MALIPUT_PROFILE.
/// __FUNCTION__ is used as the sample name.
#define MALIPUT_PROFILE_FUNC() IGN_PROFILE(__FUNCTION__)
/// \brief Scoped profiling sample as MALIPUT_PROFILE.
/// __PRETTY_FUNCTION__ is used as the sample name.
#define MALIPUT_PROFILE_PRETTY_FUNC() IGN_PROFILE(__PRETTY_FUNCTION__)

/// \brief Macro to determine if profiler is enabled and has an implementation.
#define MALIPUT_PROFILER_VALID IGN_PROFILER_VALID
#else

#define MALIPUT_PROFILE_THREAD_NAME(name) ((void)name)
#define MALIPUT_PROFILE_LOG_TEXT(name) ((void)name)
#define MALIPUT_PROFILE_BEGIN(name) ((void)name)
#define MALIPUT_PROFILE_END() ((void)0)
#define MALIPUT_PROFILE_L(name, line) ((void)name)
#define MALIPUT_PROFILE(name) ((void)name)
#define MALIPUT_PROFILE_FUNC() ((void)0)
#define MALIPUT_PROFILE_PRETTY_FUNC() ((void)0)
#define MALIPUT_PROFILER_VALID MALIPUT_PROFILER_ENABLE

#endif // MALIPUT_PROFILER_ENABLE
7 changes: 7 additions & 0 deletions src/api/CMakeLists.txt
Expand Up @@ -40,6 +40,13 @@ target_link_libraries(api
maliput::math
)

if(MALIPUT_PROFILER_ENABLE)
target_link_libraries(api
PRIVATE
ignition-common3::profiler
)
endif()

##############################################################################
# Export
##############################################################################
Expand Down