OmniSciDB  04ee39c94c
BumpAllocatorTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2019 OmniSci, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "TestHelpers.h"
18 
19 #include <Catalog/Catalog.h>
20 #include <CudaMgr/CudaMgr.h>
21 #include <QueryEngine/ResultSet.h>
23 
24 #include <gtest/gtest.h>
25 
26 #ifndef BASE_PATH
27 #define BASE_PATH "./tmp"
28 #endif
29 
31 
32 extern bool g_allow_cpu_retry;
33 extern size_t g_max_memory_allocation_size;
34 extern size_t g_min_memory_allocation_size;
35 extern bool g_enable_bump_allocator;
36 
37 namespace {
38 
39 size_t g_num_gpus{0};
40 bool g_keep_data{false};
41 
42 bool skip_tests(const ExecutorDeviceType device_type) {
43 #ifdef HAVE_CUDA
44  return device_type == ExecutorDeviceType::GPU && !QR::get()->gpusPresent();
45 #else
46  return device_type == ExecutorDeviceType::GPU;
47 #endif
48 }
49 
50 #define SKIP_NO_GPU() \
51  if (skip_tests(ExecutorDeviceType::GPU)) { \
52  LOG(WARNING) << "GPU not available, skipping GPU tests"; \
53  }
54 
55 std::shared_ptr<ResultSet> run_multiple_agg(const std::string& query_str,
56  const ExecutorDeviceType device_type) {
57  return QR::get()->runSQL(query_str, device_type, true, true);
58 }
59 
60 // Note: This assumes a homogenous GPU setup
61 struct GpuInfo {
63  size_t num_gpus;
64 };
65 
67  auto cat = QR::get()->getCatalog();
68  CHECK(cat);
69 
70  auto& data_mgr = cat->getDataMgr();
71  auto cuda_mgr = data_mgr.getCudaMgr();
72  CHECK(cuda_mgr);
73  const auto device_props_vec = cuda_mgr->getAllDeviceProperties();
74  CHECK_GE(device_props_vec.size(), size_t(1));
75  return GpuInfo{device_props_vec.front().globalMem, device_props_vec.size()};
76 }
77 
78 bool setup() {
79  // Only initialize the QueryRunner once. We will reset the system catalog using the
80  // resetWithParameters method as needed for each test.
81  QR::init(BASE_PATH, /*udf_filename=*/"", /*max_gpu_mem=*/1000000000);
82 
83  if (!QR::get()->gpusPresent()) {
84  LOG(WARNING) << "No GPUs detected. Skipping all Bump Allocator tests.";
85  return false;
86  }
87 
88  const auto gpu_info = get_gpu_info();
89  g_num_gpus = gpu_info.num_gpus; // Using the global to pass into tests
90  return true;
91 }
92 
93 } // namespace
94 
95 class LowGpuBufferMemory : public ::testing::Test {
96  public:
97  void SetUp() override {
98  const std::string drop_table_stmt{"DROP TABLE IF EXISTS test;"};
99  QR::get()->runDDLStatement(drop_table_stmt);
100  const std::string create_table_stmt{
101  "CREATE TABLE test (x INT, y BIGINT) WITH (FRAGMENT_SIZE=32);"};
102  QR::get()->runDDLStatement(create_table_stmt);
103 
104  // Insert enough data to just barely overflow
105  for (size_t i = 0; i < 64 * g_num_gpus; i++) {
106  const std::string insert_stmt{"INSERT INTO test VALUES (" + std::to_string(i) +
107  ", " + std::to_string(i) + ");"};
109  }
110 
111  // Min memory allocation size set to 2GB to guarantee OOM during allocation
112  min_mem_allocation_state_ = g_min_memory_allocation_size;
113  g_min_memory_allocation_size = 2000000000;
114 
115  // CPU retry off to disable automatic punt to CPU
116  allow_cpu_retry_state_ = g_allow_cpu_retry;
117  g_allow_cpu_retry = false;
118  }
119 
120  void TearDown() override {
121  if (!g_keep_data) {
122  const std::string drop_table_stmt{"DROP TABLE IF EXISTS test;"};
123  QR::get()->runDDLStatement(drop_table_stmt);
124  }
125 
126  g_min_memory_allocation_size = min_mem_allocation_state_;
127  g_allow_cpu_retry = allow_cpu_retry_state_;
128  }
129 
130  private:
133 };
134 
136  // Baseline correctness
137  auto result_rows =
138  run_multiple_agg("SELECT x FROM test WHERE x < 500;", ExecutorDeviceType::CPU);
139  ASSERT_EQ(result_rows->rowCount(), size_t(64 * g_num_gpus));
140 }
141 
143  SKIP_NO_GPU();
144 
145  try {
146  run_multiple_agg("SELECT x FROM test WHERE x < 500;", ExecutorDeviceType::GPU);
147  ASSERT_TRUE(false) << "Expected query to throw exception";
148  } catch (const std::exception& e) {
149  ASSERT_EQ(
150  std::string{"Query ran out of GPU memory, unable to automatically retry on CPU"},
151  std::string(e.what()));
152  }
153 }
154 
155 constexpr size_t row_count_per_gpu = 64;
156 
157 class LowGpuBufferMemoryCpuRetry : public ::testing::Test {
158  public:
159  void SetUp() override {
160  const std::string drop_table_stmt{"DROP TABLE IF EXISTS test;"};
161  QR::get()->runDDLStatement(drop_table_stmt);
162  const std::string create_table_stmt{
163  "CREATE TABLE test (x INT, y BIGINT) WITH (FRAGMENT_SIZE=32);"};
164  QR::get()->runDDLStatement(create_table_stmt);
165 
166  // Insert enough data to exceed the max buffer entry guess and force a pre-flight CPU
167  // count
168  for (size_t i = 0; i < row_count_per_gpu * g_num_gpus; i++) {
169  const std::string insert_stmt{"INSERT INTO test VALUES (" + std::to_string(i) +
170  ", " + std::to_string(i) + ");"};
172  }
173 
174  // Min memory allocation size set to 2GB to guarantee OOM during allocation
175  min_mem_allocation_state_ = g_min_memory_allocation_size;
176  g_min_memory_allocation_size = 2000000000;
177 
178  // allow CPU retry on
179  allow_cpu_retry_state_ = g_allow_cpu_retry;
180  g_allow_cpu_retry = true;
181  }
182 
183  void TearDown() override {
184  if (!g_keep_data) {
185  const std::string drop_table_stmt{"DROP TABLE IF EXISTS test;"};
186  QR::get()->runDDLStatement(drop_table_stmt);
187  }
188 
189  g_min_memory_allocation_size = min_mem_allocation_state_;
190  g_allow_cpu_retry = allow_cpu_retry_state_;
191  }
192 
193  private:
196 };
197 
199  SKIP_NO_GPU();
200 
201  try {
202  auto result_rows =
203  run_multiple_agg("SELECT x FROM test WHERE x < 500;", ExecutorDeviceType::GPU);
204  ASSERT_EQ(result_rows->rowCount(), size_t(row_count_per_gpu * g_num_gpus));
205  } catch (const std::exception& e) {
206  ASSERT_TRUE(false) << "Expected query to not throw exception. Query threw: "
207  << e.what();
208  }
209 }
210 
211 class MediumGpuBufferMemory : public ::testing::Test {
212  public:
213  void SetUp() override {
214  const std::string drop_table_stmt{"DROP TABLE IF EXISTS test;"};
215  QR::get()->runDDLStatement(drop_table_stmt);
216  const std::string create_table_stmt{
217  "CREATE TABLE test (x INT, y BIGINT) WITH (FRAGMENT_SIZE=32);"};
218  QR::get()->runDDLStatement(create_table_stmt);
219 
220  // Insert enough data to just barely overflow
221  for (size_t i = 0; i < 64 * g_num_gpus; i++) {
222  const std::string insert_stmt{"INSERT INTO test VALUES (" + std::to_string(i) +
223  ", " + std::to_string(i) + ");"};
225  }
226 
227  max_mem_allocation_state_ = g_max_memory_allocation_size;
229 
230  // CPU retry off to disable automatic punt to CPU
231  allow_cpu_retry_state_ = g_allow_cpu_retry;
232  g_allow_cpu_retry = false;
233  }
234 
235  void TearDown() override {
236  if (!g_keep_data) {
237  const std::string drop_table_stmt{"DROP TABLE IF EXISTS test;"};
238  QR::get()->runDDLStatement(drop_table_stmt);
239  }
240 
241  g_max_memory_allocation_size = max_mem_allocation_state_;
242 
243  g_allow_cpu_retry = allow_cpu_retry_state_;
244  }
245 
246  private:
249 };
250 
252  SKIP_NO_GPU();
253 
254  try {
255  run_multiple_agg("SELECT x FROM test WHERE x < 5000;", ExecutorDeviceType::GPU);
256  ASSERT_TRUE(false) << "Expected query to throw exception";
257  } catch (const std::exception& e) {
258  ASSERT_EQ(std::string(e.what()),
259  std::string{"Ran out of slots in the query output buffer"});
260  }
261 }
262 
263 int main(int argc, char** argv) {
264  testing::InitGoogleTest(&argc, argv);
265 
266  namespace po = boost::program_options;
267 
268  po::options_description desc("Options");
269 
270  // these two are here to allow passing correctly google testing parameters
271  desc.add_options()("gtest_list_tests", "list all tests");
272  desc.add_options()("gtest_filter", "filters tests, use --help for details");
273  desc.add_options()("keep-data",
274  "Don't drop tables at the end of the tests. Note that individual "
275  "tests may still create and drop tables. Use in combination with "
276  "--gtest_filter to preserve tables for a specific test group.");
277  desc.add_options()(
278  "test-help",
279  "Print all BumpAllocatorTest specific options (for gtest options use `--help`).");
280 
281  logger::LogOptions log_options(argv[0]);
282  log_options.max_files_ = 0; // stderr only by default
283  desc.add(log_options.get_options());
284 
285  po::variables_map vm;
286  po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
287  po::notify(vm);
288 
289  if (vm.count("test-help")) {
290  std::cout << "Usage: BumpAllocatorTest" << std::endl << std::endl;
291  std::cout << desc << std::endl;
292  return 0;
293  }
294 
295  if (vm.count("keep-data")) {
296  g_keep_data = true;
297  }
298 
299  logger::init(log_options);
300 
302 
303  if (!setup()) {
304  // No GPUs detected, bypass the test
305  return 0;
306  }
307 
308  int err{0};
309  try {
310  err = RUN_ALL_TESTS();
311  } catch (const std::exception& e) {
312  LOG(ERROR) << e.what();
313  }
314  QR::reset();
315  return err;
316 }
void SetUp() override
size_t g_min_memory_allocation_size
Definition: Execute.cpp:93
ExecutorDeviceType
bool g_allow_cpu_retry
Definition: Execute.cpp:72
#define LOG(tag)
Definition: Logger.h:182
#define CHECK_GE(x, y)
Definition: Logger.h:200
boost::program_options::options_description const & get_options() const
Definition: Logger.cpp:112
constexpr size_t row_count_per_gpu
#define SKIP_NO_GPU()
std::string to_string(char const *&&v)
static QueryRunner * init(const char *db_path, const std::string &udf_filename="", const size_t max_gpu_mem=0, const int reserved_gpu_mem=256<< 20)
Definition: QueryRunner.h:70
virtual std::shared_ptr< ResultSet > runSQL(const std::string &query_str, const ExecutorDeviceType device_type, const bool hoist_literals=true, const bool allow_loop_joins=true)
This file contains the class specification and related data structures for Catalog.
TEST_F(LowGpuBufferMemory, CPUMode)
void TearDown() override
void init(LogOptions const &log_opts)
Definition: Logger.cpp:260
#define BASE_PATH
virtual void runDDLStatement(const std::string &)
bool g_enable_bump_allocator
Definition: Execute.cpp:96
std::shared_ptr< Catalog_Namespace::Catalog > getCatalog() const
static QueryRunner * get()
Definition: QueryRunner.h:115
int main(int argc, char **argv)
#define CHECK(condition)
Definition: Logger.h:187
size_t g_max_memory_allocation_size
Definition: Execute.cpp:92
Basic constructors and methods of the row set interface.
size_t max_files_
Definition: Logger.h:117
static bool run
bool skip_tests(const ExecutorDeviceType device_type)
TQueryResult run_multiple_agg(std::string sql)