Skip to content

Commit

Permalink
Merge pull request #149 from boostorg/144-implement-connection-usage-…
Browse files Browse the repository at this point in the history
…information

Adds connection usage information.
  • Loading branch information
mzimbres authored Aug 30, 2023
2 parents 509635f + 401dd24 commit d8cf431
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 35 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,10 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
performance improvement where one of my benchmark programs passed
from 190k/s to 473k/s.

* The connection has a new member `get_usage()` that returns the
connection usage information, such as number of bytes writen,
received etc.

### v1.4.2 (incorporates changes to conform the boost review and more)

* Adds `boost::redis::config::database_index` to make it possible to
Expand Down
8 changes: 8 additions & 0 deletions include/boost/redis/connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ class basic_connection {
void set_receive_response(Response& response)
{ impl_.set_receive_response(response); }

/// Returns connection usage information.
usage get_usage() const noexcept
{ return impl_.get_usage(); }

private:
using timer_type =
asio::basic_waitable_timer<
Expand Down Expand Up @@ -394,6 +398,10 @@ class connection {
void set_receive_response(Response& response)
{ impl_.set_receive_response(response); }

/// Returns connection usage information.
usage get_usage() const noexcept
{ return impl_.get_usage(); }

private:
void
async_run_impl(
Expand Down
73 changes: 45 additions & 28 deletions include/boost/redis/detail/connection_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/config.hpp>
#include <boost/redis/detail/runner.hpp>
#include <boost/redis/usage.hpp>

#include <boost/system.hpp>
#include <boost/asio/basic_stream_socket.hpp>
Expand All @@ -40,16 +41,15 @@
#include <type_traits>
#include <functional>

namespace boost::redis::detail {
namespace boost::redis::detail
{

template <class Conn>
struct exec_op {
using req_info_type = typename Conn::req_info;
using adapter_type = typename Conn::adapter_type;

Conn* conn_ = nullptr;
request const* req_ = nullptr;
adapter_type adapter{};
std::shared_ptr<req_info_type> info_ = nullptr;
asio::coroutine coro{};

Expand All @@ -60,14 +60,12 @@ struct exec_op {
{
// Check whether the user wants to wait for the connection to
// be stablished.
if (req_->get_config().cancel_if_not_connected && !conn_->is_open()) {
if (info_->req_->get_config().cancel_if_not_connected && !conn_->is_open()) {
BOOST_ASIO_CORO_YIELD
asio::post(std::move(self));
return self.complete(error::not_connected, 0);
}

info_ = std::allocate_shared<req_info_type>(asio::get_associated_allocator(self), *req_, adapter, conn_->get_executor());

conn_->add_request_info(info_);

EXEC_OP_WAIT:
Expand Down Expand Up @@ -329,6 +327,10 @@ class connection_base {
/// Type of the next layer
using next_layer_type = asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp, Executor>>;

using clock_type = std::chrono::steady_clock;
using clock_traits_type = asio::wait_traits<clock_type>;
using timer_type = asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;

using receiver_adapter_type = std::function<void(resp3::basic_node<std::string_view> const&, system::error_code&)>;

using this_type = connection_base<Executor>;
Expand Down Expand Up @@ -391,12 +393,14 @@ class connection_base {
{
using namespace boost::redis::adapter;
auto f = boost_redis_adapt(resp);
BOOST_ASSERT_MSG(req.size() <= f.get_supported_response_size(), "Request and response have incompatible sizes.");
BOOST_ASSERT_MSG(req.get_expected_responses() <= f.get_supported_response_size(), "Request and response have incompatible sizes.");

auto info = std::make_shared<req_info>(req, f, get_executor());

return asio::async_compose
< CompletionToken
, void(system::error_code, std::size_t)
>(redis::detail::exec_op<this_type>{this, &req, f}, token, writer_timer_);
>(exec_op<this_type>{this, info}, token, writer_timer_);
}

template <class Response, class CompletionToken>
Expand Down Expand Up @@ -427,12 +431,12 @@ class connection_base {
receive_adapter_ = adapter::detail::make_adapter_wrapper(g);
}

usage get_usage() const noexcept
{ return usage_; }

private:
using clock_type = std::chrono::steady_clock;
using clock_traits_type = asio::wait_traits<clock_type>;
using timer_type = asio::basic_waitable_timer<clock_type, clock_traits_type, executor_type>;
using receive_channel_type = asio::experimental::channel<executor_type, void(system::error_code, std::size_t)>;
using runner_type = redis::detail::runner<executor_type>;
using runner_type = runner<executor_type>;
using adapter_type = std::function<void(std::size_t, resp3::basic_node<std::string_view> const&, system::error_code&)>;

auto use_ssl() const noexcept
Expand Down Expand Up @@ -545,7 +549,7 @@ class connection_base {
, action_{action::none}
, req_{&req}
, adapter_{}
, cmds_{std::size(req)}
, expected_responses_{req.get_expected_responses()}
, status_{status::none}
, ec_{{}}
, read_size_{0}
Expand All @@ -554,7 +558,7 @@ class connection_base {

adapter_ = [this, adapter](node_type const& nd, system::error_code& ec)
{
auto const i = std::size(*req_) - cmds_;
auto const i = req_->get_expected_responses() - expected_responses_;
adapter(i, nd, ec);
};
}
Expand Down Expand Up @@ -611,7 +615,7 @@ class connection_base {
wrapped_adapter_type adapter_;

// Contains the number of commands that haven't been read yet.
std::size_t cmds_;
std::size_t expected_responses_;
status status_;

system::error_code ec_;
Expand All @@ -625,16 +629,16 @@ class connection_base {

using reqs_type = std::deque<std::shared_ptr<req_info>>;

template <class, class> friend struct redis::detail::reader_op;
template <class, class> friend struct redis::detail::writer_op;
template <class, class> friend struct redis::detail::run_op;
template <class> friend struct redis::detail::exec_op;
template <class, class, class> friend struct redis::detail::run_all_op;
template <class, class> friend struct reader_op;
template <class, class> friend struct writer_op;
template <class, class> friend struct run_op;
template <class> friend struct exec_op;
template <class, class, class> friend struct run_all_op;

void cancel_push_requests()
{
auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
return !(ptr->is_staged() && ptr->req_->size() == 0);
return !(ptr->is_staged() && ptr->req_->get_expected_responses() == 0);
});

std::for_each(point, std::end(reqs_), [](auto const& ptr) {
Expand Down Expand Up @@ -671,7 +675,7 @@ class connection_base {
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(redis::detail::reader_op<this_type, Logger>{this, l}, token, writer_timer_);
>(reader_op<this_type, Logger>{this, l}, token, writer_timer_);
}

template <class CompletionToken, class Logger>
Expand All @@ -680,7 +684,7 @@ class connection_base {
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(redis::detail::writer_op<this_type, Logger>{this, l}, token, writer_timer_);
>(writer_op<this_type, Logger>{this, l}, token, writer_timer_);
}

template <class Logger, class CompletionToken>
Expand All @@ -691,7 +695,7 @@ class connection_base {
return asio::async_compose
< CompletionToken
, void(system::error_code)
>(redis::detail::run_op<this_type, Logger>{this, l}, token, writer_timer_);
>(run_op<this_type, Logger>{this, l}, token, writer_timer_);
}

[[nodiscard]] bool coalesce_requests()
Expand All @@ -706,8 +710,11 @@ class connection_base {
// Stage the request.
write_buffer_ += ri->req_->payload();
ri->mark_staged();
usage_.commands_sent += ri->expected_responses_;
});

usage_.bytes_sent += std::size(write_buffer_);

return point != std::cend(reqs_);
}

Expand Down Expand Up @@ -758,13 +765,13 @@ class connection_base {
return
(resp3::to_type(read_buffer_.front()) == resp3::type::push)
|| reqs_.empty()
|| (!reqs_.empty() && reqs_.front()->cmds_ == 0)
|| (!reqs_.empty() && reqs_.front()->expected_responses_ == 0)
|| !is_waiting_response(); // Added to deal with MONITOR.
}

auto get_suggested_buffer_growth() const noexcept
{
return parser_.get_suggested_buffer_growth(1024);
return parser_.get_suggested_buffer_growth(4096);
}

enum class parse_result { needs_more, push, resp };
Expand All @@ -773,6 +780,14 @@ class connection_base {

parse_ret_type on_finish_parsing(parse_result t)
{
if (t == parse_result::push) {
usage_.pushes_received += 1;
usage_.push_bytes_received += parser_.get_consumed();
} else {
usage_.responses_received += 1;
usage_.response_bytes_received += parser_.get_consumed();
}

on_push_ = false;
dbuf_.consume(parser_.get_consumed());
auto const res = std::make_pair(t, parser_.get_consumed());
Expand Down Expand Up @@ -808,7 +823,7 @@ class connection_base {
BOOST_ASSERT_MSG(is_waiting_response(), "Not waiting for a response (using MONITOR command perhaps?)");
BOOST_ASSERT(!reqs_.empty());
BOOST_ASSERT(reqs_.front() != nullptr);
BOOST_ASSERT(reqs_.front()->cmds_ != 0);
BOOST_ASSERT(reqs_.front()->expected_responses_ != 0);

if (!resp3::parse(parser_, data, reqs_.front()->adapter_, ec))
return std::make_pair(parse_result::needs_more, 0);
Expand All @@ -821,7 +836,7 @@ class connection_base {

reqs_.front()->read_size_ += parser_.get_consumed();

if (--reqs_.front()->cmds_ == 0) {
if (--reqs_.front()->expected_responses_ == 0) {
// Done with this request.
reqs_.front()->proceed();
reqs_.pop_front();
Expand Down Expand Up @@ -849,6 +864,8 @@ class connection_base {
reqs_type reqs_;
resp3::parser parser_{};
bool on_push_ = false;

usage usage_;
};

} // boost::redis::detail
Expand Down
8 changes: 6 additions & 2 deletions include/boost/redis/logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ class logger {
* @ingroup high-level-api
*/
enum class level
{ /// Emergency
{
/// Disabled
disabled,

/// Emergency
emerg,

/// Alert
Expand Down Expand Up @@ -60,7 +64,7 @@ class logger {
*
* @param l Log level.
*/
logger(level l = level::info)
logger(level l = level::disabled)
: level_{l}
{}

Expand Down
12 changes: 10 additions & 2 deletions include/boost/redis/request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,12 @@ class request {
request(config cfg = config{true, false, true, true})
: cfg_{cfg} {}

//// Returns the number of responses expected for this request.
[[nodiscard]] auto get_expected_responses() const noexcept -> std::size_t
{ return expected_responses_;};

//// Returns the number of commands contained in this request.
[[nodiscard]] auto size() const noexcept -> std::size_t
[[nodiscard]] auto get_commands() const noexcept -> std::size_t
{ return commands_;};

[[nodiscard]] auto payload() const noexcept -> std::string_view
Expand All @@ -99,6 +103,7 @@ class request {
{
payload_.clear();
commands_ = 0;
expected_responses_ = 0;
has_hello_priority_ = false;
}

Expand Down Expand Up @@ -303,8 +308,10 @@ class request {
private:
void check_cmd(std::string_view cmd)
{
++commands_;

if (!detail::has_response(cmd))
++commands_;
++expected_responses_;

if (cmd == "HELLO")
has_hello_priority_ = cfg_.hello_with_priority;
Expand All @@ -313,6 +320,7 @@ class request {
config cfg_;
std::string payload_;
std::size_t commands_ = 0;
std::size_t expected_responses_ = 0;
bool has_hello_priority_ = false;
};

Expand Down
43 changes: 43 additions & 0 deletions include/boost/redis/usage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva ([email protected])
*
* Distributed under the Boost Software License, Version 1.0. (See
* accompanying file LICENSE.txt)
*/

#ifndef BOOST_REDIS_USAGE_HPP
#define BOOST_REDIS_USAGE_HPP

namespace boost::redis
{

/** @brief Connection usage information.
* @ingroup high-level-api
*
* @note: To simplify the implementation, the commands_sent and
* bytes_sent in the struct below are computed just before writing to
* the socket, which means on error they might not represent exaclty
* what has been received by the Redis server.
*/
struct usage {
/// Number of commands sent.
std::size_t commands_sent = 0;

/// Number of bytes sent.
std::size_t bytes_sent = 0;

/// Number of responses received.
std::size_t responses_received = 0;

/// Number of pushes received.
std::size_t pushes_received = 0;

/// Number of response-bytes received.
std::size_t response_bytes_received = 0;

/// Number of push-bytes received.
std::size_t push_bytes_received = 0;
};

} // boost::redis

#endif // BOOST_REDIS_USAGE_HPP
Loading

0 comments on commit d8cf431

Please sign in to comment.