mirror of https://github.com/grpc/grpc.git
Merge pull request #15196 from AspirinSJL/load_reporter
Add load reporter for server load reportingpull/15684/head
commit
8bda53ee64
17 changed files with 1934 additions and 27 deletions
@ -0,0 +1,71 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef GRPC_SRC_CPP_SERVER_LOAD_REPORTER_UTIL_H |
||||||
|
#define GRPC_SRC_CPP_SERVER_LOAD_REPORTER_UTIL_H |
||||||
|
|
||||||
|
#include <grpc/impl/codegen/port_platform.h> |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace load_reporter { |
||||||
|
|
||||||
|
constexpr size_t kLbIdLength = 8; |
||||||
|
constexpr size_t kIpv4AddressLength = 8; |
||||||
|
constexpr size_t kIpv6AddressLength = 32; |
||||||
|
|
||||||
|
constexpr char kInvalidLbId[] = "<INVALID_LBID_238dsb234890rb>"; |
||||||
|
|
||||||
|
// Call statuses.
|
||||||
|
|
||||||
|
constexpr char kCallStatusOk[] = "OK"; |
||||||
|
constexpr char kCallStatusServerError[] = "5XX"; |
||||||
|
constexpr char kCallStatusClientError[] = "4XX"; |
||||||
|
|
||||||
|
// Tag keys.
|
||||||
|
|
||||||
|
constexpr char kTagKeyToken[] = "token"; |
||||||
|
constexpr char kTagKeyHost[] = "host"; |
||||||
|
constexpr char kTagKeyUserId[] = "user_id"; |
||||||
|
constexpr char kTagKeyStatus[] = "status"; |
||||||
|
constexpr char kTagKeyMetricName[] = "metric_name"; |
||||||
|
|
||||||
|
// Measure names.
|
||||||
|
|
||||||
|
constexpr char kMeasureStartCount[] = "grpc.io/lb/start_count"; |
||||||
|
constexpr char kMeasureEndCount[] = "grpc.io/lb/end_count"; |
||||||
|
constexpr char kMeasureEndBytesSent[] = "grpc.io/lb/bytes_sent"; |
||||||
|
constexpr char kMeasureEndBytesReceived[] = "grpc.io/lb/bytes_received"; |
||||||
|
constexpr char kMeasureEndLatencyMs[] = "grpc.io/lb/latency_ms"; |
||||||
|
constexpr char kMeasureOtherCallMetric[] = "grpc.io/lb/other_call_metric"; |
||||||
|
|
||||||
|
// View names.
|
||||||
|
|
||||||
|
constexpr char kViewStartCount[] = "grpc.io/lb_view/start_count"; |
||||||
|
constexpr char kViewEndCount[] = "grpc.io/lb_view/end_count"; |
||||||
|
constexpr char kViewEndBytesSent[] = "grpc.io/lb_view/bytes_sent"; |
||||||
|
constexpr char kViewEndBytesReceived[] = "grpc.io/lb_view/bytes_received"; |
||||||
|
constexpr char kViewEndLatencyMs[] = "grpc.io/lb_view/latency_ms"; |
||||||
|
constexpr char kViewOtherCallMetricCount[] = |
||||||
|
"grpc.io/lb_view/other_call_metric_count"; |
||||||
|
constexpr char kViewOtherCallMetricValue[] = |
||||||
|
"grpc.io/lb_view/other_call_metric_value"; |
||||||
|
|
||||||
|
} // namespace load_reporter
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // GRPC_SRC_CPP_SERVER_LOAD_REPORTER_UTIL_H
|
@ -0,0 +1,36 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef GRPC_SRC_CPP_SERVER_LOAD_REPORTER_GET_CPU_STATS_H |
||||||
|
#define GRPC_SRC_CPP_SERVER_LOAD_REPORTER_GET_CPU_STATS_H |
||||||
|
|
||||||
|
#include <grpc/impl/codegen/port_platform.h> |
||||||
|
|
||||||
|
#include <utility> |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace load_reporter { |
||||||
|
|
||||||
|
// Reads the CPU stats (in a pair of busy and total numbers) from the system.
|
||||||
|
// The units of the stats should be the same.
|
||||||
|
std::pair<uint64_t, uint64_t> GetCpuStatsImpl(); |
||||||
|
|
||||||
|
} // namespace load_reporter
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // GRPC_SRC_CPP_SERVER_LOAD_REPORTER_GET_CPU_STATS_H
|
@ -0,0 +1,45 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#ifdef GPR_LINUX |
||||||
|
|
||||||
|
#include <cstdio> |
||||||
|
|
||||||
|
#include "src/cpp/server/load_reporter/get_cpu_stats.h" |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace load_reporter { |
||||||
|
|
||||||
|
std::pair<uint64_t, uint64_t> GetCpuStatsImpl() { |
||||||
|
uint64_t busy = 0, total = 0; |
||||||
|
FILE* fp; |
||||||
|
fp = fopen("/proc/stat", "r"); |
||||||
|
uint64_t user, nice, system, idle; |
||||||
|
fscanf(fp, "cpu %lu %lu %lu %lu", &user, &nice, &system, &idle); |
||||||
|
fclose(fp); |
||||||
|
busy = user + nice + system; |
||||||
|
total = busy + idle; |
||||||
|
return std::make_pair(busy, total); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace load_reporter
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // GPR_LINUX
|
@ -0,0 +1,45 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#ifdef GPR_APPLE |
||||||
|
|
||||||
|
#include <mach/mach.h> |
||||||
|
|
||||||
|
#include "src/cpp/server/load_reporter/get_cpu_stats.h" |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace load_reporter { |
||||||
|
|
||||||
|
std::pair<uint64_t, uint64_t> GetCpuStatsImpl() { |
||||||
|
uint64_t busy = 0, total = 0; |
||||||
|
host_cpu_load_info_data_t cpuinfo; |
||||||
|
mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; |
||||||
|
if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, |
||||||
|
(host_info_t)&cpuinfo, &count) == KERN_SUCCESS) { |
||||||
|
for (int i = 0; i < CPU_STATE_MAX; i++) total += cpuinfo.cpu_ticks[i]; |
||||||
|
busy = total - cpuinfo.cpu_ticks[CPU_STATE_IDLE]; |
||||||
|
} |
||||||
|
return std::make_pair(busy, total); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace load_reporter
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // GPR_APPLE
|
@ -0,0 +1,40 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#if !defined(GPR_LINUX) && !defined(GPR_WINDOWS) && !defined(GPR_APPLE) |
||||||
|
|
||||||
|
#include <grpc/support/log.h> |
||||||
|
|
||||||
|
#include "src/cpp/server/load_reporter/get_cpu_stats.h" |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace load_reporter { |
||||||
|
|
||||||
|
std::pair<uint64_t, uint64_t> GetCpuStatsImpl() { |
||||||
|
uint64_t busy = 0, total = 0; |
||||||
|
gpr_log(GPR_ERROR, |
||||||
|
"Platforms other than Linux, Windows, and MacOS are not supported."); |
||||||
|
return std::make_pair(busy, total); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace load_reporter
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // !defined(GPR_LINUX) && !defined(GPR_WINDOWS) && !defined(GPR_APPLE)
|
@ -0,0 +1,55 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#ifdef GPR_WINDOWS |
||||||
|
|
||||||
|
#include <windows.h> |
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
#include "src/cpp/server/load_reporter/get_cpu_stats.h" |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace load_reporter { |
||||||
|
|
||||||
|
namespace { |
||||||
|
|
||||||
|
uint64_t FiletimeToInt(const FILETIME& ft) { |
||||||
|
ULARGE_INTEGER i; |
||||||
|
i.LowPart = ft.dwLowDateTime; |
||||||
|
i.HighPart = ft.dwHighDateTime; |
||||||
|
return i.QuadPart; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::pair<uint64_t, uint64_t> GetCpuStatsImpl() { |
||||||
|
uint64_t busy = 0, total = 0; |
||||||
|
FILETIME idle, kernel, user; |
||||||
|
if (GetSystemTimes(&idle, &kernel, &user) != 0) { |
||||||
|
total = FiletimeToInt(kernel) + FiletimeToInt(user); |
||||||
|
busy = total - FiletimeToInt(idle); |
||||||
|
} |
||||||
|
return std::make_pair(busy, total); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace load_reporter
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // GPR_WINDOWS
|
@ -0,0 +1,498 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <grpc/impl/codegen/port_platform.h> |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <chrono> |
||||||
|
#include <ctime> |
||||||
|
|
||||||
|
#include "src/cpp/server/load_reporter/constants.h" |
||||||
|
#include "src/cpp/server/load_reporter/get_cpu_stats.h" |
||||||
|
#include "src/cpp/server/load_reporter/load_reporter.h" |
||||||
|
|
||||||
|
#include "opencensus/stats/internal/set_aggregation_window.h" |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace load_reporter { |
||||||
|
|
||||||
|
CpuStatsProvider::CpuStatsSample CpuStatsProviderDefaultImpl::GetCpuStats() { |
||||||
|
return GetCpuStatsImpl(); |
||||||
|
} |
||||||
|
|
||||||
|
CensusViewProvider::CensusViewProvider() |
||||||
|
: tag_key_token_(::opencensus::stats::TagKey::Register(kTagKeyToken)), |
||||||
|
tag_key_host_(::opencensus::stats::TagKey::Register(kTagKeyHost)), |
||||||
|
tag_key_user_id_(::opencensus::stats::TagKey::Register(kTagKeyUserId)), |
||||||
|
tag_key_status_(::opencensus::stats::TagKey::Register(kTagKeyStatus)), |
||||||
|
tag_key_metric_name_( |
||||||
|
::opencensus::stats::TagKey::Register(kTagKeyMetricName)) { |
||||||
|
// One view related to starting a call.
|
||||||
|
auto vd_start_count = |
||||||
|
::opencensus::stats::ViewDescriptor() |
||||||
|
.set_name(kViewStartCount) |
||||||
|
.set_measure(kMeasureStartCount) |
||||||
|
.set_aggregation(::opencensus::stats::Aggregation::Sum()) |
||||||
|
.add_column(tag_key_token_) |
||||||
|
.add_column(tag_key_host_) |
||||||
|
.add_column(tag_key_user_id_) |
||||||
|
.set_description( |
||||||
|
"Delta count of calls started broken down by <token, host, " |
||||||
|
"user_id>."); |
||||||
|
::opencensus::stats::SetAggregationWindow( |
||||||
|
::opencensus::stats::AggregationWindow::Delta(), &vd_start_count); |
||||||
|
view_descriptor_map_.emplace(kViewStartCount, vd_start_count); |
||||||
|
// Four views related to ending a call.
|
||||||
|
// If this view is set as Count of kMeasureEndBytesSent (in hope of saving one
|
||||||
|
// measure), it's infeasible to prepare fake data for testing. That's because
|
||||||
|
// the OpenCensus API to make up view data will add the input data as separate
|
||||||
|
// measurements instead of setting the data values directly.
|
||||||
|
auto vd_end_count = |
||||||
|
::opencensus::stats::ViewDescriptor() |
||||||
|
.set_name((kViewEndCount)) |
||||||
|
.set_measure((kMeasureEndCount)) |
||||||
|
.set_aggregation(::opencensus::stats::Aggregation::Sum()) |
||||||
|
.add_column(tag_key_token_) |
||||||
|
.add_column(tag_key_host_) |
||||||
|
.add_column(tag_key_user_id_) |
||||||
|
.add_column(tag_key_status_) |
||||||
|
.set_description( |
||||||
|
"Delta count of calls ended broken down by <token, host, " |
||||||
|
"user_id, status>."); |
||||||
|
::opencensus::stats::SetAggregationWindow( |
||||||
|
::opencensus::stats::AggregationWindow::Delta(), &vd_end_count); |
||||||
|
view_descriptor_map_.emplace(kViewEndCount, vd_end_count); |
||||||
|
auto vd_end_bytes_sent = |
||||||
|
::opencensus::stats::ViewDescriptor() |
||||||
|
.set_name((kViewEndBytesSent)) |
||||||
|
.set_measure((kMeasureEndBytesSent)) |
||||||
|
.set_aggregation(::opencensus::stats::Aggregation::Sum()) |
||||||
|
.add_column(tag_key_token_) |
||||||
|
.add_column(tag_key_host_) |
||||||
|
.add_column(tag_key_user_id_) |
||||||
|
.add_column(tag_key_status_) |
||||||
|
.set_description( |
||||||
|
"Delta sum of bytes sent broken down by <token, host, user_id, " |
||||||
|
"status>."); |
||||||
|
::opencensus::stats::SetAggregationWindow( |
||||||
|
::opencensus::stats::AggregationWindow::Delta(), &vd_end_bytes_sent); |
||||||
|
view_descriptor_map_.emplace(kViewEndBytesSent, vd_end_bytes_sent); |
||||||
|
auto vd_end_bytes_received = |
||||||
|
::opencensus::stats::ViewDescriptor() |
||||||
|
.set_name((kViewEndBytesReceived)) |
||||||
|
.set_measure((kMeasureEndBytesReceived)) |
||||||
|
.set_aggregation(::opencensus::stats::Aggregation::Sum()) |
||||||
|
.add_column(tag_key_token_) |
||||||
|
.add_column(tag_key_host_) |
||||||
|
.add_column(tag_key_user_id_) |
||||||
|
.add_column(tag_key_status_) |
||||||
|
.set_description( |
||||||
|
"Delta sum of bytes received broken down by <token, host, " |
||||||
|
"user_id, status>."); |
||||||
|
::opencensus::stats::SetAggregationWindow( |
||||||
|
::opencensus::stats::AggregationWindow::Delta(), &vd_end_bytes_received); |
||||||
|
view_descriptor_map_.emplace(kViewEndBytesReceived, vd_end_bytes_received); |
||||||
|
auto vd_end_latency_ms = |
||||||
|
::opencensus::stats::ViewDescriptor() |
||||||
|
.set_name((kViewEndLatencyMs)) |
||||||
|
.set_measure((kMeasureEndLatencyMs)) |
||||||
|
.set_aggregation(::opencensus::stats::Aggregation::Sum()) |
||||||
|
.add_column(tag_key_token_) |
||||||
|
.add_column(tag_key_host_) |
||||||
|
.add_column(tag_key_user_id_) |
||||||
|
.add_column(tag_key_status_) |
||||||
|
.set_description( |
||||||
|
"Delta sum of latency in ms broken down by <token, host, " |
||||||
|
"user_id, status>."); |
||||||
|
::opencensus::stats::SetAggregationWindow( |
||||||
|
::opencensus::stats::AggregationWindow::Delta(), &vd_end_latency_ms); |
||||||
|
view_descriptor_map_.emplace(kViewEndLatencyMs, vd_end_latency_ms); |
||||||
|
// Two views related to other call metrics.
|
||||||
|
auto vd_metric_call_count = |
||||||
|
::opencensus::stats::ViewDescriptor() |
||||||
|
.set_name((kViewOtherCallMetricCount)) |
||||||
|
.set_measure((kMeasureOtherCallMetric)) |
||||||
|
.set_aggregation(::opencensus::stats::Aggregation::Count()) |
||||||
|
.add_column(tag_key_token_) |
||||||
|
.add_column(tag_key_host_) |
||||||
|
.add_column(tag_key_user_id_) |
||||||
|
.add_column(tag_key_metric_name_) |
||||||
|
.set_description( |
||||||
|
"Delta count of calls broken down by <token, host, user_id, " |
||||||
|
"metric_name>."); |
||||||
|
::opencensus::stats::SetAggregationWindow( |
||||||
|
::opencensus::stats::AggregationWindow::Delta(), &vd_metric_call_count); |
||||||
|
view_descriptor_map_.emplace(kViewOtherCallMetricCount, vd_metric_call_count); |
||||||
|
auto vd_metric_value = |
||||||
|
::opencensus::stats::ViewDescriptor() |
||||||
|
.set_name((kViewOtherCallMetricValue)) |
||||||
|
.set_measure((kMeasureOtherCallMetric)) |
||||||
|
.set_aggregation(::opencensus::stats::Aggregation::Sum()) |
||||||
|
.add_column(tag_key_token_) |
||||||
|
.add_column(tag_key_host_) |
||||||
|
.add_column(tag_key_user_id_) |
||||||
|
.add_column(tag_key_metric_name_) |
||||||
|
.set_description( |
||||||
|
"Delta sum of call metric value broken down " |
||||||
|
"by <token, host, user_id, metric_name>."); |
||||||
|
::opencensus::stats::SetAggregationWindow( |
||||||
|
::opencensus::stats::AggregationWindow::Delta(), &vd_metric_value); |
||||||
|
view_descriptor_map_.emplace(kViewOtherCallMetricValue, vd_metric_value); |
||||||
|
} |
||||||
|
|
||||||
|
double CensusViewProvider::GetRelatedViewDataRowDouble( |
||||||
|
const ViewDataMap& view_data_map, const char* view_name, |
||||||
|
size_t view_name_len, const std::vector<grpc::string>& tag_values) { |
||||||
|
auto it_vd = view_data_map.find(grpc::string(view_name, view_name_len)); |
||||||
|
GPR_ASSERT(it_vd != view_data_map.end()); |
||||||
|
auto it_row = it_vd->second.double_data().find(tag_values); |
||||||
|
GPR_ASSERT(it_row != it_vd->second.double_data().end()); |
||||||
|
return it_row->second; |
||||||
|
} |
||||||
|
|
||||||
|
CensusViewProviderDefaultImpl::CensusViewProviderDefaultImpl() { |
||||||
|
for (const auto& p : view_descriptor_map()) { |
||||||
|
const grpc::string& view_name = p.first; |
||||||
|
const ::opencensus::stats::ViewDescriptor& vd = p.second; |
||||||
|
// We need to use pair's piecewise ctor here, otherwise the deleted copy
|
||||||
|
// ctor of View will be called.
|
||||||
|
view_map_.emplace(std::piecewise_construct, |
||||||
|
std::forward_as_tuple(view_name), |
||||||
|
std::forward_as_tuple(vd)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
CensusViewProvider::ViewDataMap CensusViewProviderDefaultImpl::FetchViewData() { |
||||||
|
gpr_log(GPR_DEBUG, "[CVP %p] Starts fetching Census view data.", this); |
||||||
|
ViewDataMap view_data_map; |
||||||
|
for (auto& p : view_map_) { |
||||||
|
const grpc::string& view_name = p.first; |
||||||
|
::opencensus::stats::View& view = p.second; |
||||||
|
if (view.IsValid()) { |
||||||
|
view_data_map.emplace(view_name, view.GetData()); |
||||||
|
gpr_log(GPR_DEBUG, "[CVP %p] Fetched view data (view: %s).", this, |
||||||
|
view_name.c_str()); |
||||||
|
} else { |
||||||
|
gpr_log( |
||||||
|
GPR_DEBUG, |
||||||
|
"[CVP %p] Can't fetch view data because view is invalid (view: %s).", |
||||||
|
this, view_name.c_str()); |
||||||
|
} |
||||||
|
} |
||||||
|
return view_data_map; |
||||||
|
} |
||||||
|
|
||||||
|
grpc::string LoadReporter::GenerateLbId() { |
||||||
|
while (true) { |
||||||
|
if (next_lb_id_ > UINT32_MAX) { |
||||||
|
gpr_log(GPR_ERROR, "[LR %p] The LB ID exceeds the max valid value!", |
||||||
|
this); |
||||||
|
return ""; |
||||||
|
} |
||||||
|
int64_t lb_id = next_lb_id_++; |
||||||
|
// Overflow should never happen.
|
||||||
|
GPR_ASSERT(lb_id >= 0); |
||||||
|
// Convert to padded hex string for a 32-bit LB ID. E.g, "0000ca5b".
|
||||||
|
char buf[kLbIdLength + 1]; |
||||||
|
snprintf(buf, sizeof(buf), "%08lx", lb_id); |
||||||
|
grpc::string lb_id_str(buf, kLbIdLength); |
||||||
|
// The client may send requests with LB ID that has never been allocated
|
||||||
|
// by this load reporter. Those IDs are tracked and will be skipped when
|
||||||
|
// we generate a new ID.
|
||||||
|
if (!load_data_store_.IsTrackedUnknownBalancerId(lb_id_str)) { |
||||||
|
return lb_id_str; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
::grpc::lb::v1::LoadBalancingFeedback |
||||||
|
LoadReporter::GenerateLoadBalancingFeedback() { |
||||||
|
std::unique_lock<std::mutex> lock(feedback_mu_); |
||||||
|
auto now = std::chrono::system_clock::now(); |
||||||
|
// Discard records outside the window until there is only one record
|
||||||
|
// outside the window, which is used as the base for difference.
|
||||||
|
while (feedback_records_.size() > 1 && |
||||||
|
!IsRecordInWindow(feedback_records_[1], now)) { |
||||||
|
feedback_records_.pop_front(); |
||||||
|
} |
||||||
|
if (feedback_records_.size() < 2) { |
||||||
|
return ::grpc::lb::v1::LoadBalancingFeedback::default_instance(); |
||||||
|
} |
||||||
|
// Find the longest range with valid ends.
|
||||||
|
LoadBalancingFeedbackRecord* oldest = &feedback_records_[0]; |
||||||
|
LoadBalancingFeedbackRecord* newest = |
||||||
|
&feedback_records_[feedback_records_.size() - 1]; |
||||||
|
while (newest > oldest && |
||||||
|
(newest->cpu_limit == 0 || oldest->cpu_limit == 0)) { |
||||||
|
// A zero limit means that the system info reading was failed, so these
|
||||||
|
// records can't be used to calculate CPU utilization.
|
||||||
|
if (newest->cpu_limit == 0) --newest; |
||||||
|
if (oldest->cpu_limit == 0) ++oldest; |
||||||
|
} |
||||||
|
if (newest - oldest < 1 || oldest->end_time == newest->end_time || |
||||||
|
newest->cpu_limit == oldest->cpu_limit) { |
||||||
|
return ::grpc::lb::v1::LoadBalancingFeedback::default_instance(); |
||||||
|
} |
||||||
|
uint64_t rpcs = 0; |
||||||
|
uint64_t errors = 0; |
||||||
|
for (LoadBalancingFeedbackRecord* p = newest; p != oldest; --p) { |
||||||
|
// Because these two numbers are counters, the oldest record shouldn't be
|
||||||
|
// included.
|
||||||
|
rpcs += p->rpcs; |
||||||
|
errors += p->errors; |
||||||
|
} |
||||||
|
double cpu_usage = newest->cpu_usage - oldest->cpu_usage; |
||||||
|
double cpu_limit = newest->cpu_limit - oldest->cpu_limit; |
||||||
|
std::chrono::duration<double> duration_seconds = |
||||||
|
newest->end_time - oldest->end_time; |
||||||
|
lock.unlock(); |
||||||
|
::grpc::lb::v1::LoadBalancingFeedback feedback; |
||||||
|
feedback.set_server_utilization(static_cast<float>(cpu_usage / cpu_limit)); |
||||||
|
feedback.set_calls_per_second( |
||||||
|
static_cast<float>(rpcs / duration_seconds.count())); |
||||||
|
feedback.set_errors_per_second( |
||||||
|
static_cast<float>(errors / duration_seconds.count())); |
||||||
|
return feedback; |
||||||
|
} |
||||||
|
|
||||||
|
::google::protobuf::RepeatedPtrField<::grpc::lb::v1::Load> |
||||||
|
LoadReporter::GenerateLoads(const grpc::string& hostname, |
||||||
|
const grpc::string& lb_id) { |
||||||
|
std::lock_guard<std::mutex> lock(store_mu_); |
||||||
|
auto assigned_stores = load_data_store_.GetAssignedStores(hostname, lb_id); |
||||||
|
GPR_ASSERT(assigned_stores != nullptr); |
||||||
|
GPR_ASSERT(!assigned_stores->empty()); |
||||||
|
::google::protobuf::RepeatedPtrField<::grpc::lb::v1::Load> loads; |
||||||
|
for (PerBalancerStore* per_balancer_store : *assigned_stores) { |
||||||
|
GPR_ASSERT(!per_balancer_store->IsSuspended()); |
||||||
|
if (!per_balancer_store->load_record_map().empty()) { |
||||||
|
for (const auto& p : per_balancer_store->load_record_map()) { |
||||||
|
const auto& key = p.first; |
||||||
|
const auto& value = p.second; |
||||||
|
auto load = loads.Add(); |
||||||
|
load->set_load_balance_tag(key.lb_tag()); |
||||||
|
load->set_user_id(key.user_id()); |
||||||
|
load->set_client_ip_address(key.GetClientIpBytes()); |
||||||
|
load->set_num_calls_started(static_cast<int64_t>(value.start_count())); |
||||||
|
load->set_num_calls_finished_without_error( |
||||||
|
static_cast<int64_t>(value.ok_count())); |
||||||
|
load->set_num_calls_finished_with_error( |
||||||
|
static_cast<int64_t>(value.error_count())); |
||||||
|
load->set_total_bytes_sent(static_cast<int64_t>(value.bytes_sent())); |
||||||
|
load->set_total_bytes_received( |
||||||
|
static_cast<int64_t>(value.bytes_recv())); |
||||||
|
load->mutable_total_latency()->set_seconds( |
||||||
|
static_cast<int64_t>(value.latency_ms() / 1000)); |
||||||
|
load->mutable_total_latency()->set_nanos( |
||||||
|
(static_cast<int32_t>(value.latency_ms()) % 1000) * 1000000); |
||||||
|
for (const auto& p : value.call_metrics()) { |
||||||
|
const grpc::string& metric_name = p.first; |
||||||
|
const CallMetricValue& metric_value = p.second; |
||||||
|
auto call_metric_data = load->add_metric_data(); |
||||||
|
call_metric_data->set_metric_name(metric_name); |
||||||
|
call_metric_data->set_num_calls_finished_with_metric( |
||||||
|
metric_value.num_calls()); |
||||||
|
call_metric_data->set_total_metric_value( |
||||||
|
metric_value.total_metric_value()); |
||||||
|
} |
||||||
|
if (per_balancer_store->lb_id() != lb_id) { |
||||||
|
// This per-balancer store is an orphan assigned to this receiving
|
||||||
|
// balancer.
|
||||||
|
AttachOrphanLoadId(load, *per_balancer_store); |
||||||
|
} |
||||||
|
} |
||||||
|
per_balancer_store->ClearLoadRecordMap(); |
||||||
|
} |
||||||
|
if (per_balancer_store->IsNumCallsInProgressChangedSinceLastReport()) { |
||||||
|
auto load = loads.Add(); |
||||||
|
load->set_num_calls_in_progress( |
||||||
|
per_balancer_store->GetNumCallsInProgressForReport()); |
||||||
|
if (per_balancer_store->lb_id() != lb_id) { |
||||||
|
// This per-balancer store is an orphan assigned to this receiving
|
||||||
|
// balancer.
|
||||||
|
AttachOrphanLoadId(load, *per_balancer_store); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return loads; |
||||||
|
} |
||||||
|
|
||||||
|
void LoadReporter::AttachOrphanLoadId( |
||||||
|
::grpc::lb::v1::Load* load, const PerBalancerStore& per_balancer_store) { |
||||||
|
if (per_balancer_store.lb_id() == kInvalidLbId) { |
||||||
|
load->set_load_key_unknown(true); |
||||||
|
} else { |
||||||
|
load->set_load_key_unknown(false); |
||||||
|
load->mutable_orphaned_load_identifier()->set_load_key( |
||||||
|
per_balancer_store.load_key()); |
||||||
|
load->mutable_orphaned_load_identifier()->set_load_balancer_id( |
||||||
|
per_balancer_store.lb_id()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void LoadReporter::AppendNewFeedbackRecord(uint64_t rpcs, uint64_t errors) { |
||||||
|
CpuStatsProvider::CpuStatsSample cpu_stats; |
||||||
|
if (cpu_stats_provider_ != nullptr) { |
||||||
|
cpu_stats = cpu_stats_provider_->GetCpuStats(); |
||||||
|
} else { |
||||||
|
// This will make the load balancing feedback generation a no-op.
|
||||||
|
cpu_stats = {0, 0}; |
||||||
|
} |
||||||
|
std::unique_lock<std::mutex> lock(feedback_mu_); |
||||||
|
feedback_records_.emplace_back(std::chrono::system_clock::now(), rpcs, errors, |
||||||
|
cpu_stats.first, cpu_stats.second); |
||||||
|
} |
||||||
|
|
||||||
|
void LoadReporter::ReportStreamCreated(const grpc::string& hostname, |
||||||
|
const grpc::string& lb_id, |
||||||
|
const grpc::string& load_key) { |
||||||
|
std::lock_guard<std::mutex> lock(store_mu_); |
||||||
|
load_data_store_.ReportStreamCreated(hostname, lb_id, load_key); |
||||||
|
gpr_log(GPR_INFO, |
||||||
|
"[LR %p] Report stream created (host: %s, LB ID: %s, load key: %s).", |
||||||
|
this, hostname.c_str(), lb_id.c_str(), load_key.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
void LoadReporter::ReportStreamClosed(const grpc::string& hostname, |
||||||
|
const grpc::string& lb_id) { |
||||||
|
std::lock_guard<std::mutex> lock(store_mu_); |
||||||
|
load_data_store_.ReportStreamClosed(hostname, lb_id); |
||||||
|
gpr_log(GPR_INFO, "[LR %p] Report stream closed (host: %s, LB ID: %s).", this, |
||||||
|
hostname.c_str(), lb_id.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
void LoadReporter::ProcessViewDataCallStart( |
||||||
|
const CensusViewProvider::ViewDataMap& view_data_map) { |
||||||
|
auto it = view_data_map.find(kViewStartCount); |
||||||
|
if (it != view_data_map.end()) { |
||||||
|
// Note that the data type for any Sum view is double, whatever the data
|
||||||
|
// type of the original measure.
|
||||||
|
for (const auto& p : it->second.double_data()) { |
||||||
|
const std::vector<grpc::string>& tag_values = p.first; |
||||||
|
const uint64_t start_count = static_cast<uint64_t>(p.second); |
||||||
|
const grpc::string& client_ip_and_token = tag_values[0]; |
||||||
|
const grpc::string& host = tag_values[1]; |
||||||
|
const grpc::string& user_id = tag_values[2]; |
||||||
|
LoadRecordKey key(client_ip_and_token, user_id); |
||||||
|
LoadRecordValue value = LoadRecordValue(start_count); |
||||||
|
{ |
||||||
|
std::unique_lock<std::mutex> lock(store_mu_); |
||||||
|
load_data_store_.MergeRow(host, key, value); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void LoadReporter::ProcessViewDataCallEnd( |
||||||
|
const CensusViewProvider::ViewDataMap& view_data_map) { |
||||||
|
uint64_t total_end_count = 0; |
||||||
|
uint64_t total_error_count = 0; |
||||||
|
auto it = view_data_map.find(kViewEndCount); |
||||||
|
if (it != view_data_map.end()) { |
||||||
|
// Note that the data type for any Sum view is double, whatever the data
|
||||||
|
// type of the original measure.
|
||||||
|
for (const auto& p : it->second.double_data()) { |
||||||
|
const std::vector<grpc::string>& tag_values = p.first; |
||||||
|
const uint64_t end_count = static_cast<uint64_t>(p.second); |
||||||
|
const grpc::string& client_ip_and_token = tag_values[0]; |
||||||
|
const grpc::string& host = tag_values[1]; |
||||||
|
const grpc::string& user_id = tag_values[2]; |
||||||
|
const grpc::string& status = tag_values[3]; |
||||||
|
// This is due to a bug reported internally of Java server load reporting
|
||||||
|
// implementation.
|
||||||
|
// TODO(juanlishen): Check whether this situation happens in OSS C++.
|
||||||
|
if (client_ip_and_token.size() == 0) { |
||||||
|
gpr_log(GPR_DEBUG, |
||||||
|
"Skipping processing Opencensus record with empty " |
||||||
|
"client_ip_and_token tag."); |
||||||
|
continue; |
||||||
|
} |
||||||
|
LoadRecordKey key(client_ip_and_token, user_id); |
||||||
|
const uint64_t bytes_sent = |
||||||
|
CensusViewProvider::GetRelatedViewDataRowDouble( |
||||||
|
view_data_map, kViewEndBytesSent, sizeof(kViewEndBytesSent) - 1, |
||||||
|
tag_values); |
||||||
|
const uint64_t bytes_received = |
||||||
|
CensusViewProvider::GetRelatedViewDataRowDouble( |
||||||
|
view_data_map, kViewEndBytesReceived, |
||||||
|
sizeof(kViewEndBytesReceived) - 1, tag_values); |
||||||
|
const uint64_t latency_ms = |
||||||
|
CensusViewProvider::GetRelatedViewDataRowDouble( |
||||||
|
view_data_map, kViewEndLatencyMs, sizeof(kViewEndLatencyMs) - 1, |
||||||
|
tag_values); |
||||||
|
uint64_t ok_count = 0; |
||||||
|
uint64_t error_count = 0; |
||||||
|
total_end_count += end_count; |
||||||
|
if (std::strcmp(status.c_str(), kCallStatusOk) == 0) { |
||||||
|
ok_count = end_count; |
||||||
|
} else { |
||||||
|
error_count = end_count; |
||||||
|
total_error_count += end_count; |
||||||
|
} |
||||||
|
LoadRecordValue value = LoadRecordValue( |
||||||
|
0, ok_count, error_count, bytes_sent, bytes_received, latency_ms); |
||||||
|
{ |
||||||
|
std::unique_lock<std::mutex> lock(store_mu_); |
||||||
|
load_data_store_.MergeRow(host, key, value); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
AppendNewFeedbackRecord(total_end_count, total_error_count); |
||||||
|
} |
||||||
|
|
||||||
|
void LoadReporter::ProcessViewDataOtherCallMetrics( |
||||||
|
const CensusViewProvider::ViewDataMap& view_data_map) { |
||||||
|
auto it = view_data_map.find(kViewOtherCallMetricCount); |
||||||
|
if (it != view_data_map.end()) { |
||||||
|
for (const auto& p : it->second.int_data()) { |
||||||
|
const std::vector<grpc::string>& tag_values = p.first; |
||||||
|
const int64_t num_calls = p.second; |
||||||
|
const grpc::string& client_ip_and_token = tag_values[0]; |
||||||
|
const grpc::string& host = tag_values[1]; |
||||||
|
const grpc::string& user_id = tag_values[2]; |
||||||
|
const grpc::string& metric_name = tag_values[3]; |
||||||
|
LoadRecordKey key(client_ip_and_token, user_id); |
||||||
|
const double total_metric_value = |
||||||
|
CensusViewProvider::GetRelatedViewDataRowDouble( |
||||||
|
view_data_map, kViewOtherCallMetricValue, |
||||||
|
sizeof(kViewOtherCallMetricValue) - 1, tag_values); |
||||||
|
LoadRecordValue value = LoadRecordValue( |
||||||
|
metric_name, static_cast<uint64_t>(num_calls), total_metric_value); |
||||||
|
{ |
||||||
|
std::unique_lock<std::mutex> lock(store_mu_); |
||||||
|
load_data_store_.MergeRow(host, key, value); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void LoadReporter::FetchAndSample() { |
||||||
|
gpr_log(GPR_DEBUG, |
||||||
|
"[LR %p] Starts fetching Census view data and sampling LB feedback " |
||||||
|
"record.", |
||||||
|
this); |
||||||
|
CensusViewProvider::ViewDataMap view_data_map = |
||||||
|
census_view_provider_->FetchViewData(); |
||||||
|
ProcessViewDataCallStart(view_data_map); |
||||||
|
ProcessViewDataCallEnd(view_data_map); |
||||||
|
ProcessViewDataOtherCallMetrics(view_data_map); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace load_reporter
|
||||||
|
} // namespace grpc
|
@ -0,0 +1,225 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef GRPC_SRC_CPP_SERVER_LOAD_REPORTER_LOAD_REPORTER_H |
||||||
|
#define GRPC_SRC_CPP_SERVER_LOAD_REPORTER_LOAD_REPORTER_H |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include <atomic> |
||||||
|
#include <chrono> |
||||||
|
#include <deque> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include <grpc/support/log.h> |
||||||
|
#include <grpcpp/impl/codegen/config.h> |
||||||
|
|
||||||
|
#include "src/cpp/server/load_reporter/load_data_store.h" |
||||||
|
#include "src/proto/grpc/lb/v1/load_reporter.grpc.pb.h" |
||||||
|
|
||||||
|
#include "opencensus/stats/stats.h" |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace load_reporter { |
||||||
|
|
||||||
|
// The interface to get the Census stats. Abstracted for mocking.
|
||||||
|
class CensusViewProvider { |
||||||
|
public: |
||||||
|
// Maps from the view name to the view data.
|
||||||
|
using ViewDataMap = |
||||||
|
std::unordered_map<grpc::string, ::opencensus::stats::ViewData>; |
||||||
|
// Maps from the view name to the view descriptor.
|
||||||
|
using ViewDescriptorMap = |
||||||
|
std::unordered_map<grpc::string, ::opencensus::stats::ViewDescriptor>; |
||||||
|
|
||||||
|
CensusViewProvider(); |
||||||
|
virtual ~CensusViewProvider() = default; |
||||||
|
|
||||||
|
// Fetches the view data accumulated since last fetching, and returns it as a
|
||||||
|
// map from the view name to the view data.
|
||||||
|
virtual ViewDataMap FetchViewData() = 0; |
||||||
|
|
||||||
|
// A helper function that gets a row with the input tag values from the view
|
||||||
|
// data. Only used when we know that row must exist because we have seen a row
|
||||||
|
// with the same tag values in a related view data. Several ViewData's are
|
||||||
|
// considered related if their views are based on the measures that are always
|
||||||
|
// recorded at the same time.
|
||||||
|
double static GetRelatedViewDataRowDouble( |
||||||
|
const ViewDataMap& view_data_map, const char* view_name, |
||||||
|
size_t view_name_len, const std::vector<grpc::string>& tag_values); |
||||||
|
|
||||||
|
protected: |
||||||
|
const ViewDescriptorMap& view_descriptor_map() const { |
||||||
|
return view_descriptor_map_; |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
ViewDescriptorMap view_descriptor_map_; |
||||||
|
// Tag keys.
|
||||||
|
::opencensus::stats::TagKey tag_key_token_; |
||||||
|
::opencensus::stats::TagKey tag_key_host_; |
||||||
|
::opencensus::stats::TagKey tag_key_user_id_; |
||||||
|
::opencensus::stats::TagKey tag_key_status_; |
||||||
|
::opencensus::stats::TagKey tag_key_metric_name_; |
||||||
|
}; |
||||||
|
|
||||||
|
// The default implementation fetches the real stats from Census.
|
||||||
|
class CensusViewProviderDefaultImpl : public CensusViewProvider { |
||||||
|
public: |
||||||
|
CensusViewProviderDefaultImpl(); |
||||||
|
|
||||||
|
ViewDataMap FetchViewData() override; |
||||||
|
|
||||||
|
private: |
||||||
|
std::unordered_map<grpc::string, ::opencensus::stats::View> view_map_; |
||||||
|
}; |
||||||
|
|
||||||
|
// The interface to get the CPU stats. Abstracted for mocking.
|
||||||
|
class CpuStatsProvider { |
||||||
|
public: |
||||||
|
// The used and total amounts of CPU usage.
|
||||||
|
using CpuStatsSample = std::pair<uint64_t, uint64_t>; |
||||||
|
|
||||||
|
virtual ~CpuStatsProvider() = default; |
||||||
|
|
||||||
|
// Gets the cumulative used CPU and total CPU resource.
|
||||||
|
virtual CpuStatsSample GetCpuStats() = 0; |
||||||
|
}; |
||||||
|
|
||||||
|
// The default implementation reads CPU jiffies from the system to calculate CPU
|
||||||
|
// utilization.
|
||||||
|
class CpuStatsProviderDefaultImpl : public CpuStatsProvider { |
||||||
|
public: |
||||||
|
CpuStatsSample GetCpuStats() override; |
||||||
|
}; |
||||||
|
|
||||||
|
// Maintains all the load data and load reporting streams.
|
||||||
|
class LoadReporter { |
||||||
|
public: |
||||||
|
// TODO(juanlishen): Allow config for providers from users.
|
||||||
|
LoadReporter(uint32_t feedback_sample_window_seconds, |
||||||
|
std::unique_ptr<CensusViewProvider> census_view_provider, |
||||||
|
std::unique_ptr<CpuStatsProvider> cpu_stats_provider) |
||||||
|
: feedback_sample_window_seconds_(feedback_sample_window_seconds), |
||||||
|
census_view_provider_(std::move(census_view_provider)), |
||||||
|
cpu_stats_provider_(std::move(cpu_stats_provider)) { |
||||||
|
// Append the initial record so that the next real record can have a base.
|
||||||
|
AppendNewFeedbackRecord(0, 0); |
||||||
|
} |
||||||
|
|
||||||
|
// Fetches the latest data from Census and merge it into the data store.
|
||||||
|
// Also adds a new sample to the LB feedback sliding window.
|
||||||
|
// Thread-unsafe. (1). The access to the load data store and feedback records
|
||||||
|
// has locking. (2). The access to the Census view provider and CPU stats
|
||||||
|
// provider lacks locking, but we only access these two members in this method
|
||||||
|
// (in testing, we also access them when setting up expectation). So the
|
||||||
|
// invocations of this method must be serialized.
|
||||||
|
void FetchAndSample(); |
||||||
|
|
||||||
|
// Generates a report for that host and balancer. The report contains
|
||||||
|
// all the stats data accumulated between the last report (i.e., the last
|
||||||
|
// consumption) and the last fetch from Census (i.e., the last production).
|
||||||
|
// Thread-safe.
|
||||||
|
::google::protobuf::RepeatedPtrField<::grpc::lb::v1::Load> GenerateLoads( |
||||||
|
const grpc::string& hostname, const grpc::string& lb_id); |
||||||
|
|
||||||
|
// The feedback is calculated from the stats data recorded in the sliding
|
||||||
|
// window. Outdated records are discarded.
|
||||||
|
// Thread-safe.
|
||||||
|
::grpc::lb::v1::LoadBalancingFeedback GenerateLoadBalancingFeedback(); |
||||||
|
|
||||||
|
// Wrapper around LoadDataStore::ReportStreamCreated.
|
||||||
|
// Thread-safe.
|
||||||
|
void ReportStreamCreated(const grpc::string& hostname, |
||||||
|
const grpc::string& lb_id, |
||||||
|
const grpc::string& load_key); |
||||||
|
|
||||||
|
// Wrapper around LoadDataStore::ReportStreamClosed.
|
||||||
|
// Thread-safe.
|
||||||
|
void ReportStreamClosed(const grpc::string& hostname, |
||||||
|
const grpc::string& lb_id); |
||||||
|
|
||||||
|
// Generates a unique LB ID of length kLbIdLength. Returns an empty string
|
||||||
|
// upon failure. Thread-safe.
|
||||||
|
grpc::string GenerateLbId(); |
||||||
|
|
||||||
|
// Accessors only for testing.
|
||||||
|
CensusViewProvider* census_view_provider() { |
||||||
|
return census_view_provider_.get(); |
||||||
|
} |
||||||
|
CpuStatsProvider* cpu_stats_provider() { return cpu_stats_provider_.get(); } |
||||||
|
|
||||||
|
private: |
||||||
|
struct LoadBalancingFeedbackRecord { |
||||||
|
std::chrono::system_clock::time_point end_time; |
||||||
|
uint64_t rpcs; |
||||||
|
uint64_t errors; |
||||||
|
uint64_t cpu_usage; |
||||||
|
uint64_t cpu_limit; |
||||||
|
|
||||||
|
LoadBalancingFeedbackRecord( |
||||||
|
const std::chrono::system_clock::time_point& end_time, uint64_t rpcs, |
||||||
|
uint64_t errors, uint64_t cpu_usage, uint64_t cpu_limit) |
||||||
|
: end_time(end_time), |
||||||
|
rpcs(rpcs), |
||||||
|
errors(errors), |
||||||
|
cpu_usage(cpu_usage), |
||||||
|
cpu_limit(cpu_limit) {} |
||||||
|
}; |
||||||
|
|
||||||
|
// Finds the view data about starting call from the view_data_map and merges
|
||||||
|
// the data to the load data store.
|
||||||
|
void ProcessViewDataCallStart( |
||||||
|
const CensusViewProvider::ViewDataMap& view_data_map); |
||||||
|
// Finds the view data about ending call from the view_data_map and merges the
|
||||||
|
// data to the load data store.
|
||||||
|
void ProcessViewDataCallEnd( |
||||||
|
const CensusViewProvider::ViewDataMap& view_data_map); |
||||||
|
// Finds the view data about the customized call metrics from the
|
||||||
|
// view_data_map and merges the data to the load data store.
|
||||||
|
void ProcessViewDataOtherCallMetrics( |
||||||
|
const CensusViewProvider::ViewDataMap& view_data_map); |
||||||
|
|
||||||
|
bool IsRecordInWindow(const LoadBalancingFeedbackRecord& record, |
||||||
|
std::chrono::system_clock::time_point now) { |
||||||
|
return record.end_time > now - feedback_sample_window_seconds_; |
||||||
|
} |
||||||
|
|
||||||
|
void AppendNewFeedbackRecord(uint64_t rpcs, uint64_t errors); |
||||||
|
|
||||||
|
// Extracts an OrphanedLoadIdentifier from the per-balancer store and attaches
|
||||||
|
// it to the load.
|
||||||
|
void AttachOrphanLoadId(::grpc::lb::v1::Load* load, |
||||||
|
const PerBalancerStore& per_balancer_store); |
||||||
|
|
||||||
|
std::atomic<int64_t> next_lb_id_{0}; |
||||||
|
const std::chrono::seconds feedback_sample_window_seconds_; |
||||||
|
std::mutex feedback_mu_; |
||||||
|
std::deque<LoadBalancingFeedbackRecord> feedback_records_; |
||||||
|
// TODO(juanlishen): Lock in finer grain. Locking the whole store may be
|
||||||
|
// too expensive.
|
||||||
|
std::mutex store_mu_; |
||||||
|
LoadDataStore load_data_store_; |
||||||
|
std::unique_ptr<CensusViewProvider> census_view_provider_; |
||||||
|
std::unique_ptr<CpuStatsProvider> cpu_stats_provider_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace load_reporter
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // GRPC_SRC_CPP_SERVER_LOAD_REPORTER_LOAD_REPORTER_H
|
@ -0,0 +1,180 @@ |
|||||||
|
// Copyright 2018 gRPC authors. |
||||||
|
// |
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
// you may not use this file except in compliance with the License. |
||||||
|
// You may obtain a copy of the License at |
||||||
|
// |
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
// |
||||||
|
// Unless required by applicable law or agreed to in writing, software |
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
// See the License for the specific language governing permissions and |
||||||
|
// limitations under the License. |
||||||
|
|
||||||
|
syntax = "proto3"; |
||||||
|
|
||||||
|
package grpc.lb.v1; |
||||||
|
|
||||||
|
import "google/protobuf/duration.proto"; |
||||||
|
|
||||||
|
// The LoadReporter service. |
||||||
|
service LoadReporter { |
||||||
|
// Report load from server to lb. |
||||||
|
rpc ReportLoad(stream LoadReportRequest) |
||||||
|
returns (stream LoadReportResponse) { |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
message LoadReportRequest { |
||||||
|
// This message should be sent on the first request to the gRPC server. |
||||||
|
InitialLoadReportRequest initial_request = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message InitialLoadReportRequest { |
||||||
|
// The hostname this load reporter client is requesting load for. |
||||||
|
string load_balanced_hostname = 1; |
||||||
|
|
||||||
|
// Additional information to disambiguate orphaned load: load that should have |
||||||
|
// gone to this load reporter client, but was not able to be sent since the |
||||||
|
// load reporter client has disconnected. load_key is sent in orphaned load |
||||||
|
// reports; see Load.load_key. |
||||||
|
bytes load_key = 2; |
||||||
|
|
||||||
|
// This interval defines how often the server should send load reports to |
||||||
|
// the load balancer. |
||||||
|
google.protobuf.Duration load_report_interval = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message LoadReportResponse { |
||||||
|
// This message should be sent on the first response to the load balancer. |
||||||
|
InitialLoadReportResponse initial_response = 1; |
||||||
|
|
||||||
|
// Reports server-wide statistics for load balancing. |
||||||
|
// This should be reported with every response. |
||||||
|
LoadBalancingFeedback load_balancing_feedback = 2; |
||||||
|
|
||||||
|
// A load report for each <tag, user_id> tuple. This could be considered to be |
||||||
|
// a multimap indexed by <tag, user_id>. It is not strictly necessary to |
||||||
|
// aggregate all entries into one entry per <tag, user_id> tuple, although it |
||||||
|
// is preferred to do so. |
||||||
|
repeated Load load = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message InitialLoadReportResponse { |
||||||
|
// Initial response returns the Load balancer ID. This must be plain text |
||||||
|
// (printable ASCII). |
||||||
|
string load_balancer_id = 1; |
||||||
|
|
||||||
|
enum ImplementationIdentifier { |
||||||
|
IMPL_UNSPECIFIED = 0; |
||||||
|
CPP = 1; // Standard Google C++ implementation. |
||||||
|
JAVA = 2; // Standard Google Java implementation. |
||||||
|
GO = 3; // Standard Google Go implementation. |
||||||
|
} |
||||||
|
// Optional identifier of this implementation of the load reporting server. |
||||||
|
ImplementationIdentifier implementation_id = 2; |
||||||
|
|
||||||
|
// Optional server_version should be a value that is modified (and |
||||||
|
// monotonically increased) when changes are made to the server |
||||||
|
// implementation. |
||||||
|
int64 server_version = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message LoadBalancingFeedback { |
||||||
|
// Reports the current utilization of the server (typical range [0.0 - 1.0]). |
||||||
|
float server_utilization = 1; |
||||||
|
|
||||||
|
// The total rate of calls handled by this server (including errors). |
||||||
|
float calls_per_second = 2; |
||||||
|
|
||||||
|
// The total rate of error responses sent by this server. |
||||||
|
float errors_per_second = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message Load { |
||||||
|
// The (plain text) tag used by the calls covered by this load report. The |
||||||
|
// tag is that part of the load balancer token after removing the load |
||||||
|
// balancer id. Empty is equivalent to non-existent tag. |
||||||
|
string load_balance_tag = 1; |
||||||
|
|
||||||
|
// The user identity authenticated by the calls covered by this load |
||||||
|
// report. Empty is equivalent to no known user_id. |
||||||
|
string user_id = 3; |
||||||
|
|
||||||
|
// IP address of the client that sent these requests, serialized in |
||||||
|
// network-byte-order. It may either be an IPv4 or IPv6 address. |
||||||
|
bytes client_ip_address = 15; |
||||||
|
|
||||||
|
// The number of calls started (since the last report) with the given tag and |
||||||
|
// user_id. |
||||||
|
int64 num_calls_started = 4; |
||||||
|
|
||||||
|
// Indicates whether this load report is an in-progress load report in which |
||||||
|
// num_calls_in_progress is the only valid entry. If in_progress_report is not |
||||||
|
// set, num_calls_in_progress will be ignored. If in_progress_report is set, |
||||||
|
// fields other than num_calls_in_progress and orphaned_load will be ignored. |
||||||
|
oneof in_progress_report { |
||||||
|
// The number of calls in progress (instantaneously) per load balancer id. |
||||||
|
int64 num_calls_in_progress = 5; |
||||||
|
} |
||||||
|
|
||||||
|
// The following values are counts or totals of call statistics that finished |
||||||
|
// with the given tag and user_id. |
||||||
|
int64 num_calls_finished_without_error = 6; // Calls with status OK. |
||||||
|
int64 num_calls_finished_with_error = 7; // Calls with status non-OK. |
||||||
|
// Calls that finished with a status that maps to HTTP 5XX (see |
||||||
|
// googleapis/google/rpc/code.proto). Note that this is a subset of |
||||||
|
// num_calls_finished_with_error. |
||||||
|
int64 num_calls_finished_with_server_error = 16; |
||||||
|
|
||||||
|
// Totals are from calls that with _and_ without error. |
||||||
|
int64 total_bytes_sent = 8; |
||||||
|
int64 total_bytes_received = 9; |
||||||
|
google.protobuf.Duration total_latency = 10; |
||||||
|
|
||||||
|
// Optional metrics reported for the call(s). Requires that metric_name is |
||||||
|
// unique. |
||||||
|
repeated CallMetricData metric_data = 11; |
||||||
|
|
||||||
|
// The following two fields are used for reporting orphaned load: load that |
||||||
|
// could not be reported to the originating balancer either since the balancer |
||||||
|
// is no longer connected or because the frontend sent an invalid token. These |
||||||
|
// fields must not be set with normal (unorphaned) load reports. |
||||||
|
oneof orphaned_load { |
||||||
|
// Load_key is the load_key from the initial_request from the originating |
||||||
|
// balancer. |
||||||
|
bytes load_key = 12 [deprecated=true]; |
||||||
|
|
||||||
|
// If true then this load report is for calls that had an invalid token; the |
||||||
|
// user is probably abusing the gRPC protocol. |
||||||
|
// TODO(yankaiz): Rename load_key_unknown. |
||||||
|
bool load_key_unknown = 13; |
||||||
|
|
||||||
|
// load_key and balancer_id are included in order to identify orphaned load |
||||||
|
// from different origins. |
||||||
|
OrphanedLoadIdentifier orphaned_load_identifier = 14; |
||||||
|
} |
||||||
|
|
||||||
|
reserved 2; |
||||||
|
} |
||||||
|
|
||||||
|
message CallMetricData { |
||||||
|
// Name of the metric; may be empty. |
||||||
|
string metric_name = 1; |
||||||
|
|
||||||
|
// Number of calls that finished and included this metric. |
||||||
|
int64 num_calls_finished_with_metric = 2; |
||||||
|
|
||||||
|
// Sum of metric values across all calls that finished with this metric. |
||||||
|
double total_metric_value = 3; |
||||||
|
} |
||||||
|
|
||||||
|
message OrphanedLoadIdentifier { |
||||||
|
// The load_key from the initial_request from the originating balancer. |
||||||
|
bytes load_key = 1; |
||||||
|
|
||||||
|
// The unique ID generated by LoadReporter to identify balancers. Here it |
||||||
|
// distinguishes orphaned load with a same load_key. |
||||||
|
string load_balancer_id = 2; |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <grpc/impl/codegen/port_platform.h> |
||||||
|
|
||||||
|
#include <grpc/grpc.h> |
||||||
|
#include <gtest/gtest.h> |
||||||
|
|
||||||
|
#include "test/core/util/port.h" |
||||||
|
#include "test/core/util/test_config.h" |
||||||
|
|
||||||
|
#include "src/cpp/server/load_reporter/get_cpu_stats.h" |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace testing { |
||||||
|
namespace { |
||||||
|
|
||||||
|
TEST(GetCpuStatsTest, ReadOnce) { ::grpc::load_reporter::GetCpuStatsImpl(); } |
||||||
|
|
||||||
|
TEST(GetCpuStatsTest, BusyNoLargerThanTotal) { |
||||||
|
auto p = ::grpc::load_reporter::GetCpuStatsImpl(); |
||||||
|
uint64_t busy = p.first; |
||||||
|
uint64_t total = p.second; |
||||||
|
ASSERT_LE(busy, total); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(GetCpuStatsTest, Ascending) { |
||||||
|
const size_t kRuns = 100; |
||||||
|
auto prev = ::grpc::load_reporter::GetCpuStatsImpl(); |
||||||
|
for (size_t i = 0; i < kRuns; ++i) { |
||||||
|
auto cur = ::grpc::load_reporter::GetCpuStatsImpl(); |
||||||
|
ASSERT_LE(prev.first, cur.first); |
||||||
|
ASSERT_LE(prev.second, cur.second); |
||||||
|
prev = cur; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace testing
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
grpc_test_init(argc, argv); |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
@ -0,0 +1,498 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <grpc/impl/codegen/port_platform.h> |
||||||
|
|
||||||
|
#include <set> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include <gmock/gmock.h> |
||||||
|
#include <grpc/grpc.h> |
||||||
|
#include <gtest/gtest.h> |
||||||
|
|
||||||
|
#include "src/core/lib/iomgr/exec_ctx.h" |
||||||
|
#include "src/cpp/server/load_reporter/constants.h" |
||||||
|
#include "src/cpp/server/load_reporter/load_reporter.h" |
||||||
|
#include "test/core/util/port.h" |
||||||
|
#include "test/core/util/test_config.h" |
||||||
|
|
||||||
|
#include "opencensus/stats/testing/test_utils.h" |
||||||
|
|
||||||
|
namespace grpc { |
||||||
|
namespace testing { |
||||||
|
namespace { |
||||||
|
|
||||||
|
using ::grpc::lb::v1::LoadBalancingFeedback; |
||||||
|
using ::grpc::load_reporter::CensusViewProvider; |
||||||
|
using ::grpc::load_reporter::CpuStatsProvider; |
||||||
|
using ::grpc::load_reporter::LoadReporter; |
||||||
|
using ::opencensus::stats::View; |
||||||
|
using ::opencensus::stats::ViewData; |
||||||
|
using ::opencensus::stats::ViewDataImpl; |
||||||
|
using ::opencensus::stats::ViewDescriptor; |
||||||
|
using ::testing::DoubleNear; |
||||||
|
using ::testing::Return; |
||||||
|
|
||||||
|
constexpr uint64_t kFeedbackSampleWindowSeconds = 5; |
||||||
|
constexpr uint64_t kFetchAndSampleIntervalSeconds = 1; |
||||||
|
constexpr uint64_t kNumFeedbackSamplesInWindow = |
||||||
|
kFeedbackSampleWindowSeconds / kFetchAndSampleIntervalSeconds; |
||||||
|
|
||||||
|
class MockCensusViewProvider : public CensusViewProvider { |
||||||
|
public: |
||||||
|
MOCK_METHOD0(FetchViewData, CensusViewProvider::ViewDataMap()); |
||||||
|
|
||||||
|
const ::opencensus::stats::ViewDescriptor& FindViewDescriptor( |
||||||
|
const grpc::string& view_name) { |
||||||
|
auto it = view_descriptor_map().find(view_name); |
||||||
|
GPR_ASSERT(it != view_descriptor_map().end()); |
||||||
|
return it->second; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class MockCpuStatsProvider : public CpuStatsProvider { |
||||||
|
public: |
||||||
|
MOCK_METHOD0(GetCpuStats, CpuStatsProvider::CpuStatsSample()); |
||||||
|
}; |
||||||
|
|
||||||
|
class LoadReporterTest : public ::testing::Test { |
||||||
|
public: |
||||||
|
LoadReporterTest() {} |
||||||
|
|
||||||
|
MockCensusViewProvider* mock_census_view_provider() { |
||||||
|
return static_cast<MockCensusViewProvider*>( |
||||||
|
load_reporter_->census_view_provider()); |
||||||
|
} |
||||||
|
|
||||||
|
void PrepareCpuExpectation(size_t call_num) { |
||||||
|
auto mock_cpu_stats_provider = static_cast<MockCpuStatsProvider*>( |
||||||
|
load_reporter_->cpu_stats_provider()); |
||||||
|
::testing::InSequence s; |
||||||
|
for (size_t i = 0; i < call_num; ++i) { |
||||||
|
EXPECT_CALL(*mock_cpu_stats_provider, GetCpuStats()) |
||||||
|
.WillOnce(Return(kCpuStatsSamples[i])) |
||||||
|
.RetiresOnSaturation(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
CpuStatsProvider::CpuStatsSample initial_cpu_stats_{2, 20}; |
||||||
|
const std::vector<CpuStatsProvider::CpuStatsSample> kCpuStatsSamples = { |
||||||
|
{13, 53}, {64, 96}, {245, 345}, {314, 785}, |
||||||
|
{874, 1230}, {1236, 2145}, {1864, 2974}}; |
||||||
|
|
||||||
|
std::unique_ptr<LoadReporter> load_reporter_; |
||||||
|
|
||||||
|
const grpc::string kHostname1 = "kHostname1"; |
||||||
|
const grpc::string kHostname2 = "kHostname2"; |
||||||
|
const grpc::string kHostname3 = "kHostname3"; |
||||||
|
// Pad to the length of a valid LB ID.
|
||||||
|
const grpc::string kLbId1 = "kLbId111"; |
||||||
|
const grpc::string kLbId2 = "kLbId222"; |
||||||
|
const grpc::string kLbId3 = "kLbId333"; |
||||||
|
const grpc::string kLbId4 = "kLbId444"; |
||||||
|
const grpc::string kLoadKey1 = "kLoadKey1"; |
||||||
|
const grpc::string kLoadKey2 = "kLoadKey2"; |
||||||
|
const grpc::string kLoadKey3 = "kLoadKey3"; |
||||||
|
const grpc::string kLbTag1 = "kLbTag1"; |
||||||
|
const grpc::string kLbTag2 = "kLbTag2"; |
||||||
|
const grpc::string kLbToken1 = "kLbId111kLbTag1"; |
||||||
|
const grpc::string kLbToken2 = "kLbId222kLbTag2"; |
||||||
|
const grpc::string kUser1 = "kUser1"; |
||||||
|
const grpc::string kUser2 = "kUser2"; |
||||||
|
const grpc::string kUser3 = "kUser3"; |
||||||
|
const grpc::string kClientIp0 = "00"; |
||||||
|
const grpc::string kClientIp1 = "0800000001"; |
||||||
|
const grpc::string kClientIp2 = "3200000000000000000000000000000002"; |
||||||
|
const grpc::string kMetric1 = "kMetric1"; |
||||||
|
const grpc::string kMetric2 = "kMetric2"; |
||||||
|
|
||||||
|
private: |
||||||
|
void SetUp() override { |
||||||
|
auto mock_cpu = new MockCpuStatsProvider(); |
||||||
|
auto mock_census = new MockCensusViewProvider(); |
||||||
|
// Prepare the initial CPU stats data. Note that the expectation should be
|
||||||
|
// set up before the load reporter is initialized, because CPU stats is
|
||||||
|
// sampled at that point.
|
||||||
|
EXPECT_CALL(*mock_cpu, GetCpuStats()) |
||||||
|
.WillOnce(Return(initial_cpu_stats_)) |
||||||
|
.RetiresOnSaturation(); |
||||||
|
load_reporter_ = std::unique_ptr<LoadReporter>( |
||||||
|
new LoadReporter(kFeedbackSampleWindowSeconds, |
||||||
|
std::unique_ptr<CensusViewProvider>(mock_census), |
||||||
|
std::unique_ptr<CpuStatsProvider>(mock_cpu))); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class LbFeedbackTest : public LoadReporterTest { |
||||||
|
public: |
||||||
|
// Note that [start, start + count) of the fake samples (maybe plus the
|
||||||
|
// initial record) are in the window now.
|
||||||
|
void VerifyLbFeedback(const LoadBalancingFeedback& lb_feedback, size_t start, |
||||||
|
size_t count) { |
||||||
|
const CpuStatsProvider::CpuStatsSample* base = |
||||||
|
start == 0 ? &initial_cpu_stats_ : &kCpuStatsSamples[start - 1]; |
||||||
|
double expected_cpu_util = |
||||||
|
static_cast<double>(kCpuStatsSamples[start + count - 1].first - |
||||||
|
base->first) / |
||||||
|
static_cast<double>(kCpuStatsSamples[start + count - 1].second - |
||||||
|
base->second); |
||||||
|
ASSERT_THAT(static_cast<double>(lb_feedback.server_utilization()), |
||||||
|
DoubleNear(expected_cpu_util, 0.00001)); |
||||||
|
double qps_sum = 0, eps_sum = 0; |
||||||
|
for (size_t i = 0; i < count; ++i) { |
||||||
|
qps_sum += kQpsEpsSamples[start + i].first; |
||||||
|
eps_sum += kQpsEpsSamples[start + i].second; |
||||||
|
} |
||||||
|
double expected_qps = qps_sum / count; |
||||||
|
double expected_eps = eps_sum / count; |
||||||
|
// TODO(juanlishen): The error is big because we use sleep(). It should be
|
||||||
|
// much smaller when we use fake clock.
|
||||||
|
ASSERT_THAT(static_cast<double>(lb_feedback.calls_per_second()), |
||||||
|
DoubleNear(expected_qps, expected_qps / 50)); |
||||||
|
ASSERT_THAT(static_cast<double>(lb_feedback.errors_per_second()), |
||||||
|
DoubleNear(expected_eps, expected_eps / 50)); |
||||||
|
gpr_log(GPR_INFO, |
||||||
|
"Verified LB feedback matches the samples of index [%lu, %lu).", |
||||||
|
start, start + count); |
||||||
|
} |
||||||
|
|
||||||
|
const std::vector<std::pair<double, double>> kQpsEpsSamples = { |
||||||
|
{546.1, 153.1}, {62.1, 54.1}, {578.1, 154.2}, {978.1, 645.1}, |
||||||
|
{1132.1, 846.4}, {531.5, 315.4}, {874.1, 324.9}}; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST_F(LbFeedbackTest, ZeroDuration) { |
||||||
|
PrepareCpuExpectation(kCpuStatsSamples.size()); |
||||||
|
EXPECT_CALL(*mock_census_view_provider(), FetchViewData()) |
||||||
|
.WillRepeatedly( |
||||||
|
Return(::grpc::load_reporter::CensusViewProvider::ViewDataMap())); |
||||||
|
// Verify that divide-by-zero exception doesn't happen.
|
||||||
|
for (size_t i = 0; i < kCpuStatsSamples.size(); ++i) { |
||||||
|
load_reporter_->FetchAndSample(); |
||||||
|
} |
||||||
|
load_reporter_->GenerateLoadBalancingFeedback(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(LbFeedbackTest, Normal) { |
||||||
|
// Prepare view data list using the <QPS, EPS> samples.
|
||||||
|
std::vector<CensusViewProvider::ViewDataMap> view_data_map_list; |
||||||
|
for (const auto& p : LbFeedbackTest::kQpsEpsSamples) { |
||||||
|
double qps = p.first; |
||||||
|
double eps = p.second; |
||||||
|
double ok_count = (qps - eps) * kFetchAndSampleIntervalSeconds; |
||||||
|
double error_count = eps * kFetchAndSampleIntervalSeconds; |
||||||
|
double ok_count_1 = ok_count / 3.0; |
||||||
|
double ok_count_2 = ok_count - ok_count_1; |
||||||
|
auto end_count_vd = ::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndCount), |
||||||
|
{{{kClientIp0 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
ok_count_1}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
ok_count_2}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
error_count}}); |
||||||
|
// Values for other view data don't matter.
|
||||||
|
auto end_bytes_sent_vd = |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndBytesSent), |
||||||
|
{{{kClientIp0 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
0}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
0}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
0}}); |
||||||
|
auto end_bytes_received_vd = |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndBytesReceived), |
||||||
|
{{{kClientIp0 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
0}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
0}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
0}}); |
||||||
|
auto end_latency_vd = ::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndLatencyMs), |
||||||
|
{{{kClientIp0 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
0}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
0}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
0}}); |
||||||
|
view_data_map_list.push_back( |
||||||
|
{{::grpc::load_reporter::kViewEndCount, end_count_vd}, |
||||||
|
{::grpc::load_reporter::kViewEndBytesSent, end_bytes_sent_vd}, |
||||||
|
{::grpc::load_reporter::kViewEndBytesReceived, end_bytes_received_vd}, |
||||||
|
{::grpc::load_reporter::kViewEndLatencyMs, end_latency_vd}}); |
||||||
|
} |
||||||
|
{ |
||||||
|
::testing::InSequence s; |
||||||
|
for (size_t i = 0; i < view_data_map_list.size(); ++i) { |
||||||
|
EXPECT_CALL(*mock_census_view_provider(), FetchViewData()) |
||||||
|
.WillOnce(Return(view_data_map_list[i])) |
||||||
|
.RetiresOnSaturation(); |
||||||
|
} |
||||||
|
} |
||||||
|
PrepareCpuExpectation(kNumFeedbackSamplesInWindow + 2); |
||||||
|
// When the load reporter is created, a trivial LB feedback record is added.
|
||||||
|
// But that's not enough for generating an LB feedback.
|
||||||
|
// Fetch some view data so that non-trivial LB feedback can be generated.
|
||||||
|
for (size_t i = 0; i < kNumFeedbackSamplesInWindow / 2; ++i) { |
||||||
|
// TODO(juanlishen): Find some fake clock to speed up testing.
|
||||||
|
sleep(1); |
||||||
|
load_reporter_->FetchAndSample(); |
||||||
|
} |
||||||
|
VerifyLbFeedback(load_reporter_->GenerateLoadBalancingFeedback(), 0, |
||||||
|
kNumFeedbackSamplesInWindow / 2); |
||||||
|
// Fetch more view data so that the feedback record window is just full (the
|
||||||
|
// initial record just falls out of the window).
|
||||||
|
for (size_t i = 0; i < (kNumFeedbackSamplesInWindow + 1) / 2; ++i) { |
||||||
|
sleep(1); |
||||||
|
load_reporter_->FetchAndSample(); |
||||||
|
} |
||||||
|
VerifyLbFeedback(load_reporter_->GenerateLoadBalancingFeedback(), 0, |
||||||
|
kNumFeedbackSamplesInWindow); |
||||||
|
// Further fetching will cause the old records to fall out of the window.
|
||||||
|
for (size_t i = 0; i < 2; ++i) { |
||||||
|
sleep(1); |
||||||
|
load_reporter_->FetchAndSample(); |
||||||
|
} |
||||||
|
VerifyLbFeedback(load_reporter_->GenerateLoadBalancingFeedback(), 2, |
||||||
|
kNumFeedbackSamplesInWindow); |
||||||
|
} |
||||||
|
|
||||||
|
using LoadReportTest = LoadReporterTest; |
||||||
|
|
||||||
|
TEST_F(LoadReportTest, BasicReport) { |
||||||
|
// Make up the first view data map.
|
||||||
|
CensusViewProvider::ViewDataMap vdm1; |
||||||
|
vdm1.emplace( |
||||||
|
::grpc::load_reporter::kViewStartCount, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewStartCount), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1}, 1234}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser1}, 1225}, |
||||||
|
{{kClientIp0 + kLbToken1, kHostname1, kUser1}, 10}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser2}, 464}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3}, 101}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser3}, 17}, |
||||||
|
{{kClientIp2 + kLbId3 + kLbTag2, kHostname2, kUser3}, 23}})); |
||||||
|
vdm1.emplace(::grpc::load_reporter::kViewEndCount, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndCount), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
641}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
272}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
996}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
34}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
18}})); |
||||||
|
vdm1.emplace(::grpc::load_reporter::kViewEndBytesSent, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndBytesSent), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
8977}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
266}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
1276}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
77823}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
48}})); |
||||||
|
vdm1.emplace(::grpc::load_reporter::kViewEndBytesReceived, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndBytesReceived), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
2341}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
466}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
518}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
81}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
27}})); |
||||||
|
vdm1.emplace(::grpc::load_reporter::kViewEndLatencyMs, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndLatencyMs), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
3.14}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
5.26}, |
||||||
|
{{kClientIp2 + kLbToken1, kHostname1, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
45.4}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
4.4}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser2, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
2348.0}})); |
||||||
|
vdm1.emplace( |
||||||
|
::grpc::load_reporter::kViewOtherCallMetricCount, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewOtherCallMetricCount), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser2, kMetric1}, 1}, |
||||||
|
{{kClientIp1 + kLbToken1, kHostname1, kUser2, kMetric1}, 1}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, kMetric2}, |
||||||
|
1}})); |
||||||
|
vdm1.emplace( |
||||||
|
::grpc::load_reporter::kViewOtherCallMetricValue, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewOtherCallMetricValue), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser2, kMetric1}, 1.2}, |
||||||
|
{{kClientIp1 + kLbToken1, kHostname1, kUser2, kMetric1}, 1.2}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, kMetric2}, |
||||||
|
3.2}})); |
||||||
|
// Make up the second view data map.
|
||||||
|
CensusViewProvider::ViewDataMap vdm2; |
||||||
|
vdm2.emplace( |
||||||
|
::grpc::load_reporter::kViewStartCount, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewStartCount), |
||||||
|
{{{kClientIp2 + kLbToken1, kHostname1, kUser1}, 3}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3}, 778}})); |
||||||
|
vdm2.emplace(::grpc::load_reporter::kViewEndCount, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndCount), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
24}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser3, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
546}})); |
||||||
|
vdm2.emplace(::grpc::load_reporter::kViewEndBytesSent, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndBytesSent), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
747}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser3, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
229}})); |
||||||
|
vdm2.emplace(::grpc::load_reporter::kViewEndBytesReceived, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndBytesReceived), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
173}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser3, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
438}})); |
||||||
|
vdm2.emplace(::grpc::load_reporter::kViewEndLatencyMs, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewEndLatencyMs), |
||||||
|
{{{kClientIp1 + kLbToken1, kHostname1, kUser1, |
||||||
|
::grpc::load_reporter::kCallStatusOk}, |
||||||
|
187}, |
||||||
|
{{kClientIp1 + kLbToken2, kHostname2, kUser3, |
||||||
|
::grpc::load_reporter::kCallStatusClientError}, |
||||||
|
34}})); |
||||||
|
vdm2.emplace( |
||||||
|
::grpc::load_reporter::kViewOtherCallMetricCount, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewOtherCallMetricCount), |
||||||
|
{{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, kMetric1}, 1}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, kMetric2}, |
||||||
|
1}})); |
||||||
|
vdm2.emplace( |
||||||
|
::grpc::load_reporter::kViewOtherCallMetricValue, |
||||||
|
::opencensus::stats::testing::TestUtils::MakeViewData( |
||||||
|
mock_census_view_provider()->FindViewDescriptor( |
||||||
|
::grpc::load_reporter::kViewOtherCallMetricValue), |
||||||
|
{{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, kMetric1}, 9.6}, |
||||||
|
{{kClientIp1 + kLbId2 + kLbTag1, kHostname2, kUser3, kMetric2}, |
||||||
|
5.7}})); |
||||||
|
// Set up mock expectation.
|
||||||
|
EXPECT_CALL(*mock_census_view_provider(), FetchViewData()) |
||||||
|
.WillOnce(Return(vdm1)) |
||||||
|
.WillOnce(Return(vdm2)); |
||||||
|
PrepareCpuExpectation(2); |
||||||
|
// Start testing.
|
||||||
|
load_reporter_->ReportStreamCreated(kHostname1, kLbId1, kLoadKey1); |
||||||
|
load_reporter_->ReportStreamCreated(kHostname2, kLbId2, kLoadKey2); |
||||||
|
load_reporter_->ReportStreamCreated(kHostname2, kLbId3, kLoadKey3); |
||||||
|
// First fetch.
|
||||||
|
load_reporter_->FetchAndSample(); |
||||||
|
load_reporter_->GenerateLoads(kHostname1, kLbId1); |
||||||
|
gpr_log(GPR_INFO, "First load generated."); |
||||||
|
// Second fetch.
|
||||||
|
load_reporter_->FetchAndSample(); |
||||||
|
load_reporter_->GenerateLoads(kHostname2, kLbId2); |
||||||
|
gpr_log(GPR_INFO, "Second load generated."); |
||||||
|
// TODO(juanlishen): Verify the data.
|
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace testing
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
grpc_test_init(argc, argv); |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
Loading…
Reference in new issue