Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions include/nighthawk/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ envoy_basic_cc_library(
"@envoy//include/envoy/upstream:cluster_manager_interface_with_external_headers",
"@envoy//source/common/common:minimal_logger_lib",
"@envoy//source/common/common:non_copyable_with_external_headers",
"@envoy//source/common/common:statusor_lib_with_external_headers",
"@envoy//source/common/event:dispatcher_lib_with_external_headers",
"@envoy//source/common/network:utility_lib_with_external_headers",
],
Expand Down
18 changes: 18 additions & 0 deletions include/nighthawk/common/statistic.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "api/client/output.pb.h"

#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"

namespace Nighthawk {
Expand Down Expand Up @@ -115,6 +116,23 @@ class Statistic : Envoy::NonCopyable {
* @param id The id that should be set for the Statistic instance.
*/
virtual void setId(absl::string_view id) PURE;

/**
* Build a string representation of this Statistic instance.
*
* @return absl::StatusOr<std::unique_ptr<std::istream>> Status or a stream that will yield
* a serialized representation of this Statistic instance.
*/
virtual absl::StatusOr<std::unique_ptr<std::istream>> serializeNative() const PURE;

/**
* Reconstruct this Statistic instance using the serialization delivered by the input stream.
*
* @param input_stream Stream that will deliver a serialized representation.
* @return absl::Status Status indicating success or failure. Upon success the statistic
* instance this was called for will now represent what the stream contained.
*/
virtual absl::Status deserializeNative(std::istream& input_stream) PURE;
};

} // namespace Nighthawk
39 changes: 39 additions & 0 deletions source/common/statistic_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

#include <cmath>
#include <cstdio>
#include <fstream>
#include <sstream>

#include "external/dep_hdrhistogram_c/src/hdr_histogram_log.h"
#include "external/envoy/source/common/common/assert.h"
#include "external/envoy/source/common/protobuf/utility.h"

Expand Down Expand Up @@ -68,6 +70,14 @@ uint64_t StatisticImpl::min() const { return min_; };

uint64_t StatisticImpl::max() const { return max_; };

absl::StatusOr<std::unique_ptr<std::istream>> StatisticImpl::serializeNative() const {
return absl::Status(absl::StatusCode::kUnimplemented, "serializeNative not implemented.");
}

absl::Status StatisticImpl::deserializeNative(std::istream&) {
return absl::Status(absl::StatusCode::kUnimplemented, "deserializeNative not implemented.");
}

void SimpleStatistic::addValue(uint64_t value) {
StatisticImpl::addValue(value);
sum_x_ += value;
Expand Down Expand Up @@ -236,6 +246,35 @@ nighthawk::client::Statistic HdrStatistic::toProto(SerializationDomain domain) c
return proto;
}

absl::StatusOr<std::unique_ptr<std::istream>> HdrStatistic::serializeNative() const {
char* data;
if (hdr_log_encode(histogram_, &data) == 0) {
auto write_stream = std::make_unique<std::stringstream>();
*write_stream << absl::string_view(data, strlen(data));
// Free the memory allocated by hrd_log_encode.
free(data);
return write_stream;
}
ENVOY_LOG(error, "Failed to write HdrHistogram data.");
return absl::Status(absl::StatusCode::kInternal, "Failed to write HdrHistogram data");
}

absl::Status HdrStatistic::deserializeNative(std::istream& stream) {
std::string s(std::istreambuf_iterator<char>(stream), {});
struct hdr_histogram* new_histogram = nullptr;
// hdr_log_decode allocates memory for the new hdr histogram.
if (hdr_log_decode(&new_histogram, const_cast<char*>(s.c_str()), s.length()) == 0) {
// Free the memory allocated by our current hdr histogram.
hdr_close(histogram_);
// Swap in the new histogram.
// NOTE: Our destructor will eventually call hdr_close on the new one.
histogram_ = new_histogram;
return absl::OkStatus();
}
ENVOY_LOG(error, "Failed to read back HdrHistogram data.");
return absl::Status(absl::StatusCode::kInternal, "Failed to read back HdrHistogram data");
}

CircllhistStatistic::CircllhistStatistic() {
histogram_ = hist_alloc();
ASSERT(histogram_ != nullptr);
Expand Down
5 changes: 5 additions & 0 deletions source/common/statistic_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class StatisticImpl : public Statistic, public Envoy::Logger::Loggable<Envoy::Lo
uint64_t count() const override;
uint64_t max() const override;
uint64_t min() const override;
absl::StatusOr<std::unique_ptr<std::istream>> serializeNative() const override;
absl::Status deserializeNative(std::istream&) override;

protected:
std::string id_;
Expand Down Expand Up @@ -146,6 +148,9 @@ class HdrStatistic : public StatisticImpl {
return std::make_unique<HdrStatistic>();
};

absl::StatusOr<std::unique_ptr<std::istream>> serializeNative() const override;
absl::Status deserializeNative(std::istream&) override;

private:
static const int SignificantDigits;
struct hdr_histogram* histogram_;
Expand Down
36 changes: 36 additions & 0 deletions test/statistic_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,42 @@ TYPED_TEST(TypedStatisticTest, ProtoOutputEmptyStats) {
EXPECT_EQ(proto.pstdev().nanos(), 0);
}

TYPED_TEST(TypedStatisticTest, NativeRoundtrip) {
TypeParam a;

a.setId("bar");
a.addValue(6543456);
a.addValue(342335);
a.addValue(543);

const absl::StatusOr<std::unique_ptr<std::istream>> status_or_stream = a.serializeNative();
if (status_or_stream.ok()) {
// If the histogram states it implements native serialization/deserialization, put it through
// a round trip test.
TypeParam b;
absl::Status status = b.deserializeNative(*status_or_stream.value());
EXPECT_TRUE(status.ok());
EXPECT_EQ(3, b.count());
EXPECT_EQ(a.count(), b.count());
EXPECT_EQ(a.mean(), b.mean());
EXPECT_EQ(a.pstdev(), b.pstdev());
} else {
EXPECT_EQ(status_or_stream.status().code(), absl::StatusCode::kUnimplemented);
}
}

TYPED_TEST(TypedStatisticTest, AttemptsToDeserializeBogusBehaveWell) {
// Deserializing corrupted data should either result in the statistic reporting
// it didn't implement deserialization, or having it report an internal failure.
const std::vector<absl::StatusCode> expected_status_list{absl::StatusCode::kInternal,
absl::StatusCode::kUnimplemented};
TypeParam a;
std::istringstream bogus_input(std::string("BOGUS"));
const absl::Status status = a.deserializeNative(bogus_input);
EXPECT_FALSE(status.ok());
EXPECT_THAT(expected_status_list, Contains(status.code()));
}

TYPED_TEST(TypedStatisticTest, StringOutput) {
TypeParam a;

Expand Down