пятница, 12 июля 2019 г.

Network Name Resource

          За что я не люблю Microsoft - так это за отсутствие нормальной документации. Ну то есть формально какая-то документация всегда есть - но это маркетинговая документация. Цель этой документации - не объяснить как работает та или иная технология, а напичкать тебя маркетинговыми названиями и аббревиатурами. Сделать так чтобы ты запутался в бесконечных уровнях абстракции. Сделать каждый уровень абстракции еще более абстрактным чем предыдущий.  Что в купе с отсутствием информации о реальной стороне вещей порождает карго-культ технологий Microsoft. В итоге понимание того как реально работает та или иная технология, на какие протоколы она опирается, какие алгоритмы использует приходится черпать из того раздела документации где описываются известные проблемы.  
         Возьмем как пример - Network Name Resource.  Я профильтровал через свой мозг десятки страниц маркетингового булшита прежде чем найти вот эту страницу - https://techcommunity.microsoft.com/t5/Failover-Clustering/DNS-Registration-with-the-Network-Name-Resource/ba-p/371482  Прочитав ее я понял что Network Name - это не имя сети, как можно было заключить из дословного перевода , а по сути service discovery сделанный через DNS. Ты создаешь Network Name Resource (DNS имя), привязываешь к нему ресурсы на которые оно должно показывать либо статические IP. Если ты привязал к доменному имени ресурсы(сервера) то их текущие IP адреса будут определены через DHCP. Как только эти сервера появляются в сети - DNS сервер начинает отдавать IP адреса этих серверов при запросе на resolve этого доменного имени.  
            Нет, это конечно похвально что в Microsoft изобрели service discovery еще до того как это стало main stream-ом, но блин - это мертворожденное решение. Все знают про DNS cache, а значит этот service discovery заведомо обречен на то чтобы лажать. Всегда будет какая-то задержка. Клиент не может каждый раз резолвить DNS имя при его использовании. 

понедельник, 1 июля 2019 г.

What makes a leader? by Daniel Goleman

       Иногда со мной так бывает, прочитаешь какую-нибуть книгу/статью и понимаешь что - "блин, я это знал". Но знал интуитивно,  теперь знаю что большие дядьки пишущие книги про менеджмент думают приблизительно также и что у того что мне подсказывала интуиция есть научная основа. 
         Не смотря на избитый заголовок, What makes a leader? на удивление годная статья. Если в кратце - хороших лидеров отличает вещь которую сейчас принято называть эмоциональным интеллектом. В нем выделяют 5 компонентов.
  • self-awareness(самоосознание,самоанализ) - способность понимать свои сильные и слабые стороны, трезво оценивать свои силы.
  • self-regulation (саморегуляция) - способность управлять своими эмоциями, способность нивелировать влияние своих слабых сторон.
  • motivation - это слово не нуждается в переводе, оно давно затерто до дыр. Но вопреки устоявшемуся мнению - мотивация это способность оставаться мотивированным без привлечения внешних мотиваторов (деньги, престиж, должность). То есть этим суперскилом обладают только те люди для которых мотиватором является сам факт достижения результата, люди которые кайфуют от фразы - "я сделал это, я нереально крут!"
  • empathy - это способность понимать эмоции других людей. Это не про сопереживание/жалость к другим людям, а про способность понимать эмоции людей и принимать в расчет этот фактор наряду с другими факторами.
  • social skills - это способность быстро налаживать взаимоотношения с другими людьми, способность к нетворкингу,   и способность использовать эти отношения для решения своих задач. 
        social skills - это наверное та суперспособность которой я лишен начисто. Молчаливым подтверждением тому является этот "блог начинающего социопата" или "блог который никто не читает" как я его назвал. 

вторник, 25 июня 2019 г.

Похоронил GoRelic

       Сегодня похоронил один из своих open source проектов - GoRelic. Когда я его начал NewRelic был стандартом де факто для мониторинга, а никакой поддержки для приложений на Golang у них не было. Когда NewRelic анонсировал открытие API для плагинописателей - я быстренько налабал этот проект.  Но как показал опыт писать плагины для закрытых систем (одной из которых без сомнения является NewRelic) это бесперспективное занятие. 
        Их API тогда был убогим и не покрывал и 10% функциональности доступной их собственным плагинам. А когда он стал нормальным - появился официальный Golang агент.  Сегодня я выложил последнюю, 7-ю по счету версию. Больше обновлений не будет.          

пятница, 21 июня 2019 г.

Секретное поле в PerformanceNodeTiming

     Пока разбирался с PerformanceNodeTiming обнаружил там недокументированное поле environment, содержащее время когда закончилась инициализация структуры environment (относительно времени старта процесса). В очередной раз наконтрибьютил в ноду - https://github.com/nodejs/node/pull/28280  Этот pull request прошел в мастер довольно быстро. Мой предыдущий pull request c новым методом в модуле v8 до сих пор ждет мерджа.

вторник, 18 июня 2019 г.

PerformanceTiming API

      PerformanceTimingAPI - довольно занятное API, появившееся еще в версии 8.5 но почему-то обделенное вниманием js программистов. К примеру это API позволяет относительно легко (без погружения в C++ код) замониторить GC, ну и много чего еще.  Возможно причина тому - не очень хорошая документация, да и сам API нельзя назвать интуитивно понятным. Я считаю себя в общем-то не глупым парнем, но я разобрался в нем не с первого раза. Поэтому я и решил потратить время на написание этой статьи.
В основе интерфейса PerformanceTimingAPI (или perf_hooks как оно называется в исходникахлежит иерархия классов основанная на PerformanceEntry. PerformanceEntry - это нечто что ты можешь измерить. У PerformanceEntry есть поле entryType которое собственно показывает что же ты измерил. Для того чтобы получать PerformanceEntry тебе нужно с помощью PerformanceObserver подписаться на них. 
       Если у PerformanceEntry поле entryType="mark" то мы имеем дело с отметиной на timeline.  startTime = время когда произошло это событие, duration = 0, name = имя события. Единственный способ получить mark - самим создать его с помощью фукнции performance.mark("Start of something")
       Единственный сценарий использования performance mark - создать два performance.mark и измерить время между ними используя performance.measure(measureName, startMark, endMark). Этот вызов возвращает нам еще один PerformanceEntry с типом measure. В общем такая красивая и бестолковая обертка вокруг timestamp. 
    Далее следует PerformanceEntry с типом function. Этот тип PerformanceEntry можно получить если обернуть некоторую функцию в мониторинг (через performance.timerify(fn)). В этом случае каждый раз когда функция вызовется будет создан новый PerformanceEntry с типом function описывающий время выполнения функции. Функция timrify использует кастомные атрибуты Object.defineProperty() что негативно сказывается на скорости работы с объектом.
        PerformanceEntry с типом gc - описывает запуск сборщика мусора. В этом случае поле kind будет указывать на тип сборки- major, minor, incremental и тд. На самом деле PerformanceEntry  с типом gc это отдельный класс в C++ коде - GCPerformanceEntry. По каким-то причинам разработчики решили не экспортировать его в мир JS. 
        PerforamanceEntry с типом http2 предназначены для мониторинга http2 сессий и потоков. Если name = HTTP2Stream то этот PerformanceEntry описывает создание нового http2 stream. Если name = HTTP2Session - то соответсвенно сессии.
      Напоследок расскажу о самом сомнительном на мой взгляд архитектурном решении - PerformanceNodeTiming. Он наследуется от PerformanceEntry но у своего родителя он использует только  два поля - entityType = node и duration. Все остальные поля его собственные - они описывают процесс инициализации Node.js: сколько времени потратили на инициализацию процесса ноды(nodeStart), сколько потратили на инициализацию v8 (v8Start), сколько потратили на инициализацию environment-а(поле так и называется environment и оно почему-то не документировано),  сколько времени потратили на bootstrap (bootstrapComplete), сколько времени потребовалось для запуска event loop (loopStart). 
        В отличии от всех остальных PerformanceEntry, работа с PerformanceNodeTiming ведется  не через PerformanceObserver, а через статическое поле performance.nodeTiming. Если ты подпишешься на PerformanceObserver с типом "node" то ты не получишь ничего.  Второй момент - мне не совсем понятно как мониторить инициализацию воркеров. Ведь у них как минимум свой environment и свой eventLoop который также будет инициализироваться. А если я два воркера запускаю одновременно ? В общем это лишний раз доказывает что singleton это антипаттерн проектирования. 

вторник, 11 июня 2019 г.

libuv internals и как эта библиотека интегрирована в Node.js

       С libuv сложилась такая парадоксальная ситуация - про нее все одновременно знают и не знают. Все знают что это нечто используемое Node.js для работы с сокетами и файловой системой и нечто обладающее магическим event loop. Ну а дальше показания расходятся. Причем даже у "экспертов" выступающих на конференциях. 
        В документации libuv есть довольно красивая и но малоинформативная диаграмма:

На мой взгляд функциональность предоставляемая libuv очень просто и понятно описана вот тут https://github.com/danbev/learning-libuv libuv можно использовать для того чтобы:
  • Работать с сетью (tcp, upd, pipe и тд)
  • Асинхронно работать с файловой системой
  • Асинхронно работать с DNS
  • Работать с нитями
  • Обрабатывать сигналы
  • Работать с таймерами

Это довольно большой список, но на самом деле интерфейс libuv основывается всего на трех концепциях: event loop(uv_loop_t), handle (uv_handle_t), request(uv_request_t):
  • event loop - это то, чем собственно занят поток выполнения. бегаем по кругу, дергаем callback-и. Детальнее опишу ниже.
  • handle  представляет собой некий ресурс. Это может быть обертка вокруг ресурса операционной системы (сокет, файловый дескриптор) или просто нечто вроде таймера умеющее дергать callback   
  • request - это некий запрос. Он может использовать хендлер (запрос на запись в файл) или может не использовать хендлеры.
Также внутри libuv сидит thread pool но я бы рассматривал его как деталь реализации а не часть интерфейса. Thread pool используется для превращения синхронных действий в асинхронные и в настоящее время используется только при работе с файловой системой и DNS. Ну еще мы сами момжем попросить libuv выполнить какую-то работу асинхронно в thread pool а не в основном потоке исполнения.
        Так вот, что же из себя представляет event loop ? На мой взгляд его проще всего описать вот такой диаграммой: 
Все прямоугольнички нарисованные на этой диаграмме детально описаны вот тут - https://github.com/libuv/libuv/blob/v1.x/docs/src/design.rst#the-io-loop
Я же хочу остановится на том как это все интегрировано в Node.js. Практически вся работа с libuv сосредоточена в evn.cc. Начинается все с void Environment::InitializeLibuv() - тут мы инициализируем таймеры, check handles и тд. 
        Теперь собственно посмотрим что из прямоугольничков оригинального event loop'а используется в Node.js:
  • Первое это таймеры. При инициализации Node.js создает один libuv таймер который запускает все JS таймеры. Время срабатывания таймера - минимальное среди всех установленных JS таймеров
  • Idle handlers в мир JavaScript никак не транслируются. Они используются внутри для управления логикой которую libuv использует для рассчета времени которое она должна провести заблокированной в poll вызове
  • Prepare handles - также в мир JavaScript не транслируются. Там стоит системный хендлер который служит для измерения времени которое мы фактически проведем заблокированными в poll вызове
  • Check handles - здесь Node.js также вешает один хэндлер который выполняет все callback-и установленные через setImmediate()
  • Close handlers - также не транслируются в JS. Здесь libuv запускает on close callback-и закрываемых ресурсов (хендлеров). 
В общем как-то так. Вообще по моему скроменому мнению - libuv одна из самых понятно написанных библиотек. Тут довольно интересно организорвано наследование структур - через макросы. Все поля структуры объявляются внутри макроса. Соответсвенно в дочерней структуре сначала идет макрос объявляющий родительские поля, а потом макрос объявляющий собственные поля:







пятница, 7 июня 2019 г.

V8 internals - отслеживание ссылок на объекты в куче V8

       То что спецификацию (как минимум первые версии) для языка JavaScript писали не очень умные люди с лихвой компенсируется мозгами тех кто пишет V8.  Чем больше разбираюсь в проекте - тем больше восхищаюсь этими людьми. И так - как же GC V8 узнает о ссылках на объекты в куче из C++ кода ? 
         Вся работа с объектами размещенными в куче построена на так называемых хендлерах. Так как GC может перемещать объекты размещенные в куче во время сборки мусора прямая работа с этими объектами (по указателю) запрещена. Все обращения только через хендлеры. Этих хендлеров есть несколько типов, все они объявлены в v8.h
  • Local - это хендлер время жизни которого ограничено временем жизни области видимости в которой он объявлен.  Как только мы выходим из области видимости - стэк отматывается и хендлер становится недействительным.
  • Persistent - это хендлер время жизни которого не контролируется областью видимости. Живет до тех пока явно не вызовешь Reset()
  • Global - тоже самое что и Persistent но c поддержкой move семантики
  • Eternal - это хендлер используемый для "вечных" ссылок. То есть ссылок живущих до тех пор пока жив Isolate. Они немного более удобны с точки зрения GC чем Persistent 
  • TracedGlobal - тоже самое что и Global но с подержкой трассировки GC. Для чего он может использоваться честно до конца не разобрался
Вся магия по управлению Local хэндлерами через область видимости реализуюется в классе HandleScope и Isolate.  Инстанс HandleScope обычно создается до создания первого хендлера. Все созданные после этого Local хэндлеры создаются в этой области видимости. HandleScope могут быть вложенными. При выходе за границы области видимости вызывается деструктор HandleScope  в котором происходит вызов HandleScope::CloseScope() который подчищает Local хэндлеры созданные в этой области видимости.  
        Тут собственно и начинается самое интересное. Выясняется что внутри Isolate (экземпляр виртуальной машины V8 со своей кучей и всем остальным) есть список буферов в которых собственно размещаются указатели на объекты размещенные в куче. val_ который хранится в Local хендлере это указатель на этот адрес, размещенный в буфере Isolate. Каждый раз когда мы создаем новый хендлер HandleScope::CreateHandle() выделает в буфере класса Isolate новый указатель который указывает на кучу. А возвращает указатель на этот указатель. Собственно когда нужно собрать мусор, GC пробегает по этому списку буферов заполнгенному указателями на кучу и помечает объекты как используемые. При перемещении объектов GC также меняет указатели если кто-то ссылается на этот объект. В общем получается весьма эффективная система.