ОтелРезервуарЭкземпляров
Простой резервуар exemplars фиксированного размера (SimpleFixedSizeExemplarReservoir); его методы — контракт своего резервуара.
Описание
Резервуар exemplars (ExemplarReservoir) отбирает exemplars одной серии потока метрики. SDK предлагает резервуару измерения серии методом Предложить, а при сборе забирает отобранные exemplars методом Собрать и добавляет их в точку данных серии — в ключ exemplars (формат — в ОтелДанныеМетрики).
ОтелРезервуарЭкземпляров — встроенный простой резервуар. Его создаёт ОтелФабрикаПростыхРезервуаров: SDK использует эту фабрику по умолчанию для большинства агрегаций (см. Резервуар по умолчанию), а приложение передаёт её в представление, чтобы задать размер. Как резервуар отбирает измерения, на какой интервал создаётся и что попадает в exemplar, описано на странице фабрики. Напрямую резервуар создают, например, в своём резервуаре, чтобы поручить встроенному отбор и формирование exemplars (см. Пример).
Резервуар хранит exemplars и счётчик измерений отдельно для каждого ключа серии (КлючАтрибутов), поэтому один объект может обслуживать несколько серий; резервуар, созданный фабрикой, получает измерения одной серии. Методы безопасны для одновременного вызова из нескольких потоков (спецификация Metrics SDK, раздел Concurrency requirements): данные резервуара защищены блокировкой.
Свой резервуар подключают так же — фабрикой в представлении. Что для этого нужно реализовать, описано в разделе Свой резервуар.
Другие резервуары SDK
Те же методы реализуют резервуары двух других фабрик SDK:
ОтелВыровненныйРезервуарГистограммы— его создаёт ОтелФабрикаВыровненныхРезервуаровГистограммы, конструктор —Новый ОтелВыровненныйРезервуарГистограммы(Границы)с обязательным массивом границ бакетов. Хранит не больше одного exemplar на бакет гистограммы:МаксРазмер()возвращает число границ плюс один,Предложитькладёт exemplar в слот бакета, в который попадает значение,Собратьвозвращает заполненные слоты в порядке бакетов. Ключи exemplar те же; как резервуар отбирает измерения внутри бакета — на странице фабрики.ОтелНоопРезервуарЭкземпляров— его создаёт ОтелФабрикаNoopРезервуаров, конструктор без параметров.ПредложитьиОчиститьничего не делают,Собратьвозвращает пустой массив. МетодаМаксРазмеру него нет.
Конструктор
Новый ОтелРезервуарЭкземпляров(МаксРазмер)| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
МаксРазмер | Число | 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:
// Резервуар exemplars одной серии: хранит измерение с наибольшим значением.
// SDK вызывает методы одного резервуара по очереди, блокировка не нужна.
// Число, Неопределено - наибольшее предложенное значение
Перем Максимум;
// Соответствие, Неопределено - exemplar этого измерения
Перем Экземпляр;
Процедура Предложить(КлючАтрибутов, Значение, АтрибутыИзмерения = Неопределено,
АтрибутыСерии = Неопределено, КонтекстСпана = Неопределено,
Timestamp = Неопределено, Контекст = Неопределено) Экспорт
Если Максимум <> Неопределено И Значение <= Максимум Тогда
Возврат;
КонецЕсли;
Максимум = Значение;
Экземпляр = Новый Соответствие();
Экземпляр.Вставить("timeUnixNano", ОтелУтилиты.ТекущееВремяВНаносекундах());
Экземпляр.Вставить("asDouble", Значение);
Если КонтекстСпана <> Неопределено Тогда
Экземпляр.Вставить("traceId", КонтекстСпана.ИдТрассировки());
Экземпляр.Вставить("spanId", КонтекстСпана.ИдСпана());
КонецЕсли;
// Атрибуты измерения, которых нет в атрибутах серии
Отфильтрованные = Новый ОтелАтрибуты();
Если АтрибутыИзмерения <> Неопределено Тогда
Для Каждого КлючИЗначение Из АтрибутыИзмерения.ВСоответствие() Цикл
Если АтрибутыСерии = Неопределено
ИЛИ АтрибутыСерии.Получить(КлючИЗначение.Ключ) = Неопределено Тогда
Отфильтрованные.Установить(КлючИЗначение.Ключ, КлючИЗначение.Значение);
КонецЕсли;
КонецЦикла;
КонецЕсли;
Если Отфильтрованные.Количество() > 0 Тогда
Экземпляр.Вставить("filteredAttributes", Отфильтрованные.ВМассивOtlp());
КонецЕсли;
КонецПроцедуры
Функция Собрать(КлючАтрибутов) Экспорт
Результат = Новый Массив();
Если Экземпляр <> Неопределено Тогда
Результат.Добавить(Экземпляр);
КонецЕсли;
Возврат Результат;
КонецФункцииФабрика — класс в файле ФабрикаРезервуаровМаксимума.os:
// Фабрика резервуаров exemplars: новый резервуар на каждый вызов.
Функция СоздатьРезервуар() Экспорт
Возврат Новый РезервуарМаксимума();
КонецФункцииПодключение к гистограмме длительности запросов:
#Использовать 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; фабрика к нему такая же, как ФабрикаРезервуаровМаксимума, но возвращает Новый РезервуарМедленных():
// ОтелРезервуарЭкземпляров - до трех exemplars из запросов не короче секунды
Перем Резервуар;
Процедура Предложить(КлючАтрибутов, Значение, АтрибутыИзмерения = Неопределено,
АтрибутыСерии = Неопределено, КонтекстСпана = Неопределено,
Timestamp = Неопределено, Контекст = Неопределено) Экспорт
Если Значение >= 1 Тогда
Резервуар.Предложить(КлючАтрибутов, Значение, АтрибутыИзмерения, АтрибутыСерии,
КонтекстСпана, Timestamp, Контекст);
КонецЕсли;
КонецПроцедуры
Функция Собрать(КлючАтрибутов) Экспорт
Возврат Резервуар.Собрать(КлючАтрибутов);
КонецФункции
Процедура ПриСозданииОбъекта()
Резервуар = Новый ОтелРезервуарЭкземпляров(3);
КонецПроцедуры