-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
DynamoDB: Log stats for partition id per operation #147
Changes from 3 commits
6695db2
46732a4
3eca229
9ed557d
6532da8
1a3197f
ae1e2e7
5887f69
a2fd649
3891f85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,8 @@ void DynamoFilter::onEncodeComplete(const Buffer::Instance& data) { | |
|
||
chargeBasicStats(status); | ||
|
||
chargeTablePartitionIdStats(data); | ||
|
||
if (Http::CodeUtility::is4xx(status)) { | ||
chargeFailureSpecificStats(data); | ||
} | ||
|
@@ -208,4 +210,44 @@ void DynamoFilter::chargeFailureSpecificStats(const Buffer::Instance& data) { | |
} | ||
} | ||
|
||
void DynamoFilter::chargeTablePartitionIdStats(const Buffer::Instance& data) { | ||
// Only log partition stats for single table operations. | ||
if (table_descriptor_.table_name.empty() || operation_.empty()) { | ||
return; | ||
} | ||
std::string body = buildBody(encoder_callbacks_->encodingBuffer(), data); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you are potentially going to buildBody() multiple times I think, and now this is always going to happen in general. Let's make sure this only happens once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. buildBody is now being called from onEncodeComplete. |
||
if (!body.empty()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: place body.empty() in the if above, makes code below less nested. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved body.empty check to above if. |
||
try { | ||
std::vector<Dynamo::RequestParser::PartitionDescriptor> partitions = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit, remove Dynamo:: from everywhere in this file. You don't need to specify the namespace prefix within the namespace. Makes code less verbose There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cleaned up all 'Dynamo::' in the code. |
||
Dynamo::RequestParser::parsePartitions(body); | ||
for (const Dynamo::RequestParser::PartitionDescriptor& partition : partitions) { | ||
std::string stats_string = DynamoFilter::buildPartitionStatString( | ||
stat_prefix_, table_descriptor_.table_name, operation_, partition.partition_id_); | ||
stats_.counter(stats_string).add(partition.capacity_); | ||
} | ||
} catch (const Json::Exception& jsonEx) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'd just remove jsonEx as it's not used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. delete jsonEx There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deleted for all instances. |
||
// Body parsing failed. This should not happen, just put a stat for that. | ||
stats_.counter(fmt::format("{}invalid_resp_body", stat_prefix_)).inc(); | ||
} | ||
} | ||
} | ||
|
||
std::string DynamoFilter::buildPartitionStatString(const std::string& stat_prefix, | ||
const std::string& table_name, | ||
const std::string& operation, | ||
const std::string& partition_id) { | ||
std::string stats_table_prefix = fmt::format("{}table.{}", stat_prefix, table_name); | ||
// Use only the last 7 characters from the partition id. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is the partition_id uuid4? (wondering what those 7 chars look like) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the partition_id is a uuid. We are choosing to use the last 7 characters for now. |
||
std::string stats_partition_postfix = | ||
fmt::format(".Capacity.{}.__partition_id={}", operation, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lower case 'c' for capacity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
partition_id.substr(partition_id.size() - 7, partition_id.size())); | ||
// Calculate how many characters are available for the table prefix. | ||
size_t remaining_size = MAX_NAME_SIZE - stats_partition_postfix.size() - 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we have here -1, MAX_NAME_SIZE is the actual length we can have in stats name, see RawStatData struct (it allocates MAX_NAME_SIZE+1 for \0). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our check is size < MAX_NAME_SIZE There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like assert is broken, should be <= MAX_NAME_SIZE. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
// Truncate the table prefix if the curent string is too large. | ||
if (stats_table_prefix.size() > remaining_size) { | ||
stats_table_prefix = stats_table_prefix.substr(0, remaining_size); | ||
} | ||
return fmt::format("{}{}", stats_table_prefix, stats_partition_postfix); | ||
} | ||
|
||
} // Dynamo |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,11 @@ class DynamoFilter : public Http::StreamFilter { | |
encoder_callbacks_ = &callbacks; | ||
} | ||
|
||
static std::string buildPartitionStatString(const std::string& stat_prefix, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. private to DynamoFilter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will create a utility class for this method. I want to keep this public to improve testability within dynamo_filter_test.cc |
||
const std::string& table_name, | ||
const std::string& operation, | ||
const std::string& partition_id); | ||
|
||
private: | ||
void onDecodeComplete(const Buffer::Instance& data); | ||
void onEncodeComplete(const Buffer::Instance& data); | ||
|
@@ -46,11 +51,13 @@ class DynamoFilter : public Http::StreamFilter { | |
uint64_t status); | ||
void chargeFailureSpecificStats(const Buffer::Instance& data); | ||
void chargeUnProcessedKeysStats(const Buffer::Instance& data); | ||
void chargeTablePartitionIdStats(const Buffer::Instance& data); | ||
|
||
static const size_t MAX_NAME_SIZE = 127; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. risky as this depends on stats const, why not use const from RawStats directly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated to use RawStats var. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated. |
||
|
||
Runtime::Loader& runtime_; | ||
std::string stat_prefix_; | ||
Stats::Store& stats_; | ||
|
||
bool enabled_{}; | ||
std::string operation_{}; | ||
RequestParser::TableDescriptor table_descriptor_{"", true}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -125,4 +125,28 @@ bool RequestParser::isBatchOperation(const std::string& operation) { | |
BATCH_OPERATIONS.end(); | ||
} | ||
|
||
std::vector<RequestParser::PartitionDescriptor> | ||
RequestParser::parsePartitions(const std::string& data) { | ||
Json::StringLoader json(data); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the common case are we parsing the JSON multiple times? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is possible. Maybe I should update DynamoFilter::buildBody to return Json and update all of dynamo_request_parser to take in a json object? |
||
std::vector<RequestParser::PartitionDescriptor> partition_descriptors; | ||
|
||
if (json.hasObject("ConsumedCapacity")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can make this logic less nested by using getObject() with allow_empty=true There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
Json::Object consumed_capacity = json.getObject("ConsumedCapacity"); | ||
if (consumed_capacity.hasObject("Partitions")) { | ||
Json::Object partitions = consumed_capacity.getObject("Partitions"); | ||
|
||
partitions.iterate( | ||
[&partition_descriptors, &partitions](const std::string& key, const Json::Object&) { | ||
// Capacity is a double and it is rounded up to the nearest integer. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's not intuitive to me what is going on here. Since this is not yet in the public docs, can you have a slightly larger comment on what the API returns here and how we are processing it |
||
// The partition stat will be incremented by the capacity value. | ||
uint64_t capacity_integer = | ||
static_cast<uint64_t>(std::ceil(partitions.getDouble(key, 0.0))); | ||
partition_descriptors.emplace_back(key, capacity_integer); | ||
return true; | ||
}); | ||
} | ||
} | ||
return partition_descriptors; | ||
} | ||
|
||
} // Dynamo |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,13 @@ class RequestParser { | |
bool is_single_table; | ||
}; | ||
|
||
struct PartitionDescriptor { | ||
PartitionDescriptor(std::string partition, uint64_t capacity) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pass as const std::string& partition There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
: partition_id_(partition), capacity_(capacity) {} | ||
std::string partition_id_; | ||
uint64_t capacity_; | ||
}; | ||
|
||
/** | ||
* Parse operation out of x-amz-target header. | ||
* @return empty string if operation cannot be parsed. | ||
|
@@ -66,6 +73,12 @@ class RequestParser { | |
*/ | ||
static bool isBatchOperation(const std::string& operation); | ||
|
||
/** | ||
* Parse the Partition ids and the capacity from the response body. | ||
* @return empty set if there are no partition ids or a set of partition ids | ||
*/ | ||
static std::vector<PartitionDescriptor> parsePartitions(const std::string& data); | ||
|
||
private: | ||
static const Http::LowerCaseString X_AMZ_TARGET; | ||
static const std::vector<std::string> SINGLE_TABLE_OPERATIONS; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,6 +136,23 @@ std::vector<std::string> Object::getStringArray(const std::string& name) const { | |
return string_array; | ||
} | ||
|
||
double Object::getDouble(const std::string& name) const { | ||
json_t* real = json_object_get(json_, name.c_str()); | ||
if (!real || !json_is_real(real)) { | ||
throw Exception(fmt::format("key '{}' missing or not an double in '{}'", name, name_)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. grammar There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. |
||
} | ||
|
||
return json_real_value(real); | ||
} | ||
|
||
double Object::getDouble(const std::string& name, const double& default_value) const { | ||
if (!json_object_get(json_, name.c_str())) { | ||
return default_value; | ||
} else { | ||
return getDouble(name); | ||
} | ||
} | ||
|
||
void Object::iterate(const ObjectCallback& callback) { | ||
const char* key; | ||
json_t* value; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,14 +81,14 @@ class Object { | |
|
||
/** | ||
* Get a string value by name. | ||
* @param name suppies the key name. | ||
* @param name supplies the key name. | ||
* @return std::string the value. | ||
*/ | ||
std::string getString(const std::string& name) const; | ||
|
||
/** | ||
* Get a string value by name or return a default if name does not exist. | ||
* @param name suppies the key name. | ||
* @param name supplies the key name. | ||
* @param default_value supplies the value to return if name does not exist. | ||
* @return std::string the value. | ||
*/ | ||
|
@@ -101,6 +101,21 @@ class Object { | |
*/ | ||
std::vector<std::string> getStringArray(const std::string& name) const; | ||
|
||
/** | ||
* Get a double value by name. | ||
* @param name supplies the key name. | ||
* @return double the value. | ||
*/ | ||
double getDouble(const std::string& name) const; | ||
|
||
/** | ||
* Get a double value by name. | ||
* @param name supplies the key name. | ||
* @param default_value supplies the value to return if name does not exist. | ||
* @return double the value. | ||
*/ | ||
double getDouble(const std::string& name, const double& default_value) const; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just double, const double& not needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. |
||
|
||
/** | ||
* Iterate Object and call callback on key-value pairs | ||
*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'd need to update docs, once review is close to the final state: https://lyft.github.io/envoy/docs/configuration/http_filters/dynamodb_filter.html#config-http-filters-dynamo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove newline at 60 and 62