Нагрузочное тестирование c Yandex.Tank и JMeter · GitHub

Нагрузочное тестирование c Yandex.Tank и JMeter · GitHub World of Tanks
Содержание
  1. Domain.tree — описание домена
  2. Основные правила yaml, которые вы всегда должны помнить
  3. Единый ast чтобы править всеми
  4. Языки основанные на форматах
  5. Что такое YAML
  6. Пробелы или отступы
  7. Для чего создан YAML
  8. Преимущества YAML и примеры использования
  9. Скаляр (пара «ключ-значение»)
  10. Коллекции и списки
  11. Отличия YAML от JSON и XML
  12. Вложенные коллекции
  13. Синтаксис YAML
  14. Словари
  15. Access.log.tree — структурированные логи
  16. Ast tree
  17. Data preparation
  18. Jack.tree — lisp без скобочек
  19. Jmeter
  20. Json ast
  21. Monitoring (telegraf)
  22. Prerequisites
  23. Sql.tree — запросы к субд
  24. Tree как протокол общения
  25. Uploader (overload)
  26. Wasm.tree — ассемблер без мишуры
  27. Yaml — инструменты — дока
  28. Вложение tree-узлов
  29. Возможность явного указания типов
  30. Глубокая tree-иерархия
  31. Запуск
  32. Конфигурация
  33. Координаты ошибки
  34. Кратко
  35. Куда пойти, куда податься
  36. Минификация
  37. Минификация tree
  38. Многострочные данные
  39. Модели данных
  40. Модель json
  41. Модель toml
  42. Модель tree
  43. Модель xml
  44. Модель yaml
  45. Недостатки модели json
  46. Недостатки модели xml
  47. Нерасширяемость модели json
  48. Несколько документов в одном файле
  49. Нестрогость yaml
  50. Обработка tree
  51. Один дома
  52. Поддержка редакторами
  53. Поддержка языками
  54. Поточная обработка
  55. Пример json
  56. Пример toml
  57. Пример tree
  58. Пример xml
  59. Пример yaml
  60. Производные tree узлы
  61. Просто tree-узел
  62. Разные типы узлов
  63. Расширяемость модели
  64. Расширяемость модели xml
  65. Расширяемость модели yaml
  66. Свойства узла tree
  67. Священные войны
  68. Скорость обработки
  69. Словари или ассоциативные массивы
  70. Сообщения об ошибках в tree
  71. Списки или массивы
  72. Список tree-узлов
  73. Ссылки
  74. Строгость
  75. Строки необязательно заключать в кавычки
  76. Сырые данные
  77. Удобочитаемость
  78. Удобочитаемость json
  79. Удобочитаемость xml
  80. Упраздняя llvm
  81. Установка
  82. Формат tree
  83. Форматы
  84. Экранирование
  85. Экранирование в json
  86. Экранирование в tree
  87. Экранирование в xml
  88. Экранирование в yaml
  89. Элементы файла yaml: базовый синтаксис
  90. Язык grammar.tree
  91. Язык grammar.tree vs ebnf
  92. Язык json.tree vs json
  93. Язык view.tree vs typescript
  94. Язык xml.tree vs xml
  95. Заключение

Domain.tree — описание домена

Раз уж мы заговорили о базах данных. Примерно так я описываю модель предметной области.

hyoo_api_person
    descr Живой пользователь сервиса
    inherit hyoo_api_entity
    field
        id
            descr Уникальный человекопонятный идентификатор
            example person=jin
            key unique
            type text
            edit author
        avatar
            descr Ссылки на аватары
            type list hyoo_api_image
            edit author
        mail
            descr Привязанные имейлы
            type set hyoo_api_mail

Из такого формального описания автоматически формируется серверный API, правила ACL, схема СУБД и админка для управления всем этим делом.

Основные правила yaml, которые вы всегда должны помнить

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

  • Вкладки НЕ допускаются в YAML. Вы должны использовать пространство для отступа.
  • Хотя количество места не имеет значения, если отступ дочернего узла больше, чем отступ родительского узла, рекомендуется оставлять такое же количество пробелов.
  • Между различными элементами YAML должен быть пробел (объяснено позже).
  • YAML чувствителен к регистру.
  • YAML-файл должен заканчиваться расширениями вроде .yamlили .yml.
  • YAML поддерживает кодировку UTF-8, UTF-16 и UTF-32.

Давайте теперь разберемся с синтаксисом YAML.

Единый ast чтобы править всеми

Ну и, наконец, мы подошли ко главной мысли этого доклада. Tree — это прям идеальный кандидат на место универсального связующего AST. Вы только посмотрите, какой длинный пусть проходит TypeScript код от исходников до результирующего бандла при сборке на типичном проекте.

code =(P)=> loader =(P)=> compiler =(SP)=> bundler =(SP)=> terser =(S)=> bundle

P - Parse
S - Serialize

И каждый инструмент заново парсит ваши исходники в своё AST, обрабатывает, сериализует и передаёт далее. Если же договориться о едином формате AST, то можно значительно упростить реализацию утилит и снизить накладные расходы на обработку кода.

code =(P)=> loader =====> compiler ======> bundler ======> terser =(S)=> bundle

Даже если часть утилит будут запускаться в отдельных процессах (а значит промежуточная сериализация неизбежна), формат tree позволит передавать AST максимально быстро, за счёт минимальных накладных расходов на парсинг и сериализацию.

Языки основанные на форматах

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

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

Далее я покажу несколько примеров таких языков для формата tree.

Что такое YAML

YAML — это язык сериализации данных. Он удобен для человека и хорошо работает с различными языками программирования, такими как Perl и Python.

YAML расшифровывается как «YAML Ain’t markup language» Нагрузочное тестирование c Yandex.Tank и JMeter · GitHubНагрузочное тестирование c Yandex.Tank и JMeter · GitHubхотя ранее эта аббревиатура означала противоположное «Yet Another Markup Language». Название было изменено, чтобы показать, что это не язык разметкиНагрузочное тестирование c Yandex.Tank и JMeter · GitHubНагрузочное тестирование c Yandex.Tank и JMeter · GitHubкаковым по ошибке его многие восприняли, а язык сериализации данных. Благодаря своим возможностям сериализации этот язык является достойной заменой таким языкам, как JSON и XML. К слову, YAML v1.2 является строгим надмножеством JSON.

Пробелы или отступы

В YAML вы делаете отступ с помощью пробелов, а не табуляции. И между элементами ДОЛЖЕН быть пробел.

Правильная спецификация:

Kind: Service

Неправильная спецификация:

Kind:Service

Потому что в приведенном выше утверждении нет пробела после двоеточия!

Для чего создан YAML

YAML создан для упрощения сериализации сложных структур данных. По текущей редакции спецификации YAML v1.2.2 (от 1 октября 2021 года) официальные цели языка таковы:

  1. YAML должен легко читаться человеком.
  2. YAML должен быть переносимым между языками программирования.
  3. YAML должен отвечать встроенным структурам данных динамических языков.
  4. YAML должен применять последовательную модель для поддержки общих инструментов.
  5. YAML должен поддерживать обработку в один проход.
  6. YAML должен быть выразительным и расширяемым.
  7. YAML должен быть легким в реализации и использовании.

Преимущества YAML и примеры использования

  • Удобство восприятия и освоения человеком. Определения структур данных на YAML очень просты для понимания, пользователям легко воспринимать даже сложные структуры данных, описанных на этом языке. Поэтому он прост в изучении.
  • Независимость от языка программирования. YAML поддерживает списки (массивы) и словари (ассоциативные массивы, хэши) в форме, понятной для разных языков. Создав определение на YAML один раз, тот же файл можно использовать из файла Python, Ruby и т.д.
  • Однозначность. YAML однозначно представляет структуры сериализованных данных, поэтому нет особой необходимости в комментариях и документации. Поскольку структуры данных однозначны, для чтения и записи YAML можно с легкостью использовать сценарии автоматизации. Можно создать сценарий для чтения структуры данных из YAML-файла и ее преобразования в другой формат (JSON или XML).
  • Пригодность для контроля версий. Содержимое YAML-файла представляет собой простой текст, и такие файлы можно легко добавлять в репозитории, например, в Git или Subversion.
  • Строгость синтаксиса. Спецификация YAML подразумевает минимальную гибкость, что повышает надежность языка.
  • Скорость обработки. YAML быстро загружается и легко обрабатывается в памяти.
  • Безопасность реализации. Многие проблемы безопасности в языках программирования связаны с анализом недоверенных данных (например, JSON). Java, JavaScript, PHP, Python, Ruby и другие языки дают возможность воспользоваться этими уязвимостями, передавая парсеру строки с непредвиденным содержимым. YAML предназначен для предотвращения этих рисков. Он указывает типы данных для каждого фрагмента потока YAML.

Вот некоторые общие примеры использования YAML:

  1. файлы конфигурации (например, для Dancer, Dar, docker-compose, Google App Engine, Ruby on Rails и Symfony);
  2. совместное использование данных программами на различных языках;
  3. файлы журналов;
  4. отладка сложных структур данных;
  5. обмен сообщениями между процессами;
  6. хранение объектов.

Скаляр (пара «ключ-значение»)

Скаляры — это строки и числа, из которых состоят данные на странице. Проще говоря, это пары ключ-значение.

kind: Service
metadata:  
  name: web-app-svc

Коллекции и списки

Элементы или элементы списка и коллекции — это строки, которые начинаются на одном уровне отступа, начиная с тире, за которым следует пробел.

- web-app-prod 
- prod-deployments 
- prom-monitored

Это базовый список, в котором каждый элемент находится в отдельной строке с открывающим тире.

Отличия YAML от JSON и XML

Для наглядности приведем примеры описания одной и той же структуры данных на XML, JSON и YAML.

XML

  1. XML является языком разметки.
  2. В нем используются теги, что снижает удобочитаемость по сравнению с остальными двумя форматами.
  3. Схемы позволяют проводить проверки и создавать пользовательские типы.
  4. Поддержка пространств имен дают возможность избежать конфликтов имен.
  5. Этот формат очень подходит для работы с крупными файлами данных со сложной схемой.

JSON

{
    "gas_giants":
    [
        {
            "name": "Jupiter",
            "mass": 1.8982E 27,
            "mean_radius": 69.911
        },
 
        {
            "name": "Saturn",
            "mass": 5.6834E 26,
            "mean_radius": 58.232
        }
    ]
}

Вложенные коллекции

Если вы хотите создать вложенную последовательность с элементами и подпунктами, вы можете сделать это, поместив один пробел перед каждым тире в подпунктах.

- 
 - web-app-prod 
 - prod-deployments 
 - prom-monitored
-  
 - web-app-test 
 - staging-deployments 
 - not-monitored

Синтаксис YAML

YAML поддерживает кодировки UTF-8 и UTF-16. Поэтому ключи и значения могут содержать не только латиницу.

В YAML учитывается регистр символов.

Для форматирования структур в YAML используются отступы. Они состоят из пробелов, а табуляция не используется.

Словари

Словари представляют собой формат key: value с отступом содержимого.

ports:    
- port: 8080         #Сервисный порт
  targetPort: 8080   #Порт Pod
  nodePort: 30012  #Порт узла из диапазона - 30000-32767

Вы можете объединять и смешивать коллекции списков и словарей следующим образом:

ports:    
- port: 8080         #порт service
  targetPort: 8080   #порт Pod
  nodePort:       
  - 30012       
  - 30013       
  - 30014

Это очень простые концепции YAML, но они необходимы инженеру DevOps.

Для YAML не нужен специальный редактор. Ваш любимый текстовый редактор уже должен поддерживать YAML или при необходимости использовать плагин.

Есть много вещей, которые вы можете изучить глубже. Для этого вы всегда можете обратиться к официальной документации YAML.

Access.log.tree — структурированные логи

А что если логи сразу выводить в двумерном виде, одновременно легко читаемом как машинами, так и человеком?

Ast tree

Теперь давайте возьмём несколько более сложный tree файл.

Data preparation

Write your dataset in data section in your dataset file. There are two styles to write your dataset, numbered dataset or not numbered dataset. Please remember that you should only use one style.

Jack.tree — lisp без скобочек

На самом деле писать на сыром ассемблере слишком многословно. Поэтому следующим шагом идёт реализация мета-языка, позволяющего расширять язык средствами самого этого же языка. Ядро такого языка должно получиться предельно минималистичным, а все идиомы в него будут подключаться как сторонние библиотеки, написанные на этом же языке.

jack
    import wasm
    tree func $fact
        > $x #8
        < #8 switch
            test is-zero $x
            then #8 1
            else mul
                $x
                $fact sub
                    $x
                    #8 1

Грубо говоря, программа на этом языке итеративно модифицирует свой собственный AST таким образом, что на выходе получается wasm-бинарник. Звучит, возможно, пугающе, но благодаря тому, что tree сохраняет координаты исходников, проследить источник ошибки не составляет труда. В репозитории можно глянуть куцый прототип.

Про WoT:  Гайд по советскому тяжелому танку 6 уровня Т-150 World of Tanks . Разбор стратегий, зон пробития и тактик на картах.

$mol_jack

Jmeter

JMeter – альтернативный генератор нагрузки для танка.

Он более сложен в настройке, чем дефолтный генератор – phantom, но в тоже время более гибок. Мы будем использовать его для нетривиальных сценариев запросов – например, для эмуляции пользовательских сессий, когда запросы от одного пользователя должны выполнятся последовательно.

Json ast

Возьмём простой JSON файл и засунем его в ASTExplorer.

Monitoring (telegraf)

Для настроки Телеграфа нужно настроить ssh-соединение с целью. Для этого публичный ключ танка нужно добавить на целевой сервис.

Далее нужно сконфигурировать секцию в основном конфиге load.yaml.

Как видно, для мониторинга нужен отдельный конфиг – monitoring.xml. В простейшем виде он выглядит так.

Prerequisites

This program runs on Python 2 or Python 3 and needs some requirements written in requirements.txt. Install it using the command below (after you install pip on your system):

Sql.tree — запросы к субд

Помните неуклюжие запросы к MongoDB? Давайте попробуем написать свой SQL:

Tree как протокол общения

Можно пойти дальше и не просто писать логи в формате tree, а в принципе продвинуть идею, что вывод любой программы должен быть структурированным. У многих утилит есть флаги для вывода ответа в виде JSON или XML, но человеку читать такой вывод напряжно — приходится переоткрывать выдачу в инструментах наглядного представления, чтобы понять что там возвращается и как этому подступиться.

Uploader (overload)

Для начала нужно получить токен.

Для этого нужно авторизоваться на сайте, кликнуть на аватарку и скопировать токен. Затем нужно создать файл token.txt и положить туда токен.

Простейший конфиг выглядит так.

Wasm.tree — ассемблер без мишуры

Я сейчас работаю над компилятором в байт коды более наглядного wasm.tree описания.

func
    name $fact
    param $x i64
    result i64
    body switch
        test i64.eqz local.get $x
        then i64.const 1
        else i64.mul
            local.get $x
            call $fact i64.sub
                local.get $x
                64.const 1

Из этого ассемблера генерится список байт-кодов на языке bin.tree, который уже элементарной функцией перегоняется в бинарник.

00
61
73
6d
01
00
00
00
.
.
.

Когда будет что-то более-менее завершённое — попробую протолкнуть этот синтаксис в качестве WAT2.0. Кому не безразлична судьба WebAssembly — присоединяйтесь к разработке.

Yaml — инструменты — дока

YAML (YAML Ain’t Markup Language или Yet Another Markup Language) — человекопонятный язык для хранения структурированной информации. Используется для написания конфигов, чтобы стартовать проект, проверять тесты и для линтеров.
Расширение файла может быть .yaml или .yml — это всё YAML.

Разберём на примере как будет выглядеть вариант конфигурации питомца на YAML. Определим питомцу:

  • Имя, которое впоследствии лучше заменить на реальное имя любимца.
  • Категории его пушистости и возможные звуки которые он издаёт.
  • Количество прекрасных лапок.
  • Время, которое питомец тратит на обед.
bestCatConfiguration:  # TODO: Вписать имя своего питомца  name: "insertYourCatName"  # Тут пример комментария  isFluffy: true  sounds:    - "mur"    - "pur"    - "miau"  paws: 4  lunchTime: 9.99bestCatConfiguration:name:"insertYourCatName"isFluffy:truesounds:-"mur"-"pur"-"miau"paws:4lunchTime:9.99

В качестве отступов используются пробелы. Табы не допускаются, потому что иерархия в файле обозначается двойным пробелом. Поэтому лучше настроить себе линтер на проверку и правку. Пара похожа на JSON и работает по принципу ключ-значение. Определяем ключ и разделяем его со значением символом :. После двоеточия следует значение.

В коде можно использовать комментарии. Их определяем через символ # и после него пишем текст комментария.

Можно использовать с кавычками и без, в одно и несколько слов. Все в вашей власти! Ниже примеры:

Без лишних кавычек:

name: insertYourCatNamename: insertYourCatName

Вариант с несколькими словами:

name: insert Your Cat Namename: insert Your Cat Name

С использованием двойных или одинарных кавычек (в данном случае двойные):

name: "insertYourCatName"name:"insertYourCatName"

Можно использовать целые числа значения или с плавающей точкой. Для дробных чисел можно использовать только точку для разделения, потому что при использовании запятой YAML посчитает значение как строку. Например:

Целое число:

paws: 4paws:4

Число с плавающей точкой:

# кот всегда говорит что ему не доложили порциюlunchTime: 2.99lunchTime:2.99

Массивы и списки используют для перечисления данных.
В YAML описывать списки можно двумя способами:

Как массив:

sounds: ["mur", "pur", "miau"]sounds:["mur","pur","miau"]

Как список:

sounds:  - "mur"  - "pur"  - "miau"sounds:-"mur"-"pur"-"miau"

Якорь — это приспособление для создания переменных, на которые можно ссылаться. Якорь позволяет вынести повторяющиеся части конфига и использовать ссылку на них в других местах. Так мы можем уменьшить дублирование кода.

Чтобы обозначить якорь используем символ &. Чтобы задать значение ссылочной переменной, нужно использовать символ *. Символы << показывают что мы просто вставим элементы без привязки к значению ключа.

Зададим базовую конфигурацию питомца с крутым котячьим именем и прекрасным возрастом в два года. Далее прописываем код <<: *base в конфигурации. Это означает, что мы заполняем поля базовыми значениями. Обращаю внимание на то, что если у нас уже определено поле, то базовая конфигурация туда не пропишется. Следовательно, у второго кота будет его гордый возраст в семь лет.

base: &base  name: cool cat name  age: 2cat1: &cat1# Добавляем все значения из base:  <<: *basecat2: &cat2  <<: *base  # «Перезаписываем» значение для поля `age`:  age: 7base:&basename: cool cat name
  age:2cat1:&cat1<<:*basecat2:&cat2<<:*baseage:7

Во что превратится наш код:

cat1:  name: cool cat name  age: 2cat2:  name: cool cat name  age: 7cat1:name: cool cat name
  age:2cat2:name: cool cat name
  age:7

Преимущества YAML в том, что мы можем использовать комментарии. В JSON они нам не доступны.
Корневым узлом в JSON может быть только объект или массив. YAML даёт полную свободу и разрешает использовать любой из допустимых типов данных как корневой узел.

Например, JSON:

{  "demo": "demo text"}{"demo":"demo text"}

А вот YAML с той же информацией:

demo: "demo text"demo:"demo text"

Иерархия в файле YAML обозначается двойными пробелами, а в JSON объекты и массивы обозначаются фигурными и квадратными скобками соответственно.

Приятной особенностью YAML является возможность не использовать кавычки для строк. Это не обозначает, что они не поддерживаются. Всё, как и указано выше — строки можно обозначать и кавычками, и без. У JSON политика жёстче — строки должны быть в двойных кавычках.

Один из способов использования — это настройка github actions. Это очень полезная штука, которая позволяет проверять пул-реквесты по заданным в YAML-конфигах правилам.
Если в правилах описать запуск линтера и тестов в ответ на событие в репозитории (например, pull-request), то они будут запускаться каждый раз при появлении этого события. Так мы можем оградить главную ветку репозитория от сломанного кода.
Базовый список для минимального action это имя, в каком случае он будет выполняться (на какое событие), где запускать (операционная система) и шаги которые необходимо выполнить.

name: GitHub Actions Exampleon: [pull_request]jobs:  Explore-GitHub-Actions:    runs-on: ubuntu-latest    steps:      - run: echo "Ура! Я запустился на ${{ github.event_name }}."      - run: echo "Я работаю на ОС - ${{ runner.os }}"name: GitHub Actions Example
on:[pull_request]jobs:Explore-GitHub-Actions:runs-on: ubuntu-latest
    steps:-run: echo "Ура! Я запустился на ${{ github.event_name }}."
      -run: echo "Я работаю на ОС - ${{ runner.os }}"

В экшенах можно пользоваться переменными, например, операционная система ${{ runner.os }} описана выше одноимённым ключом runs-on и мы получим значение Linux. В случае ${{ github.event_name }} выведет значение ключа onpull_request.

Вложение tree-узлов

Но что если мы хотим добавить иерархичности и поместить список узлов внутрь первого?

house
    roof
    wall
    door
    window
    floor

Просто пишем вложенные узлы с табом в качестве отступа. Знакомые с языком Python тут могут заметить похожий подход — использование хорошего стиля форматирования кода в качестве основы синтаксиса, а не опциональной возможности.

Возможность явного указания типов

Перед значением можно явно указать его тип, например, чтобы трактовать дату как строку.

Глубокая tree-иерархия

Продолжая добавлять отступы мы можем создавать иерархии любой вложенности.

house
    roof
    wall
        door
        window
            glass
    floor

Запуск

Достаточно выполнить команду

yandex-tank -c config.yaml

Конфигурация

Основной файл конфигурации – load.yaml.

Документация иногда продолжает ссылать на старый формат – ini, но верить ей не стоит – парсер ini-конфигов сломан, и ряд опций в этом формате задать просто не получится.

Если нужный вам параметр представлен в только в ini-формате, узнать его название в формате yaml можно из исходников. Например, вот пример описания схемы секции плагина JMeter.

Пример простейшего конфига.

Здесь следует обратить внимание на следующие поля:

Пример патронов

Координаты ошибки

Во время парсинга часто теряется информация об исходном расположении получаемых из формата узлов. Например, мы получили JSON, начали его обработку, и где-то в глубине вдруг поняли, что в базе данных у нас нет того пользователя, что указан в файле. В этот момент мы должны показать ошибку, но в тексте этой ошибки мы не можем указать в каком месте какого именно файла она допущена. Всё потому, что эта информация при парсинге теряется. И это весьма распространённая проблема.

В XML-узлах есть ссылка на ресурс из которого он получен, но где он в этом ресурсе — ищите глазами. Для решения этой проблемы есть специальные парсеры, которые дают на выходе не массивы и словари, а Абстрактное Синтаксическое Дерево. Но работать с ним уже не так-то просто, да ещё и медленно это дело.

Чтож, информация эта важная, и я предлагаю её не терять. Никогда не терять. Сохранение координат узлов нам ещё пригодится, когда речь пойдёт об AST и сорсмапах.

Кратко

Для тестирования поведения сервиса под нагрузкой используется утилита Yandex Tank.

Куда пойти, куда податься

Надеюсь мне удалось заразить вас идеями о светлом будущем. Но чтобы его приблизить нам вместе надо над этим поработать. Один я, боюсь, всё это не вытяну. Так что пишите, зовите и не пропадайте.

Про WoT:  Инвайт коды и ссылки для World of Tanks 2020 (Декабрь) – Рабочие, Многоразовые

Минификация

Многие форматы поддерживают различные способы форматирования одних и тех же данных. Но это всегда компромисс между размером и удобочитаемостью.

Минификация tree

Нет 

Наш путь бескомпромиссный — формат должен быть одновременно и предельно компактным, и легко воспринимаемым человеком.

Многострочные данные

Но как записать всё же многострочный текст содержащий в том числе и символы перевода строки? Всё просто: берём узел данных и помещаем в него список других узлов данных.


    Тут ‍
        много ‍
             котиков ‍

При запросе строкового содержимого корневого узла данных все вложенные узлы данных будут соединены через символ перевода строки.

Модели данных

Разные форматы основаны на разных моделях данных. Выбранная модель отвечает на следующие два вопроса.

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

Модель json

Модель JSON основана на том, что всё дерево состоит из не типизированных списков и словарей. Плюс ограниченный набор примитивов в качестве листьев дерева.

Модель toml

Модель TOML как у JSON, но чуть более приземлённая. Например, тут различаются целые и вещественные числа, что важно для компилируемых языков, а так же есть поддержка времени.

С расширяемостью же тут всё так же плохо, как и в JSON.

Модель tree

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

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

Модель xml

XML основан на модели типизированных элементов в которых находится один словарь атрибутов и один список вложенных типизированных узлов.

Модель yaml

Модель YAML во многом аналогична модели JSON. Разве что тут есть поддержка времени и внутренних ссылок.

Недостатки модели json

Наивно было бы полагать, что двух типов структурных узлов хватит для всего. Например, возьмём словарь. Ключи в нём не упорядочены, то есть могут возвращаться парсером в любом порядке.

{
    "foo": 777,
    "bar": 666
}

А если нам нужен словарь с упорядоченными ключами?

[
    [ "foo" , 777 ],
    [ "bar" , 666 ]
]

Нам пришлось кардинально поменять синтаксис и налепить массивы массивов. А ведь это всего-лишь другой тип словаря.

Недостатки модели xml

Это довольно гибкая модель, однако она имеет ряд ограничений: значениями атрибутов могут быть только строки, а вложенный список узлов может быть только один. Не смотря на то, что формат XML и так не самый простой, банальный словарь с поддеревьями в качестве значений требует дополнительных соглашений.

Тут panel — это компонент, а body — уже не компонент, а параметр. Ему бы место в атрибутах, да только в атрибуты можно лишь строки помещать и ничего более.

Нерасширяемость модели json

Ну и самый главный недостаток модели JSON в её нерасширяемости, из-за чего приходится вводить кучу хитрых правил, чтобы упихнуть всё многообразие прикладных типов их отношений. Возьмём для примера запрос к MongoDB, авторы которой решили, что JSON отлично походит на роль языка запросов.

{
    "$or": [
        {
            "sex": "female",
            "age": { "$gt": 16 },
        },
        {
            "hobby": {
                "$regex": "\b(?:java|type)script\b"
            }
        }
    ]
}

Мы видим, что парные логические операции OR и AND имеют совершенно различный синтаксис. Предиката равенства катастрофически не хватает, ведь нужны ещё предикаты «больше», «меньше» и даже «соответствует регулярному выражению». И, кстати, сами регулярные выражения не представимы в JSON иначе как в виде строки и соглашения, что если она находится в словаре для ключа с именем «$regexp», то это сериализованная регулярка и при парсинге нужно создать соответствующий объект.

Несколько документов в одном файле

Один файл YAML (*.yaml, *.yml) может содержать несколько документов YAML. Каждый документ начинается с трех дефисов.

Нестрогость yaml

a: true # boolean
b: tru  # string
c: (-:  # error
d: :-)  # string

Вот таких приколов в YAML довольно много.

Обработка tree

Или другой пример — мы решили, что «auth» неудачное название и нужно заменить его на «credentials». Поэтому мы пишем не сложный скрипт для автоматического рефакторинга:

//  server credentials
//      login root
//      password qwerty
const new_config = config.list(
    input.hack({

        'auth' : ( tree , context )=> [
            tree.struct( 'credentials' , tree.hack( context ) ),
        ] ,

        '' : ( tree , context )=> [
            tree.clone( tree.hack( context ) ),
        ] ,

    })
)
fs.writeFileSync( config_path , new_config )

И таким образом вы можете легко рефакторить любые языки, основанные на tree формате, без поиска для каждого языка отдельного парсера и разбирательства с тем, как у него происходит работа с AST.

Один дома

Часто бывают ситуации, когда вложенный узел только один и тогда увеличивать из-за него уровень отступа для всех вложенных в него узлов будет как-то расточительно.

street
    house
        wall
            door
            window

Поэтому просто выстраиваем такие узлы в одну линию, разделяя пробелами.

street house wall
    window
    door

Узлы же записанные с отступом уже вкладываются в последний узел на предыдущей линии.

Поддержка редакторами

Если вы пользуетесь редактором для которого ещё нет плагина, то это хорошая возможность реализовать его. Сделать это будет проще, чем для любого другого языка.

Поддержка языками

Опять же, призываю заинтересовавшихся реализовать поддержку в своём любимом языке и попробовать применить его с пользой.

Поточная обработка

Бывает, что данных много, а памяти мало, но работать с данными нужно быстро. А бывает, что данные вообще не кончаются. Например, вам нужно непрерывно обрабатывать логи по мере их поступления. В этих случаях спасают возможности по поточной обработке данных.

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

Это не значит, что к ним нельзя прикрутить поточную обработку. Например, для XML есть более низкоуровневые SAX парсеры, позволяющие вам работать не с деревом элементов, а с потоком тегов: открылся такой-то тег, пришла строка, закрылся такой-то тег. А для JSON есть целая вязанка протоколов стриминга сообщений.

Форматы же поддерживающие поточную обработку, можно легко дополнять дописыванием данных в конец. Можно склеивать несколько потоков данных в один и, наоборот, нарезать на части. Можно обрабатывать по частям, не дожидаясь завершения передачи. И всё это без потери корректности работы с форматом.

Пример json

На смену XML приходит более простой и дерзкий формат данных — JSON.

Пример toml

Про TOML же мало кто слышал. Однако, взгляните на пример и вам станет ясно, почему я о нём вообще упоминаю.

[servers]

[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"

[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"

Да, это фактически, стандартизированный INI-конфиг, которого покусал JSON. В результате чего он вобрал в себя худшее из обоих миров.

Пример tree

Наконец, в качестве спойлера, позвольте показать вам минимальный не пустой файл в формате Tree, который мы и будем разрабатывать далее.

spoiler

Пример xml

XML — некогда самый популярный формат, можно сказать «технологический стандарт». Но не смотря на всю свою мощь, сейчас он изживает своё, так как является слишком сложным для современного веб-разработчика.

Пример yaml

Кто-то уже пророчит YAML на смену JSON.

Производные tree узлы

У каждого узла есть методы для создания новых узлов, на его основе. Эти фабрики, создавая новые узлы, просовывает в них span от оригинального узла. Это позволяет даже после десятков преобразований понять с чего всё началось.

interface $mol_tree2 {
    struct : ( type , kids )=> $mol_tree2
    data : ( value , kids )=> $mol_tree2
    list : ( kids )=> $mol_tree2
    clone : ( kids )=> $mol_tree2
}

Просто tree-узел

Итак, нам нужно создать узел с именем «house». Какой минимальный код для этого нужен?

house

Просто пишем это имя и всё.

Разные типы узлов

Наконец, оба типа узлов мы можем использовать вперемешку в любых комбинациях. Например, опишем некоторого пользователя.

Расширяемость модели

Итого, по расширяемости всё очень плохо. Популярные форматы либо расширяемые, но неимоверно переусложнённые, либо простые, но совершенно не расширяемые.

Обратите внимание на YAML. Его грамматика насчитывает две сотни паттернов. Он настолько сложен, что вы скорее всего не найдёте ни одной полной и правильной реализации его парсера. Да что уж там, даже два одинаково работающих парсера JSON нужно ещё поискать, а ведь там всего казалось бы 30 паттернов.

Нашей целью же будет создание предельно простого, не допускающего разночтений, но в то же время максимально расширяемого формата.

Расширяемость модели xml

Благодаря пространствам имён, в рамках одного XML документа могут вперемешку идти множество языков, не ломая интерпретацию друг друга.

Расширяемость модели yaml

Главное преимущество YAML в аннотациях типа, позволяющих объяснять процессору какой алгоритм использовать для распаковки данных.

--- !!omap
- foo: 777
- bar: 666

В данном примере мы говорим парсеру «возьми этот список пар ключ-значение» и преобразуй его в объект OrderedMap (упорядоченный словарь).

Свойства узла tree

В реализация на TypeScript каждый узел имеет примерно следующий интерфейс.

interface $mol_tree2 {
    type: string
    value: string
    kids: $mol_tree2[]
    span: $mol_span
}

Span — это ссылка на серию байт в исходном ресурсе.

interface $mol_span {
    uri: string
    row: number
    col: number
    length: number
}

Священные войны

Частая проблема при работе с различными форматами — это бесконечные споры о, казалось бы, мелочах.

Эти споры отнимают время и эмоции, но они совершенно бессмысленны. Лучше, если формат будет иметь единые, чётко заданные правила, которые одинаково понимаются любым инструментом и человеком. Поэтому наш формат будет предельно жёстким, без каких-либо вольностей.

Скорость обработки

Простота, жёсткость и отсутствие экранирования потенциально даёт куда большую возможную скорость обработки.

Например, в JSON, чтобы записать произвольную строку, нужно пройтись по каждому символу и перед определёнными вывести в выходной буфер обратную косую черту. То есть мы даже не можем заранее узнать сколько памяти нам на выделить для выходного буфера. А во время парсинга нужно делать обратную операцию с формированием новой строки. Мы не можем переиспользовать исходный кусок памяти.

serialization:    foobar    =>  "foo\bar"

parsing:         "foo\bar"  =>   foobar

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

Про WoT:  Шкурки в игре World of Tanks: что это такое, описание

Словари или ассоциативные массивы

Словари представлены в виде пар ключей и значений. Значение отделяется от ключа двоеточием и пробелом. Пробел здесь обязателен. В формате JSON словари заключаются в фигурные скобки.

---
# Словарь: блочный формат
Газовые гиганты:
  ледяные:
  - Уран
  -  Нептун
  не ледяные:
  - Юпитер
  - Сатурн
 
  # Словарь: в одну строку
Газовые гиганты: {ледяные: [Уран, Нептун], не ледяные: [Юпитер, Сатурн]}

Несколько документов в одном файле

Документы в одном файле разделяются тремя дефисами.

---
планета:
  название: Земля
  спутники: [Луна]
---
планета:
  название: Марс
  спутники: [Фобос, Деймос]

Чтобы обозначить конец файла без начала нового, используются три точки.

Строки

Строки в YAML не обязательно заключать в кавычки. Строки без кавычек могут содержать кавычки.

Одинарные кавычки используются, когда не требуется экранировать символы. Если в такую строку нужно вставить одинарную кавычку, она экранируется также одинарной кавычкой.

Строки в двойных кавычках могут содержать экранированные символы в стиле языка C.

Это показано в приведенном ниже примере.

Сообщения об ошибках в tree

Для примера, возьмём конфиг, найдём в нём пароль и если он не подойдёт — кинем исключение, где будет написано в каком месте какого файла написан неправильный пароль.

const config_path = './config.tree'
const config_text = fs.readFileSync( config_path )
const config = $mol_tree2.fromString( config_text , config_path )
//  server auth
//      login root
//      password qwerty

const password = config.select( 'server' , 'auth' , 'password' , '' )

if( !auth( password.text() ) ) {
    // AuthError: Wrong password
    // default
    // ./config.tree#5:3-11
    throw password.error( 'Wrong password' , AuthError )
}

Списки или массивы

Блочный формат

--- # Список: блочный формат
- Юпитер
- Сатурн
- Уран
- Нептун

В блочном формате элементы списка начинаются с дефиса и пробела.

Встроенный формат

Во встроенном формате элементы списка разделяются запятыми, за которыми следуют обязательные пробелы. Список заключается в квадратные скобки, как в JSON.

--- # Список: в одну строку
  [Юпитер, Сатурн, Уран, Нептун]

Список tree-узлов

А если нам нужен не один узел, а целый список?

house
roof
wall
door
window
floor

Просто пишем их на отдельных строках.

Ссылки

Ссылки позволяют избежать повторения фрагментов конфигурации.

Строгость

Как правило с пониманием написанного нет никаких проблем. Но YAML тут отличился.

Строки необязательно заключать в кавычки

В YAML поддерживаются строки без кавычек и с одинарными или двойными кавычками. Даже если ключ словаря состоит из нескольких слов, его не обязательно заключать в кавычки.

Сырые данные

Когда же нам нужно записать произвольные данные, символы в которых не должны как-то по особому обрабатываться, просто пишем их после обратной косой черты без какого-либо экранирования.

Любые данные (^_^)/

Обратная косая черта выбрана для ассоциации с экранированием. Она как бы экранирует весь текст до конца строки. Но, если быть точными, то это скорее не экранирование, а своеобразные кавычки. Обратная косая черта — открывающая, а символ перевода строки — завершающая.

Удобочитаемость

Наглядность синтаксиса важна при самых различных сценариях работы с форматом: при написании, при ревью кода, при разрешении конфликтов, при отладке, при изучении.

Скорость вашей работы и предсказуемость её результатов напрямую зависит от способа сериализации формата. Однако у некоторых форматов с этим серьёзные проблемы.

Удобочитаемость json

XML хотя бы поддерживает многострочный текст, а вот JSON, например, этим похвастаться уже не может. Форматы этого типа идут от информационной структуры, в которую уже вкрапляются текстовые и не только текстовые значения.

{ "greetings": "Привет, Алиса!nКак дела?nНе могла бы ты принести мне кофе?n" }

Удобочитаемость xml

XML построен вокруг текста, внутрь которого вкрапляются теги с дополнительной информацией. Пока этой информации не много, всё хорошо, но чем её больше, тем сложнее воспринимать текст, что нивелирует полезность этой фичи.

Упраздняя llvm

Можно пойти ещё дальше и генерировать не wasm байткоды, а прямо таки байткоды целевого процессора, просто добавив ещё один трансформер в пайплайн.

compile pipelines:

                jack.tree => wasm.tree =============> bin.tree
                jack.tree => wasm.tree => arm.tree => bin.tree
any-dsl.tree => jack.tree => wasm.tree => arm.tree => bin.tree

При этом на любом уровне можно запускать дополнительные трансформеры, которые смогут оптимизировать код, применяя информацию, доступную на соответствующих уровнях абстракции.

optimization midlewares:

jack.tree => jack.tree
wasm.tree => wasm.tree
arm.tree => arm.tree

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

Опять же, присоединяйтесь к разработке, это может получиться крутая штука на замену LLVM.

Установка

В контейнере должна быть установлена Java. Можно подготовить собственный образ либо установить пакет вручную.

apt-get update
apt-get install openjdk-8-jre

Формат tree

Что ж, резюмируя ранее сказанное, давайте сформулируем все требования для нашего нового формата.

Форматы

Сравнивать мы будем 5 форматов.

Про первые три не слышал только глухой. А вот два последних для многих — тёмные лошадки. Ну ничего, сегодня я пролью на них свет.

Экранирование

Близкая к читаемости тема — это экранирование. Наличие оного в той или иной мере неизбежно приводит к снижению читаемости. При разработке экранирования стоит иметь ввиду следующие моменты.

Экранирование в json

Похожая проблема есть и в JSON, хоть и в меньшей мере. Если вы когда-нибудь писали плагины для подсветки синтаксиса VSCode, то знаете, что грамматики там описываются именно в JSON формате, куда записываются регулярные выражения.

/"[sS]*"/

Регулярки и сами по себе не самые наглядные штуки, а заэкранированные и того хуже. Допустить в них ошибку в таких условиях очень просто, а дебажить это не очень-то легко.

""[\s\S]*""

Экранирование в tree

Нет 

Самое удобочитаемое экранирование — это отсутствие экранирования. Поэтому у нас его не будет. Вы возможно подумали, что я сошёл с ума, но чуть позже я покажу, как этого добиться.

Экранирование в xml

XML — чудесный пример, как делать экранирование не надо.

foo > 0 && foo < 10

Из простого и наглядного текста получается какой-то криптотекст, который приходится мысленно интерпретировать, чтобы понять что тут написано.

`foo &gt; 0 &amp;&amp; foo &lt; 10`

Экранирование в yaml

В YAML проблему экранирования в целом решили, но какой ценой.

И всё это вам нужно знать, чтобы правильно прочитать любой YAML файл.

Элементы файла yaml: базовый синтаксис

Файл YAML используется для описания данных. В файле YAML все содержимое представляет собой набор пар ключ-значение, где значение может быть любым, от строки до дерева.

Разберемся на примере. Это файл манифеста службы Kubernetes.

kind: Service
metadata:  
  name: web-app-svc
spec:  
  type: NodePort  
  ports:  
  - port: 8080         #Сервисный порт
    targetPort: 8080   #Порт Pod
    nodePort: 30012  #Порт узла из диапазона - 30000-32767  
  selector:    
    app: web-app

Это набор ключевых элементов паров значений: Name: Value.

Как видно из файла выше, файл YAML состоит из ряда различных элементов. Вместе они могут использоваться для описания самых разных структур.

Язык grammar.tree

Язык grammar.tree — предназначен для описания формальных грамматик. К примеру, давайте напишем полную формальную грамматику собственно формата tree.

tree .is .optional .list_of line

line .is .sequence
    .optional indent
    .optional nodes
    new_line

nodes .is .sequence
    .optional .list_of struct
    .optional data
    .with_delimiter space

struct .is .list_of .byte
    .except special
data .is .sequence
    data_prefix
    .optional .list_of .byte
        .except new_line

special .is .any_of
    new_line
    data_prefix
    indent
    space

new_line .is .byte A
indent .is .list_of .byte 9
data_prefix .is .byte 5C
space .is .list_of .byte 20

Как видите, грамматика формата реально предельно простая, что позволяет буквально за час написать парсер на любом языке не прибегая даже к генераторам парсеров.

Читать такую грамматику можно буквально: tree — это опциональный список линий, а линия — это последовательность из опционального отступа, опционального списка узлов и обязательного символа перевода строки. Ну и так далее.

Язык grammar.tree vs ebnf

Сравнивая grammar.tree с Расширенной Формой Бэкуса Наура можно обратить внимание, что первый несколько многословен, но понятен и лаконичен, а последний — компактен, но для понимания требует предварительной подготовки, выразительные возможности всё же несколько уступают, а его ориентация на однострочное представление выглядит несколько неуклюже при многострочной записи.

tree .is .optional .list_of line

line .is .sequence
    .optional indent
    .optional nodes
    new_line

nodes .is .sequence
    .optional .list_of struct
    .optional data
    .with_delimiter space
tree = { line };

line = [ indent ],
    [ nodes ],
    new_line;

nodes = data |
    struct,
    { space , struct },
    [ space , data ];

Язык json.tree vs json

А json.tree — это язык для описания модели json.

Язык view.tree vs typescript

Язык view.tree — используется для композиции компонент в разработанном мной фреймворке $mol.

$my_details $mol_view
    sub /
        <= Pager $mol_paginator
            value?val <=> page?val 0

Тут описан компонент, который владеет другим компонентом и их свойства двусторонне связаны друг с другом. Можете обратить внимание, что внутри view.tree используются в том числе и язык json.tree для описания массивов, словарей, чисел и прочих JSON типов.

Из такого простого и лаконичного кода генерируется довольно развесистый TypeScript класс. Писать его руками можно, но муторно и без иерархии не очень наглядно.

class $my_details extends $mol_view {

    sub() { return [ this.Pager() ] }

    @ $mol_mem Pager() {
        const Pager = new $mol_paginator
        Pager.value = val => this.page( val )
        return Pager
    }

    @ $mol_mem page( val = 0 ) {
        return val
    }

}

Язык xml.tree vs xml

Язык xml.tree — это способ представления модели данных XML в tree формате. Из него можно генерировать любого вида XML. И наоборот, любой XML может быть сконвертирован в xml.tree.

! doctype html
html
    meta @ charset utf-8
    link
        @ href web.css
        @ rel stylesheet
    script @ src web.js
    body
        h1 Procter & Gamble

Заключение

YAML — это понятный, компактный и удобный язык. Его можно использовать в файлах конфигурации для обмена информацией между процессами, хранения объектов и в других целях. Благодаря тому, что YAML является надмножеством JSON, опытному программисту его легко понять и освоить (по аналогии с JSON).

В заключение приводим несколько полезных ссылок для более тесного знакомства с YAML.

Оцените статью
TankMod's
Добавить комментарий