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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a request time metric to redis upstream #7890

Merged
merged 60 commits into from
Sep 10, 2019
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e6731a9
Redo test
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 12, 2019
35b6128
formatting
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 12, 2019
028924c
Merge branch 'master' into upstream_rq_time-metric
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 13, 2019
bad7ea4
fix ws
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 13, 2019
a1288f1
add docs
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 13, 2019
44cc44d
Add skeleton of stats stuff
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 14, 2019
723a8fd
fix
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 14, 2019
2c169e2
record command value
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 14, 2019
8b05c9b
fix formatting
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 14, 2019
e74161a
update
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 15, 2019
8336e68
update
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 15, 2019
359679e
patched up a bit
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 15, 2019
506e724
fix counter
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 15, 2019
054400b
Revert moving RedisClusterStats
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 15, 2019
e5770f1
disable timer
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 15, 2019
a110c26
Add config setting to enable stats
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 15, 2019
4f72856
mend
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 15, 2019
7af7c7c
fix
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 16, 2019
e06eb57
try to fix
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 16, 2019
04314f5
revert
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 16, 2019
3e8636e
fix stats
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 16, 2019
6c5a8ca
TODO: Remove prefix?
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 16, 2019
89c1b26
improve stat prefix?
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 16, 2019
b4e5944
fix ws/format
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 16, 2019
ab8047b
fix prefixing
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 16, 2019
2441b00
update to get command latency
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 20, 2019
90afb6f
update per henry
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 20, 2019
94c0a7e
Merge branch 'master' into upstream_rq_time-metric
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 20, 2019
c945058
remove uneeded millis option, make latency timer have latency suffix
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 20, 2019
54b168f
lowercase commands for metrics
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 20, 2019
0f93e03
bit more docs
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 20, 2019
7f0001c
more docs
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 20, 2019
d04ef29
fix docs
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 20, 2019
4a028ae
address henry comments
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 21, 2019
b25b4d2
simplify slightly
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 21, 2019
becaba2
use builtins
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 22, 2019
f6ec8bf
updated
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 22, 2019
703d0fe
Kick CI
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 22, 2019
fe218a7
Kick CI
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 22, 2019
7f70392
Per jmarantz comments
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 23, 2019
66cb5fd
respond to comments
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 26, 2019
3ce76bf
Merge branch 'master' into upstream_rq_time-metric
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Aug 26, 2019
7877ee7
Merge branch 'master' into upstream_rq_time-metric
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 5, 2019
6d7c4b5
Update for jmarantz new comments, part 1, basics
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 5, 2019
8d9d3d1
Update for jmarantz new comments, part 2, use stat_name_pool and stat…
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 5, 2019
bc5a96f
fix formatting
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 5, 2019
4e82245
update stat_name to command
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 5, 2019
abf2297
attempt #1, move stats to config
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 5, 2019
841b4a3
guard command timer on stats enabled
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 6, 2019
2520b40
fix test override
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 6, 2019
f76c164
Update command stats ptr name and remove redundant temp
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 6, 2019
140d9e5
Pass scope around, and store symboltable on command stats
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 6, 2019
414e5b3
Remove the move part
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 6, 2019
9ec13ee
refactor boolean enable flag, since now a global redis stats
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 6, 2019
4ad0e2c
remove some unneeded moves
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 6, 2019
228af54
fix clang tidy errors
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 6, 2019
f2b036f
Merge branch 'master' into upstream_rq_time-metric
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 9, 2019
d578a4b
Add TODO comment for singleton and remove duplicate params
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 10, 2019
6904da0
fix format
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 10, 2019
4fe9265
Update per mklein comments
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg Sep 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ message RedisProxy {
// downstream unchanged. This limit defaults to 100.
google.protobuf.UInt32Value max_upstream_unknown_connections = 6;

// Enable per-command statistics per upstream cluster, in addition to the filter level aggregate
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
// count.
bool enable_command_stats = 8;

// ReadPolicy controls how Envoy routes read commands to Redis nodes. This is currently
// supported for Redis Cluster. All ReadPolicy settings except MASTER may return stale data
// because replication is asynchronous and requires some delay. You need to ensure that your
Expand Down
5 changes: 5 additions & 0 deletions docs/root/intro/arch_overview/other_protocols/redis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ Every Redis cluster has its own extra statistics tree rooted at *cluster.<name>.

max_upstream_unknown_connections_reached, Counter, Total number of times that an upstream connection to an unknown host is not created after redirection having reached the connection pool's max_upstream_unknown_connections limit
upstream_cx_drained, Counter, Total number of upstream connections drained of active requests before being closed
upstream_commands.upstream_rq_time, Histogram, Histogram of upstream request times for all types of requests
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
upstream_commands.[command].success, Counter, Total number of successful requests for a specific Redis command
upstream_commands.[command].error, Counter, Total number of failed or cancelled requests for a specific Redis command
upstream_commands.[command].total, Counter, Total number of requests for a specific Redis command (sum of success and error)
upstream_commands.[command].latency, Histogram, Latency of requests for a specific Redis command

Supported commands
------------------
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Version history
* listeners: added :ref:`continue_on_listener_filters_timeout <envoy_api_field_Listener.continue_on_listener_filters_timeout>` to configure whether a listener will still create a connection when listener filters time out.
* listeners: added :ref:`HTTP inspector listener filter <config_listener_filters_http_inspector>`.
* redis: added :ref:`read_policy <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProxy.ConnPoolSettings.read_policy>` to allow reading from redis replicas for Redis Cluster deployments.
* redis: added :ref:`enable_command_stats <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProxy.ConnPoolSettings.enable_command_stats>` to enable per command statistics for upstream clusters.
* rbac: added support for DNS SAN as :ref:`principal_name <envoy_api_field_config.rbac.v2.Principal.Authenticated.principal_name>`.
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
* lua: extended `httpCall()` and `respond()` APIs to accept headers with entry values that can be a string or table of strings.
* performance: new buffer implementation enabled by default (to disable add "--use-libevent-buffers 1" to the command-line arguments when starting Envoy).
Expand Down
1 change: 1 addition & 0 deletions source/extensions/clusters/redis/redis_cluster.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl {
uint32_t maxBufferSizeBeforeFlush() const override { return 0; }
std::chrono::milliseconds bufferFlushTimeoutInMs() const override { return buffer_timeout_; }
uint32_t maxUpstreamUnknownConnections() const override { return 0; }
bool enableCommandStats() const override { return false; }
// This is effectively not in used for making the "Cluster Slots" calls.
// since we call cluster slots on both the master and slaves, ANY is more appropriate here.
Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy readPolicy() const override {
Expand Down
16 changes: 16 additions & 0 deletions source/extensions/filters/network/common/redis/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ envoy_cc_library(
deps = [
":client_interface",
":codec_lib",
":redis_command_stats_lib",
"//include/envoy/router:router_interface",
"//include/envoy/stats:timespan",
"//include/envoy/thread_local:thread_local_interface",
"//include/envoy/upstream:cluster_manager_interface",
"//source/common/buffer:buffer_lib",
Expand All @@ -78,3 +80,17 @@ envoy_cc_library(
":codec_lib",
],
)

envoy_cc_library(
name = "redis_command_stats_lib",
srcs = ["redis_command_stats.cc"],
hdrs = ["redis_command_stats.h"],
deps = [
":codec_interface",
":supported_commands_lib",
"//include/envoy/stats:stats_interface",
"//include/envoy/stats:timespan",
"//source/common/common:utility_lib",
"//source/common/stats:symbol_table_lib",
],
)
5 changes: 5 additions & 0 deletions source/extensions/filters/network/common/redis/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ class Config {
*/
virtual uint32_t maxUpstreamUnknownConnections() const PURE;

/**
* @return when enabled, upstream cluster per-command statistics will be recorded.
*/
virtual bool enableCommandStats() const PURE;

/**
* @return the read policy the proxy should use.
*/
Expand Down
42 changes: 33 additions & 9 deletions source/extensions/filters/network/common/redis/client_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ ConfigImpl::ConfigImpl(
3)), // Default timeout is 3ms. If max_buffer_size_before_flush is zero, this is not used
// as the buffer is flushed on each request immediately.
max_upstream_unknown_connections_(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_upstream_unknown_connections, 100)) {
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_upstream_unknown_connections, 100)),
enable_command_stats_(config.enable_command_stats()) {
switch (config.read_policy()) {
case envoy::config::filter::network::redis_proxy::v2::
RedisProxy_ConnPoolSettings_ReadPolicy_MASTER:
Expand Down Expand Up @@ -49,9 +50,11 @@ ConfigImpl::ConfigImpl(
ClientPtr ClientImpl::create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher,
EncoderPtr&& encoder, DecoderFactory& decoder_factory,
const Config& config) {

std::unique_ptr<ClientImpl> client(
new ClientImpl(host, dispatcher, std::move(encoder), decoder_factory, config));
auto redis_command_stats = std::make_shared<RedisCommandStats>(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we want to hold RedisCommandStats in your Config, not in ClientImpl. It will be expensive to create, basically read-only, and there could be more than one Redis client in a process.

Choose a reason for hiding this comment

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

I will give this a try tmw.

host->cluster().statsScope(), "upstream_commands", config.enableCommandStats());
std::unique_ptr<ClientImpl> client(new ClientImpl(host, dispatcher, std::move(encoder),
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
decoder_factory, config,
std::move(redis_command_stats)));
client->connection_ = host->createConnection(dispatcher, nullptr, nullptr).connection_;
client->connection_->addConnectionCallbacks(*client);
client->connection_->addReadFilter(Network::ReadFilterSharedPtr{new UpstreamReadFilter(*client)});
Expand All @@ -61,11 +64,13 @@ ClientPtr ClientImpl::create(Upstream::HostConstSharedPtr host, Event::Dispatche
}

ClientImpl::ClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher,
EncoderPtr&& encoder, DecoderFactory& decoder_factory, const Config& config)
EncoderPtr&& encoder, DecoderFactory& decoder_factory, const Config& config,
RedisCommandStatsPtr&& redis_command_stats)
: host_(host), encoder_(std::move(encoder)), decoder_(decoder_factory.create(*this)),
config_(config),
connect_or_op_timer_(dispatcher.createTimer([this]() -> void { onConnectOrOpTimeout(); })),
flush_timer_(dispatcher.createTimer([this]() -> void { flushBufferAndResetTimer(); })) {
flush_timer_(dispatcher.createTimer([this]() -> void { flushBufferAndResetTimer(); })),
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
time_source_(dispatcher.timeSource()), redis_command_stats_(redis_command_stats) {
host->cluster().stats().upstream_cx_total_.inc();
host->stats().cx_total_.inc();
host->cluster().stats().upstream_cx_active_.inc();
Expand Down Expand Up @@ -94,9 +99,15 @@ PoolRequest* ClientImpl::makeRequest(const RespValue& request, PoolCallbacks& ca

const bool empty_buffer = encoder_buffer_.length() == 0;

pending_requests_.emplace_back(*this, callbacks);
std::string to_lower_command(redis_command_stats_->getCommandFromRequest(request));
Copy link
Contributor

Choose a reason for hiding this comment

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

hmmm....should we do the lower-casing inside getCommandFromRequest? Or is it useful in some contexts to have non-lower-cased names?

I think from reading this PR that you don't use the command name at all unless stats are enabled. Is that right? In that case I think we should skip the lower-casing and string-copying if enableCommandsStats() is false.

Choose a reason for hiding this comment

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

Ok, in my update to the PR, I've added an "unused" statname, and used that for this ie

  Stats::StatName stat_name;
  if (config_.enableCommandStats()) {
    // Only lowercase command and get stat_name if we enable command stats
    std::string to_lower_command(redis_command_stats_->getCommandFromRequest(request));
    to_lower_table_.toLowerCase(to_lower_command);
    stat_name = redis_command_stats_->getStatName(to_lower_command);

    redis_command_stats_->updateStatsTotal(stat_name);
  } else {
    // If disabled, we use a placeholder stat name "unused" that is not used
    stat_name = redis_command_stats_->getUnusedStatName();
  }

to_lower_table_.toLowerCase(to_lower_command);
pending_requests_.emplace_back(*this, callbacks, to_lower_command);
encoder_->encode(request, encoder_buffer_);

if (config_.enableCommandStats()) {
redis_command_stats_->updateStatsTotal(to_lower_command);
}

// If buffer is full, flush. If the buffer was empty before the request, start the timer.
if (encoder_buffer_.length() >= config_.maxBufferSizeBeforeFlush()) {
flushBufferAndResetTimer();
Expand Down Expand Up @@ -186,6 +197,14 @@ void ClientImpl::onRespValue(RespValuePtr&& value) {
ASSERT(!pending_requests_.empty());
PendingRequest& request = pending_requests_.front();
const bool canceled = request.canceled_;

if (redis_command_stats_->enabled()) {
bool success = !canceled && (value->type() != Common::Redis::RespType::Error);
redis_command_stats_->updateStats(success, request.command_);
request.command_request_timer_->complete();
}
request.aggregate_request_timer_->complete();

PoolCallbacks& callbacks = request.callbacks_;

// We need to ensure the request is popped before calling the callback, since the callback might
Expand Down Expand Up @@ -225,8 +244,13 @@ void ClientImpl::onRespValue(RespValuePtr&& value) {
putOutlierEvent(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS);
}

ClientImpl::PendingRequest::PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks)
: parent_(parent), callbacks_(callbacks) {
ClientImpl::PendingRequest::PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks,
std::string command)
: parent_(parent), callbacks_(callbacks), command_{command},
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
aggregate_request_timer_(
parent_.redis_command_stats_->createAggregateTimer(parent_.time_source_)),
command_request_timer_(
parent_.redis_command_stats_->createCommandTimer(command_, parent_.time_source_)) {
parent.host_->cluster().stats().upstream_rq_total_.inc();
parent.host_->stats().rq_total_.inc();
parent.host_->cluster().stats().upstream_rq_active_.inc();
Expand Down
16 changes: 14 additions & 2 deletions source/extensions/filters/network/common/redis/client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
#include <chrono>

#include "envoy/config/filter/network/redis_proxy/v2/redis_proxy.pb.h"
#include "envoy/stats/timespan.h"
#include "envoy/thread_local/thread_local.h"
#include "envoy/upstream/cluster_manager.h"

#include "common/buffer/buffer_impl.h"
#include "common/common/hash.h"
#include "common/common/to_lower_table.h"
#include "common/network/filter_impl.h"
#include "common/protobuf/utility.h"
#include "common/singleton/const_singleton.h"
#include "common/upstream/load_balancer_impl.h"
#include "common/upstream/upstream_impl.h"

#include "extensions/filters/network/common/redis/client.h"
#include "extensions/filters/network/common/redis/redis_command_stats.h"

namespace Envoy {
namespace Extensions {
Expand Down Expand Up @@ -49,6 +52,7 @@ class ConfigImpl : public Config {
uint32_t maxUpstreamUnknownConnections() const override {
return max_upstream_unknown_connections_;
}
bool enableCommandStats() const override { return enable_command_stats_; }
ReadPolicy readPolicy() const override { return read_policy_; }

private:
Expand All @@ -58,6 +62,7 @@ class ConfigImpl : public Config {
const uint32_t max_buffer_size_before_flush_;
const std::chrono::milliseconds buffer_flush_timeout_;
const uint32_t max_upstream_unknown_connections_;
const bool enable_command_stats_;
ReadPolicy read_policy_;
};

Expand Down Expand Up @@ -94,19 +99,23 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne
};

struct PendingRequest : public PoolRequest {
PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks);
PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks, std::string command);
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
~PendingRequest() override;

// PoolRequest
void cancel() override;

ClientImpl& parent_;
PoolCallbacks& callbacks_;
std::string command_;
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider using your StatNameSet to store command_ as a StatName only. Then when you pass them around you don't need to do a bunch of string-copies, and it will enable you to compose all the stat-names strictly from other stat-names via SymbolTable::join(), which should be faster and lock-free.

If you know the most likely set of Redis commands you can remember them as builtins in your StatNameSet at construction time, and never thereafter have to take locks to get StatNames from known commands.

Choose a reason for hiding this comment

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

Per my other comment on how commands are extracted from Redis RespValue, I don't think we have a great way to do this. We'll always need to map a string command to a StatName.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes we will need one map lookup for the command since it is received in the wire as a string.

But ideally you should have a finite set of known commands that you can register as built-ins at construction time so that the StatNameSet can finish the lookup in its built-in map, which doesn't require a lock.

If you receive an unexpected command then the StatNameSet will fall back to taking a lock and doing the lookup in its dynamic map, which then falls back to the global symbol table.

Copy link
Contributor

Choose a reason for hiding this comment

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

My suggestion stands. Store this in the structure as Stats::StatName command_; and change call-sites to match. I'll mark up the other lines of code that have to change for this to work.

Choose a reason for hiding this comment

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

Ok, I've updated this and will push update here shortly.

bool canceled_{};
Stats::CompletableTimespanPtr aggregate_request_timer_;
Stats::CompletableTimespanPtr command_request_timer_;
};

ClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, EncoderPtr&& encoder,
DecoderFactory& decoder_factory, const Config& config);
DecoderFactory& decoder_factory, const Config& config,
RedisCommandStatsPtr&& redis_command_stats);
void onConnectOrOpTimeout();
void onData(Buffer::Instance& data);
void putOutlierEvent(Upstream::Outlier::Result result);
Expand All @@ -129,6 +138,9 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne
Event::TimerPtr connect_or_op_timer_;
bool connected_{};
Event::TimerPtr flush_timer_;
Envoy::TimeSource& time_source_;
const RedisCommandStatsPtr redis_command_stats_;
const ToLowerTable to_lower_table_;
};

class ClientFactoryImpl : public ClientFactory {
Expand Down
106 changes: 106 additions & 0 deletions source/extensions/filters/network/common/redis/redis_command_stats.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include "extensions/filters/network/common/redis/redis_command_stats.h"

#include "extensions/filters/network/common/redis/supported_commands.h"

namespace Envoy {
namespace Extensions {
namespace NetworkFilters {
namespace Common {
namespace Redis {

RedisCommandStats::RedisCommandStats(Stats::Scope& scope, const std::string& prefix, bool enabled)
: scope_(scope), stat_name_set_(scope.symbolTable()), prefix_(stat_name_set_.add(prefix)),
jmarantz marked this conversation as resolved.
Show resolved Hide resolved
enabled_(enabled), upstream_rq_time_(stat_name_set_.add("upstream_rq_time")) {
// Note: Even if this is disabled, we track the upstream_rq_time.
if (enabled_) {
// Create StatName for each Redis command. Note that we don't include Auth or Ping.
for (const std::string& command :
Extensions::NetworkFilters::Common::Redis::SupportedCommands::simpleCommands()) {
createStats(command);
}
for (const std::string& command :
Extensions::NetworkFilters::Common::Redis::SupportedCommands::evalCommands()) {
createStats(command);
}
for (const std::string& command : Extensions::NetworkFilters::Common::Redis::SupportedCommands::
hashMultipleSumResultCommands()) {
createStats(command);
}
createStats(Extensions::NetworkFilters::Common::Redis::SupportedCommands::mget());
createStats(Extensions::NetworkFilters::Common::Redis::SupportedCommands::mset());
}
}

void RedisCommandStats::createStats(std::string name) {
stat_name_set_.add(name + ".total");
stat_name_set_.add(name + ".success");
stat_name_set_.add(name + ".error");
stat_name_set_.add(name + ".latency");
}

Stats::SymbolTable::StoragePtr RedisCommandStats::addPrefix(const Stats::StatName name) {
Stats::StatNameVec names_with_prefix;
names_with_prefix.reserve(2);
names_with_prefix.push_back(prefix_);
names_with_prefix.insert(names_with_prefix.end(), name);
return scope_.symbolTable().join(names_with_prefix);
}

Stats::Counter& RedisCommandStats::counter(std::string name) {
Stats::StatName stat_name = stat_name_set_.getStatName(name);
const Stats::SymbolTable::StoragePtr stat_name_storage = addPrefix(stat_name);
return scope_.counterFromStatName(Stats::StatName(stat_name_storage.get()));
}

Stats::Histogram& RedisCommandStats::histogram(std::string name) {
Stats::StatName stat_name = stat_name_set_.getStatName(name);
return histogram(stat_name);
}

Stats::Histogram& RedisCommandStats::histogram(Stats::StatName stat_name) {
const Stats::SymbolTable::StoragePtr stat_name_storage = addPrefix(stat_name);
return scope_.histogramFromStatName(Stats::StatName(stat_name_storage.get()));
}

Stats::CompletableTimespanPtr
RedisCommandStats::createCommandTimer(std::string name, Envoy::TimeSource& time_source) {
Stats::StatName stat_name = stat_name_set_.getStatName(name + latency_suffix_);
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
return std::make_unique<Stats::TimespanWithUnit<std::chrono::microseconds>>(histogram(stat_name),
time_source);
}

Stats::CompletableTimespanPtr
RedisCommandStats::createAggregateTimer(Envoy::TimeSource& time_source) {
return std::make_unique<Stats::TimespanWithUnit<std::chrono::microseconds>>(
histogram(upstream_rq_time_), time_source);
}

std::string RedisCommandStats::getCommandFromRequest(const RespValue& request) {
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
// Get command from RespValue
switch (request.type()) {
case RespType::Array:
return getCommandFromRequest(request.asArray().front());
jmarantz marked this conversation as resolved.
Show resolved Hide resolved
case RespType::Integer:
FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg marked this conversation as resolved.
Show resolved Hide resolved
return std::to_string(request.asInteger());
case RespType::Null:
return "null";
default:
return request.asString();
}
}

void RedisCommandStats::updateStatsTotal(std::string command) { counter(command + ".total").inc(); }

void RedisCommandStats::updateStats(const bool success, std::string command) {
if (success) {
counter(command + ".success").inc();
} else {
counter(command + ".error").inc();
}
}

} // namespace Redis
} // namespace Common
} // namespace NetworkFilters
} // namespace Extensions
} // namespace Envoy
Loading