| // 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. |
| |
| #pragma once |
| |
| #include <any> |
| #include <atomic> |
| #include <boost/lexical_cast.hpp> |
| #include <chrono> |
| #include <functional> |
| #include <map> |
| #include <memory> |
| #include <thread> |
| #include <type_traits> |
| |
| #include "common/atomic_shared_ptr.h" |
| #include "common/compiler_util.h" |
| #include "common/config.h" |
| #include "fmt/format.h" |
| |
| // more usage can see 'util/debug_points_test.cpp' |
| // using {} around code, to avoid duplicate variable name |
| #define DBUG_EXECUTE_IF(debug_point_name, code) \ |
| if (UNLIKELY(config::enable_debug_points)) { \ |
| auto dp = DebugPoints::instance()->get_debug_point(debug_point_name); \ |
| if (dp) { \ |
| [[maybe_unused]] auto DP_NAME = debug_point_name; \ |
| code; \ |
| } \ |
| } |
| |
| // define some common debug actions |
| // usage example: DBUG_EXECUTE_IF("xxx", DBUG_BLOCK); |
| #define DBUG_BLOCK \ |
| { \ |
| LOG(INFO) << "start debug block " << DP_NAME; \ |
| while (DebugPoints::instance()->is_enable(DP_NAME)) { \ |
| std::this_thread::sleep_for(std::chrono::milliseconds(10)); \ |
| } \ |
| LOG(INFO) << "end debug block " << DP_NAME; \ |
| } |
| |
| // DBUG_RUN_CALLBACK is usually use in be ut, to exchange local variable between the injected code and callback code. |
| // usage example: DBUG_EXECUTE_IF("xxx", DBUG_RUN_CALLBACK(yyy,...)); |
| #define DBUG_RUN_CALLBACK(...) \ |
| do { \ |
| dp->execute_callback(__VA_ARGS__); \ |
| } while (0) |
| |
| // example of debug point with callback. |
| // |
| // void demo_callback() { |
| // int a = 0; |
| // |
| // DBUG_EXECUTE_IF("set_a", DBUG_RUN_CALLBACK(&a)); |
| // DBUG_EXECUTE_IF("get_a", DBUG_RUN_CALLBACK(a)); |
| // } |
| // |
| // TEST(DebugPointsTest, Callback) { |
| // config::enable_debug_points = true; |
| // DebugPoints::instance()->clear(); |
| // |
| // int got_a = 0; |
| // |
| // std::function<void(int*)> set_handler = [](int* a) { *a = 1000; }; |
| // std::function<void(int)> get_handler = [&got_a](int a) { got_a = a; }; |
| // DebugPoints::instance()->add_with_callback("set_a", set_handler); |
| // DebugPoints::instance()->add_with_callback("get_a", get_handler); |
| // |
| // demo_callback(); |
| // |
| // EXPECT_EQ(1000, got_a); |
| // } |
| |
| namespace doris { |
| |
| struct DebugPoint { |
| std::atomic<int64_t> execute_num {0}; |
| int64_t execute_limit = -1; |
| int64_t expire_ms = -1; |
| |
| std::map<std::string, std::string> params; |
| |
| // Usually `callback` use in be ut, to exchange local variable between callback code and injected code, |
| // or change with different injected handlers. |
| // test/util/debug_points_test.cpp#Callback give a example. |
| std::any callback; |
| |
| template <typename T> |
| T param(const std::string& key, T default_value = T()) { |
| auto it = params.find(key); |
| if (it == params.end()) { |
| return default_value; |
| } |
| if constexpr (std::is_same_v<T, bool>) { |
| if (it->second == "true") { |
| return true; |
| } |
| if (it->second == "false") { |
| return false; |
| } |
| return boost::lexical_cast<T>(it->second); |
| } else if constexpr (std::is_arithmetic_v<T>) { |
| return boost::lexical_cast<T>(it->second); |
| } else if constexpr (std::is_same_v<T, const char*>) { |
| return it->second.c_str(); |
| } else { |
| static_assert(std::is_same_v<T, std::string>); |
| return it->second; |
| } |
| } |
| |
| template <typename... ARGS> |
| void execute_callback(ARGS... args) { |
| if (!callback.has_value()) { |
| throw std::invalid_argument("No set callback"); |
| } |
| |
| auto func = std::any_cast<std::function<void(ARGS...)>>(callback); |
| func(args...); |
| } |
| }; |
| |
| class DebugPoints { |
| public: |
| bool is_enable(const std::string& name); |
| std::shared_ptr<DebugPoint> get_debug_point(const std::string& name); |
| void remove(const std::string& name); |
| void clear(); |
| |
| // if not enable debug point or its params not contains `key`, then return `default_value` |
| // url: /api/debug_point/add/name?k1=v1&k2=v2&... |
| template <typename T> |
| T get_debug_param_or_default(const std::string& name, const std::string& key, |
| const T& default_value) { |
| auto debug_point = get_debug_point(name); |
| return debug_point ? debug_point->param(key, default_value) : default_value; |
| } |
| |
| // url: /api/debug_point/add/name?value=v |
| template <typename T> |
| T get_debug_param_or_default(const std::string& name, const T& default_value) { |
| return get_debug_param_or_default(name, "value", default_value); |
| } |
| |
| void add(const std::string& name, std::shared_ptr<DebugPoint> debug_point); |
| |
| // more 'add' functions for convenient use |
| void add(const std::string& name) { add(name, std::make_shared<DebugPoint>()); } |
| void add_with_params(const std::string& name, |
| const std::map<std::string, std::string>& params) { |
| auto debug_point = std::make_shared<DebugPoint>(); |
| debug_point->params = params; |
| add(name, debug_point); |
| } |
| template <typename T> |
| void add_with_value(const std::string& name, const T& value) { |
| add_with_params(name, {{"value", fmt::format("{}", value)}}); |
| } |
| |
| template <typename... ARGS> |
| void add_with_callback(const std::string& name, std::function<void(ARGS...)> callback) { |
| auto debug_point = std::make_shared<DebugPoint>(); |
| debug_point->callback = callback; |
| add(name, debug_point); |
| } |
| |
| static DebugPoints* instance(); |
| |
| private: |
| DebugPoints(); |
| |
| using DebugPointMap = std::map<std::string, std::shared_ptr<DebugPoint>>; |
| |
| // handler(new_debug_points) |
| void update(std::function<void(DebugPointMap&)>&& handler); |
| |
| private: |
| atomic_shared_ptr<const DebugPointMap> _debug_points; |
| }; |
| |
| } // namespace doris |