Skip to content

ОтелРезервуарЭкземпляров ​

Простой резервуар exemplars фиксированного размера (SimpleFixedSizeExemplarReservoir); его методы — контракт своего резервуара.

Описание ​

Резервуар exemplars (ExemplarReservoir) отбирает exemplars одной серии потока метрики. SDK предлагает резервуару измерения серии методом Предложить, а при сборе забирает отобранные exemplars методом Собрать и добавляет их в точку данных серии — в ключ exemplars (формат — в ОтелДанныеМетрики).

ОтелРезервуарЭкземпляров — встроенный простой резервуар. Его создаёт ОтелФабрикаПростыхРезервуаров: SDK использует эту фабрику по умолчанию для большинства агрегаций (см. Резервуар по умолчанию), а приложение передаёт её в представление, чтобы задать размер. Как резервуар отбирает измерения, на какой интервал создаётся и что попадает в exemplar, описано на странице фабрики. Напрямую резервуар создают, например, в своём резервуаре, чтобы поручить встроенному отбор и формирование exemplars (см. Пример).

Резервуар хранит exemplars и счётчик измерений отдельно для каждого ключа серии (КлючАтрибутов), поэтому один объект может обслуживать несколько серий; резервуар, созданный фабрикой, получает измерения одной серии. Методы безопасны для одновременного вызова из нескольких потоков (спецификация Metrics SDK, раздел Concurrency requirements): данные резервуара защищены блокировкой.

Свой резервуар подключают так же — фабрикой в представлении. Что для этого нужно реализовать, описано в разделе Свой резервуар.

Другие резервуары SDK ​

Те же методы реализуют резервуары двух других фабрик SDK:

  • ОтелВыровненныйРезервуарГистограммы — его создаёт ОтелФабрикаВыровненныхРезервуаровГистограммы, конструктор — Новый ОтелВыровненныйРезервуарГистограммы(Границы) с обязательным массивом границ бакетов. Хранит не больше одного exemplar на бакет гистограммы: МаксРазмер() возвращает число границ плюс один, Предложить кладёт exemplar в слот бакета, в который попадает значение, Собрать возвращает заполненные слоты в порядке бакетов. Ключи exemplar те же; как резервуар отбирает измерения внутри бакета — на странице фабрики.
  • ОтелНоопРезервуарЭкземпляров — его создаёт ОтелФабрикаNoopРезервуаров, конструктор без параметров. Предложить и Очистить ничего не делают, Собрать возвращает пустой массив. Метода МаксРазмер у него нет.

Конструктор ​

bsl
Новый ОтелРезервуарЭкземпляров(МаксРазмер)
ПараметрТипПо умолчаниюОписание
МаксРазмерЧисло1Сколько exemplars резервуар хранит для одного ключа серии

Методы ​

МаксРазмер ​

Возвращает размер резервуара — значение МаксРазмер из конструктора. SDK метод не вызывает.

Возвращает: Число


Предложить ​

Предлагает резервуару измерение. SDK вызывает метод для каждого измерения серии, которое пропустил фильтр exemplars провайдера; что SDK передаёт в параметрах — в разделе Параметры Предложить.

Резервуар решает, сохранить ли измерение, до того как создаёт exemplar (спецификация Metrics SDK, раздел ExemplarReservoir: при отборе резервуару следует избегать выделения памяти). Первые МаксРазмер измерений ключа сохраняются подряд, далее N-е измерение ключа с вероятностью МаксРазмер/N заменяет случайно выбранный exemplar (Algorithm R). Сохранённый exemplar — Соответствие с ключами:

  • timeUnixNano — Timestamp, а если он не передан — время вызова;
  • asDouble — Значение;
  • traceId и spanId — ID из КонтекстСпана; без него — из спана контекста Контекст, если контекст этого спана валиден. Текущий контекст резервуар сам не читает: без обоих параметров ID в exemplar нет;
  • filteredAttributes — атрибуты АтрибутыИзмерения, ключей которых нет в АтрибутыСерии, в формате OTLP. Если таких атрибутов нет, ключа нет.
ПараметрТипПо умолчаниюОписание
КлючАтрибутовСтрока—Ключ серии: exemplars и счётчик измерений у каждого ключа свои
ЗначениеЧисло—Значение измерения
АтрибутыИзмеренияОтелАтрибуты, НеопределеноНеопределеноВсе атрибуты измерения
АтрибутыСерииОтелАтрибуты, НеопределеноНеопределеноАтрибуты серии — атрибуты измерения, которые сохранил поток метрики
КонтекстСпанаОтелКонтекстСпана, НеопределеноНеопределеноКонтекст спана, в котором записано измерение
TimestampСтрока, НеопределеноНеопределеноВремя измерения — строка с числом наносекунд от начала эпохи Unix
КонтекстСоответствие, НеопределеноНеопределеноКонтекст измерения (Context)

Собрать ​

Возвращает exemplars ключа серии. Массив новый: следующие вызовы Предложить его не меняют. Резервуар при этом не очищается: exemplars и счётчик измерений ключа сохраняются, и повторный вызов вернёт те же exemplars, пока новые измерения их не заменят.

ПараметрТипПо умолчаниюОписание
КлючАтрибутовСтрока—Ключ серии, как в Предложить

Возвращает: Массив из Соответствие — exemplars (ключи — в описании Предложить); пустой массив, если у ключа их нет


Очистить ​

Удаляет exemplars и счётчики измерений всех ключей: отбор начинается заново. У резервуаров, созданных фабрикой, SDK метод не вызывает — на новый интервал сбора серия получает новый резервуар. Общий резервуар, заданный устаревшим УстановитьРезервуарЭкземпляров, SDK очищает при каждом сборе потока, кумулятивном и дельта: exemplars точки относятся к интервалу с прошлого сбора.

Свой резервуар ​

Свой резервуар (спецификация Metrics SDK, раздел Custom ExemplarReservoir) подключают через представление. В параметр РезервуарЭкземпляров конструктора ОтелПредставление передают фабрику — объект с функцией СоздатьРезервуар() без параметров, которая на каждый вызов возвращает новый резервуар. Представление регистрируют методом ЗарегистрироватьПредставление(Селектор, Представление)построителя провайдера или работающего провайдера. Фабрика и резервуар — обычные классы приложения: интерфейса для них в библиотеке нет, SDK вызывает методы по имени.

Методы резервуара ​

У резервуара, созданного фабрикой, SDK вызывает только два метода — с теми же сигнатурами, что у ОтелРезервуарЭкземпляров:

  • Предложить — для каждого измерения серии, которое пропустил фильтр exemplars провайдера (ОтелФильтрЭкземпляров). Проверять фильтр резервуару не нужно;
  • Собрать(КлючАтрибутов) — при каждом сборе потока метрики, один раз для каждой серии, даже если резервуару ничего не предлагалось.

Очистить и МаксРазмер у таких резервуаров SDK не вызывает, реализовывать их не нужно. Очистить SDK вызывает только у одного общего резервуара, заданного инструменту устаревшим методом УстановитьРезервуарЭкземпляров, а МаксРазмер не вызывает вовсе.

Объявляйте Предложить с семью параметрами и значениями по умолчанию, как у ОтелРезервуарЭкземпляров: синхронные инструменты передают все семь параметров, асинхронные — только первые четыре. Если у параметров с пятого по седьмой нет значений по умолчанию, вызов от асинхронного инструмента завершается ошибкой о недостаточном числе параметров. SDK пишет её в лог только на уровне отладки, и exemplars асинхронных инструментов пропадают незаметно.

Параметры Предложить ​

ПараметрСинхронные инструментыАсинхронные инструменты
КлючАтрибутовКлюч серии — Ключ() её атрибутов; пустая строка у серии без атрибутов, служебный ключ у серии переполнения. Один и тот же во всех вызовах одного резервуараТак же
ЗначениеЗначение из Добавить или ЗаписатьЗначение наблюдения
АтрибутыИзмеренияАтрибуты из Добавить или Записать — тот же объект, что передало приложение; Неопределено, если атрибуты не переданыАтрибуты наблюдения; пустой ОтелАтрибуты, если наблюдение записано без атрибутов
АтрибутыСерииАтрибуты серии после фильтра атрибутов потока (РазрешенныеКлючиАтрибутов и ИсключенныеКлючиАтрибутов представления) — копия, сделанная при создании серии; Неопределено, если измерение записано без атрибутов. У серии переполнения — один атрибут otel.metric.overflow со значением ИстинаАтрибуты серии после фильтра атрибутов потока — копия; у серии переполнения — otel.metric.overflow
КонтекстСпанаКонтекст спана из Контекст, а если Контекст не передан — текущего спана потока, который записывает измерение; Неопределено, если спана нет или его контекст невалиденНе передаётся
TimestampВсегда Неопределено: время exemplar резервуар определяет самНе передаётся
КонтекстКонтекст, переданный приложением в Добавить или Записать; Неопределено, если не переданНе передаётся

Атрибуты измерения резервуар получает целиком вместе с атрибутами серии: так exemplar может сохранить атрибуты, которых нет в серии (спецификация Metrics SDK, раздел ExemplarReservoir). Не храните ссылку на АтрибутыИзмерения: приложение может изменить объект после записи, поэтому нужное переводите в exemplar сразу.

Асинхронные инструменты предлагают резервуару наблюдения при сборе: фильтр exemplars для них проверяет текущий контекст потока, который выполняет сбор, а контекст спана резервуар не получает.

Когда SDK создаёт резервуар ​

SDK вызывает СоздатьРезервуар() для каждой новой серии каждого потока метрики. Потоки отдельные у каждого читателя и у каждого представления, выбравшего инструмент, поэтому одно измерение может попасть в несколько резервуаров. Резервуар служит одному интервалу сбора:

  • у синхронного инструмента серия получает резервуар при первом измерении в интервале. После сбора читателем при дельта-временности интервал начинается без серий, и резервуар создаётся при следующем измерении серии, а при кумулятивной все серии потока получают новые резервуары сразу после сбора;
  • у асинхронного инструмента серии и их резервуары создаются при каждом сборе — для наборов атрибутов, которые наблюдались в этом сборе.

Поэтому всё, что отобрал резервуар, относится к интервалу точки данных (спецификация Metrics SDK, раздел ExemplarReservoir). С фильтром ОтелФильтрЭкземпляров.ВсегдаВыключен() и у потоков с агрегацией drop резервуары серий не создаются.

Кроме того, SDK вызывает СоздатьРезервуар(), чтобы проверить фабрику, — при создании инструмента и при каждом новом разрешении его потоков, например после регистрации представления в работающем провайдере (см. Проверка фабрики). Созданный так резервуар не используется, а проверка выполняется и с фильтром ВсегдаВыключен().

Фабрику одного представления SDK использует во всех его потоках метрики — у всех выбранных инструментов и всех читателей. Поэтому СоздатьРезервуар() должен быть быстрым, без побочных эффектов и безопасным для одновременного вызова из нескольких потоков. Возвращайте новый объект на каждый вызов (резервуар создаётся для каждой серии — спецификация Metrics SDK, раздел Custom ExemplarReservoir): резервуар, общий для нескольких серий, SDK не очищает между интервалами и может вызывать одновременно из разных потоков.

Проверка фабрики ​

Проверяя фабрику, SDK вызывает СоздатьРезервуар() у объекта из параметра РезервуарЭкземпляров. Если вызов завершился исключением — метода нет или он сам выбросил исключение, — объект отклоняется: в лог oscript.lib.opentelemetry.metrics пишется ошибка «Резервуар exemplars View для инструмента '<имя>' не является фабрикой (нет метода СоздатьРезервуар): резервуар создается на каждую серию, используется резервуар по умолчанию». Текст один и тот же в обоих случаях, исходная ошибка в лог не попадает. Так отклоняется и резервуар, переданный вместо фабрики, например ОтелРезервуарЭкземпляров. Остальные настройки представления применяются, а поток получает резервуар по умолчанию для своей агрегации (см. Резервуар по умолчанию).

Что возвращает СоздатьРезервуар() и есть ли у резервуара нужные методы, SDK не проверяет: ошибки проявятся при вызовах.

Потоки и исключения ​

SDK не вызывает методы одного резервуара одновременно:

  • у синхронного инструмента Предложить выполняется в потоке, который записывает измерение, под блокировкой потока метрики. Пока работает Предложить, другие измерения этого потока метрики ждут, поэтому метод должен быть быстрым — без ввода-вывода и ожиданий. Собрать при кумулятивном сборе выполняется под той же блокировкой, а при дельта-сборе — после закрытия интервала, когда новых вызовов Предложить у резервуара уже не будет;
  • у асинхронного инструмента Предложить и Собрать вызываются по очереди в потоке, который выполняет сбор.

Вызовы одного резервуара приходят из разных потоков — потоков приложения и потока сбора, — но не одновременно, поэтому резервуару, который фабрика создаёт на каждый вызов, своя блокировка не нужна. Блокировка нужна, если резервуар обращается к общим данным, например к полям фабрики.

Исключения:

  • из Предложить SDK перехватывает и пишет в лог на уровне отладки: «Ошибка при захвате exemplar: …». Измерение агрегируется, exemplar не сохраняется;
  • из Собрать — SDK перехватывает и пишет в лог предупреждение «Exemplars потока метрики '<имя>' не собраны: ошибка резервуара: …». Точка данных серии выдаётся без exemplars, остальные данные сбора не меняются. Так же действует результат, который нельзя обойти циклом Для Каждого; Неопределено вместо пустого массива SDK считает отсутствием exemplars и в лог не пишет;
  • из СоздатьРезервуар() — при проверке отклоняет фабрику. При создании серии и при замене резервуара после кумулятивного сбора SDK пишет в лог предупреждение «Резервуар exemplars потока метрики '<имя>' не создан: …», серия остаётся без резервуара до следующего интервала сбора: измерения агрегируются, exemplars не сохраняются;
  • из Очистить общего резервуара — SDK пишет в лог предупреждение «Резервуар exemplars потока метрики '<имя>' не очищен и отключен: …» и отключает резервуар, чтобы точки не получили exemplars прошлых интервалов. Сбор продолжается, точки потока выдаются без exemplars.

Что возвращает Собрать ​

Собрать возвращает Массив exemplars серии; пустой массив — у точки данных нет exemplars. SDK копирует массив, поэтому можно вернуть свой внутренний, а сами exemplars кладёт в точку данных без проверок и изменений: OTLP-экспортёр передаёт их как есть. Поэтому exemplar — Соответствие с ключами OTLP JSON, как у встроенных резервуаров: timeUnixNano, asDouble, traceId и spanId, filteredAttributes (типы — в ОтелДанныеМетрики, откуда брать значения — в описании Предложить).

Время SDK не передаёт: берите текущее — ОтелУтилиты.ТекущееВремяВНаносекундах(). Атрибуты измерения, которых нет в атрибутах серии, exemplar должен сохранить в filteredAttributes (спецификация Metrics SDK, раздел ExemplarReservoir); массив атрибутов в формате OTLP возвращает ВМассивOtlp() объекта ОтелАтрибуты.

Пример ​

Резервуар, который хранит exemplar измерения с наибольшим значением — например, самого медленного запроса за интервал, — класс приложения в файле РезервуарМаксимума.os:

bsl
// Резервуар exemplars одной серии: хранит измерение с наибольшим значением.
// SDK вызывает методы одного резервуара по очереди, блокировка не нужна.

// Число, Неопределено - наибольшее предложенное значение
Перем Максимум;
// Соответствие, Неопределено - exemplar этого измерения
Перем Экземпляр;

Процедура Предложить(КлючАтрибутов, Значение, АтрибутыИзмерения = Неопределено,
        АтрибутыСерии = Неопределено, КонтекстСпана = Неопределено,
        Timestamp = Неопределено, Контекст = Неопределено) Экспорт

    Если Максимум <> Неопределено И Значение <= Максимум Тогда
        Возврат;
    КонецЕсли;
    Максимум = Значение;

    Экземпляр = Новый Соответствие();
    Экземпляр.Вставить("timeUnixNano", ОтелУтилиты.ТекущееВремяВНаносекундах());
    Экземпляр.Вставить("asDouble", Значение);
    Если КонтекстСпана <> Неопределено Тогда
        Экземпляр.Вставить("traceId", КонтекстСпана.ИдТрассировки());
        Экземпляр.Вставить("spanId", КонтекстСпана.ИдСпана());
    КонецЕсли;

    // Атрибуты измерения, которых нет в атрибутах серии
    Отфильтрованные = Новый ОтелАтрибуты();
    Если АтрибутыИзмерения <> Неопределено Тогда
        Для Каждого КлючИЗначение Из АтрибутыИзмерения.ВСоответствие() Цикл
            Если АтрибутыСерии = Неопределено
                    ИЛИ АтрибутыСерии.Получить(КлючИЗначение.Ключ) = Неопределено Тогда
                Отфильтрованные.Установить(КлючИЗначение.Ключ, КлючИЗначение.Значение);
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    Если Отфильтрованные.Количество() > 0 Тогда
        Экземпляр.Вставить("filteredAttributes", Отфильтрованные.ВМассивOtlp());
    КонецЕсли;

КонецПроцедуры

Функция Собрать(КлючАтрибутов) Экспорт
    Результат = Новый Массив();
    Если Экземпляр <> Неопределено Тогда
        Результат.Добавить(Экземпляр);
    КонецЕсли;
    Возврат Результат;
КонецФункции

Фабрика — класс в файле ФабрикаРезервуаровМаксимума.os:

bsl
// Фабрика резервуаров exemplars: новый резервуар на каждый вызов.

Функция СоздатьРезервуар() Экспорт
    Возврат Новый РезервуарМаксимума();
КонецФункции

Подключение к гистограмме длительности запросов:

bsl
#Использовать opentelemetry

Каталог = ТекущийСценарий().Каталог;
ПодключитьСценарий(ОбъединитьПути(Каталог, "РезервуарМаксимума.os"), "РезервуарМаксимума");
ПодключитьСценарий(ОбъединитьПути(Каталог, "ФабрикаРезервуаровМаксимума.os"), "ФабрикаРезервуаровМаксимума");

Экспортер = Новый ОтелЭкспортерМетрик(Новый ОтелHttpТранспорт("http://localhost:4318"));

Представление = Новый ОтелПредставление(, , , , , ,
    Новый ФабрикаРезервуаровМаксимума()); // РезервуарЭкземпляров

Провайдер = Новый ОтелПостроительПровайдераМетрик()
    .ЗарегистрироватьЧитатель(Новый ОтелПериодическийЧитательМетрик(Экспортер))
    .ЗарегистрироватьПредставление(Новый ОтелСелекторИнструментов("http.server.request.duration"), Представление)
    .Построить();

Метр = Провайдер.ПолучитьМетр("my-app");
Гистограмма = Метр.СоздатьГистограмму("http.server.request.duration", "Длительность запросов", "s");

// Exemplar сохраняется для измерений в контексте сэмплированного спана:
// фильтр exemplars по умолчанию - ОтелФильтрЭкземпляров.ПоТрассировке()
Атрибуты = Новый ОтелАтрибуты();
Атрибуты.Установить("http.route", "/orders");
Гистограмма.Записать(0.75, Атрибуты);

Резервуар может не формировать exemplars сам, а передавать отобранные измерения встроенному. Резервуар медленных запросов — файл РезервуарМедленных.os; фабрика к нему такая же, как ФабрикаРезервуаровМаксимума, но возвращает Новый РезервуарМедленных():

bsl
// ОтелРезервуарЭкземпляров - до трех exemplars из запросов не короче секунды
Перем Резервуар;

Процедура Предложить(КлючАтрибутов, Значение, АтрибутыИзмерения = Неопределено,
        АтрибутыСерии = Неопределено, КонтекстСпана = Неопределено,
        Timestamp = Неопределено, Контекст = Неопределено) Экспорт
    Если Значение >= 1 Тогда
        Резервуар.Предложить(КлючАтрибутов, Значение, АтрибутыИзмерения, АтрибутыСерии,
            КонтекстСпана, Timestamp, Контекст);
    КонецЕсли;
КонецПроцедуры

Функция Собрать(КлючАтрибутов) Экспорт
    Возврат Резервуар.Собрать(КлючАтрибутов);
КонецФункции

Процедура ПриСозданииОбъекта()
    Резервуар = Новый ОтелРезервуарЭкземпляров(3);
КонецПроцедуры

См. также ​