1 // Copyright 2017 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "google/cloud/bigtable/benchmarks/benchmark.h"
16 #include "google/cloud/bigtable/benchmarks/random_mutation.h"
17 #include <future>
18 #include <iomanip>
19 #include <sstream>
20
21 /**
22 * @file
23 *
24 * Measure the latency of `bigtable::Table::Apply()` and
25 * `bigtable::Table::ReadRow()` on a long running program.
26 *
27 * This benchmark measures the latency of `bigtable::Table::Apply()` and
28 * `bigtable::Table::ReadRow()` on a program running for many hours. The
29 * benchmark:
30 * - Creates an empty table with a single column family.
31 * - The column family contains 10 columns, each column filled with a random
32 * 100 byte string.
33 * - The name of the table starts with `long`, followed by random characters.
34 * - If there is a collision on the table name the benchmark aborts immediately.
35 *
36 * After successfully creating the table, the main phase of the benchmark
37 * starts. During this phase the benchmark:
38 *
39 * - Starts T threads, executing the following loop:
40 * - Runs for S seconds (typically hours), constantly executing this
41 * basic block:
42 * - Select a row at random, read it.
43 * - Select a row at random, read it.
44 * - Select a row at random, write to it.
45 *
46 * The test then waits for all the threads to finish and reports effective
47 * throughput.
48 *
49 * Using a command-line parameter the benchmark can be configured to create a
50 * local gRPC server that implements the Cloud Bigtable APIs used by the
51 * benchmark. If this parameter is not used the benchmark uses the default
52 * configuration, that is, a production instance of Cloud Bigtable unless the
53 * CLOUD_BIGTABLE_EMULATOR environment variable is set.
54 */
55
56 /// Helper functions and types for the apply_read_latency_benchmark.
57 namespace {
58 namespace bigtable = google::cloud::bigtable;
59 using bigtable::benchmarks::Benchmark;
60 using bigtable::benchmarks::BenchmarkResult;
61 using bigtable::benchmarks::FormatDuration;
62 using bigtable::benchmarks::kColumnFamily;
63 using bigtable::benchmarks::kNumFields;
64 using bigtable::benchmarks::MakeBenchmarkSetup;
65 using bigtable::benchmarks::MakeRandomMutation;
66 using bigtable::benchmarks::OperationResult;
67
68 /// Run an iteration of the test, returns the number of operations.
69 google::cloud::StatusOr<long> RunBenchmark( // NOLINT(google-runtime-int)
70 bigtable::benchmarks::Benchmark& benchmark, std::string app_profile_id,
71 std::string const& table_id, std::chrono::seconds test_duration);
72
73 } // anonymous namespace
74
main(int argc,char * argv[])75 int main(int argc, char* argv[]) {
76 auto setup = bigtable::benchmarks::MakeBenchmarkSetup("long", argc, argv);
77 if (!setup) {
78 std::cerr << setup.status() << "\n";
79 return -1;
80 }
81
82 Benchmark benchmark(*setup);
83 // Create and populate the table for the benchmark.
84 benchmark.CreateTable();
85
86 // Start the threads running the latency test.
87 std::cout << "# Running Endurance Benchmark:\n";
88 auto latency_test_start = std::chrono::steady_clock::now();
89 // NOLINTNEXTLINE(google-runtime-int)
90 std::vector<std::future<google::cloud::StatusOr<long>>> tasks;
91 for (int i = 0; i != setup->thread_count(); ++i) {
92 auto launch_policy = std::launch::async;
93 if (setup->thread_count() == 1) {
94 // If the user requests only one thread, use the current thread.
95 launch_policy = std::launch::deferred;
96 }
97 tasks.emplace_back(std::async(launch_policy, RunBenchmark,
98 std::ref(benchmark), setup->app_profile_id(),
99 setup->table_id(), setup->test_duration()));
100 }
101
102 // Wait for the threads and combine all the results.
103 long combined = 0; // NOLINT(google-runtime-int)
104 int count = 0;
105 for (auto& future : tasks) {
106 auto result = future.get();
107 if (!result) {
108 std::cerr << "Error returned by task[" << count
109 << "]: " << result.status() << "\n";
110 } else {
111 combined += *result;
112 }
113 ++count;
114 }
115 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
116 std::chrono::steady_clock::now() - latency_test_start);
117 auto throughput = 1000.0 * static_cast<double>(combined) /
118 static_cast<double>(elapsed.count());
119 std::cout << "# DONE. Elapsed=" << FormatDuration(elapsed)
120 << ", Ops=" << combined << ", Throughput: " << throughput
121 << " ops/sec\n";
122
123 benchmark.DeleteTable();
124 return 0;
125 }
126
127 namespace {
128
RunOneApply(bigtable::Table & table,Benchmark const & benchmark,google::cloud::internal::DefaultPRNG & generator)129 OperationResult RunOneApply(bigtable::Table& table, Benchmark const& benchmark,
130 google::cloud::internal::DefaultPRNG& generator) {
131 auto row_key = benchmark.MakeRandomKey(generator);
132 bigtable::SingleRowMutation mutation(std::move(row_key));
133 for (int field = 0; field != kNumFields; ++field) {
134 mutation.emplace_back(MakeRandomMutation(generator, field));
135 }
136 auto op = [&table, &mutation]() -> google::cloud::Status {
137 return table.Apply(std::move(mutation));
138 };
139 return Benchmark::TimeOperation(std::move(op));
140 }
141
RunOneReadRow(bigtable::Table & table,Benchmark const & benchmark,google::cloud::internal::DefaultPRNG & generator)142 OperationResult RunOneReadRow(bigtable::Table& table,
143 Benchmark const& benchmark,
144 google::cloud::internal::DefaultPRNG& generator) {
145 auto row_key = benchmark.MakeRandomKey(generator);
146 auto op = [&table, &row_key]() -> google::cloud::Status {
147 return table
148 .ReadRow(std::move(row_key), bigtable::Filter::ColumnRangeClosed(
149 kColumnFamily, "field0", "field9"))
150 .status();
151 };
152 return Benchmark::TimeOperation(std::move(op));
153 }
154
RunBenchmark(bigtable::benchmarks::Benchmark & benchmark,std::string app_profile_id,std::string const & table_id,std::chrono::seconds test_duration)155 google::cloud::StatusOr<long> RunBenchmark( // NOLINT(google-runtime-int)
156 bigtable::benchmarks::Benchmark& benchmark, std::string app_profile_id,
157 std::string const& table_id, std::chrono::seconds test_duration) {
158 BenchmarkResult partial = {};
159
160 auto data_client = benchmark.MakeDataClient();
161 bigtable::Table table(std::move(data_client), std::move(app_profile_id),
162 table_id);
163
164 auto generator = google::cloud::internal::MakeDefaultPRNG();
165
166 auto start = std::chrono::steady_clock::now();
167 auto end = start + test_duration;
168
169 for (auto now = start; now < end; now = std::chrono::steady_clock::now()) {
170 auto op_result = RunOneReadRow(table, benchmark, generator);
171 if (!op_result.status.ok()) {
172 return op_result.status;
173 }
174 partial.operations.emplace_back(op_result);
175 ++partial.row_count;
176 op_result = RunOneReadRow(table, benchmark, generator);
177 if (!op_result.status.ok()) {
178 return op_result.status;
179 }
180 partial.operations.emplace_back(op_result);
181 ++partial.row_count;
182 op_result = RunOneApply(table, benchmark, generator);
183 if (!op_result.status.ok()) {
184 return op_result.status;
185 }
186 partial.operations.emplace_back(op_result);
187 ++partial.row_count;
188 }
189 partial.elapsed =
190 std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
191 std::ostringstream msg;
192 Benchmark::PrintLatencyResult(msg, "long", "Partial::Op", partial);
193 std::cout << msg.str() << std::flush;
194 // NOLINTNEXTLINE(google-runtime-int)
195 return static_cast<long>(partial.operations.size());
196 }
197
198 } // anonymous namespace
199