diff --git a/be/CMakeLists.txt b/be/CMakeLists.txt index 49df7889bdb93f..29a6b176044f4a 100644 --- a/be/CMakeLists.txt +++ b/be/CMakeLists.txt @@ -170,6 +170,9 @@ set_target_properties(protoc PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/ add_library(gtest STATIC IMPORTED) set_target_properties(gtest PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/libgtest.a) +add_library(benchmark STATIC IMPORTED) +set_target_properties(benchmark PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/libbenchmark.a) + add_library(gmock STATIC IMPORTED) set_target_properties(gmock PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/libgmock.a) @@ -500,6 +503,7 @@ set(COMMON_THIRDPARTY gflags brpc protobuf + benchmark openssl crypto leveldb @@ -694,6 +698,7 @@ if (${MAKE_TEST} STREQUAL "ON") add_subdirectory(${TEST_DIR}/util) add_subdirectory(${TEST_DIR}/plugin) add_subdirectory(${TEST_DIR}/plugin/example) + add_subdirectory(${TEST_DIR}/tools) endif () # Install be diff --git a/be/test/olap/tablet_schema_helper.h b/be/test/olap/tablet_schema_helper.h index 66c83fcf9e73c0..b2183c3a7387ff 100644 --- a/be/test/olap/tablet_schema_helper.h +++ b/be/test/olap/tablet_schema_helper.h @@ -80,7 +80,19 @@ TabletColumn create_varchar_key(int32_t id, bool is_nullable = true) { column._type = OLAP_FIELD_TYPE_VARCHAR; column._is_key = true; column._is_nullable = is_nullable; - column._length = 8; + column._length = 65533; + column._index_length = 4; + return column; +} + +TabletColumn create_string_key(int32_t id, bool is_nullable = true) { + TabletColumn column; + column._unique_id = id; + column._col_name = std::to_string(id); + column._type = OLAP_FIELD_TYPE_STRING; + column._is_key = true; + column._is_nullable = is_nullable; + column._length = 2147483643; column._index_length = 4; return column; } @@ -98,10 +110,10 @@ TabletColumn create_with_default_value(std::string default_value) { } void set_column_value_by_type(FieldType fieldType, int src, char* target, MemPool* pool, - size_t _length = 0) { + size_t _length = 8) { if (fieldType == OLAP_FIELD_TYPE_CHAR) { std::string s = std::to_string(src); - char* src_value = &s[0]; + const char* src_value = s.c_str(); int src_len = s.size(); auto* dest_slice = (Slice*)target; @@ -111,7 +123,16 @@ void set_column_value_by_type(FieldType fieldType, int src, char* target, MemPoo memset(dest_slice->data + src_len, 0, dest_slice->size - src_len); } else if (fieldType == OLAP_FIELD_TYPE_VARCHAR) { std::string s = std::to_string(src); - char* src_value = &s[0]; + const char* src_value = s.c_str(); + int src_len = s.size(); + + auto* dest_slice = (Slice*)target; + dest_slice->size = src_len; + dest_slice->data = (char*)pool->allocate(src_len); + memcpy(dest_slice->data, src_value, src_len); + } else if (fieldType == OLAP_FIELD_TYPE_STRING) { + std::string s = std::to_string(src); + const char* src_value = s.c_str(); int src_len = s.size(); auto* dest_slice = (Slice*)target; @@ -123,4 +144,36 @@ void set_column_value_by_type(FieldType fieldType, int src, char* target, MemPoo } } +void set_column_value_by_type(FieldType fieldType, const std::string& src, char* target, + MemPool* pool, size_t _length = 8) { + if (fieldType == OLAP_FIELD_TYPE_CHAR) { + const char* src_value = src.c_str(); + int src_len = src.size(); + + auto* dest_slice = (Slice*)target; + dest_slice->size = _length; + dest_slice->data = (char*)pool->allocate(dest_slice->size); + memcpy(dest_slice->data, src_value, src_len); + memset(dest_slice->data + src_len, 0, dest_slice->size - src_len); + } else if (fieldType == OLAP_FIELD_TYPE_VARCHAR) { + const char* src_value = src.c_str(); + int src_len = src.size(); + + auto* dest_slice = (Slice*)target; + dest_slice->size = src_len; + dest_slice->data = (char*)pool->allocate(src_len); + memcpy(dest_slice->data, src_value, src_len); + } else if (fieldType == OLAP_FIELD_TYPE_STRING) { + const char* src_value = src.c_str(); + int src_len = src.size(); + + auto* dest_slice = (Slice*)target; + dest_slice->size = src_len; + dest_slice->data = (char*)pool->allocate(src_len); + memcpy(dest_slice->data, src_value, src_len); + } else { + *(int*)target = std::stoi(src); + } +} + } // namespace doris diff --git a/be/test/test_util/test_util.cpp b/be/test/test_util/test_util.cpp index be118cd8cf1453..f9b034c825148b 100644 --- a/be/test/test_util/test_util.cpp +++ b/be/test/test_util/test_util.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -74,4 +73,38 @@ void InitConfig() { } } +bool equal_ignore_case(std::string lhs, std::string rhs) { + std::transform(lhs.begin(), lhs.end(), lhs.begin(), ::tolower); + std::transform(rhs.begin(), rhs.end(), rhs.begin(), ::tolower); + return lhs == rhs; +} + +std::mt19937_64 rng_64(std::chrono::steady_clock::now().time_since_epoch().count()); + +int rand_rng_int(int l, int r) { + std::uniform_int_distribution u(l, r); + return u(rng_64); +} +char rand_rng_char() { + return (rand_rng_int(0, 1) ? 'a' : 'A') + rand_rng_int(0, 25); +} +std::string rand_rng_string(size_t length) { + string s; + while (length--) { + s += rand_rng_char(); + } + return s; +} +std::string rand_rng_by_type(FieldType fieldType) { + if (fieldType == OLAP_FIELD_TYPE_CHAR) { + return rand_rng_string(rand_rng_int(1, 8)); + } else if (fieldType == OLAP_FIELD_TYPE_VARCHAR) { + return rand_rng_string(rand_rng_int(1, 128)); + } else if (fieldType == OLAP_FIELD_TYPE_STRING) { + return rand_rng_string(rand_rng_int(1, 100000)); + } else { + return std::to_string(rand_rng_int(1, 1000000)); + } +} + } // namespace doris diff --git a/be/test/test_util/test_util.h b/be/test/test_util/test_util.h index 738adb580ad0d3..fe06aa62c5ac1f 100644 --- a/be/test/test_util/test_util.h +++ b/be/test/test_util/test_util.h @@ -17,8 +17,13 @@ #pragma once +#include +#include +#include #include +#include "olap/tablet_schema.h" + namespace doris { #define LOOP_LESS_OR_MORE(less, more) (AllowSlowTests() ? more : less) @@ -36,4 +41,11 @@ std::string GetCurrentRunningDir(); // Initialize config file. void InitConfig(); +bool equal_ignore_case(std::string lhs, std::string rhs); + +int rand_rng_int(int l, int r); +char rand_rng_char(); +std::string rand_rng_string(size_t length = 8); +std::string rand_rng_by_type(FieldType fieldType); + } // namespace doris diff --git a/be/test/tools/CMakeLists.txt b/be/test/tools/CMakeLists.txt new file mode 100644 index 00000000000000..2389387704663f --- /dev/null +++ b/be/test/tools/CMakeLists.txt @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# where to put generated libraries +set(EXECUTABLE_OUTPUT_PATH "${BUILD_DIR}/test/env") +# where to put generated binaries +set(EXECUTABLE_OUTPUT_PATH "${BUILD_DIR}/test/tools") + +ADD_BE_TEST(benchmark_tool) diff --git a/be/test/tools/benchmark_tool.cpp b/be/test/tools/benchmark_tool.cpp new file mode 100644 index 00000000000000..1e8d4e23e7aaa9 --- /dev/null +++ b/be/test/tools/benchmark_tool.cpp @@ -0,0 +1,632 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/compiler_util.h" +#include "common/logging.h" +#include "gutil/strings/split.h" +#include "gutil/strings/substitute.h" +#include "olap/comparison_predicate.h" +#include "olap/fs/block_manager.h" +#include "olap/fs/fs_util.h" +#include "olap/in_list_predicate.h" +#include "olap/olap_common.h" +#include "olap/row_block2.h" +#include "olap/row_cursor.h" +#include "olap/rowset/segment_v2/binary_dict_page.h" +#include "olap/rowset/segment_v2/binary_plain_page.h" +#include "olap/rowset/segment_v2/page_builder.h" +#include "olap/rowset/segment_v2/page_decoder.h" +#include "olap/rowset/segment_v2/segment_iterator.h" +#include "olap/rowset/segment_v2/segment_writer.h" +#include "olap/tablet_schema.h" +#include "olap/tablet_schema_helper.h" +#include "olap/types.h" +#include "runtime/mem_pool.h" +#include "runtime/mem_tracker.h" +#include "test_util/test_util.h" +#include "util/debug_util.h" +#include "util/file_utils.h" + +DEFINE_string(operation, "Custom", + "valid operation: Custom, BinaryDictPageEncode, BinaryDictPageDecode, SegmentScan, " + "SegmentWrite, " + "SegmentScanByFile, SegmentWriteByFile"); +DEFINE_string(input_file, "./sample.dat", "input file directory"); +DEFINE_string(column_type, "int,varchar", "valid type: int, char, varchar, string"); +DEFINE_string(rows_number, "10000", "rows number"); +DEFINE_string(iterations, "10", + "run times, this is set to 0 means the number of iterations is automatically set "); + +const std::string kSegmentDir = "./segment_benchmark"; + +std::string get_usage(const std::string& progname) { + std::stringstream ss; + ss << progname << " is the Doris BE benchmark tool.\n"; + ss << "Stop BE first before use this tool.\n"; + + ss << "Usage:\n"; + ss << "./benchmark_tool --operation=Custom\n"; + ss << "./benchmark_tool --operation=BinaryDictPageEncode " + "--rows_number=10000 --iterations=40\n"; + ss << "./benchmark_tool --operation=BinaryDictPageDecode " + "--rows_number=10000 --iterations=40\n"; + ss << "./benchmark_tool --operation=SegmentScan --column_type=int,varchar " + "--rows_number=10000 --iterations=0\n"; + ss << "./benchmark_tool --operation=SegmentWrite --column_type=int " + "--rows_number=10000 --iterations=10\n"; + ss << "./benchmark_tool --operation=SegmentScanByFile --input_file=./sample.dat " + "--iterations=10\n"; + ss << "./benchmark_tool --operation=SegmentWriteByFile --input_file=./sample.dat " + "--iterations=10\n"; + + ss << "Sampe data file format: \n" + << "The first line defines Shcema\n" + << "The rest of the content is DataSet\n" + << "For example: \n" + << "int,char,varchar\n" + << "123,hello,world\n" + << "321,good,bye\n"; + return ss.str(); +} + +static int seg_id = 0; + +namespace doris { +class BaseBenchmark { +public: + BaseBenchmark(std::string name, int iterations) : _name(name), _iterations(iterations) {} + virtual ~BaseBenchmark() {} + + void add_name(std::string str) { _name += str; } + + virtual void init() {} + virtual void run() {} + + void register_bm() { + auto bm = benchmark::RegisterBenchmark(_name.c_str(), [&](benchmark::State& state) { + //first turn will use more time + this->init(); + this->run(); + for (auto _ : state) { + state.PauseTiming(); + this->init(); + state.ResumeTiming(); + this->run(); + } + }); + if (_iterations != 0) { + bm->Iterations(_iterations); + } + bm->Unit(benchmark::kMillisecond); + } + +private: + std::string _name; + int _iterations; +}; + +class BinaryDictPageBenchmark : public BaseBenchmark { +public: + BinaryDictPageBenchmark(std::string name, int iterations) : BaseBenchmark(name, iterations) {} + virtual ~BinaryDictPageBenchmark() override {} + + virtual void init() override {} + virtual void run() override {} + + void encode_pages(const std::vector& contents) { + PageBuilderOptions options; + BinaryDictPageBuilder page_builder(options); + + results.clear(); + page_start_ids.clear(); + page_start_ids.push_back(0); + for (size_t i = 0; i < contents.size(); i++) { + const Slice* ptr = &contents[i]; + size_t add_num = 1; + Status ret = page_builder.add(reinterpret_cast(ptr), &add_num); + if (page_builder.is_page_full()) { + OwnedSlice s = page_builder.finish(); + results.emplace_back(std::move(s)); + page_start_ids.push_back(i + 1); + page_builder.reset(); + } + } + OwnedSlice s = page_builder.finish(); + results.emplace_back(std::move(s)); + page_start_ids.push_back(contents.size()); + + Status status = page_builder.get_dictionary_page(&dict_slice); + } + void decode_pages() { + int slice_index = 0; + for (auto& src : results) { + PageDecoderOptions dict_decoder_options; + std::unique_ptr dict_page_decoder( + new BinaryPlainPageDecoder(dict_slice.slice(), dict_decoder_options)); + dict_page_decoder->init(); + + // decode + PageDecoderOptions decoder_options; + BinaryDictPageDecoder page_decoder(src.slice(), decoder_options); + page_decoder.init(); + page_decoder.set_dict_decoder(dict_page_decoder.get()); + + //check values + + size_t num = page_start_ids[slice_index + 1] - page_start_ids[slice_index]; + + auto tracker = std::make_shared(); + MemPool pool(tracker.get()); + TypeInfo* type_info = get_scalar_type_info(OLAP_FIELD_TYPE_VARCHAR); + std::unique_ptr cvb; + ColumnVectorBatch::create(num, false, type_info, nullptr, &cvb); + ColumnBlock column_block(cvb.get(), &pool); + ColumnBlockView block_view(&column_block); + + page_decoder.next_batch(&num, &block_view); + + slice_index++; + } + } + +private: + std::vector results; + OwnedSlice dict_slice; + std::vector page_start_ids; +}; + +class BinaryDictPageEncodeBenchmark : public BinaryDictPageBenchmark { +public: + BinaryDictPageEncodeBenchmark(std::string name, int iterations, int rows_number) + : BinaryDictPageBenchmark(name + "/rows_number:" + std::to_string(rows_number), + iterations), + _rows_number(rows_number) {} + virtual ~BinaryDictPageEncodeBenchmark() override {} + + virtual void init() override { + src_strings.clear(); + for (int i = 0; i < _rows_number; i++) { + src_strings.emplace_back(rand_rng_string(rand_rng_int(1, 8))); + } + + slices.clear(); + for (auto s : src_strings) { + slices.emplace_back(s.c_str()); + } + } + virtual void run() override { encode_pages(slices); } + +private: + std::vector slices; + std::vector src_strings; + int _rows_number; +}; + +class BinaryDictPageDecodeBenchmark : public BinaryDictPageBenchmark { +public: + BinaryDictPageDecodeBenchmark(std::string name, int iterations, int rows_number) + : BinaryDictPageBenchmark(name + "/rows_number:" + std::to_string(rows_number), + iterations), + _rows_number(rows_number) {} + virtual ~BinaryDictPageDecodeBenchmark() override {} + + virtual void init() override { + src_strings.clear(); + for (int i = 0; i < _rows_number; i++) { + src_strings.emplace_back(rand_rng_string(rand_rng_int(1, 8))); + } + + slices.clear(); + for (auto s : src_strings) { + slices.emplace_back(s.c_str()); + } + + encode_pages(slices); + } + virtual void run() override { decode_pages(); } + +private: + std::vector slices; + std::vector src_strings; + int _rows_number; +}; // namespace doris + +class SegmentBenchmark : public BaseBenchmark { +public: + SegmentBenchmark(std::string name, int iterations, std::string column_type) + : BaseBenchmark(name, iterations), + _tracker(std::make_shared()), + _pool(_tracker.get()) { + if (FileUtils::check_exist(kSegmentDir)) { + FileUtils::remove_all(kSegmentDir); + } + FileUtils::create_dir(kSegmentDir); + + init_schema(column_type); + } + SegmentBenchmark(std::string name, int iterations) + : BaseBenchmark(name, iterations), + _tracker(std::make_shared()), + _pool(_tracker.get()) { + if (FileUtils::check_exist(kSegmentDir)) { + FileUtils::remove_all(kSegmentDir); + } + FileUtils::create_dir(kSegmentDir); + } + virtual ~SegmentBenchmark() override { + if (FileUtils::check_exist(kSegmentDir)) { + FileUtils::remove_all(kSegmentDir); + } + } + + const Schema& get_schema() { return *_schema.get(); } + + virtual void init() override {} + virtual void run() override {} + + void init_schema(std::string column_type) { + std::string column_valid = "/column_type:"; + + std::vector tokens = strings::Split(column_type, ","); + std::vector columns; + + bool first_column = true; + for (auto token : tokens) { + bool valid = true; + + if (equal_ignore_case(token, "int")) { + columns.emplace_back(create_int_key(columns.size() + 1)); + } else if (equal_ignore_case(token, "char")) { + columns.emplace_back(create_char_key(columns.size() + 1)); + } else if (equal_ignore_case(token, "varchar")) { + columns.emplace_back(create_varchar_key(columns.size() + 1)); + } else if (equal_ignore_case(token, "string")) { + columns.emplace_back(create_string_key(columns.size() + 1)); + } else { + valid = false; + } + + if (valid) { + if (first_column) { + first_column = false; + } else { + column_valid += ','; + } + column_valid += token; + } + } + + _tablet_schema = _create_schema(columns); + _schema = std::make_shared(_tablet_schema); + + add_name(column_valid); + } + + void build_segment(std::vector> dataset, + std::shared_ptr* res) { + // must use unique filename for each segment, otherwise page cache kicks in and produces + // the wrong answer (it use (filename,offset) as cache key) + std::string filename = strings::Substitute("$0/seg_$1.dat", kSegmentDir, ++seg_id); + std::unique_ptr wblock; + fs::CreateBlockOptions block_opts({filename}); + fs::fs_util::block_manager()->create_block(block_opts, &wblock); + SegmentWriterOptions opts; + SegmentWriter writer(wblock.get(), 0, &_tablet_schema, opts); + writer.init(1024); + + RowCursor row; + row.init(_tablet_schema); + + for (auto tokens : dataset) { + for (int cid = 0; cid < _tablet_schema.num_columns(); ++cid) { + RowCursorCell cell = row.cell(cid); + set_column_value_by_type(_tablet_schema._cols[cid]._type, tokens[cid], + (char*)cell.mutable_cell_ptr(), &_pool, + _tablet_schema._cols[cid]._length); + } + writer.append_row(row); + } + + uint64_t file_size, index_size; + writer.finalize(&file_size, &index_size); + wblock->close(); + + Segment::open(filename, seg_id, &_tablet_schema, res); + } + + std::vector> generate_dataset(int rows_number) { + std::vector> dataset; + while (rows_number--) { + std::vector row_data; + for (int cid = 0; cid < _tablet_schema.num_columns(); ++cid) { + row_data.emplace_back(rand_rng_by_type(_tablet_schema._cols[cid]._type)); + } + dataset.emplace_back(row_data); + } + return dataset; + } + +private: + TabletSchema _create_schema(const std::vector& columns, + int num_short_key_columns = -1) { + TabletSchema res; + int num_key_columns = 0; + for (auto& col : columns) { + if (col.is_key()) { + num_key_columns++; + } + res._cols.push_back(col); + } + res._num_columns = columns.size(); + res._num_key_columns = num_key_columns; + res._num_short_key_columns = + num_short_key_columns != -1 ? num_short_key_columns : num_key_columns; + res.init_field_index_for_test(); + return res; + } + + std::shared_ptr _tracker; + MemPool _pool; + TabletSchema _tablet_schema; + std::shared_ptr _schema; +}; // namespace doris + +class SegmentWriteBenchmark : public SegmentBenchmark { +public: + SegmentWriteBenchmark(std::string name, int iterations, std::string column_type, + int rows_number) + : SegmentBenchmark(name + "/rows_number:" + std::to_string(rows_number), iterations, + column_type), + _dataset(generate_dataset(rows_number)) {} + virtual ~SegmentWriteBenchmark() override {} + + virtual void init() override {} + virtual void run() override { build_segment(_dataset, &_segment); } + +private: + std::vector> _dataset; + std::shared_ptr _segment; +}; + +class SegmentWriteByFileBenchmark : public SegmentBenchmark { +public: + SegmentWriteByFileBenchmark(std::string name, int iterations, std::string file_str) + : SegmentBenchmark(name + "/file_path:" + file_str, iterations) { + std::ifstream file(file_str); + assert(file.is_open()); + + std::string column_type; + std::getline(file, column_type); + init_schema(column_type); + while (file.peek() != EOF) { + std::string row_str; + std::getline(file, row_str); + std::vector tokens = strings::Split(row_str, ","); + assert(tokens.size() == _tablet_schema.num_columns()); + _dataset.push_back(tokens); + } + + add_name("/rows_number:" + std::to_string(_dataset.size())); + } + virtual ~SegmentWriteByFileBenchmark() override {} + + virtual void init() override {} + virtual void run() override { build_segment(_dataset, &_segment); } + +private: + std::vector> _dataset; + std::shared_ptr _segment; +}; + +class SegmentScanBenchmark : public SegmentBenchmark { +public: + SegmentScanBenchmark(std::string name, int iterations, std::string column_type, int rows_number) + : SegmentBenchmark(name + "/rows_number:" + std::to_string(rows_number), iterations, + column_type), + _dataset(generate_dataset(rows_number)) {} + virtual ~SegmentScanBenchmark() override {} + + virtual void init() override { build_segment(_dataset, &_segment); } + virtual void run() override { + StorageReadOptions read_opts; + read_opts.stats = &stats; + std::unique_ptr iter; + _segment->new_iterator(get_schema(), read_opts, nullptr, &iter); + RowBlockV2 block(get_schema(), 1024); + + int left = _dataset.size(); + int rowid = 0; + while (left > 0) { + int rows_read = std::min(left, 1024); + block.clear(); + iter->next_batch(&block); + left -= rows_read; + rowid += rows_read; + } + } + +private: + std::vector> _dataset; + std::shared_ptr _segment; + OlapReaderStatistics stats; +}; + +class SegmentScanByFileBenchmark : public SegmentBenchmark { +public: + SegmentScanByFileBenchmark(std::string name, int iterations, std::string file_str) + : SegmentBenchmark(name, iterations) { + std::ifstream file(file_str); + assert(file.is_open()); + + std::string column_type; + std::getline(file, column_type); + init_schema(column_type); + while (file.peek() != EOF) { + std::string row_str; + std::getline(file, row_str); + std::vector tokens = strings::Split(row_str, ","); + assert(tokens.size() == _tablet_schema.num_columns()); + _dataset.push_back(tokens); + } + + add_name("/rows_number:" + std::to_string(_dataset.size())); + } + virtual ~SegmentScanByFileBenchmark() override {} + + virtual void init() override { build_segment(_dataset, &_segment); } + virtual void run() override { + StorageReadOptions read_opts; + read_opts.stats = &stats; + std::unique_ptr iter; + _segment->new_iterator(get_schema(), read_opts, nullptr, &iter); + RowBlockV2 block(get_schema(), 1024); + + int left = _dataset.size(); + int rowid = 0; + while (left > 0) { + int rows_read = std::min(left, 1024); + block.clear(); + iter->next_batch(&block); + left -= rows_read; + rowid += rows_read; + } + } + +private: + std::vector> _dataset; + std::shared_ptr _segment; + OlapReaderStatistics stats; +}; + +// This is sample custom test. User can write custom test code at custom_init()&custom_run(). +// Call method: ./benchmark_tool --operation=Custom +class CustomBenchmark : public BaseBenchmark { +public: + CustomBenchmark(std::string name, int iterations, std::function init_func, + std::function run_func) + : BaseBenchmark(name, iterations), _init_func(init_func), _run_func(run_func) {} + virtual ~CustomBenchmark() override {} + + virtual void init() override { _init_func(); } + virtual void run() override { _run_func(); } + +private: + std::function _init_func; + std::function _run_func; +}; +void custom_init() {} +void custom_run_plus() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (UNLIKELY(q == 1024)) q = 0; + } +} +void custom_run_mod() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (q %= 1024) q = 0; + } +} + +class MultiBenchmark { +public: + MultiBenchmark() {} + ~MultiBenchmark() { + for (auto bm : benchmarks) { + delete bm; + } + } + + void add_bm() { + if (equal_ignore_case(FLAGS_operation, "Custom")) { + benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_plus", std::stoi(FLAGS_iterations), + doris::custom_init, doris::custom_run_plus)); + benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_mod", std::stoi(FLAGS_iterations), + doris::custom_init, doris::custom_run_mod)); + } else if (equal_ignore_case(FLAGS_operation, "BinaryDictPageEncode")) { + benchmarks.emplace_back(new doris::BinaryDictPageEncodeBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), std::stoi(FLAGS_rows_number))); + } else if (equal_ignore_case(FLAGS_operation, "BinaryDictPageDecode")) { + benchmarks.emplace_back(new doris::BinaryDictPageDecodeBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), std::stoi(FLAGS_rows_number))); + } else if (equal_ignore_case(FLAGS_operation, "SegmentScan")) { + benchmarks.emplace_back(new doris::SegmentScanBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_column_type, + std::stoi(FLAGS_rows_number))); + } else if (equal_ignore_case(FLAGS_operation, "SegmentWrite")) { + benchmarks.emplace_back(new doris::SegmentWriteBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_column_type, + std::stoi(FLAGS_rows_number))); + } else if (equal_ignore_case(FLAGS_operation, "SegmentScanByFile")) { + benchmarks.emplace_back(new doris::SegmentScanByFileBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_input_file)); + } else if (equal_ignore_case(FLAGS_operation, "SegmentWriteByFile")) { + benchmarks.emplace_back(new doris::SegmentWriteByFileBenchmark( + FLAGS_operation, std::stoi(FLAGS_iterations), FLAGS_input_file)); + } else { + std::cout << "operation invalid!" << std::endl; + } + } + void register_bm() { + for (auto bm : benchmarks) { + bm->register_bm(); + } + } + +private: + std::vector benchmarks; +}; + +} //namespace doris + +int main(int argc, char** argv) { + std::string usage = get_usage(argv[0]); + gflags::SetUsageMessage(usage); + google::ParseCommandLineFlags(&argc, &argv, true); + + doris::StoragePageCache::create_global_cache(1 << 30, 0.1); + + doris::MultiBenchmark multi_bm; + multi_bm.add_bm(); + multi_bm.register_bm(); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + + return 0; +} diff --git a/docs/.vuepress/sidebar/en.js b/docs/.vuepress/sidebar/en.js index f075724425f966..8791ab30b75106 100644 --- a/docs/.vuepress/sidebar/en.js +++ b/docs/.vuepress/sidebar/en.js @@ -601,6 +601,7 @@ module.exports = [ directoryPath: "developer-guide/", children: [ "debug-tool", + "benchmark-tool", "fe-eclipse-dev", "fe-idea-dev", "be-vscode-dev", diff --git a/docs/.vuepress/sidebar/zh-CN.js b/docs/.vuepress/sidebar/zh-CN.js index efe1ee6bc45667..50363555feec81 100644 --- a/docs/.vuepress/sidebar/zh-CN.js +++ b/docs/.vuepress/sidebar/zh-CN.js @@ -605,6 +605,7 @@ module.exports = [ directoryPath: "developer-guide/", children: [ "debug-tool", + "benchmark-tool", "fe-eclipse-dev", "fe-idea-dev", "be-vscode-dev", diff --git a/docs/en/developer-guide/benchmark-tool.md b/docs/en/developer-guide/benchmark-tool.md new file mode 100644 index 00000000000000..536881d7d4c70d --- /dev/null +++ b/docs/en/developer-guide/benchmark-tool.md @@ -0,0 +1,151 @@ +--- +{ + "title": "Doris BE Storage Layer Benchmark Tool", + "language": "zh-CN" +} + +--- + + + +# Doris BE Storage Layer Benchmark Tool + +## usage + +It can be used to test the performance of some parts of the BE storage layer (for example, segment, page). According to the input data, the designated object is constructed, and the google benchmark is used for performance testing. + +## Compilation + +1. To ensure that the environment has been able to successfully compile the Doris ontology, you can refer to [Installation and deployment] (https://doris.apache.org/master/en/installing/compilation.html)。 + +2. Execute`run-be-ut.sh` + +3. The compiled executable file is located in `./be/ut_build_ASAN/test/tools/benchmark_tool` + +## operator + +#### Use randomly generated data set for segment read test + +The data set will be used to write a `segment` file first, and then the time-consuming scan of the entire `segment` will be counted. + +> ./benchmark_tool --operation=SegmentScan --column_type=int,varchar --rows_number=10000 --iterations=0 + +The `column_type` here can set the schema, the column type of the `segment` layer currently supports `int, char, varchar, string`, the length of the `char` type is `8`, and both `varchar` and `string` types have length restrictions Is the maximum value. The default value is `int,varchar`. + +The data set is generated according to the following rules. +>int: Random in [1,1000000]. + +The data character set of string type is uppercase and lowercase English letters, and the length varies according to the type. +> char: Length random in [1,8]。 +> varchar: Length random in [1,128]。 +> string: Length random in [1,100000]。 + +`rows_number` indicates the number of rows of data, the default value is `10000`. + +`iterations` indicates the number of iterations, the benchmark will repeat the test, and then calculate the average time. If `iterations` is `0`, it means that the number of iterations is automatically selected by the benchmark. The default value is `10`. + +#### Use randomly generated data set for segment write test + +Perform time-consuming statistics on the process of adding data sets to segments and writing them to disk. + +> ./benchmark_tool --operation=SegmentWrite + +#### Use the data set imported from the file for segment read test + +> ./benchmark_tool --operation=SegmentScanByFile --input_file=./sample.dat + +The `input_file` here is the imported data set file. +The first row of the data set file defines the schema, and each row corresponds to a row of data, and each data is separated by `,`. + +Example: +``` +int,char,varchar +123,hello,world +321,good,bye +``` + +The type support is also `int`, `char`, `varchar`, `string`. Note that the data length of the `char` type cannot exceed 8. + +#### Use the data set imported from the file for segment write test + +> ./benchmark_tool --operation=SegmentWriteByFile --input_file=./sample.dat + +#### Use randomly generated data set for page dictionary encoding test + +> ./benchmark_tool --operation=BinaryDictPageEncode --rows_number=10000 --iterations=0 + +Randomly generate varchar with a length between [1,8], and perform time-consuming statistics on encoding. + +#### Use randomly generated data set for page dictionary decoding test + +> ./benchmark_tool --operation=BinaryDictPageDecode + +Randomly generate varchar with a length between [1,8] and encode, and perform time-consuming statistics on decoding. + +## Custom test + +Here, users are supported to use their own functions for performance testing, which can be implemented in `/be/test/tools/benchmark_tool.cpp`. + +For example: +```cpp +void custom_run_plus() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (UNLIKELY(q == 1024)) q = 0; + } +} +void custom_run_mod() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (q %= 1024) q = 0; + } +} +``` +You can join the test by registering `CustomBenchmark`. +```cpp +benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_plus", 0, + custom_init, custom_run_plus)); +benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_mod", 0, + custom_init, custom_run_mod)); +``` +The `custom_init` here is the initialization step of each round of testing (not counted as time-consuming). If the user has an object that needs to be initialized, it can be implemented by a derived class of `CustomBenchmark`. +After running, the results are as follows: +``` +2021-08-30T10:29:35+08:00 +Running ./benchmark_tool +Run on (96 X 3100.75 MHz CPU s) +CPU Caches: + L1 Data 32 KiB (x48) + L1 Instruction 32 KiB (x48) + L2 Unified 1024 KiB (x48) + L3 Unified 33792 KiB (x2) +Load Average: 0.55, 0.53, 0.39 +---------------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------------- +custom_run_plus 0.812 ms 0.812 ms 861 +custom_run_mod 1.30 ms 1.30 ms 539 +``` diff --git a/docs/zh-CN/developer-guide/benchmark-tool.md b/docs/zh-CN/developer-guide/benchmark-tool.md new file mode 100644 index 00000000000000..8b1694395ea492 --- /dev/null +++ b/docs/zh-CN/developer-guide/benchmark-tool.md @@ -0,0 +1,150 @@ +--- +{ + "title": "Doris BE存储层Benchmark工具", + "language": "zh-CN" +} + +--- + + + +# Doris BE存储层Benchmark工具 + +## 用途 + + 可以用来测试BE存储层的一些部分(例如segment、page)的性能。根据输入数据构造出指定对象,利用google benchmark进行性能测试。 + +## 编译 + +1. 确保环境已经能顺利编译Doris本体,可以参考[编译与部署](https://doris.apache.org/master/zh-CN/installing/compilation.html)。 + +2. 运行目录下的`run-be-ut.sh` + +3. 编译出的可执行文件位于`./be/ut_build_ASAN/test/tools/benchmark_tool` + +## 使用 + +#### 使用随机生成的数据集进行Segment读取测试 + +会先利用数据集写入一个`segment`文件,然后对scan整个`segment`的耗时进行统计。 + +> ./benchmark_tool --operation=SegmentScan --column_type=int,varchar --rows_number=10000 --iterations=0 + +这里的`column_type`可以设置表结构,`segment`层的表结构类型目前支持`int、char、varchar、string`,`char`类型的长度为`8`,`varchar`和`string`类型长度限制都为最大值。默认值为`int,varchar`。 + +数据集按以下规则生成。 +>int: 在[1,1000000]内随机。 + +字符串类型的数据字符集为大小写英文字母,长度根据类型不同。 +> char: 长度在[1,8]内随机。 +> varchar: 长度在[1,128]内随机。 +> string: 长度在[1,100000]内随机。 + +`rows_number`表示数据的行数,默认值为`10000`。 + +`iterations`表示迭代次数,benchmark会重复进行测试,然后计算平均耗时。如果`iterations`为`0`则表示由benchmark自动选择迭代次数。默认值为`10`。 + +#### 使用随机生成的数据集进行Segment写入测试 + +对将数据集添加进segment并写入磁盘的流程进行耗时统计。 + +> ./benchmark_tool --operation=SegmentWrite + +#### 使用从文件导入的数据集进行Segment读取测试 + +> ./benchmark_tool --operation=SegmentScanByFile --input_file=./sample.dat + +这里的`input_file`为导入的数据集文件。 +数据集文件第一行为表结构定义,之后每行分别对应一行数据,每个数据用`,`隔开。 + +举例: +``` +int,char,varchar +123,hello,world +321,good,bye +``` + +类型支持同样为`int`、`char`、`varchar`、`string`,注意`char`类型数据长度不能超过8。 + +#### 使用从文件导入的数据集进行Segment写入测试 + +> ./benchmark_tool --operation=SegmentWriteByFile --input_file=./sample.dat + +#### 使用随机生成的数据集进行page字典编码测试 + +> ./benchmark_tool --operation=BinaryDictPageEncode --rows_number=10000 --iterations=0 + +会随机生成长度在[1,8]之间的varchar,并对编码进行耗时统计。 + +#### 使用随机生成的数据集进行page字典解码测试 + +> ./benchmark_tool --operation=BinaryDictPageDecode + +会随机生成长度在[1,8]之间的varchar并编码,并对解码进行耗时统计。 + +## Custom测试 + +这里支持用户使用自己编写的函数进行性能测试,具体可以实现在`/be/test/tools/benchmark_tool.cpp`。 +例如实现有: +```cpp +void custom_run_plus() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (UNLIKELY(q == 1024)) q = 0; + } +} +void custom_run_mod() { + int p = 100000; + int q = 0; + while (p--) { + q++; + if (q %= 1024) q = 0; + } +} +``` +则可以通过注册`CustomBenchmark`来加入测试。 +```cpp +benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_plus", 0, + custom_init, custom_run_plus)); +benchmarks.emplace_back( + new doris::CustomBenchmark("custom_run_mod", 0, + custom_init, custom_run_mod)); +``` +这里的`init`为每轮测试的初始化步骤(不会计入耗时),如果用户有需要初始化的对象则可以通过`CustomBenchmark`的派生类来实现。 +运行后有如下结果: +``` +2021-08-30T10:29:35+08:00 +Running ./benchmark_tool +Run on (96 X 3100.75 MHz CPU s) +CPU Caches: + L1 Data 32 KiB (x48) + L1 Instruction 32 KiB (x48) + L2 Unified 1024 KiB (x48) + L3 Unified 33792 KiB (x2) +Load Average: 0.55, 0.53, 0.39 +---------------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------------- +custom_run_plus 0.812 ms 0.812 ms 861 +custom_run_mod 1.30 ms 1.30 ms 539 +``` diff --git a/run-be-ut.sh b/run-be-ut.sh index 4ca5c274e1a600..07d97d1a8c8ed3 100755 --- a/run-be-ut.sh +++ b/run-be-ut.sh @@ -128,7 +128,7 @@ if [ ${RUN} -ne 1 ]; then fi echo "******************************" -echo " Running Backend Unit Test " +echo " Running Backend Unit Test " echo "******************************" cd ${DORIS_HOME} diff --git a/thirdparty/build-thirdparty.sh b/thirdparty/build-thirdparty.sh index 0163a52040b8ba..2a1299d42ba050 100755 --- a/thirdparty/build-thirdparty.sh +++ b/thirdparty/build-thirdparty.sh @@ -914,6 +914,21 @@ build_hdfs3() { make -j $PARALLEL && make install } +# benchmark +build_benchmark() { + check_if_source_exist $BENCHMARK_SOURCE + + cd $TP_SOURCE_DIR/$BENCHMARK_SOURCE + + cmake -E make_directory "build" + CXXFLAGS="-lresolv -pthread -lrt" cmake -E chdir "build" cmake -DBENCHMARK_ENABLE_GTEST_TESTS=OFF -DCMAKE_BUILD_TYPE=Release ../ + cmake --build "build" --config Release + + mkdir $TP_INCLUDE_DIR/benchmark + cp $TP_SOURCE_DIR/$BENCHMARK_SOURCE/include/benchmark/benchmark.h $TP_INCLUDE_DIR/benchmark/ + cp $TP_SOURCE_DIR/$BENCHMARK_SOURCE/build/src/libbenchmark.a $TP_LIB_DIR/ +} + # See https://github.com/apache/incubator-doris/issues/2910 # LLVM related codes have already be removed in master, so there is # no need to build llvm tool here. @@ -968,5 +983,6 @@ build_lzma build_xml2 build_gsasl build_hdfs3 +build_benchmark echo "Finished to build all thirdparties" diff --git a/thirdparty/vars.sh b/thirdparty/vars.sh index 6088e7e0756874..12261cd09de4a0 100644 --- a/thirdparty/vars.sh +++ b/thirdparty/vars.sh @@ -390,6 +390,13 @@ PDQSORT_DOWNLOAD="http://ftp.cise.ufl.edu/ubuntu/pool/universe/p/pdqsort/pdqsort PDQSORT_NAME="pdqsort.tar.gz" PDQSORT_SOURCE="pdqsort-0.0.0+git20180419" PDQSORT_MD5SUM="39261c3e7b40aa7505662fac29f22d20" + +# benchmark +BENCHMARK_DOWNLOAD="https://github.com/google/benchmark/archive/v1.5.6.tar.gz" +BENCHMARK_NAME=benchmark-1.5.6.tar.gz +BENCHMARK_SOURCE=benchmark-1.5.6 +BENCHMARK_MD5SUM="668b9e10d8b0795e5d461894db18db3c" + # all thirdparties which need to be downloaded is set in array TP_ARCHIVES export TP_ARCHIVES="LIBEVENT OPENSSL @@ -447,5 +454,6 @@ XML2 GSASL HDFS3 LIBDIVIDE -PDQSORT" +PDQSORT +BENCHMARK"