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