Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

API метрик LuaJIT (getmetrics)

Tarantool может возвращать метрики текущего экземпляра через Lua API или C API.

misc.getmetrics()

getmetrics()

Получить значения метрик в виде таблицы.

Параметры: отсутствуют

Возвращает

Таблица

Пример: metrics_table = misc.getmetrics()

Значения таблицы getmetrics

Таблица метрик содержит 19 значений. Все значения имеют тип number и являются результатом приведения к double, поэтому возможна очень незначительная потеря точности. Значения, имена которых начинаются с gc_, связаны со сборщиком мусора LuaJIT; более подробное описание сборщика мусора можно найти на странице Lua-users wiki и в презентации создателя Lua. Значения, имена которых начинаются с jit_, связаны с "фазами" процесса JIT-компиляции; более подробное описание фаз JIT можно найти на cern.ch.

Значения, описанные как "монотонные", являются кумулятивными, то есть это "итог с момента начала всех операций", а не "с момента последнего вызова getmetrics()". Возможно переполнение.

Поскольку многие значения монотонные, типичный анализ включает вызов getmetrics(), сохранение таблицы, повторный вызов getmetrics() и сравнение таблицы с сохраненной. Разница — это "кривая наклона". Интересная кривая наклона – та, которая показывает ускорение, например, разница между последним и предыдущим значением постоянно растет. Некоторые элементы таблицы, описанные здесь, используются в примерах далее в этом разделе.

Имя

Содержание

Монотонный?

gc_allocated

Количество байт выделенной памяти.

Да

gc_cdatanum

Количество выделенных объектов cdata.

Нет

gc_freed

Количество байт освобожденной памяти.

Да

gc_steps_atomic

Количество шагов сборщика мусора, атомарные фазы, инкрементальные.

Да

gc_steps_finalize

Количество шагов сборщика мусора, финализация.

Да

gc_steps_pause

Количество шагов сборщика мусора, паузы.

Да

gc_steps_propagate

Количество шагов сборщика мусора, распространение.

Да

gc_steps_sweep

Количество шагов сборщика мусора, фазы очистки (см. описание фазы очистки).

Да

gc_steps_sweepstring

Количество шагов сборщика мусора, фазы очистки для строк.

Да

gc_strnum

Количество выделенных строковых объектов.

Нет

gc_tabnum

Количество выделенных объектов-таблиц.

Нет

gc_total

Количество байт текущей выделенной памяти (обычно равно gc_allocated минус gc_freed).

Нет

gc_udatanum

Количество выделенных объектов udata.

Нет

jit_mcode_size

Общий размер всех выделенных областей машинного кода.

Нет

jit_snap_restore

Общее количество восстановлений снимков, основанное на количестве проверок-утверждений, приводящих к остановке выполнения трасс (см. внешний руководство по Snap).

Да

jit_trace_abort

Общее количество прерванных трасс.

Да

jit_trace_num

Количество JIT-трасс.

Нет

strhash_hit

Количество интернированных строк: если строка с тем же значением найдена через хеш, новая не создается и не выделяется.

Да

strhash_miss

Общее количество выделений строк за время работы платформы.

Да

C API getmetrics

Функция Lua getmetrics() является оберткой для C-функции luaM_metrics().

C-программы могут подключить заголовочный файл libmisclib.h. Определения в libmisclib.h включают следующие строки:

struct luam_Metrics { /* имена, описанные ранее для Lua */ }LUAMISC_API void luaM_metrics(lua_State *L, struct luam_Metrics *metrics);

Имена членов struct luam_Metrics совпадают с именами значений таблицы getmetrics в Lua. Типы данных всех членов struct luam_Metricssize_t. Функция luaM_metrics() заполняет структуру *metrics метриками, относящимися к состоянию Lua, привязанному к сопрограмме L.

Пример с C-программой

Пройдите руководство по хранимым процедурам на C. Замените пример easy.c на следующий:

#include "module.h"#include <lmisclib.h>int easy(box_function_ctx_t *ctx, const char *args, const char *args_end){  lua_State *ls = luaT_state();  struct luam_Metrics m;  luaM_metrics(ls, &m);  printf("allocated memory = %lu\n", m.gc_allocated);  return 0;}

После возврата к клиенту и выполнения запросов вплоть до строки capi_connection:call('easy') вывод будет выглядеть примерно как "allocated memory = 4431950", хотя число может отличаться.

Пример с gc_strnum, strhash_miss и strhash_hit

Для отслеживания выделения новых строковых объектов:

function f()  collectgarbage("collect")  local oldm = misc.getmetrics()  local table_of_strings = {}  for i = 3000, 4000 do table.insert(table_of_strings, tostring(i)) end  for i = 3900, 4100 do table.insert(table_of_strings, tostring(i)) end  local newm = misc.getmetrics()  print("gc_strnum diff = " .. newm.gc_strnum - oldm.gc_strnum)  print("strhash_miss diff = " .. newm.strhash_miss - oldm.strhash_miss)  print("strhash_hit diff = " .. newm.strhash_hit - oldm.strhash_hit)endf()

Результат, вероятно, будет следующим: gc_strnum diff = 1100, так как добавлено 1202 строки, но 101 из них – дубликаты; strhash_miss_diff = 1100 – по той же причине; strhash_hit_diff = 101 плюс небольшая погрешность – по той же причине.

Слово вероятно использовано, потому что есть вероятность, что строки уже были выделены где-то ранее. Хорошим признаком является ситуация, когда кривая наклона strhash_miss меньше кривой наклона strhash_hit.

Остальные значения gc_*numgc_cdatanum, gc_tabnum, gc_udatanum – можно получить аналогичным образом. Любое из значений gc_*num может быть полезно при поиске утечек памяти – общее количество этих объектов не должно постоянно расти. Более общий способ поиска утечек памяти – отслеживание gc_total. Также jit_mcode_size можно использовать для контроля объема памяти, выделенной для трасс машинного кода.

Пример с gc_allocated и gc_freed

Для отслеживания влияния приложения на сборщик мусора (чем меньше, тем лучше):

function f()  for i = 1, 10 do collectgarbage("collect") end  local oldm = misc.getmetrics()  local newm = misc.getmetrics()  oldm = misc.getmetrics()  collectgarbage("collect")  newm = misc.getmetrics()  print("gc_allocated diff = " .. newm.gc_allocated - oldm.gc_allocated)  print("gc_freed diff = " .. newm.gc_freed - oldm.gc_freed)endf()

Результат: gc_allocated diff = 800, gc_freed diff = 800. Это показывает, что сам по себе local ... = getmetrics() вызывает выделение памяти (поскольку создается таблица и производится присваивание), а также показывает, что при повторном использовании имени переменной (в данном случае oldm) происходит освобождение памяти. Обычно освобождение не происходит немедленно, но collectgarbage("collect") принудительно выполняет его, чтобы мы могли увидеть эффект.

Пример с gc_allocated и оптимизацией памяти

Чтобы проверить, возможна ли оптимизация памяти при работе с таблицами:

function f()  collectgarbage("collect")  local oldm = misc.getmetrics()  local t = {}  for i = 1, 513 do    t[i] = i  end  local newm = misc.getmetrics()  local diff = newm.gc_allocated - oldm.gc_allocated  print("diff = " .. diff)endf()

Результат покажет, что diff равен примерно 18000.

Теперь посмотрим, что произойдет при другом способе инициализации таблицы:

function f()  local table_new = require "table.new"  local oldm = misc.getmetrics()  local t = table_new(513, 0)  for i = 1, 513 do    t[i] = i  end  local newm = misc.getmetrics()  local diff = newm.gc_allocated - oldm.gc_allocated  print("diff = " .. diff)endf()

Результат покажет, что diff равен примерно 6000.

gc_steps_atomic и gc_steps_propagate

Кривые наклона элементов gc_steps_* также можно использовать для контроля нагрузки на сборщик мусора. Во время длительных операций значения gc_steps_* будут расти, но большие интервалы между увеличениями gc_steps_atomic – хороший признак. Поскольку gc_steps_atomic увеличивается только один раз за цикл сборки мусора, этот показатель отражает количество выполненных циклов сборки мусора.

Кроме того, по увеличениям значения gc_steps_propagate можно косвенно оценить количество объектов. Эти значения также коррелируют с множителем шага сборщика мусора. Например, количество инкрементальных шагов может расти, но в соответствии с конфигурацией множителя шага один шаг может обработать лишь небольшое количество объектов. Поэтому эти метрики следует учитывать при настройке сборщика мусора.

Следующая функция позволяет бегло оценить, вызывает ли SQL-запрос значительную нагрузку:

function f()  collectgarbage("collect")  local oldm = misc.getmetrics()  collectgarbage("collect")  box.execute([[DROP TABLE _vindex;]])  local newm = misc.getmetrics()  print("gc_steps_atomic = " .. newm.gc_steps_atomic - oldm.gc_steps_atomic)  print("gc_steps_finalize = " .. newm.gc_steps_finalize - oldm.gc_steps_finalize)  print("gc_steps_pause = " .. newm.gc_steps_pause - oldm.gc_steps_pause)  print("gc_steps_propagate = " .. newm.gc_steps_propagate - oldm.gc_steps_propagate)  print("gc_steps_sweep = " .. newm.gc_steps_sweep - oldm.gc_steps_sweep)endf()

Вывод покажет, что метрики gc_steps_* существенно не отличаются от тех, которые были бы без вызова box.execute().

Пример с jit_trace_num и jit_trace_abort

JIT-компиляторы "трассируют" код в поисках возможностей для компиляции. jit_trace_abort показывает, как часто происходили неудачные попытки (чем меньше, тем лучше), а jit_trace_num – сколько трасс было сгенерировано с момента последней очистки (обычно чем больше, тем лучше).

Следующая функция не содержит кода, который может вызвать проблемы для LuaJIT:

function f()  jit.flush()  for i = 1, 10 do collectgarbage("collect") end  local oldm = misc.getmetrics()      collectgarbage("collect")  local sum = 0  for i = 1, 57 do    sum = sum + 57  end  for i = 1, 10 do collectgarbage("collect") end  local newm = misc.getmetrics()  print("trace_num = " .. newm.jit_trace_num - oldm.jit_trace_num)  print("trace_abort = " .. newm.jit_trace_abort - oldm.jit_trace_abort)endf()

Результат: trace_num = 1, trace_abort = 0. Отлично.

Следующая функция, по-видимому, содержит код, который может вызвать проблемы для LuaJIT:

jit.opt.start(0, "hotloop=2", "hotexit=2", "minstitch=15")_G.globalthing = 5function f()  jit.flush()  collectgarbage("collect")  local oldm = misc.getmetrics()  collectgarbage("collect")  local sum = 0  for i = 1, box.space._vindex:count()+ _G.globalthing do    box.execute([[SELECT RANDOMBLOB(0);]])    require('buffer').ibuf()    _G.globalthing = _G.globalthing - 1  end  local newm = misc.getmetrics()  print("trace_num = " .. newm.jit_trace_num - oldm.jit_trace_num)  print("trace_abort = " .. newm.jit_trace_abort - oldm.jit_trace_abort)endf()

Результат: trace_num = от 2 до 4, trace_abort = 1. Это означает, что вместо одной потребовалось сгенерировать до четырех трасс, и это означает, что что-то заставило LuaJIT отказаться от попыток. Дальнейшее трассирование покажет, что проблема кроется не в подозрительных операторах внутри функции, а в вызове jit.opt.start. (Изучение файла jit.dump может помочь в исследовании процесса компиляции трасс.)

Пример с jit_snap_restore и снижением производительности

Если кривые наклона метрики jit_snap_restore растут после изменений в существующем коде, это может означать, что LuaJIT чаще останавливает выполнение трасс, что может свидетельствовать о снижении производительности.

Начнем со следующего кода:

function f()  local function foo(i)    return i <= 5 and i or tostring(i)  end  -- параметр minstitch эмулирует поведение без сшивки  jit.opt.start(0, "hotloop=2", "hotexit=2", "minstitch=15")  local sum = 0  local oldm = misc.getmetrics()  for i = 1, 10 do    sum = sum + foo(i)  end  local newm = misc.getmetrics()  local diff = newm.jit_snap_restore - oldm.jit_snap_restore  print("diff = " .. diff)endf()

Результат: diff = 3, так как есть один побочный выход при завершении цикла и два побочных выхода в интерпретатор до того, как LuaJIT может решить, что фрагмент кода "горячий" (значение параметра hotloop по умолчанию равно 56 согласно Running LuaJIT).

Теперь изменим только одну строку внутри функции local foo, и код примет вид:

function f()  local function foo(i)    -- math.fmod еще не компилируется!    return i <= 5 and i or math.fmod(i, 11)  end  -- параметр minstitch эмулирует поведение без сшивки  jit.opt.start(0, "hotloop=2", "hotexit=2", "minstitch=15")  local sum = 0  local oldm = misc.getmetrics()  for i = 1, 10 do    sum = sum + foo(i)  end  local newm = misc.getmetrics()  local diff = newm.jit_snap_restore - oldm.jit_snap_restore  print("diff = " .. diff)endf()

Результат: diff будет больше, так как количество побочных выходов увеличилось. Таким образом, этот тест показывает, что изменение кода повлияло на производительность.