Семплирующий профилировщик запросов
Cемплирующий профилировщик запросов (sampling query profiler) предоставляет возможность интроспекции производительности запросов в ADQM. Например, с его помощью в исходном коде можно найти функции, которые наиболее часто вызываются во время выполнения запроса, или отследить затрачиваемое процессорное и реальное время, включая время простоя.
Включение профилировщика запросов
-
В интерфейсе ADCM на странице конфигурации сервиса ADQMDB активируйте опцию Show advanced и в секции Log settings включите настройку trace_log, чтобы профилировщик запросов начал собирать трассировки стека и сохранять их в таблицу
trace_log
. В этой же секции настройте параметры таблицыtrace_log
:-
Database — имя базы данных, в которой будет храниться таблица;
-
Flush interval, milliseconds — интервал сброса данных из буфера памяти в таблицу (в миллисекундах);
-
TTL, days — время хранения строк в таблице (в днях).
Нажмите Save и выполните действие Reconfig and restart для сервиса ADQMDB, чтобы сохранить информацию об указанных параметрах конфигурации и перезапустить сервис.
ПРИМЕЧАНИЕ-
Ключом партиционирования таблицы
trace_log
автоматически устанавливается столбецevent_date
— дата в момент получения экземпляра трассировки стека. Подробнее столбцы таблицы описаны в статье trace_log. -
Данные в таблице
trace_log
актуальны только для работающего сервера. После перезапуска сервера ADQM таблица не очищается и все сохраненные адреса виртуальной памяти могут стать недействительными.
-
-
С помощью параметров
query_profiler_real_time_period_ns
иquery_profiler_cpu_time_period_ns
настройте таймер реального времени и таймер CPU, чтобы определить периодичность, с которой профилировщик запросов будет собирать трассировки стека и записывать их в таблицуtrace_log
.
По умолчанию оба таймера настроены на выполнение одной выборки в секунду. Такая частота выборки позволяет собрать достаточно информации о кластере ADQM, при этом профилирование не влияет на производительность серверов ADQM. Если необходимо профилировать каждый запрос отдельно, можно попробовать использовать более высокую частоту выборки, например, 100 раз в секунду:SET query_profiler_real_time_period_ns=10000000;
SET query_profiler_cpu_time_period_ns=10000000;
Собирать трассировки стека с частотой выше нескольких тысяч выборок в секунду обычно невозможно.
Анализ отчетов профилировщика
-
Установите пакет adqm-clickhouse-debuginfo.
-
Включите функции интроспекции для профилирования запросов (в целях безопасности эти функции отключены по умолчанию):
SET allow_introspection_functions = 1;
-
Используйте функции интроспекции для анализа данных таблицы
trace_log
(см. следующий раздел).
Функции интроспекции
В этом разделе описаны функции, которые можно использовать для анализа форматов ELF и DWARF при профилировании запросов.
-
Перед выполнением примеров использования функций интроспекции, убедитесь, что пакет adqm-clickhouse-debuginfo установлен и активирована настройка
allow_introspection_functions = 1
. -
В приведенных примерах функции интроспекции применяются для анализа первого экземпляра трассировки стека из таблицы
trace_log
. Используйте следующий запрос, чтобы получить первую строку этой таблицы:SELECT * FROM system.trace_log LIMIT 1 FORMAT Vertical;
Row 1: ────── event_date: 2023-09-04 event_time: 2023-09-04 10:18:01 event_time_microseconds: 2023-09-04 10:18:01.858935 timestamp_ns: 1693822681858935963 revision: 54472 trace_type: Memory thread_id: 11216 query_id: 7b53b488-6e38-495a-ab63-e0ed985bf2de trace: [282096402,282019952,282019801,282176985,373506363,387689543,282729773,282744526,140712295374501,140712292395789] size: 8760 event: increment: 0
Поле
trace
содержит трассировку стека в момент выборки, где каждый элемент массива — это адрес виртуальной памяти внутри процесса сервера ADQM. Функции интроспекции можно применять к одному адресу или ко всей трассировке стека.
addressToLine
Функция addressToLine
преобразует адрес виртуальной памяти внутри процесса сервера ADQM в имя файла и номер строки в исходном коде.
Cинтаксис вызова функции:
addressToLine(<address_of_binary_instruction>)
где <address_of_binary_instruction>
— адрес инструкции в запущенном процессе (значение типа UInt64).
Функция возвращает значение типа String:
-
Имя файла исходного кода и номер строки в этом файле, разделяемые двоеточием.
-
Имя бинарного файла, если функция не может найти отладочную информацию.
-
Пустую строку, если передаваемый в качестве аргумента адрес не является допустимым.
-
Получите имя файла исходного кода и номер строки для одного адреса:
SELECT addressToLine(282096402::UInt64);
В выводе результата выполнения функции
./build/src/Common/StackTrace.cpp
— имя файла, а287
— номер строки:┌─addressToLine(CAST('282096402', 'UInt64'))─┐ │ ./build/src/Common/StackTrace.cpp:287 │ └────────────────────────────────────────────┘
-
Примените функцию ко всей трассировке стека. Используйте функцию arrayMap, чтобы обработать каждый отдельный элемент массива
trace
с помощью функцииaddressToLine
:SELECT arrayStringConcat(arrayMap(x -> addressToLine(x), trace), '\n') AS trace_source_code_lines FROM system.trace_log LIMIT 1 FORMAT Vertical;
Row 1: ────── trace_source_code_lines: ./build/src/Common/StackTrace.cpp:287 ./build/src/Common/MemoryTracker.cpp:202 ./build/src/Common/MemoryTracker.cpp:338 ./build/src/Common/ThreadStatus.cpp:185 ./build/contrib/llvm-project/libcxx/include/__memory/unique_ptr.h:302 ./build/contrib/llvm-project/libcxx/include/__memory/shared_ptr.h:701 ./build/base/base/wide_integer_impl.h:789 ./build/contrib/llvm-project/libcxx/include/__memory/unique_ptr.h:302 /usr/lib64/libpthread-2.17.so /usr/lib64/libc-2.17.so
addressToLineWithInlines
Функция addressToLineWithInlines
похожа на addressToLine
, но возвращает массив со всеми встроенными функциями (поэтому она вычисляется медленнее).
Cинтаксис вызова функции:
addressToLineWithInlines(<address_of_binary_instruction>)
где <address_of_binary_instruction>
— адрес инструкции в запущенном процессе (значение типа UInt64).
Функция возвращает массив строк:
-
Массив, в котором первый элемент содержит имя файла исходного кода и номер строки в этом файле (разделенные двоеточием), остальные элементы включают информацию о встроенных функциях — имя файла исходного кода с встроенной функцией, номер строки и имя функции.
-
Массив с одним элементом — именем бинарного файла, если функция не смогла найти отладочную информацию.
-
Пустой массив, если передаваемый в качестве аргумента адрес не является допустимым.
-
Примените функцию к одному адресу:
SELECT addressToLineWithInlines(373506363::UInt64) FORMAT Vertical;
Row 1: ────── addressToLineWithInlines(CAST('373506363', 'UInt64')): ['./build/src/Interpreters/ThreadStatusExt.cpp:185','./build/contrib/llvm-project/libcxx/include/__memory/unique_ptr.h:302:std::__1::unique_ptr<DB::QueryProfilerReal, std::__1::default_delete<DB::QueryProfilerReal>>::reset[abi:v15000](DB::QueryProfilerReal*)','./build/src/Interpreters/ThreadStatusExt.cpp:415:DB::ThreadStatus::finalizeQueryProfiler()']
-
Примените функцию ко всей трассировке стека. Используйте функцию arrayJoin, чтобы разделить массив на строки:
SELECT address, addressToLineWithInlines(arrayJoin(trace) AS address) FROM system.trace_log WHERE query_id = '7b53b488-6e38-495a-ab63-e0ed985bf2de' FORMAT Vertical;
Row 1: ────── address: 282096402 addressToLineWithInlines(arrayJoin(trace)): ['./build/src/Common/StackTrace.cpp:287'] Row 2: ────── address: 282019952 addressToLineWithInlines(arrayJoin(trace)): ['./build/src/Common/MemoryTracker.cpp:202'] Row 3: ────── address: 282019801 addressToLineWithInlines(arrayJoin(trace)): ['./build/src/Common/MemoryTracker.cpp:338'] Row 4: ────── address: 282176985 addressToLineWithInlines(arrayJoin(trace)): ['./build/src/Common/ThreadStatus.cpp:185'] Row 5: ────── address: 373506363 addressToLineWithInlines(arrayJoin(trace)): ['./build/src/Interpreters/ThreadStatusExt.cpp:185','./build/contrib/llvm-project/libcxx/include/__memory/unique_ptr.h:302:std::__1::unique_ptr<DB::QueryProfilerReal, std::__1::default_delete<DB::QueryProfilerReal>>::reset[abi:v15000](DB::QueryProfilerReal*)','./build/src/Interpreters/ThreadStatusExt.cpp:415:DB::ThreadStatus::finalizeQueryProfiler()'] ...
addressToSymbol
Функция addressToSymbol
преобразует адрес виртуальной памяти внутри процесса сервера ADQM в символ из объектного файла.
Cинтаксис вызова функции:
addressToSymbol(<address_of_binary_instruction>)
где <address_of_binary_instruction>
— адрес инструкции в запущенном процессе (значение типа UInt64).
Функция возвращает значение типа String:
-
Символ из объектного файла.
-
Пустую строку, если передаваемый в качестве аргумента адрес не является допустимым.
-
Получите символ для одного адреса:
SELECT addressToSymbol(282096402::UInt64);
┌─addressToSymbol(CAST('282096402', 'UInt64'))─┐ │ _ZN10StackTrace10tryCaptureEv │ └──────────────────────────────────────────────┘
-
Примените функцию ко всей трассировке стека. Используйте функцию arrayMap, чтобы обработать каждый отдельный элемент массива
trace
с помощью функцииaddressToSymbol
:SELECT arrayStringConcat(arrayMap(x -> addressToSymbol(x), trace), '\n') AS trace_symbols FROM system.trace_log LIMIT 1 FORMAT Vertical;
Row 1: ────── trace_symbols: _ZN10StackTrace10tryCaptureEv _ZN13MemoryTracker9allocImplElbPS_ _ZN13MemoryTracker9allocImplElbPS_ _ZN2DB12ThreadStatus20flushUntrackedMemoryEv _ZN2DB12ThreadStatus15detachFromGroupEv _ZNSt3__110__function16__policy_invokerIFvvEE11__call_implINS0_20__default_alloc_funcIZN24ThreadFromGlobalPoolImplILb1EEC1IZN2DB28PullingAsyncPipelineExecutor4pullERNS9_5ChunkEmE3$_0JEEEOT_DpOT0_EUlvE_S2_EEEEvPKNS0_16__policy_storageE _ZN14ThreadPoolImplINSt3__16threadEE6workerENS0_15__list_iteratorIS1_PvEE _ZNSt3__114__thread_proxyB6v15000INS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEZN14ThreadPoolImplINS_6threadEE12scheduleImplIvEET_NS_8functionIFvvEEElNS_8optionalImEEbEUlvE0_EEEEEPvSJ_ start_thread __clone
demangle
Функция demangle
преобразует символ, который можно получить с помощью функции addressToSymbol
, в имя функции C++.
Cинтаксис вызова функции:
demangle(<symbol>)
где <symbol>
— символ из объектного файла (значение типа String).
Функция возвращает значение типа String:
-
Имя функции C++.
-
Пустую строку, если передаваемый в качестве аргумента символ не является допустимым.
-
Получите имя функции для одного адреса:
SELECT demangle(addressToSymbol(282096402::UInt64));
┌─demangle(addressToSymbol(CAST('282096402', 'UInt64')))─┐ │ StackTrace::tryCapture() │ └────────────────────────────────────────────────────────┘
-
Примените функцию ко всей трассировке стека. Используйте функцию arrayMap, чтобы обработать каждый отдельный элемент массива
trace
с помощью функцииdemangle
:SELECT arrayStringConcat(arrayMap(x -> demangle(addressToSymbol(x)), trace), '\n') AS trace_functions FROM system.trace_log LIMIT 1 FORMAT Vertical;
Row 1: ────── trace_functions: StackTrace::tryCapture() MemoryTracker::allocImpl(long, bool, MemoryTracker*) MemoryTracker::allocImpl(long, bool, MemoryTracker*) DB::ThreadStatus::flushUntrackedMemory() DB::ThreadStatus::detachFromGroup() void std::__1::__function::__policy_invoker<void ()>::__call_impl<std::__1::__function::__default_alloc_func<ThreadFromGlobalPoolImpl<true>::ThreadFromGlobalPoolImpl<DB::PullingAsyncPipelineExecutor::pull(DB::Chunk&, unsigned long)::$_0>(DB::PullingAsyncPipelineExecutor::pull(DB::Chunk&, unsigned long)::$_0&&)::'lambda'(), void ()>>(std::__1::__function::__policy_storage const*) ThreadPoolImpl<std::__1::thread>::worker(std::__1::__list_iterator<std::__1::thread, void*>) void* std::__1::__thread_proxy[abi:v15000]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, void ThreadPoolImpl<std::__1::thread>::scheduleImpl<void>(std::__1::function<void ()>, long, std::__1::optional<unsigned long>, bool)::'lambda0'()>>(void*) start_thread __clone
tid
Функция tid
возвращает идентификатор потока, в котором обрабатывается текущий Block.
Cинтаксис вызова функции:
tid()
Функция возвращает значение типа Uint64.
SELECT tid();
┌─tid()─┐ │ 10959 │ └───────┘
logTrace
Функция logTrace
выводит сообщение в лог сервера для каждого Block.
Синтаксис вызова функции:
logTrace('<message>')
где <message>
— сообщение, которое отправляется в серверный лог.
Функция всегда возвращает 0
.
SELECT logTrace('logTrace message');
┌─logTrace('logTrace message')─┐ │ 0 │ └──────────────────────────────┘