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

DynamoDB: Log stats for partition id per operation #147

Merged
merged 10 commits into from
Oct 17, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
42 changes: 42 additions & 0 deletions source/common/dynamo/dynamo_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ void DynamoFilter::onEncodeComplete(const Buffer::Instance& data) {

chargeBasicStats(status);

chargeTablePartitionIdStats(data);
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

Copy link
Member

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


if (Http::CodeUtility::is4xx(status)) {
chargeFailureSpecificStats(data);
}
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

buildBody is now being called from onEncodeComplete.

if (!body.empty()) {
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 =
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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) {
Copy link
Member

Choose a reason for hiding this comment

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

i'd just remove jsonEx as it's not used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed.

Copy link
Member

Choose a reason for hiding this comment

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

delete jsonEx

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

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

is the partition_id uuid4? (wondering what those 7 chars look like)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

lower case 'c' for capacity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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;
Copy link
Member

@RomanDzhabarov RomanDzhabarov Oct 13, 2016

Choose a reason for hiding this comment

The 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).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

looks like assert is broken, should be <= MAX_NAME_SIZE.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
9 changes: 8 additions & 1 deletion source/common/dynamo/dynamo_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class DynamoFilter : public Http::StreamFilter {
encoder_callbacks_ = &callbacks;
}

static std::string buildPartitionStatString(const std::string& stat_prefix,
Copy link
Member

Choose a reason for hiding this comment

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

private to DynamoFilter

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
Expand All @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to use RawStats var.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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};
Expand Down
24 changes: 24 additions & 0 deletions source/common/dynamo/dynamo_request_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

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

In the common case are we parsing the JSON multiple times?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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")) {
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The 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
13 changes: 13 additions & 0 deletions source/common/dynamo/dynamo_request_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ class RequestParser {
bool is_single_table;
};

struct PartitionDescriptor {
PartitionDescriptor(std::string partition, uint64_t capacity)
Copy link
Member

Choose a reason for hiding this comment

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

pass as const std::string& partition

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Expand Down Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions source/common/json/json_loader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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_));
Copy link
Member

Choose a reason for hiding this comment

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

grammar

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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;
Expand Down
19 changes: 17 additions & 2 deletions source/common/json/json_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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;
Copy link
Member

Choose a reason for hiding this comment

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

just double, const double& not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed.


/**
* Iterate Object and call callback on key-value pairs
*/
Expand Down
1 change: 1 addition & 0 deletions source/precompiled/precompiled.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <arpa/inet.h>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <fstream>
#include <iostream>
Expand Down
Loading