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 #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BENCHMARKS_BENCHMARK_H 16 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BENCHMARKS_BENCHMARK_H 17 18 #include "google/cloud/bigtable/benchmarks/embedded_server.h" 19 #include "google/cloud/bigtable/benchmarks/setup.h" 20 #include "google/cloud/bigtable/table.h" 21 #include "google/cloud/internal/random.h" 22 #include "google/cloud/status_or.h" 23 #include <chrono> 24 #include <deque> 25 #include <thread> 26 27 namespace google { 28 namespace cloud { 29 namespace bigtable { 30 namespace benchmarks { 31 /// The result of a single operation. 32 struct OperationResult { 33 google::cloud::Status status; 34 std::chrono::microseconds latency; 35 }; 36 37 struct BenchmarkResult { 38 std::chrono::milliseconds elapsed; 39 std::deque<OperationResult> operations; 40 long row_count; // NOLINT(google-runtime-int) 41 }; 42 43 /** 44 * Common code used by the Cloud Bigtable C++ Client benchmarks. 45 */ 46 class Benchmark { 47 public: 48 explicit Benchmark(BenchmarkSetup setup); 49 ~Benchmark(); 50 51 Benchmark(Benchmark const&) = delete; 52 Benchmark& operator=(Benchmark const&) = delete; 53 54 /// Create a table for the benchmark, return the table_id. 55 std::string CreateTable(); 56 57 /// Delete the table used in the benchmark. 58 void DeleteTable(); 59 60 /// Populate the table with initial data. 61 google::cloud::StatusOr<BenchmarkResult> PopulateTable(); 62 63 /// Return a `bigtable::DataClient` configured for this benchmark. 64 std::shared_ptr<bigtable::DataClient> MakeDataClient(); 65 66 /// Create a random key. 67 std::string MakeRandomKey(google::cloud::internal::DefaultPRNG& gen) const; 68 69 /// Return the key for row @p id. 70 std::string MakeKey(long id) const; // NOLINT(google-runtime-int) 71 72 /// Measure the time to compute an operation. 73 template <typename Operation> TimeOperation(Operation && op)74 static OperationResult TimeOperation(Operation&& op) { 75 auto start = std::chrono::steady_clock::now(); 76 auto status = op(); 77 using std::chrono::duration_cast; 78 auto elapsed = duration_cast<std::chrono::microseconds>( 79 std::chrono::steady_clock::now() - start); 80 return OperationResult{status, elapsed}; 81 } 82 83 /// Print the result of a throughput test in human readable form. 84 static void PrintThroughputResult(std::ostream& os, 85 std::string const& test_name, 86 std::string const& phase, 87 BenchmarkResult const& result); 88 89 /// Print the result of a latency test in human readable form. 90 static void PrintLatencyResult(std::ostream& os, std::string const& test_name, 91 std::string const& operation, 92 BenchmarkResult& result); 93 94 /// Return the header for CSV results. 95 static std::string ResultsCsvHeader(); 96 97 /// Print the result of a benchmark as a CSV line. 98 void PrintResultCsv(std::ostream& os, std::string const& test_name, 99 std::string const& op_name, 100 std::string const& measurement, 101 BenchmarkResult& result) const; 102 103 //@{ 104 /** 105 * @name Embedded server counter accessors. 106 * 107 * Return 0 if there is no embedded server, or the value from the 108 * corresponding embedded server counter. This class is tested largely by 109 * observing how many calls it makes on the embedded server. Because the 110 * embedded server has no memory, that is the only observable effect when 111 * unit testing the class. 112 */ 113 int create_table_count() const; 114 int delete_table_count() const; 115 int mutate_row_count() const; 116 int mutate_rows_count() const; 117 int read_rows_count() const; 118 //@} 119 120 private: 121 /// Populate the table rows in the range [@p begin, @p end) 122 google::cloud::StatusOr<BenchmarkResult> PopulateTableShard( 123 bigtable::Table& table, long begin, // NOLINT(google-runtime-int) 124 long end); // NOLINT(google-runtime-int) 125 126 /** 127 * Return how much space to reserve for digits if the table has @p table_size 128 * elements. 129 */ 130 int KeyWidth() const; 131 132 private: 133 BenchmarkSetup setup_; 134 int key_width_; 135 bigtable::ClientOptions client_options_; 136 std::unique_ptr<EmbeddedServer> server_; 137 std::thread server_thread_; 138 }; 139 140 /// Helper class to pretty print durations. 141 struct FormatDuration { 142 template <typename Rep, typename Period> FormatDurationFormatDuration143 explicit FormatDuration(std::chrono::duration<Rep, Period> d) 144 : ns(std::chrono::duration_cast<std::chrono::nanoseconds>(d)) {} 145 std::chrono::nanoseconds ns; 146 }; 147 148 /** 149 * Pretty print an elapsed time. 150 * 151 * The benchmarks need to report time in human readable terms. This operator 152 * streams a FormatDuration in hours, minutes, seconds and sub-seconds. Any 153 * component that is zero gets ommitted, e.g. 1 hour exactly is printed as 1h. 154 * 155 * If the time is less than 1 second then the format uses millisecond or 156 * microsecond resolution, as appropriate. 157 * 158 * @param os the destination stream. 159 * @param duration the duration value. 160 * @return the stream after printing. 161 */ 162 std::ostream& operator<<(std::ostream& os, FormatDuration duration); 163 164 } // namespace benchmarks 165 } // namespace bigtable 166 } // namespace cloud 167 } // namespace google 168 169 #endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_BENCHMARKS_BENCHMARK_H 170