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

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

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

 

 -Статистика

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

Habrahabr/New








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

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

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

[Из песочницы] Runtime перекраска приложения

Понедельник, 24 Июля 2017 г. 17:23 + в цитатник
Привет, Хабр!

Недавно мне выпала интересная задача перекрасить приложение по JSON объекту, стянутому с сервера. Google диктует идею, что все цвета/темы прописаны в xml. Из-за чего легким движением руки не выйдет везде заменить какой-нибудь R.color.primary_button с синего на зеленый.

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

Небольшая предыстория


Наше приложение имеет несколько вариаций, каждая из которых прописана с использованием productFlavors. Любое изменение какой-либо мелочи (например, цвета текста) требует вмешательства разработчика, поэтому был принят ряд мер по разделению приложения и его ресурсов. В рамках этой задачи так же обратили внимание, что любое изменение цветовой схемы влечёт за собой обновление приложения в PlayMarket/AppStore. Потому один из разработчиков выдвинул идею: «А давайте стягивать цветовую схему с сервера и перекрашивать приложение в runtime».

Итак, что представляет собой поле действий:

  • 47 различных экранов;
  • ~50 shapes и selectors;
  • ~70 разных цветов (одни элементы могут иметь градиент и рамку, другие – специфичны для конкретного экрана).

По существующему опыту были выделены следующие решения:

  1. В каждой Activity написать код, который будет перекрашивать UI (решение в лоб, всем Views назначаются id и в каждой Activity программно задаются цвета).
  2. Наследование от всех используемых UI элементов (развитие первого решения, исключающее внесение изменений в Activity, за место этого переписываются xml).
  3. Обертка над Resources или над чем-нибудь еще, что позволило бы реализовать требуемую задачу во время создания View или Shape.

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

Эксперимент номер один. Попытка завернуть Resources


В Android есть монополист на все ресурсы – это Resources. Любое создание View или Shape получает экземпляр этого класса из переданного в конструкторе контекста. И единственный способ вмешаться в работу конструктора – подменить Context.

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

Теперь о класс Resources


При изучении этого класса обнаружилось, что многие методы, которые хотелось бы перегрузить – пакетные. Никто не мешает перегрузить, например, getColor, но он не используется ни при построении View, ни в TypedArray (нужен для извлечения набора значений ресурсов соответствующего переданному набору атрибутов). А то, что используется – скрыто. Таким образом, провалилась первая, наивная, идея.

Но при этом было отмечено обильное использование TypedValue и TypedArray. В целом, Resources и работа с ним построены на активной работе через эти два класса.

С первым нет никаких проблем, в Resources существует метод getValue. Перегрузив этот метод, сразу получаешь правильно работающий getColor (в случае цвета) и getDrawable (в случае ColoredDraawble).

А с TypedArray всё куда хуже. Этот класс не обернуть, потому что его конструкторы private. Его поля закрыты и он не обладает методами для их изменения. Вмешаться в его заполнение тоже не получится, потому что это происходит через final класс AssetManager. Единственное, что у меня вышло с ним сделать, это получить доступ к нужному полю через рефлексию.

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

Уже во время второго эксперимента встретил еще одну проблему с оберткой Resources. Оказалось, что в Android уже существует android.support.v7.widget.ResourcesWrapper. Его реализации могут для какого-нибудь компонента обернуть твой класс и выдать совсем другой результат. Кстати, ResourcesWrapper – пакетный и скрыт для простых смертных.

Эксперимент номер два


По причине неспособности сделать всё централизованно, задача была разбита на две части:

  1. Замена ресурсов в View.
  2. Замена ресурсов в Shape и Selector.

O View. Подмена LayoutInflater


Наверное, многие знакомы с github.com/chrisjenx/Calligraphy. Для второго эксперимента была выбрана идея, используемая в этой библиотеке, а именно подмена LayoutInflater. Подмена LayoutInflater происходит так же через ContextWrapper. Внутри LayoutInflater переопределяются фабрики, обрабатывающие View (одна из них, к сожалению, через рефлексию). А внутри фабрики реализован код, который в зависимости от View и атрибутов занимается подменой нужных ресурсов.

О Shape


Тут сложнее. Фабрики для них нет. Cоздание происходит внутри Resources через статический метод createFromXml, который парсит переданный xml файл, а далее используется TypedArray. Аналогично происходит и с ColorStateList.

Вмешаться в работу создания не выйдет (за исключением способа, описанного в первом эксперименте). А созданный объект не хранит в себе Id ресурса, из-за чего перекрасить его после создания так же не получится. Но можно пойти в обход. В Resources существует метод getXml. Он позволяет получить любой xml и распарсить его самостоятельно. Таким образом, имея Id и Resources можно получить любой Drawable и внести в него требуемые изменения.

ColorStateList (В отличии от любой реализации Drawble) не дает изменять свой контент. Тут либо использовать рефлексию, либо создавать новый экземпляр и реализовывать кеширование на своей стороне.

Еще немного о кэше ресурсов


Первоначально была надежда использовать кэш Resources просто изменив в нем нужные Drawable и ColorStateList. Но от этого пришлось отказаться по двум причинам.

Первая описана выше и затрагивает ColorStateList. Без рефлексии свойства его экземпляров изменить нельзя, а значит закешированные в Resources экземпляры использовать не выйдет.

Вторая связана с кэшированием ColorDrawable и единичных ColorStateList (это когда запрашивается ColorStateList для цвета, а не selector). Их кэширование оптимизировано и происходит не по id ресурса, а по цвету, на который ссылается ресурс.

Результат


В итоге в приложении есть:

  1. Свой собственный LayoutInflater, который вносит изменения в View.
  2. Великий Singletone с набором методов вида getDrawable(int resId, Resources baseResource), который занимается хранением цветовой схемы, Drawables и ColorStateLists.
  3. Базовая активность, содержащая перекраску статус бара и оборачивание контекста.

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

Плата за это: как минимум увеличенная нагрузка при создании View, в случае Shapes и Selectors – двойная. А так же возможные проблемы при переходе на следующую версию API (сейчас мы используем 24) и device specific баги.

Я верю, что среди вас есть те, кто сталкивался с подобными проблемами. И было бы интересно увидеть ваши мысли на тему runtime перекраски в комментариях.

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

https://habrahabr.ru/post/334010/


Метки:  

«Необходимость возникает с обеих сторон»: программный комитет DevOops о конференции и о DevOps

Понедельник, 24 Июля 2017 г. 17:01 + в цитатник


Хотя понятие DevOps на слуху уже далеко не первый день, о нём до сих пор не прекращаются споры, начиная с вопроса «что это вообще такое». Словосочетание «DevOps-конференция» тоже порождает вопросы: например, если тут сходятся «dev» и «ops», то мероприятие рассчитано на зрителей с бэкграундом в разработке или в администрировании?

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

  • Барух Садогурский (JFrog)
  • Олег Анастасьев (Одноклассники)
  • Алексей Акопян (Dell EMC)
  • Кирилл Толкачёв (Альфа-Лаборатория)
  • Александр Тарасов (Одноклассники)


А в ходе обсуждения спонтанно возник вопрос к вам всем — так что при чтении этого текста вы можете ещё и лично повлиять на программу конференции!

JUG.ru: В связи с понятием «DevOps» можно увидеть споры. А у вас по таким вопросам солидарность, или есть внутренние дискуссии?

Барух Садогурский: По-моему, что такое DevOps, все уже давно определились.









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






Барух: Я с тобой соглашусь в том, что если начинать говорить «окей, а как мы это делаем», то всё по-разному. Но как мне кажется, есть понимание общей идеи, таких вещей, как «you build it, you own it». А как именно это имплементировать, уже не так важно.

Александр: Часто DevOps понимают в узком смысле, когда developers — это именно разработчики, а operations — именно системные администраторы (а не сопровождение в целом, которое может быть разным). Но для меня это более широкое понятие: чтобы Continuous Delivery и прочие подобные вещи взлетали, как правило, требуется участие всей команды. Есть аналитики, тестировщики, есть сам процесс деплоймента, а после этого сопровождение в виде мониторингов и прочих штук. Хотя в самом слове «DevOps» нет ничего про QA, даже если просто открыть Википедию, сразу видишь на диаграмме три круга, одним из которых является QA.



JUG.ru: В девопсе сходятся разные стороны — а с какой приходит основная активность? От людей из разработки или из администрирования?

Барух: Большинство девопсников приходят со стороны администрирования. Грубо говоря, в чём наша задача в девопсе? Мы хотим, чтобы разработчики вместо перекидывания написанного на сисадминов могли всё это протаскивать прямо в продакшен, и если там это упало, то это их проблемы.

Это значит, что они должны научиться этой второй половине: грубо говоря, должны стать новыми сисадминами. Там совсем другой skill set, совсем другой набор инструментов, и так далее. Кто их может научить, кто может написать все приложения, которые им нужны, кто может построить автоматизацию внутри их конторы? Те самые бывшие администраторы, которых нынче порой называют devops engineers. То есть люди, которые приходят со стороны администрирования, становятся devops enablers, они настраивают все и инструменты и процессы, переучивают культуру, ходят и рассказывают, почему вам нужно теперь делать вот так — это все как раз бывшие админы. А бывшие программисты будут пользоваться вот этим всем, грубо говоря, для того, чтобы этот девопс выполнять.

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

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

Барух: Ну, Олег, я так подозреваю, что тут мы оба правы и необходимость возникает с обеих сторон, но популярные инструменты, про которые мы говорим (кроме CI-серверов, просто потому что они были созданы до девопса), всякие Infrastructure-as-a-Service, Chef, Ansible, тот же самый Docker и так далее — они созданы людьми из администрирования, а не разработки. То есть это их инструменты.

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

Барух: Я тут так скажу: это нужно бизнесу.

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

JUG.ru: А что всё это означает применительно к конференциям? На кого в первую очередь рассчитана DevOops: людей с бэкграундом в разработке или администрировании?

Барух: Традиционно практически все западные девопс-конференции (вроде DevOps Days, DevOps Enterprise Summit и так далее) очень ориентированы на DevOps из администрирования. Они там обсуждают кишки своих инструментов и, грубо говоря, «как выдрессировать программистов, чтобы они делали правильный девопс».

И мне кажется, в нашей конференции интересно как раз то, что у нас будут и программисты. Основная аудитория всех конференций JUG.ru Group вообще программистская. Подход «даже если вы не админы, приходите на конференцию по девопсу» — он вообще всегда правильный, но для нас особенно правилен и интересен: во-первых, потому что это для нас естественная аудитория, а во-вторых, потому что это достаточно необычно для традиционных конференций по девопсу.

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

JUG.ru: Уже по списку ключевых тем очевидно, что доклады действительно будут сурово техническими: везде сразу указаны конкретные инструменты. Не просто «Continuous Delivery», а с перечислением «Jenkins, TeamCity, Bamboo». А более общие выступления, не привязанные к конкретному продукту, предполагаются?

Барух: Мне кажется, тут есть две составляющих. Во-первых, разговоры про какой-то общий Continuous Delivery — они абстрактны, ни о чём. Это будет или что-то очень поверхностное, или что-то совершенно без конкретного приложения. А два наших основополагающих фактора при выборе докладов, как обычно с конференциями JUG.ru Group — во-первых, полезность, во-вторых, хардкорность. И какие-то общие доклады будут не особо полезны, не особо технологичны, не особо хардкорны.

Во-вторых, есть вопрос, который мы, на мой взгляд, пока что не решили. Из-за бэкграунда JUG.ru у нас заточенность под конкретные технологии и инструменты: кишочки, хардкор и технологии, а не soft skills и аджайл.

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

Однако проблемы — они в поведении людей. Как изменить его так, чтобы из двух лагерей — программистов и админов — получился один процесс? И это у нас пока никто не затрагивает: во-первых, в принципе не очень понятно, как это затрагивать, а во-вторых, такой доклад по меркам JUG.ru будет заклеймён лайтовым и софт скиллзом.

Я надеюсь, что открывающий кейноут будет именно такой. По крайней мере, мой с Леонидом Игольником закрывающий кейноут будет не на 100% о технологиях. Но вопрос тут остаётся.

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

Барух: Очень зависит от доклада. Как раз к моему стону по поводу того, что у нас из треугольника «people-process-tools» сплошные tools: вот такой доклад бы очень здорово привнёс people и process, если бы это была не просто болтовня «я сказал моим подчинённым, и они сначала были против...», а именно с выкладками, числами, с процессами, что изменилось, кого уволили, а кого наняли. Вот это всё, мне кажется, было бы не просто хороший доклад, а прекрасный кейноут.

Александр: Это интересный взгляд со стороны, с upper-level. Очень часто люди, которые работают на рядовых позициях, просто не понимают, какие там проблемы могут быть и почему в крупных компаниях принимаются те или иные решения, которые с первого взгляда кажутся нелогичными. Если об этом красиво и хорошо рассказать, то может получиться очень интересно.

Барух: А может быть, сделать какой-то парный доклад? Грубо говоря, кто-то достаточно высокого уровня со стороны бизнеса, кто-то со стороны разработки. И показать вот эту дискуссию: с одной стороны, как разработчикам уговорить начальство, а с другой стороны, как начальству тяжело с разработчиками? Или даже собрать нескольких людей C-level и устроить панельную дискуссию?

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

JUG.ru: Да не вопрос. Читатели, у которых есть мнение по этому поводу — пожалуйста, пройдите по ссылке и ткните в один из вариантов, вы поможете нам сделать конференцию лучше.

Возвращаясь к «доклады привязаны к инструменту»: а докладчиками будут преимущественно те, кто эти инструменты активно использует, или и те, кто их создаёт?


Барух: Есть разные случаи. У нас есть докладчики из компании HashiCorp, производящей очень большое количество очень хороших продуктов для девопса, которые мы все любим. У нас, надеюсь, будет один из разработчиков Google Cloud Platform — их, естественно, тоже очень любят. Будет даже человек, который имел отношение к разработке JFrog Artifactory, поверите вы или нет!

JUG.ru: В связи с тем, что ты сам работаешь в JFrog, и с тем, что некоторые другие спикеры будут представлять собственные проекты, небось найдутся желающие сказать «да будет сплошной ужасный маркетинг, никакой пользы». Хочется на это что-то ответить?

Барух: Ну, само собой разумеется, что мы стремимся делать доклады максимально глубокими и интересными, а ни в коем случае не каким-то рекламным product pitch. Доклады выбираются по своей технической составляющией, мы делаем прогоны и репетиции, чтобы быть уверенными, что они не будут product pitch. Те, кто был на других конференциях JUG.ru Group, в этом уже могли убедиться.

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

Скажу так: если ты какой-либо проект использовал или планируешь использовать, то доклад об этом проекте на DevOops тебе будет интересен и полезен. Если не используешь и использовать не думаешь, то ты на этот доклад просто не идёшь. У нас будет несколько треков, всегда можно выбрать. Но я не сомневаюсь, что если человек использует Terraform или Packer, то он с огромным удовольствием пойдёт на доклад человека, который работает в HashiCorp.

И ещё благодаря тому, что конференция в России, мы более-менее защищены от натиска желающих порекламироваться. В России пока что недостаточно развита разработка продуктов для девопса. У нас практически нет кого-то, кто подаётся сам «вот я вам хочу рассказать про свой продукт». Большинство продуктов, которые будут обсуждаться на этой конференции — и CI-сервера, и средства развёртки, и виртуализация — разработаны за рубежом. И поэтому докладчик из этой компании может к нам попасть, только если мы сами его приглашаем. А когда мы его приглашаем, естественно, принимаем все необходимые меры, чтобы гарантировать, что это не будет product pitch.

Александр: Бывают ещё случаи, когда человек вроде и не особенно топит за технологию, но доклад оставляет сахарно-ватное ощущение, что всё так замечательно, прям бери и будет сразу работать, никаких подводных камней… Как правило, такие доклады относятся к категории «101», это обзорные доклады, или доклады начального уровня. Они полезны людям, которые ничего не знают про данную технологию или знают очень мало.

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

JUG.ru: На других конференциях JUG.ru, например, Joker, уже были «околодевопсные» доклады. Что происходит теперь, когда появилась отдельная конференция — они все переезжают туда, или на Joker такие по-прежнему можно будет увидеть?

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

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

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

JUG.ru: Спасибо за ответы. Хочется ли ещё что-то добавить?

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

JUG.ru: То есть от людей, которые придут на первую конференцию, очень зависит, какой станет вторая? Хороший стимул идти.




DevOops впервые состоится 20 октября в Санкт-Петербурге, билеты уже в продаже (и дорожают со временем!). Ключевые темы:

— Контейнеры, Оркестрация (Docker, Kubernetes, Clusters, etc).
— Виртуализация, Облачные технологии (AWS, Azure, Heroku и другие).
— Мониторинг и аудит приложений (Prometeus, OkMeter, DataDog, BPF, Dynatrace, XRebel, Glimpse, Zipkin, OpenTrace и другие).
— Continuous Delivery (Jenkins, TeamCity, Bamboo).
— Configuration Management (puppet, chef, ansible).
— Security (Vault, etc.)
— Разбор полетов на примерах крупных проектов, внедряющих DevOps: успешных и провальных.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333916/


Метки:  

Печать на произвольном размере бумаги в Linux

Понедельник, 24 Июля 2017 г. 16:54 + в цитатник

Метки:  

ФИАС умер, да здравствует… да здравствует… да не понятно что пока

Понедельник, 24 Июля 2017 г. 16:21 + в цитатник
На сегодняшний день в сфере логистики и связанной с ней e-commerce складывается довольно непривлекательная ситуация. С одной стороны, конечно же, во всем виновата Почта РФ, потом ДПД, СДЭК, Яндекс.Доставка, отдельно взятые курьеры ну и конечно же Госдеп — ну куда ж без него. Проблема же, на мой взгляд имеет более фундаментальный характер, имеющий траекторию пике с хохмами по пути следования. Как написать адрес правильно? Где взять почтовый индекс и кто-куда ходит. Куда доставляет Почта, куда добираются на собаках ДПД и СДЭК? И вот тут давайте разбираться.

Основная проблема называется так: «А черт его знает в какой стране мы живем». С одной стороны у нас незаметно в декабре 2014 года, после принятия Постановления Правительства прошло фундаментальное изменение связанное с тем, что теперь официально почтовым (не позволяет язык назвать это адресом) объектом является Муниципальный адрес. Его кортеж безумен как в своей тавтологии, так и запутанности, стирающий границы понимания деревень и сел. Причина проста: в ФНС решили, что ввиду отсутствия закона о почтовом адресе и Административном делении, давайте использовать муниципальный принцип. Их, конечно, до этого момента понять было можно. Ну какая им разница — ну нет у них задачи географического, административного, почтового и тд деления. Чисто внутренний запрос. И все было бы так, пока они не ввели это в ранг почтового адреса постановлением.

Это с одной стороны. С другой, так уже сложилась деловая практика, опять же объективно понятно почему, что в качестве эталонного справочника использовался КЛАДР, а у Почты РФ не было своего эталонного справочника. В общем, все подсели под исключительно ведомственный справочник — КЛАДР. И пока это соответствовало положению вещей в понимании обычного человека, который живет в Московской области, г. Троицк. И бог с ним, что ФНС не успевала отследить миграцию почтовых отделений. Погрешность в 5 процентов — вполне извинительно для того полезного дела, которое доброврольно взвалила на себя ФНС. Ну добавила она новый формат ФИАС, продолжая вести параллельно КЛАДР. Почта РФ в это время готовила свой ЦХДПА и народонаселение ожидало, что Почта его все же рано или поздно опубликует. Ну была еще надежда на то, что Росреестр разродится. Но там тоже нет дела до нужд трудящихся. Ибо нечего использовать сторонних экспертов, самим денег не хватает. В общем, базу так свести и не могут до сих пор. Ждали, что опять пронесет.

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

Но… все пошло не по «авось» и ситуация изменилась кардинально. ФНС передала ведение КЛАДР/ФИАС в дочернюю организацию «Налог-сервис». Там же все довели до полного абсурда. Убили позиционность, заменив на иерархичность. Отказались от административного деления, в результате чего ленинский проспект в Москве из единого объекта превратился в кучу отрезков, в которых невозможно понять в Москве он или уже город спутник, да и вообще соединяется ли он между собой. Полностью потеряли историю изменений и многое другое.

И тут стали возникать разного рода идеи типа words3word, использования геокоординат напрямую и т.д. Казалось бы 21 век на дворе. В чем проблема? Но она есть. И их много:

1. Как переучить всех людей писать слова-координаты, просто координаты? адреса-то не все запоминают?

2. Что считать эталоном? Можно ли использовать недостоверные данные собранные проектами типа OSM или Яндексом? Который, кстати, недавно непроизвольно изменил адресный ландшафт. Видно какому-то программисту в Яндексе показалось не круто использовать дроби и он на карты вместо дома 21/2 нанес 21_2. А доблестные муниципальные власти объявили тендер на печать табличек на дома. Откуда доблестные подрядчики взяли данные? Конечно же не из Росреестра, а с карт Яндекса. Внесение же регламентации возвращает этот проект из свободного в государев — те все в исходную точку.

3. Как быть с юридически и финансово значимой корреспонденцией? Как гарантировать ее доставку по сомнительным эталонам или координатам?

4. До какой точности довести координаты и как понять что точка достигнута? До какого числа после запятой указывать координаты? Как долго почтальон должен ходить с GPS навигатором заходя на цель как истребитель?

5. Любой тех. процесс на Почте РФ или у любой логистической компании должен строиться на возможности отсутствия компьютера-интернета на каком-либо участке. Поэтому адрес должен быть интуитивно понятен. Это, кстати, одна из претензий к ФИАС.

6. Что делать с новостройками, которые в половине случаев граничат с самостроем типа многоэтажек на дачных участках, пронумерованных как участок 1 подъезд 2 этаж 3. Хотя на самом деле это 2 многоэтажных корпуса на участке в 30 соток? Ну не говоря о реальном самострое в южных пределах нашей родины?

Выходов, в целом, не очень много. Ждать возврата сознания у ФНС не приходится. Хотя бы потому, что это внутренняя ведомственная разработка, хоть и выведенная Постановлением правительства в общую головную боль. Анализ этого постановления — это отдельная тема статьи. Например, многим ли известно что в нем допускается адресация на национальных языках, введены абсурдные типы такие как «сооружение», разрешены литеры на латинице (как отличить П и Р?) и т.д.

Второе, с надеждой ждать, когда Почта опубликует ЦХДПА. Вот только вопрос: а оно ей надо? Заем ей поддерживать конкурентов? В данном случае — я бы подумал на месте Почты РФ. Остается только кто-то типа Яндека, кто получит «ярлык» от государства и станет чем-то типа «уполномоченного агента» с федеральным бюджетом. Но опять же регламенты никто не отменял, а кто их делать будет....? И не появятся ли опять дома с подчеркиванием? И кроме, того надо понимать, что адреса это не только место на карте, имеющие координаты, этажи, квартиры, но элементы социальной коммуникации, обмена вербальной! информацией, части делового и торгового оборота. Это культурный слой в конце-концов…

А пока скачиваем очередную версию ФИАС и… не ставим ее. Пишем парсеры на почтовый сайт в надежде выкачать оттуда хоть какие-то обновления почтовых индексов. И, да, коптеры наше все — доставка по координатам, в надежде что не влетит в соседнее окно с детской или кружка юных техников на первом этаже посылка с виски или еще с чем-то поинтересней. А дети, дети знают как его изловить и вскрыть…
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334008/


Метки:  


Процитировано 1 раз

[Из песочницы] Как рекламная сеть Vungle пытается усложнить жизнь своим клиентам

Понедельник, 24 Июля 2017 г. 16:20 + в цитатник
Начиная с января 2014 года, в своих приложениях я использовал Vungle. На тот момент это была одна из самых прибыльных рекламных сетей для мобильных приложений и одна из немногих, поддерживающих рекламные видеоролики.

image
Интерфейс Vungle в 2014 году

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

image

Через 2 года ситуация изменилась — Unity представила свою рекламную сеть, куда ушло множество разработчиков и рекламодателей, а Vungle решил сделать редизайн.

image

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

image

Благодаря формулам в Excel кое-как удалось вычленить данные о доходах и сложить, но это, мягко говоря, было не очень удобно. Тогда же было принято решение в новых приложениях использовать Unity Ads. Спустя время решил зайти и посмотреть доход у старых приложений за прошедший год, но тут ждал сюрприз.

image

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

image

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

image

Ладно. Подумал, что API вернет мне немного текста, где я увижу общий доход. Получил ключ аккаунта, составил ссылку и перешел. API вернуло мне ЭТО. Как и раньше, доход высвечивался только для одного дня, без каких-либо итогов, но теперь еще и с разбивкой по странам. Большое спасибо.

image

Разозлившись, решил дойти до конца. Написал приложение на Unity, которое загружает по ссылкам json, обрабатывает и складывает общий доход.

Очень плохой код на UnityScript
#pragma strict
import System;
class MainMainVungle{
	var mvungle: MainVungle;
}
class MainVungle {
	var vungle: Vungle[];
}
class Vungle {
	var date: String;
	var impressions: int;
	var views: int;
	var completes: int;
	var clicks: int;
	var revenue: float;
	var eCPM: float;
	var geo_eCPMs: VunlgeCounty[];
}
class VunlgeCounty {
	var country: String;
	var views: int;
	var clicks: int;
	var revenue: float;
	var eCPM: float;
}
var apps: String[];
private var url: String = "https://ssl.vungle.com/api/applications/{0}?key=[ВАШ КЛЮЧ API]&start={1}&end={2}&geo=all";
private var templateJson: String = '{"vungle":';
var startDate = "2016-09-27";
var mmvungle: MainMainVungle[];
var revenue: float;
function Start () {
var cTime: Date = System.DateTime.Now;
var i: int;
for (i = 0; i< apps.Length; i++){
	var cUrl = String.Format(url, apps[i], startDate, cTime.Year+"-"+StringTime(cTime.Month)+"-"+StringTime(cTime.Day));
	var www : WWW = new WWW(cUrl);
	yield www;
	var json: String = templateJson+ www.text+"}";
	mmvungle[i].mvungle = JsonUtility.FromJson(json, MainVungle);
	Debug.Log("Loading "+apps[i]);
}
for (i = 0; i< mmvungle.Length; i++){
	for (var v: int = 0; v< mmvungle[i].mvungle.vungle.Length; v++){
		revenue+=mmvungle[i].mvungle.vungle
	}
}
Debug.Log("Revenue: "+revenue);
}

function StringTime(v: int){
	if (v < 10){return "0"+v;}
	else {return ""+v;}
}

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

image

Интересно, зачем Vungle так старательно пытается усложнить жизнь своим пользователям?
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334006/


Метки:  

[Перевод] Сети Docker изнутри: связь между контейнерами в Docker Swarm и Overlay-сети

Понедельник, 24 Июля 2017 г. 16:06 + в цитатник

В предыдущей статье я рассказал, как Docker использует виртуальные интерфейсы Linux и bridge-интерфейсы, чтобы установить связь между контейнерами по bridge-сетям. В этот раз я расскажу, как Docker использует технологию vxlan, чтобы создавать overlay-сети, которые используются в swarm-кластерах, а также где можно посмотреть и проинспектировать эту конфигурацию. Также я расскажу, как различные типы сетей решают разные задачи связи для контейнеров, которые запущены в swarm-кластерах.


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


Оглавление



Docker Swarm и Overlay-сети


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


Например, у меня есть 3 ноды docker swarm кластера:


docker node ls


Для начала я создам overlay-сеть под названием my-overlay-network:


docker network create


Затем запущу сервис с контейнером, на котором запущен простой веб-сервер, который смотрит портом 8080 во внешний мир. Этот сервис будет иметь 3 реплики, и я отмечу, что он связан только с одной сетью (my-overlay-network):


docker service create


Если затем вывести список всех интерфейсов, доступных любому запущенному контейнеру, то их будет 3. В то же время, если запустить контейнер на одном хосте, то можно ожидать только 1 интерфейс:


docker ps


Контейнер связан с my-overlay-network через eth2, что можно понять по IP-адресу. eth0 и eth1 связаны с другими сетями. Если запустить docker network ls, то можно увидеть 2 дополнительные сети, которые добавились: docker_gwbridge и ingress, а по адресам подсетей можно понять, что они привязаны к eth0 и eth1:


docker network ls


Overlay


Overlay-сеть создает подсеть, которую могут использовать контейнеры в разных хостах swarm-кластера. Контейнеры на разных физических хостах могут обмениваться данными по overlay-сети (если все они прикреплены к одной сети).


Например, для веб-приложения, которое мы запустили, можно увидеть по одному контейнеру на каждом хосте в swarm-кластере:


docker service ps webapp


Я могу получить overlay IP-адрес для каждого контейнера при помощи команды ifconfig eth2 (eth2 — это интерфейс, присоединенный к overlay-сети).


На swarm01:


docker ps


Потом с контейнера на swarm02 у меня должна быть возможность пингануть 10.10.10.5 (IP контейнера на swarm01):


docker ps


vxlan


Overlay-сеть использует технологию vxlan, которая инкапсулирует layer 2 фреймы в layer 4 пакеты (UDP/IP). При помощи этого действия Docker создает виртуальные сети поверх существующих связей между хостами, которые могут оказаться внутри одной подсети. Любые точки, которые являются частью этой виртуальной сети, выглядят друг для друга так, будто они связаны поверх свича и не заботятся об устройстве основной физической сети.


Чтобы увидеть этот процесс в действии, можно сделать захват трафика на хостах, которые являются частью overlay-сети. В последнем примере захват трафика на swarm01 или swarm02 выявит icmp-трафик между контейнерами, которые на них запущены (vxlan использует udp port 4789):


sudo tcpdump


В этом примере в пакетах можно видеть два слоя. Первый — это туннельный трафик udp vxlan между хостами по порту 4789, а внутри можно увидеть второй — icmp трафик с IP-адресами контейнера.


Шифрование


Захват трафика в этом примере показал, что если ты видишь трафик между хостами, то увидишь и трафик внутри контейнеров, проходящий по overlay-сети. Именно поэтому в Docker есть опция шифроования. Можно запустить автоматическое IPSec-шифрование vxlan-туннелей, просто добавив --opt encrypted при создании сети.


Если запустить такой же тест, но с использованием зашифрованной overlay-сети, то можно увидеть только зашифрованные пакеты между хостами:


docker network create


Инспектирование интерфейсов vxlan-туннелей


Как и bridge-сети, Docker создает bridge-интерфейс для каждой overlay-сети, который соединяет виртуальные туннельные интерфейсы, выполняющие vxlan туннельную связь между хостами. Впрочем, эти туннельные интерфейсы (bridge и vxlan) создаются не напрямую на туннельном хосте. Они находятся в разных контейнерах, которые Docker запускает для каждой создаваемой overlay-сети.


Чтобы действительно проинспектировать эти интерфейсы, надо использовать nsenter для запуска команд внутри сети контейнера, который управляет туннелями и виртуальными интерфейсами. Эту команду надо запустить на хостах с контейнерами, которые участвуют в overlay-сети.


Также надо отредактировать /etc/systemd/system/multi-user.target.wants/docker.service на хосте и закомментировать MountFlags=slave по инструкции из этого обсуждения.


sudo ls -l


Наконец, если запустить захват трафика на veth-интерфейсе, то мы увидим трафик, который покидает контейнер, но до того, как он будет направлен в vxlan туннель (упомянутый выше пинг все еще работает):


sudo nsenter


ingress


Вторая сеть, к которой были присоединены контейнеры, — это сеть ingress. Это overlay-сеть, но она устанавливается по умолчанию сразу после запуска swarm-кластера. Эта сеть отвечает за связи, которые устанавливаются с контейнерами со стороны внешнего мира. Также именно в ней происходит балансировка нагрузки, которую предоставляет swarm-кластер.


Балансировку нагрузки выполняет IPVS в контейнере, который Docker swarm запускает по умолчанию. Можно увидеть, что этот контейнер прикреплен к ingress-сети (я использовал тот же веб-сервис, что и раньше: он раскрывает порт 8080, который прикрепляется к порту 80 в контейнерах):


docker service create


Для начала, взглянем на хост — на любой хост, который участвует в swarm-кластере:


sudo iptables


Здесь мы видим правило, по которому трафик, предназначенный для порта 8080, перенаправляется по адресу 172.19.0.2. Этот адрес принадлежит контейнеру ingress-sbox, если проинспектировать его интерфейсы, то мы получим слещующее:


sudo ls -l


Docker использует mangle-правила iptables, чтобы назначить определенный номер пакетам для порта 8080. IPVS будет использовать этот номер, чтобы балансировать нагрузку в подходящие контейнеры:


sudo nsenter


Как Docker swarm использует iptables и IPVS для балансировки нагрузки контейнеров, можно более детально изучить по видеоролику Deep Dive into Docker 1.12 Networking.


Docker_gwbridge


Наконец, поговорим о сети docker_gwbridge. Это bridge-сеть с соответствующим интерфейсом под названием docker_gwbridge, который создается на каждом хосте swarm-кластера. Сеть docker_gwbridge соединяет трафик из контейнеров swarm-кластера с внешним миром. Например, такой трафик получится, если мы направим запрос в Google.


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


Заключение


Контейнер, запущенный на swarm-кластере, по умолчанию может быть соединён с тремя и более сетями. Первая сеть, docker_gwbridge, позволяет контейнерам поддерживать связь с внешним миром. Сеть ingress нужна только для того, чтобы устанавливать входящие соединения из внешнего мира. И, наконец, сети overlay: их создает сам пользователь и их можно прикрепить к контейнерам. Эти сети служат общей подсетью для контейнеров единой сети, в которой они могут обмениваться данными напрямую (даже если они запущены на разных физических хостах).


Также существуют пространства разных сетей, которые создаются по умолчанию на swarm-кластере. Эти пространства помогают управлять vxlan-туннелями для overlay-сетей и правилами балансировки нагрузки для входящих связей.


Ссылки/ресурсы


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

https://habrahabr.ru/post/334004/


Метки:  

TamTam: как мы делали новый мессенджер

Понедельник, 24 Июля 2017 г. 15:41 + в цитатник

Метки:  

[Из песочницы] Вынос локального сервера в сеть с помощью другого внешнего сервера

Понедельник, 24 Июля 2017 г. 15:21 + в цитатник
Добрый день, хабровчане! История такова: сколько себя помню, всегда дома висел какой-нибудь сервер, который очень хотелось вывести во всеми нами любимый Интернет.
«Ну и что тут сложного? Практически любой провайдер предоставляет статический белый IP за небольшую плату!», – скажите Вы и будете абсолютно правы. Но это платно, да и вообще хотелось попробовать чего-нибудь более оригинального. Основная проблема доступа к моему серверу – NAT. Если вдруг кто не знает, что это, ниже оставил пояснение из Википедии.

NAT
NAT (от англ. Network Address Translation — «преобразование сетевых адресов») — это механизм в сетях TCP/IP, позволяющий преобразовывать IP-адреса транзитных пакетов. Также имеет названия IP Masquerading, Network Masquerading и Native Address Translation.
Преобразование адреса методом NAT может производиться почти любым маршрутизирующим устройством — маршрутизатором, сервером доступа, межсетевым экраном. Наиболее популярным является SNAT, суть механизма которого состоит в замене адреса источника (англ. source) при прохождении пакета в одну сторону и обратной замене адреса назначения (англ. destination) в ответном пакете. Наряду с адресами источник/назначение могут также заменяться номера портов источника и назначения.

Принимая пакет от локального компьютера, роутер смотрит на IP-адрес назначения. Если это локальный адрес, то пакет пересылается другому локальному компьютеру. Если нет, то пакет надо переслать наружу в интернет. Но ведь обратным адресом в пакете указан локальный адрес компьютера, который из интернета будет недоступен. Поэтому роутер «на лету» транслирует (подменяет) обратный IP-адрес пакета на свой внешний (видимый из интернета) IP-адрес и меняет номер порта (чтобы различать ответные пакеты, адресованные разным локальным компьютерам). Комбинацию, нужную для обратной подстановки, роутер сохраняет у себя во временной таблице. Через некоторое время после того, как клиент и сервер закончат обмениваться пакетами, роутер сотрет у себя в таблице запись о n-ом порте за сроком давности.

Если своими словами, это внешний маршрутизатор, позволяющий Вам отправлять запросы в интернет, но не позволяющий Вам их получать. Ответ-то из Интернета Вы получаете, а значит уже не все потеряно. Мне вспомнилась старая программа LogMeIn Hamach, она же позволяла нам обмениваться пакетами данных при том, что ВСЕ клиенты были в закрытой NAT'ом сети. А что, если реализовать что-нибудь подобное:



Что здесь нарисовано? OPI – это моя Orange Pi PC, которая выполняет роль сервера, NAT – это, как несложно догадаться, маршрутизатор моего оператора (он может быть не один, но сути это не меняет), KVM – внешний сервер товарища, CLI – клиент. Возможно, у вас возник вопрос: «По какой причине ты не мог просто выкинуть все свои сервисы на сервер товарища?». Ответ прост: не хочу. В конце концов и дисковое пространство пришлось бы использовать чужое, а меня это не устраивает. Не говоря уже об усложнении администрирования и обслуживания сервера…

OPI подключается к KVM и между ними устанавливается VPN канал. А далее клиент подключается к KVM, и эта машина в свою очередь отправляет запрос через VPN к OPI.
Почему KVM? Сервер товарища – обычный VDS(Virtual Dedicated Server). Обычно это либо KVM (Kernel-based Virtual Machine), либо OVZ (OpenVZ). OVZ в нашем случае не подходит, так как iptables там работают как-то не так, да и вообще штука очень странная.

Настройка сервера


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

Первым делом надо установить сам PPTP демон на сервере:

apt install pptpd

Следующим шагом его надо настроить. Откроем его конфиг файл /etc/pptpd.conf и укажем IP адрес сервера и диапазон IP адресов клиентов:

localip 10.0.0.1
remoteip 10.0.0.100-200

Теперь нужно создать учетные записи VPN клиентов. Их список находится в файле /etc/ppp/chap-secrets

# client server secret IP addresses
orange pptpd pass123 10.0.0.100

Мы создали клиента orange с паролем pass123 и IP адресом 10.0.0.100. Если вместо IP адреса указать *, то клиент получит любой свободный IP адрес из диапазона, указанного в remoteip. Нам рандома явно не надо. Теперь еще пару штрихов с настройками PPTPD. Добавим DNS сервера в файле /etc/ppp/pptpd-options

ms-dns 8.8.8.8
ms-dns 8.8.4.4

И перезагрузим PPTPD:

service pptpd restart

Очень важный шаг – включение IP форвардинга. Это позволит Вам пересылать пакеты между публичным IP и приватными IP, которые Вы настроили при помощи PPTP. Редактируем файл /etc/sysctl.conf и раскомментируем строку:

net.ipv4.ip_forward = 1

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

~$ ifconfig
	ens3 Link encap:Ethernet HWaddr 52:54:00:f8:0c:4a 
	inet addr:31.148.99.234 Bcast:31.148.99.255 Mask:255.255.255.0
	inet6 addr: fe80::5054:ff:fef8:c4a/64 Scope:Link
	UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
	RX packets:8808733 errors:0 dropped:0 overruns:0 frame:0
	TX packets:3300625 errors:0 dropped:0 overruns:0 carrier:0
	collisions:0 txqueuelen:1000 
	RX bytes:3511383831 (3.5 GB) TX bytes:3245380453 (3.2 GB)

	lo Link encap:Local Loopback 
	inet addr:127.0.0.1 Mask:255.0.0.0
	inet6 addr: ::1/128 Scope:Host
	UP LOOPBACK RUNNING MTU:65536 Metric:1
	RX packets:216 errors:0 dropped:0 overruns:0 frame:0
	TX packets:216 errors:0 dropped:0 overruns:0 carrier:0
	collisions:0 txqueuelen:1 
	RX bytes:16618 (16.6 KB) TX bytes:16618 (16.6 KB)

У меня он называется ens3. Однако, чаще всего он называется eth0.

Создаем правило iptables:

iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE && iptables-save

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

iptables --table nat --append POSTROUTING --out-interface ppp0 -j MASQUERADE
iptables -I INPUT -s 10.0.0.0/8 -i ppp0 -j ACCEPT
iptables --append FORWARD --in-interface ens3 -j ACCEPT

ppp0 – виртуальный интерфейс сети.

Настройка локального сервера


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

Первым делом на Orange PI установим PPTP клиент:

apt install pptp-linux

Создадим файл /etc/ppp/peers/pptpserver и впишем следующее:

pty "pptp 31.148.99.234 --nolaunchpppd"
name orange
password pass123
remotename PPTP
require-mppe-128

Не забудьте поменять IP адрес сервера и прочие данные на свои.

Далее закомментируем все строки в файле /etc/ppp/options вставкой символа #
Файл /etc/ppp/options.pptp нужно привести к следующему виду:

lock
noauth
nobsdcomp
nodeflate
defaultroute
replacedefaultroute
mtu 1400
persist
maxfail 0
lcp-echo-interval 20
lcp-echo-failure 3

И, наконец, пробуем подключиться:

pon pptpserver

Если все получилось, попробуем посмотреть на наш виртуальный интерфейс:

~$ ifconfig ppp0
	ppp0 Link encap:Point-to-Point Protocol 
	inet addr:10.0.0.100 P-t-P:10.0.0.1 Mask:255.255.255.255
	UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1496 Metric:1
	RX packets:1075 errors:0 dropped:0 overruns:0 frame:0
	TX packets:959 errors:0 dropped:0 overruns:0 carrier:0
	collisions:0 txqueuelen:3 
	RX bytes:154176 (154.1 KB) TX bytes:194499 (194.4 KB)

Попробуем пропинговать Orange PI с внешнего сервера:

~$ ping 10.0.0.100
	PING 10.0.0.100 (10.0.0.100) 56(84) bytes of data.
	64 bytes from 10.0.0.100: icmp_seq=1 ttl=64 time=8.91 ms
	64 bytes from 10.0.0.100: icmp_seq=2 ttl=64 time=8.80 ms
	64 bytes from 10.0.0.100: icmp_seq=3 ttl=64 time=8.93 ms
	64 bytes from 10.0.0.100: icmp_seq=4 ttl=64 time=9.00 ms

Работает!

Проброс портов


Теперь самая малость: перенаправление пакетов с сервера на Orange PI. Тут уже способы могут быть разными, но так как на этом сервере 80 и 443 порт не используются вообще, мы можем просто попросить сервер все пакеты для этих портов перенаправлять на OPI

iptables -t nat -A PREROUTING -p tcp -d 31.148.99.234 --dport 80 -j DNAT --to-destination 10.0.0.100:80
iptables -A FORWARD -i ppp0 -d 10.0.0.100 -p tcp --dport 80 -j ACCEPT
iptables -t nat -A PREROUTING -p tcp -d 31.148.99.234 --dport 443 -j DNAT --to-destination 10.0.0.100:443
iptables -A FORWARD -i ppp0 -d 10.0.0.100 -p tcp --dport 443 -j ACCEPT

На забудьте поменять IP адреса на свои. Проверим, что все работает:



Здорово! Цель достигнута!

Маленькие доработки


Но вдруг в здании, где располагается Orange PI выключается свет и… После перезагрузки никто опять не может получить доступ к нашему апельсину. Почему? Потому что сам по себе Orange PI к VPN не подключится. Напишем простой скрипт на bash, который будет вциклически выполняться, проверяя, установлено ли соединение:

#!/bin/sh
while [ 0 ]
do
 if ifconfig ppp0>>/dev/null
 then
  sleep 7
 else
  pon pptpserver
  if $?
  then
   echo $(date) Connected
  else
   echo $(date) Connection error
  fi
 fi
 sleep 3
done

Теперь добавим его в автозагрузку. В файл /etc/rc.local впишем строку с расположением скрипта. В моем случае это:

/root/scripts/ppp.sh

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

chmod +x /root/scripts/ppp.sh

Внимательный пользователь мог заметить, что в скрипте есть команды echo, но ведь им некуда делать вывод! Изначально, я планировал сделать в виде сервиса, чтобы вывод удобно капал туда, но в итоге банально поленился, работает же. Кстати, а работает ли? Перезагружаем наш апельсин и видим, что интерфейс ppp0 успешно поднялся, а наши сервисы доступны из интернета. Цель достигнута, господа!

Итог


Все это сделано исключительно в развлекательных и учебных целях. Практической пользы это почти не несет, так как нам все равно необходим внешний сервер, да и нагрузка не его сеть удваивается, ибо ему приходиться принимать пакеты и сразу же передавать их дальше. Однако, у этого метода есть преимущества, т.к. реальный IP нашего сервера будет скрыт, этот внешний сервер сможет лучше противостоять DDoS атакам, а также сможет выводить беспокойным пользователям страницу о технических работах, если Orange PI будет недоступен. Конечно же, для этого нужно будет еще серьезно постараться, но разве не в самом пути к цели все удовольствие, товарищи?

Ссылки на используемые ресурсы


1. Как настроить VPN с помощью PPTP
2. Проброс портов через NAT
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333996/


Метки:  

Разработка для Sailfish OS: Работа c календарем и списком контактов

Понедельник, 24 Июля 2017 г. 15:00 + в цитатник
Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке приложений для мобильной платформы Sailfish OS. В данной статье речь пойдет об управлении контактами и событиями календаря устройства.

Управление контактами


Все распространенные мобильные операционные системы предоставляют API для управления контактами, хранящимися на устройстве. И Sailfish OS не является исключением. Sailfsh OS предоставляет возможность хранения и управления данными контактов, а также возможность переноса контактов между устройствами.

Мы, как разработчики, можем получить доступ к данным контактов с помощью плагина Nemo QML Plugin Contacts. Данный плагин позволяет получать данные о контактах и редактировать их. Стоит отметить, что плагин не имеет текстовой документации, поэтому разбираться с тем, как он работает, пришлось непосредственно на основании исходного кода.

Плагины Nemo предоставляют доступ к различным службам Sailfish OS. Мы уже рассказывали о таких плагинах как Nemo QML Plugin Notification и Nemo QML Plugin D-Bus в наших предыдущих статьях о работе с уведомлениями и системой D-Bus.

Nemo QML Plugin Contacts предоставляет четыре QML-компонента:

  • PeopleModel;
  • PeopleNameGroupModel;
  • PeopleVCardModel;
  • Person.

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

PeopleModel содержит информацию о каждом контакте: имя, фамилию, номер телефона, e-mail, дату рождения или еще какие-нибудь данные, указанные пользователем при добавлении контакта в записную книжку устройства. PeopleNameGroupModel содержит информацию лишь об именах контактов и о том, сколько контактов с данным именем содержится в записной книжке. А каждый объект в PeopleVCardModel является своего рода «визитной карточкой» формата vCard.

Компонент Person же просто содержит в себе информацию о контакте.

Давайте подробнее остановимся на том, какие данные можно извлечь с помощью PeopleModel и Person. Компоненты PeopleNameGroupModel и PeopleVCardModel не предоставляют так же много полезной информации, поэтому далее мы их рассматривать не будем. Но Вы всегда можете ознакомиться с ними самостоятельно.

Каждый объект модели PeopleModel предоставляет следующие данные о контакте:

  • firstName — имя контакта;
  • lastName — фамилия контакта;
  • favorite — true, если контакт помечен как «избранный», false — в противном случае;
  • avatar — путь до изображения с аватаром контакта на устройстве;
  • contactId — идентификатор контакта в базе данных устройства;
  • phoneNumbers — список номеров телефонов контакта;
  • emailDetails — информация об электронных адресах;
  • person — объект типа Person, содержащий больше информации о контакте.

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

  • id — идентификатор записи в базе данных;
  • firstName — имя контакта;
  • lastName — фамилия контакта;
  • middleName — отчество контакта;
  • companyName — название компании, где работает контакт;
  • role — должность контакта в компании;
  • department — отдел компании, где работает контакт;
  • favorite — true, если контакт помечен как «избранный», false — в противном случае;
  • avatar — путь до изображения с аватаром контакта на устройстве;
  • phoneNumbers — список номеров телефонов контакта;
  • emailDetails — информация об электронных адресах;
  • addressDetails — информация об адресе проживания;
  • birthday — дата рождения контакта.

Рассмотрим теперь подробнее как использовать плагин Nemo QML Plugin Contacts непосредственно в проекте.

Первое, что необходимо сделать – это добавить плагин в зависимости проекта. Для этого необходимо в yaml-файл в раздел Requires добавить плагин nemo-qml-plugin-contacts-qt5 следующим образом:

...
# Runtime dependencies which are not automatically detected
Requires:
  - sailfishsilica-qt5 >= 0.10.9
  - nemo-qml-plugin-contacts-qt5
...

Напомним, что yaml-файл располагается в директории rpm/ в корне проекта.

Для использования плагина внутри QML-кода необходимо импортировать модуль org.nemomobile.contacts.

import org.nemomobile.contacts 1.0

И теперь мы можем объявлять и использовать компоненты, описанные выше.

PeopleModel {
    id: peopleModel
    filterType: PeopleModel.FilterAll
    requiredProperty: PeopleModel.PhoneNumberRequired
}
SilicaListView {
    anchors.fill: parent
    header: PageHeader {title: qsTr("Contacts")}
    model: peopleModel
    delegate: ListItem {
        width: parent.width
        Column {
            width: parent.width
            Label {text: firstName + " " + lastName}
            Label {text: qsTr("Phone numbers: ") + phoneNumbers.join(", ")}
        }
    }
}

Данный пример демонстрирует создание компонента PeopleModel и использование его в качестве модели для списка SilicaListView. Для PeopleModel мы настраиваем свойство requiredProperty где указываем, что необходимо получить информацию о телефонных номерах. Также requiredProperty может принимать значение EmailAddressRequired для получения информации об электронных адресах контакта. С помощью значения FilterAll, переданного в свойство filterType, мы извлекаем список всех контактов. Свойство filterType также может принимать FilterFavorites и FilterOnline для извлечения только «избранных» контактов или только тех, которые сейчас онлайн.

Внутри делегата списка мы показанным выше образом можем использовать поля модели, такие как firstName, lastName и phoneNumbers. Поле phoneNumbers является списком строк, поэтому здесь используется метод join(), для объединения всех номеров телефонов в одну строку.


Для редактирования контакта необходимо внести изменения в поле person типа Person элемента модели PeopleModel. Затем, вызвать на объекте модели метод savePerson(Person person), куда в качестве аргумента необходимо передать измененный контакт person. В итоге, изменения данных контакта будут внесены в базу данных устройства. Новый контакт при этом создан не будет. Для добавления нового контакта в записную книжку устройства нужно создать новый объект типа Person и сохранить его данные аналогичным образом.

Управление событиями календаря


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

Для работы с событиями календаря используется плагин Nemo QML Plugin Calendar. Он позволяет получать данные о событиях календаря и редактировать их внутри своих приложений. Данный плагин, так же как и Nemo QML Plugin Contacts, не имеет никакой текстовой документации, и разбираться с тем как он работает, тоже придется на основании исходного кода.

Плагин Nemo QML Plugin Calendar предоставляет несколько компонентов. В данной статье мы поговорим об основных из них, тех, что позволяют получить доступ к событиям календаря, а также редактировать их:

  • AgendaModel;
  • CalendarEvent;
  • CalendarEventModification;
  • Calendar.

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

AgendaModel {
    id: agendaModel
    startDate: new Date()
    endDate: new Date(2018, 0)
}

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

Элементами модели AgendaModel являются объекты CalendarEvent, каждый из которых содержит информацию об одном отдельно взятом событии. CalendarEvent содержит следующие свойства:

  • displayLabel — название события;
  • description — описание события;
  • startTime — время начала события;
  • endTime — время окончания события;
  • allDay — true, если событие помечено как событие на «весь день»;
  • location — место проведения мероприятия;
  • color — цвет, которым помечается событие в календаре;
  • uniqueId — уникальный идентификатор события в базе данных.

Все перечисленные свойства являются свойствами только для чтения. Для изменения же события используется компонент CalendarEventModification. Он содержит ровно те же свойства, что и CalendarEvent, но здесь все свойства могут быть отредактированы. Также CalendarEventModification обладает методом save(), который сохраняет изменения в базе данных.

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

  • CalendarEventModification createNewEvent();
  • CalendarEventModification createModification(CalendarEvent event);
  • void remove(string uniqueId).

Функция createNewEvent() создает новый объект типа CalendarEventModification, позволяющий заполнять данные о событии календаря. После вызова метода save() на созданном объекте будет добавлена новая запись в базу данных устройства. Таким образом может быть добавлено новое событие календаря.

Функция createModification() позволяет создать объект CalendarEventModification на основе уже существующего события календаря — объекта CalendarEvent. Как мы писали выше, объект типа CalendarEvent не может быть отредактирован, т.к. все его свойства исключительно для чтения. Именно поэтому к нему и применяется функция createModification(), чтобы получить возможность отредактировать событие. После вызова метода save() изменения события будут сохранены в базе данных устройства. Новое событие создано не будет, но будет изменено то, из которого сделали модификацию.

Функция remove() позволяет удалить существующее событие календаря из базы данных, используя его уникальный id.

Так как Calendar является синглтоном, то и создавать объект нет необходимости. Вызывать методы можно напрямую, используя лишь имя компонента.

Calendar.createNewEvent();
Calendar.createModification(event);
Calendar.remove(event.uniqueId);

Для добавления плагина Nemo QML Plugin Calendar в зависимости проекта необходимо прописать его в yaml-файле, так же как и плагин для работы с контактами.

...
# Runtime dependencies which are not automatically detected
Requires:
  - sailfishsilica-qt5 >= 0.10.9
  - nemo-qml-plugin-calendar-qt5
...

Для использования плагина внутри QML-кода необходимо импортировать модуль org.nemomobile.calendar.

import org.nemomobile.calendar 1.0

Рассмотрим теперь пример реализации отображения списка событий календаря.

AgendaModel {
    id: agendaModel
    startDate: new Date() 
    endDate: new Date(2018, 0)
}
SilicaListView {
    anchors.fill: parent
    header: PageHeader { title: qsTr("Calendar events") }
    model: agendaModel
    delegate: ListItem {
        width: parent.width
        contentHeight: column.height
        Column {
            id: column; 
            width: parent.width
            Label { text: eventDateTimeToString(event) }
            Label { text: event.displayLabel }
            Label { text: qsTr("Location: ") + event.location }
        }
    }
}
function eventDateTimeToString(event) {
    return Qt.formatTime(event.startTime, "HH:mm") + " – " 
           + Qt.formatTime(event.endTime, "HH:mm") + "\t" 
           + Qt.formatDate(event.startTime, Qt.SystemLocaleShortDate);
}

Данный пример похож на описанный выше с отображением списка контактов. Здесь так же создается объект-модель, и описывается список SilicaListView. Каждый элемент списка отображает время и дату события календаря, его название и местоположение проведения мероприятия. Для форматирования и корректного отображения даты и времени здесь создана JavaScript-функция eventDateTimeToString(), которая принимает на вход объект события CalendarEvent, и с помощью функций formatTime() и formatDate() формирует строку для отображения.


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

Dialog {
    property var eventModification
    SilicaFlickable {
        // Компоненты ValueButton для установки даты, времени начала и времени конца события.           
        TextField {
            id: eventLabelTextField
            label: qsTr("Event")
            text: eventModification.displayLabel
        } 
        // Компонент TextField с id=locationTextField для установки места проведения мероприятия. 
    }
    onAccepted: {
        eventModification.displayLabel = eventLabelTextField.text;
        eventModification.location = locationTextField.text;
        eventModification.save();
    }
}

Свойство eventModification представляет собой объект типа CalendarEventModification. Это будет абсолютно пустой объект, если мы создаем новое событие, либо это будет объект с данными уже существующего события. Для изменения свойств данного объекта будет использоваться диалог. Далее мы описываем компоненты для редактирования информации о событии. Здесь мы используем три компонента ValueButton для редактирования значений даты, времени начала и времени окончания события, а также два компонента TextField для редактирования названия события и местоположения его проведения. В обработчике сигнала onAccepted вызываем метод save(), сохраняющий все изменения события в базу данных устройства. Таким образом мы создали диалоговое окно, позволяющее редактировать события и создавать новые.


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

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

PullDownMenu {
    MenuItem {
        text: "Add new event"
        onClicked: pageStack.push(Qt.resolvedUrl("EditEventDialog.qml"), 
                                  {eventModification: Calendar.createNewEvent()})
    }
} 

При нажатии на элемент меню вызывается обработчик сигнала onClicked, где описан вызов, открывающий диалог для редактирования события, куда в качестве свойства eventModification передается новый объект созданный с помощью Calendar.createNewEvent(). В итоге будет создано новое событие и передано в диалог, где оно и будет редактироваться.


Для редактирования событий воспользуемся контекстным меню элементов списка SilicaListView. Создадим компонент ContextMenu и добавим к нему пункт «Edit».

SilicaListView {
    // ...
    model: agendaModel
    delegate: ListItem {
          // ...
          menu: ContextMenu {
               MenuItem {
                   text: "Edit"
                   onClicked: pageStack.push(Qt.resolvedUrl("EditEventDialog.qml"),
                                             {eventMod: Calendar.createModification(event)})
               }
        }
    }
}

По нажатию на пункт «Edit» будет открываться диалог для редактирования события. В качестве свойства eventModification передается объект CalendarEventModifiation, созданный с помощью метода createModification() объекта Calendar. В качестве параметра функции createModification() здесь используется объект event, полученный из модели AgendaModel и все его данные будут содержаться в новом объекте для редактирования, который передается в диалог.

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

MenuItem {
    text: qsTr("Delete")
    onClicked: Calendar.remove(event.uniqueId)
}

По нажатию на пункт меню «Delete» мы удаляем запись события из базы данных с помощью вызова Calendar.remove(), куда в качестве параметра передается уникальный идентификатор события, содержащийся в поле uniqueId. Таким образом мы реализовали возможность создания, редактирования и удаления событий календаря.


Заключение


На этом все. Данная статья демонстрирует пример работы с контактами и событиями календаря в Sailfish OS. Несмотря на то, что для описанных выше плагинов и компонентов нет никакой текстовой документации, разобраться с тем как их использовать не составит большого труда. В ходе написания статьи были созданы два готовых примера доступных на GitHub: пример управления контактами и пример управления событиями календаря.

Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.

Автор: Иван Щитов
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/329692/


Метки:  

Команда веб-энтузиастов представила P2P-браузер Beaker

Понедельник, 24 Июля 2017 г. 14:53 + в цитатник
На волне обсуждения возможной монополизации рынка «облачных» услуг ИТ-гигантами вроде Google Cloud и AWS все большее число децентрализованных проектов заявляют о себе. Один из свежих примеров — открытый P2P-браузер Beaker.

Он разработан в партнерстве с командой, которая занимается поддержкой проекта Dat. Он, как и новый браузер, основан на P2P-протоколе Dat.

/ Flickr / hackNY.org / CC

Цель проекта — предоставить возможность создавать и размещать веб-сайты «прямо в браузере». Для этого достаточно сделать в прямом смысле слова «пару кликов»: создать локальную папку и поделиться соответствующей URL (демонстрационное видео).

При отправке содержимого используется протокол Dat (Distributed Dataset Synchronization and Versioning), специально разработанный для передачи данных в рамках распределенной сети.

Beaker позволяет посетителям вашего сайта копировать его содержимое и размещать у себя по аналогии с «re-seeding’ом» торрентов. Для локальной копии можно делать fork’и и внедрять любые изменения.

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

Beaker не предоставляет так назваемую «browsing privacy» — все обладатели URL для того или иного сайта «видят» устройства, которые его «хостят» или пытаются получить доступ. При этом одна из ключевых возможностей браудера — защищенный обмен файлами с помощью протокола Dat. Подробнее об этом можно почитать здесь. Документация по Dat-протоколу расположена тут.

Немного о разработке нашего IaaS-провайдера:

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

https://habrahabr.ru/post/333988/


Метки:  

[Перевод - recovery mode ] Почему мы выбрали TypeScript: история разработчиков из Reddit

Понедельник, 24 Июля 2017 г. 14:16 + в цитатник
image Примерно полгода назад CEO Reddit Стив сообщил о том, что мы перепроектируем сайт. Главный вопрос тут — как именно мы этим занимаемся. В наше время фронтенд-разработка очень сильно отличается от того, что было во времена, когда Reddit появился на свет. Сейчас имеется огромный выбор вариантов для каждой подсистемы веб-приложения. Как рендерить страницы? Как стилизовать контент? Как хранить и обслуживать картинки и видеофайлы? Как писать код? В современных условиях ни на один из этих вопросов нет готового ответа.

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

О богатстве выбора и требованиях к языку


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

  1. BuckleScript
  2. ClojureScript
  3. CoffeeScript
  4. Elm
  5. ElixirScript
  6. JavaScript 2016 и будущие версии языка
  7. JavaScript + аннотации
  8. Nim
  9. PureScript
  10. Reason
  11. TypeScript

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

  1. Это должен быть язык со строгой типизацией. Типы, на микроуровне, играют роль документации. Они помогают, до определённой степени, обеспечивать правильность кода, и, что важнее, упрощают рефакторинг. Ещё одним соображением, в силу которого мы искали язык со строгой типизацией, была скорость разработки. Мы стремились найти строго типизированный язык, так как хотели ускорить работу. Такая идея идёт вразрез с тем видением типизации, которое сложилось у многих программистов. А именно, принято считать, что это — дополнительная нагрузка на разработчика, которая снижает скорость работы. Однако повышение скорости разработки означает ещё и увеличение вероятности появления ошибок. Нам нужна была типизация для того, чтобы код содержал как можно меньше ошибок, даже если пишут его быстро. Строгая типизация, кроме того, полезна в быстрорастущих проектах. Наша команда инженеров постоянно увеличивается в размерах, и число тех, кто работает над кодом, стабильно растёт.

  2. Нужно, чтобы существовали хорошие вспомогательные инструменты. Учитывая то, чем мы собирались заниматься (серьёзное перепроектирование сайта), у нас не было времени на то, чтобы самостоятельно создавать значительное число вспомогательных средств. Было очень важно, чтобы мы могли быстро приступить к работе, используя готовые решения с открытым кодом. Если говорить конкретнее, то вот о каких именно инструментах идёт речь. Это — интеграция с популярными системами сборки (например, с Webpack), поддержка линтера, простая интеграция с фреймворками для тестирования. Если интеграция вспомогательных систем для какого-то языка была неочевидной, мы больше этот язык не рассматривали.

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

  4. Наши разработчики должны освоить язык достаточно быстро. В вышеприведённом списке есть прекрасные языки, единственный минус которых — слишком большой срок, который нужен разработчикам для того, чтобы их освоить. Среди них хочется отметить Elm и PureScript. Мы серьёзно обсуждали вопрос их использования. Однако, в итоге оказалось, что их внедрение означало бы слишком большой объём работы, необходимый для освоения новых концепций программирования разработчиками, которые с ними не знакомы, для выхода их на уровень, позволяющий продуктивно работать над проектом.

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

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

После рассмотрения этих требований мы остановились на двух вариантах. Первый — TypeScript. Второй — JavaScript + Flow. Но, прежде чем сделать окончательный выбор, мы хотели как можно лучше понять особенности TypeScript и Flow, а также различия между ними.

Компиляция или аннотирование?


Одно из важных различий TypeScript и Flow заключается в том, что TypeScript — это язык, который компилируется в JavaScript, а Flow — это набор аннотаций типов, которые можно добавлять к JavaScript-коду. Корректность аннотированного кода проверяет статический анализатор.

Вышеописанные различия прямо влияют на то, как именно пишут программы. Взгляните, например, на работу с перечислениями в TypeScript и Flow.

TypeScript

enum VoteDirection {
  upvoted = 1,
  notvoted = 0,
  downvoted = -1,
};
const voteState: VoteDirection = VoteDirection.upvoted;

Flow

const voteDirections = {
  upvoted: 1,
  notvoted: 0,
  downvoted: -1,
};
type VoteDirection = $Keys;
const voteState: VoteDirection = voteDirections.upvoted;

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

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

В противовес обработке TypeScript, Babel автоматически удаляет аннотации типов Flow. Если бы мы выбрали Flow, процесс сборки приложения не усложнился бы.

Проверка корректности кода


В области проверок корректности кода Flow обычно показывает себя лучше, чем TypeScript. Во Flow, по умолчанию, запрещается использовать типы, допускающие значение NULL. В TypeScript 2.x была добавлена поддержка типов, в которых не допускается NULL, однако, эту возможность нужно включать самостоятельно. Кроме того, Flow лучше выводит типы, в то время как TypeScript часто обращается к типу any.

Помимо типов, допускающих значение NULL и вывода типов, Flow лучше в вопросах ковариантности и контравариантности (вот материал на эту тему). Одна из типичных проблемных ситуаций здесь — работа с типизированными массивами. По умолчанию массивы во Flow инвариантны. Это означает, что следующая конструкция во Flow вызовет ошибку:

Flow

class Animal {}
class Bird extends Animal {}

const foo: Array = [];

foo.push(new Animal());
/*
foo.push(new A);
        ^ A. This type is incompatible with
const foo: Array = [];
                ^ B
*/

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

Typescript

class Animal {}
class Bird extends Animal {}

const foo: Array = [];

foo.push(new Animal()); // в typescript всё нормально

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

Экосистема


Всё, что было сказано до сих пор, говорит о преимуществах Flow. Его легче интегрировать в проект, он лучше справляется с проверкой типов. Почему же мы остановились на TypeScript?

Одно из важнейших преимуществ TypeScript — его экосистема. Он отличается потрясающей поддержкой библиотек. Практически все библиотеки, которыми мы пользовались, либо имеют описания типов в самих библиотеках, либо представлены в DefinitelyTyped. Кроме того, TypeScript обладает отличной поддержкой IntelliSense в VSCode и в плагинах для других популярных редакторов, которыми мы пользуемся (например, среди них — Atom и SublimeText). Более того, мы обнаружили, что TypeScript умеет обрабатывать аннотации JSDoc. Это оказалось особенно полезным, так как мы переходили на TypeScript с JavaScript.

Кроме того, TypeScript отличается большей «социальной значимостью», и есть ощущение, что срок его жизни будет достаточно долгим. Существует несколько крупных проектов, использующих TypeScript (среди них — VSCode, Rxjs, Angular, да и сам TypeScript), поэтому у нас имеется уверенность в том, что набор его возможностей сможет соответствовать целям нашего проекта, и в том, что язык в ближайшие годы никуда не денется. По поводу же Flow нас беспокоит то, что он был создан для решения специфических задач в Facebook, и то, что его развитие будет определяться тем же диапазоном задач. TypeScript, с другой стороны, ориентирован на широкий спектр проблем, работой над ним занимается Microsoft. Всё это позволяет нам предполагать, что если мы столкнёмся с ошибками и сообщим о них, нас услышат и примут меры.

Кроме того, так как TypeScript — это язык, являющийся надмножеством JavaScript, мы можем ожидать, что развитие TypeScript, в частности, поддержка им новых типов и языковых конструкций, будет следовать за развитием JS. Язык и система типов будут развиваться параллельно, так как работа над ними идёт одновременно.

Итоги


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

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

Уважаемые читатели! Пользуетесь ли вы TypeScript или Flow?
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333986/


Метки:  

[Из песочницы] Найдена новая версия программы. Устанавливаем?

Понедельник, 24 Июля 2017 г. 13:55 + в цитатник
image

Пути «романтики потребления” новых версий программного обеспечения у всех свои, но финал один – “Если работает, не трогай!”…

Стоп Apache Hadoop 3.0.0 alpha


Apache Hadoop 3.0.0 alpha была первой ласточкой, когда я столкнулся с ошибкой, которая превратилась в стоп фактор для возможности дальнейшей эксплуатации. Сейчас можно сказать, что это была альфа версия и ответственность за риски при ее эксплуатации – это твоя личная драма. Согласен, НО видимо дух романтики затмевал реальную оценку рисков. Он значительно подпитывался тем фактом, что с ноября 2015 пользуюсь Apache Tomcat 9, а это alpha версия. Чувство, что ты используешь самую крайнюю версию и соответственно получаешь самые новые возможности снижает долю сомнений в рискованности такого выбора, тем более что каких-то серьезных проблем при ее эксплуатации припомнить было нельзя.

Стоп Apache Tomcat 9.0.0.M22 и JetBrains IntelliJ IDEA 2017.1


В среднем раз в месяц, а то и чаще выходит новая версия Apache Tomcat 9 (alpha). После ее выхода, сразу же обновлял версию у себя на стендах, сначала на тестовых для разработчика, потом и на боевых. Все бы ничего, НО JetBrains IntelliJ IDEA 2017.1.2 при перезагрузке app сервера Apache Tomcat 9.0.0.M22 проектов JavaEE просто стала зависать при том, что рестарт службы из оснастки ОС проходит без проблем community рекомендовало завести кейс, решил подождать новой версии, т.к. на памяти были проблемы с Apache Tomcat 9.0.0.M19. В июле вышла новая версия JetBrains IntelliJ IDEA 2017.2, НО проблема с Apache Tomcat 9.0.0.M22 сохранилась.
В итоге пришлось, отказаться от Apache Tomcat 9 и перейти на Apache Tomcat 8.5.16.

Нужно ли устанавливать новую версию software, если не обновляется hardware?


Месяц назад во время достаточно длительного переезда из одного города в другой (более 1200 км) рассчитывал занять 3-4 часа времени своего путешествия – работой над WEB приложением (frontend — Java Script/HTML5, backend Java SE/Java EE/ORM, СУБД Oracle, MySQL, SQLite). Так получилось, что неделю до поездки вышла новая версия JetBrains IntelliJ IDEA 2017.1.2
и я обновился до этой версии. Из новых фишек версии по моим впечатлениям был богатый анализ кода, который позволяет до компиляции исключить количество ошибок и среда разработки еще больше дает подсказок. Все бы хорошо, НО вскрылась достаточно серьезная проблема: при редактировании кода (в том проекте это был контекст Java Script/HTML5 и Java SE/Java EE) утилизация CPU у ноутбука с достаточно серьезными железными характеристиками (Inter Core i5-2520, Memory DDR3-16 Гб, SSD) приобретала 100% характер на 20-30 секунд. Что фактически превратилось в катастрофу для заряда батареи. Вместо ожидаемых 3-3,5 часов, все закончилось через 40 минут. Т.е. при том, что ранее JetBrains IntelliJ IDEA при загрузке проекта утилизировала 1,5 Гб памяти, что в принципе решалось дополнительной планкой памяти, теперь еще добавилась утилизация CPU, которую “успокоить” так и не удалось.

Предполагал, что возможно у IntelliJ IDEA есть возможность отключения “интеллектуальной части” для обеспечения возможности работы на оборудовании образцов 3-х летней давности. получил ответ Community, активируйте File | Power Save Mode. Активация этой опции в IntelliJ IDEA жизни батареи не прибавил. Неужели уже вопрос на уровне hardware?

Как Docker может повлиять на Cisco Any Connect 4.5.00058?


На этой неделе из-за желания установить Docker на домашний ПК решил выполнить переустановку ОС Windows 10, т.к. Docker не устраивала текущая версия ядра ОС.
Для исполнения своих профессиональных обязанностей мне приходится подключаться к определенному ландшафту серверов через VPN. Для этих целей использую Cisco Any Connect. К своему удивлению был поставлен перед фактом, что при активации VPN туннеля мои локальные хосты стали недоступными. Проверка сетевых маршрутов показала, что приоритет метрика VPN интерфейса оказался выше. Решил выполнить корректировку интерфейсов Ethernet и VPN вручную определив им уровень приоритета. На этом Cisco Any Connect удивлять не закончил: при активации VPN туннеля приоритет сетевого интерфейса был выбран снова как и раньше. В общем Cisco Any Connect 4.5.00058 более не учитывает настройки администратора ОС и работает на основании своих представлений выбора приоритетов сетевой маршрутизации. Итого пришлось отказаться в пользу OpenConnect, которое позволяет учитывать пожелания администратора ОС.

Послесловие


Со временем происходит переосмысление любых взглядов.
Нужно понимать, что альфы, беты и т.д. — это поглотители твоего свободного времени и дополнительные точки отказа в эксплуатируемой системе или разработанном приложении. И если ты отказался от них, то это не означает, что ты теперь не достоен считать себя пионером в своей области: ты адекватно понимаешь риски и тратишь время с большей пользой.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333984/


Метки:  

Спускаемся с небес: давайте просто посчитаем?

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




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

Сколько вешать в граммах?


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



Многие решают идти проторенными тропами и вкладываться в железо, ПО, места в стойках, инженеров, в общем – все, как привыкли. И это не удивительно, ведь стратегию развития IT, как в крупном бизнесе, так и в государственных структурах обычно отдают на откуп соответствующих подразделений, которым «все эти облака» и даром не нужны. Но за прошедшее десятилетие в России достигнут существенный прогресс в плане развития широкополосного доступа на значительной территории нашей необъятной страны, и активные работы в этом направлении продолжаются (по крайней мере, по заверениям Минкомсвязи). Кроме этого существует ещё два фактора, способных привести, если не к взрывному, то по крайней-мере довольно бурному росту облачного спроса:

  • Слабые кадры. Компетентный IT-персонал и наиболее сильные кадры вымываются в столичные регионы, а также уезжают за рубеж. Такая ситуация будет подталкивать руководство на местах избавляться от IT-обузы в виде недееспособных сотрудников.
  • Требования и нормативы. А ведь облачные площадки полностью соответствуют регулирующему законодательству, прежде всего в разрезе защиты персональных данных. Реализация требований законодательства, как для бизнеса, так и для государственных ведомств – все это дополнительная головная боль, которая способна привести к целому ряду неприятных последствий. Хочется, чтобы она была изначально устранена.


Что касается доступных решений, в РФ для создания вычислительной инфраструктуры в облаке наибольшее распространение получили продукты VMware, вариации на базе Openstack и сервисы Microsoft. Любая из данных категорий обладает обширным инструментарием, который позволяет создавать все разновидности облачных решений, кроме того, в каждом семействе продуктов доступна расширенная функциональность, позволяющая выйти за рамки IaaS. Благодаря возможности создания гибридных облаков помимо публичных, перечисленные платформы легко адаптируются для соответствия законодательной базе РФ. Но каждый раз возникает вопрос: к каким рискам может привести облачная система, и сколько она в действительности будет стоить?

Пример расчета


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

Таким образом, внедрение новой ИС предполагает наличие трёх контуров — продуктивного, предпродуктивного (нагрузочное тестирование и резервный сервер для продуктивного) — конфигурация близкая к продуктивной, тестовый. Расчётные показатели по потребностям в ресурсах показывают необходимость в трёх серверах с конфигурацией 2 CPU класса E5-2640v3, RAM 64GB, порядка 2-3 Тб емкости, желательно на быстрых дисках (например, 12 дисков SAS 15k rpm по 600ГБ, а лучше – SSD). Цикл формирования отчётности ежемесячный, соответственно, в течение месяца преимущественно требуется доступ к уже сформированным отчётам.

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

Предположим, что в существующей инфраструктуре уже нет ресурсов для его размещения, например, для задействования существующего тестового контура потребуется увеличение ёмкости СХД, приобретение дополнительного сервера под расширение фермы виртуализации. Нередко одновременно с этим в SAN-сети оказываются исчерпаны свободные порты и потребуется значительно большее вложение. Но даже бюджет сервера в указанной конфигурации с трёхгодичной поддержкой будет составлять порядка $30000 (если говорить про HPE DL380 G9, хотя можно взять что-то подешевле и ограничиться $15000).

Что же предлагает нам облако? Так как тип загрузки тестовых ресурсов – эпизодический, интерес представляет размещение со схемой оплаты pay-as-you-go, т.е. по фактически потреблённым ресурсам. На этапе внедрения ИС, тестовая среда используется интенсивно на протяжении трёх месяцев, что примерно составляет 8ч x 24дня x 3 = 576 часов, в последствии, тестовая система используется каждые два месяца по 40 часов, то есть за первый год получаем 576 + 160 часов, за последующие два по 240 часов в год.

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

Итак, берём ВМ с профилем m4.4xlarge в Amazon AWS EC2:


vCPU — 16.
ECU — 53.5.
Память (ГиБ) — 64.
Хранилище инстансов (ГБ) — EBS.
Использование Linux/UNIX — $0.96 за час.

В результате получается следующий расчет:


1-ый год $0.96 * (576+160) = $706,56.
2-ой год $0.96 * 240 = $230,4.
3-ий год $230,4.

Что касается системы хранения, берем EBS. Например, возьмём Amazon EBS General Purpose SSD (gp2) по $0.119 за ГБ-месяц выделенного хранилища. Для дискового пространства расчёты несколько отличаются, так как ВМ будет использоваться только в рабочие часы, а резервировать дисковое пространство придётся на весь период эксплуатации ВМ. Таким образом, получаем 24 ч x 30 дней x 3 = 2160 часов на внедрение и 480 часов сопровождение в первый год, а также по 720 часов на сопровождение в последующие годы. После выполнения каждого цикла работ диск с данными высвобождается и за его аренду можно уже не платить.

Итого по дисковой подсистеме получаем:


1-ый год $0.119 * 3000 * 2160/(24*90) + $0.119 * 3000 * 480/(24*120) = $1309.
2-ой год $0.119 * 3000 * 720/(24*180) = $357.
3-ий год $357.

Общая сумма за всю аренду достигает следующих показателей


1-ый год 706,56 + 1309 = $2015,56.
2-ой год 230,4 + 357 = $587,4.
3-ий год $587,4.

Получается, что мы заплатим $3190,36 за три года. Хорошо, пусть придется брать эти сервисы у других провайдеров, так что округлим сумму до $5000. Возможно также потребуется использование снэпшотов или продолжительность работы ВМ окажется выше, но стоимость нового сервера будет проблематично достичь даже при самом пессимистичном прогнозе.

Заключение


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

Более того, поскольку облачные решения по сути своей являются экстерриториальными, желающим доступны любые сервисы, в том числе таких «монстров» как Microsoft или VMware, предоставляющих, как инструментарий для построения собственно облачных платформ, так и свои готовые облачные продукты. Сервисы таких мировых лидеров облачных решений, как Amazon и Google, менее востребованы отечественным бизнесом, что обусловлено их политикой продвижения услуг в России (а точнее её отсутствием). Но зато у них есть удобные калькуляторы, и никто не запрещает использовать их продвинутым командами или индивидуалами в сфере разработки ПО. Для большинства же будут актуальны многочисленные российские платформы, которые с радостью предоставят по запросу свою версию подобного расчета, причем совершенно бесплатно. Так что не стесняйтесь считать и спрашивать – может быть уже сегодня облако окажется дешевле, удобнее и надежнее, чем перегруженная инфраструктура, если говорить о задачах разработки и тестирования.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333976/


Как и зачем скрывать телефонные номера

Понедельник, 24 Июля 2017 г. 13:23 + в цитатник

Мало известный за пределами телеком-тусовки факт: когда вы звоните куда-нибудь со своего телефона, ваш номер, который отображается для входящего звонка – это просто произвольная текстовая строка, которую подставил ваш оператор сотовой связи. И если между двумя устройствами есть что-нибудь интересное, к примеру, машина с Asterisk или облако с Voximplant, то эту строку можно поменять на любую другую. Ситуация, когда при звонке вместо номера звонящего человека «рисуется» совсем другой номер называется маскировка телеком-данных (Phone Number Masking). И этот прием широко используется для решения ряда интересных задач. Читайте о них под катом.

Такси и другие Uber-like решения


Давать водителю настоящий номер клиента – не самая лучшая идея. Потом водитель может позвонить не по делу, – вот это всё. Подмена номера работает так: как только водитель принял заказ, это становится известно backend'у сервиса такси. И если водителю нужно позвонить клиенту, то вместо этого он звонит на специальный сервисный номер. Звонок приходит в облако Voximplant, где из JavaScript делается HTTP-запрос к backend и получает информацию о заказе. После чего облако звонит клиенту, и, как только он берет трубку, соединяет его со входящим звонком от водителя. Если вам интересны подробности, почитайте, как это реализовано у Wheely.

Такой способ позволяет водителю и клиенту не знать номера друг друга: все звонки идут через облако. В исходящем звонке до клиента Caller ID устанавливается в номер контакт-центра. Благодаря этому, если клиент после поездки позвонит на “номер водителя”, его встретит голосовое меню и предложит связаться либо с водителем, либо с оператором контакт-центра. И если водитель не работает, то клиенту можно предложить что сервис сам ему перезвонит когда водитель выйдет на работу. Все эти функции можно реализовать в несколько строчек JavaScript-кода:


Доставка и курьерские службы


Похоже на сервисы такси, но со своими нюансами. Очень часто звонки делают не через сотовую связь, а с помощью приложений: курьеры звонят для согласования логистики, а интернет сейчас сильно дешевле даже оптовых предложений на сотовую связь для компаний. Выглядит это следующим образом: у курьера есть веб/android/ios-приложение с кнопкой «связаться с заказчиком». При нажатии на нее соответствующий SDK устанавливает TCP/WebSocket подключение к облаку Voximplant, сигнализирует звонок и передает голос по протоколу (S)RTP. При звонке обратно курьеру используется механизм Push-уведомлений, так что даже если приложение закрыто, это не помешает «достучаться» до службы доставки.

Если использовать одно облако для всей коммуникационной автоматики, то можно делать интересные интегрированные решения. Например, вызвав облачный JavaScript-сценарий через HTTP API можно подтвердить доставку. А с помощью голосового меню клиент может прямо из этого звонка связаться с курьером, если он сейчас доступен, или с контакт-центром. При этом маскировка номера делает клиенту «один номер» на который он «просто звонит». А под капотом JavaScript по положению звезд определяет, куда отправить звонок.


Веб интерфейсы систем управления заказами


Благодаря WebRTC (наша свежая статья про WebRTC) можно звонить с веб-страниц и на веб-страницы, а облако с JavaScript наперевес коммутирует эти звонки с традиционной телефонией. Маскировка номера работает и здесь. Откуда номер у веб страницы? Для всех исходящих звонков из CRM облако клиенты подставляют свой «внешний» номер телефона. Если на этот телефон позвонят, то дальнейшие действия со звонком просто прописывается в JavaScript-сценарии. Например, распределить на того оператора колл-центра, с которым клиент общался последний раз. Или автоматически рассказать о статусе заказа. Или эскалировать звонок на поддержку второго уровня, если это пятый звонок за час.

Зачем нужна маскировка номера


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

Фотография до ката взята из блога.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333972/


Метки:  

[Перевод] XBRL: просто о сложном - Глава 4. Отчет XBRL

Понедельник, 24 Июля 2017 г. 13:10 + в цитатник

4. Отчет XBRL


В этой главе мы рассмотрим отчеты XBRL. Как и прежде, основное внимание уделяем тому, что можно сделать, а не как это делается.


Отчет (instance document) содержит факты и ссылается на таксономию для придания фактам смысла:


image


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


Схематически состав отчета можно изобразить следующим образом:


image


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


4.1. Корневой элемент xbrl


Отчет содержит различные виды данных:


  • Ссылки на одну или несколько таксономий;
  • Ссылки на базы ссылок (linkbase) – опционально;
  • Ссылки на роли (roles) и роли дуг (arcroles) – опционально;
  • Контекст (context);
  • Единицы измерения (units);
  • Факты (fact) – простые (item) и кортежи (tuple);
  • Сноски (footnote).

Общим контейнером для всех составных частей отчета является обязательный элемент xbrl.


4.2. Ссылки


Отчет может содержать некоторые ссылки на внешние ресурсы.


4.2.1. Ссылка на таксономию


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


4.2.2. Ссылки на базы ссылок


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


4.2.3. Ссылки на роли и роли дуг


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


4.3. Контекст


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


4.3.1. Составитель отчета


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


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


Опционально, составитель отчетности может также иметь элемент segment для дополнительной детализации, напр. бизнес-единица внутри крупной компании или территориальное подразделение государственной организации. В спецификации не дается каких-либо указаний относительно того, как именно следует использовать этот элемент – это любое XML-выражение, не использующее синтаксис XBRL.


4.3.2. Отчетный период


Период может быть двух типов – на дату (instant) и собственно за период (duration). Во втором случае можно либо явно задать начало и окончание периода, либо указать за все время (forever).


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


4.3.3. Сценарий


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


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


4.4. Единица измерения


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


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


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


4.5. Факт


Факт (item) – это отдельное значение, которое передается в качестве отчетных данных. Факт не может содержать в себе другие факты, для таких случаев используются кортежи фактов (см. далее).


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


4.5.1. Ссылка на контекст


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


  • для фактов на дату контекст должен содержать instant-период;
  • для фактов за период контекст должен содержать корректную пару значений атрибутов startDate (начало периода) и endDate (окончание периода), либо иметь значение forever (за все время).

4.5.2. Ссылка на единицу измерения


Числовые факты должны ссылаться на единицу измерения путем указания ее id.


4.5.3. Точность и десятичные знаки


Числовые факты должны содержать либо точность (precision), либо количество десятичных знаков (decimals). Это важно при сравнении значений, а также при выполнении расчетов, особенно при округлении и усечении.


Атрибут precision указывает количество значимых знаков факта (до десятичного разделителя). Атрибут decimals указывает точность значения в количестве знаков после десятичного разделителя. Эти атрибуты являются взаимоисключающими, т.е. недопустимо использовать оба из них в одном факте.


Атрибут precision определяется автоматически при наличии атрибута decimals. Спецификация XBRL дает точные правила такого определения. Также, она приводит примеры того, как читать эти атрибуты.

4.6. Кортежи фактов


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


Кортежи объединяют факты, при этом в качестве элементов кортежа могут использоваться как факты, так и другие кортежи. Сам по себе кортеж не может ссылаться на контекст или единицу измерения. Также, он не может иметь атрибутов тип периода (periodType) и баланс (balance).


4.7. Сноски


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


Сноски (footnotes) – это дополнительные данные к фактам и ссылки на них, которые существуют только в пределах отчета. Для ссылок на сноски используется роль footnotes. Сама сноска содержит текст и должна иметь метку языка.


4.8. Равенство


Для сравнения фактов или кортежей фактов могут иметь значение несколько форм равенства:


  • Идентичность
  • Структура
  • Родительский элемент
  • Значение
  • XPath
  • Контекст
  • Единица измерения

Спецификация XBRL содержит большую таблицу, которая точно определяет, как каждая форма равенства должна применяться к различным типам аргументов, таким как узлы, атрибуты, составители отчета и т.д.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333896/


Метки:  

Точное вычисление средних и ковариаций методом Уэлфорда

Понедельник, 24 Июля 2017 г. 13:05 + в цитатник

Метод Уэлфорда — простой и эффективный способ для вычисления средних, дисперсий, ковариаций и других статистик. Этот метод обладает целым рядом прекрасных свойств:


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

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


Настоящая статья пытается заполнить эти пробелы.



Содержание



Введение


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


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


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


У статьи следующая структура. В пункте 1 мы рассмотрим простейшую задачу вычисления среднего, на примере которой поймём, что эта задача не так уж и проста, как это кажется на первый взгляд. Пункт 2 вводит используемые в данной статье обозначения, которые пригодятся в разделах 3 и 4, посвящённых выводу формул метода Уэлфорда для, соответственно, вычисления взвешенных средних и взвешенных ковариаций. Если вам неинтересны технические подробности вывода формул, вы можете пропустить эти разделы. Пункт 5 содержит результаты экспериментального сравнения методов, а в заключении находится пример реализации алгоритмов на языке С++.


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


1. Погрешности при вычислении средних


Начну c классического примера. Пусть есть простой класс, вычисляющий среднее для набора чисел:


class TDummyMeanCalculator {
private:
    float SumValues = 0.;
    size_t CountValues = 0;
public:
    void Add(const float value) {
        ++CountValues;
        SumValues += value;
    }
    double Mean() const {
        return CountValues ? SumValues / CountValues : 0.;
    }
};

Попробуем применить его на практике следующим способом:


int main() {
    size_t n;
    while (std::cin >> n) {
        TDummyMeanCalculator meanCalculator;
        for (size_t i = 0; i < n; ++i) {
            meanCalculator.Add(1e-3);
        }
        std::cout << meanCalculator.Mean() << std::endl;
    }
    return 0;
}

Что же выведет программа?


Ввод Вывод
10000 0.001000040
1000000 0.000991142
100000000 0.000327680
200000000 0.000163840
300000000 0.000109227

Начиная с некоторого момента, сумма перестаёт меняться после прибавления очередного слагаемого: это происходит, когда SumValues оказывается равным 32768, т.к. для представления результата суммирования типу float просто не хватает разрядности.


Из этой ситуации есть несколько выходов:


  1. Перейти с float на double.
  2. Использовать один из более сложных методов суммирования.
  3. Использовать метод Кэхэна для суммирования.
  4. Наконец, можно использовать метод Уэлфорда для непосредственного вычисления среднего.

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


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


class TWelfordMeanCalculator {
private:
    float MeanValues = 0.;
    size_t CountValues = 0;
public:
    void Add(const float value) {
        ++CountValues;
        MeanValues += (value - MeanValues) / CountValues;
    }

    double Mean() const {
        return MeanValues;
    }
};

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


Этот пример иллюстрирует основное достоинство метода Уэлфорда: все участвующие в арифметических операциях величины оказываются "сравнимы" по величине, что, как известно, способствует хорошей точности вычислений.


Ввод Вывод
10000 0.001
1000000 0.001
100000000 0.001
200000000 0.001
300000000 0.001

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


2. Обозначения


Вывод формулы часто можно сделать очень простым и понятным, если выбрать правильные обозначения. Попробуем и мы. Будем считать, что заданы две последовательности вещественных чисел: $x$ и $y$, и последовательность соответствующих им весов $w$:


$x = x_1,x_2,...,x_n,...$


$y = y_1,y_2,...,y_n,...$


$w = w_1,w_2,...,w_n,...$


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


$S_n^{a,b,...,z} = \sum_{i=1}^n{a_i\cdot b_i\cdot ...\cdot z_i}$


Тогда, например, $S_n^{w}$ — это сумма весов первых $n$ элементов, $S_n^{wx}$ — это взвешенная сумма первых $n$ чисел первой последовательности, а $S_n^{wxy}$ — сумма взвешенных произведений:


$S_n^{w} = \sum_{i=1}^n{w_i}$


$S_n^{wx} = \sum_{i=1}^n{w_i\cdot x_i}$


$S_n^{wxy} = \sum_{i=1}^n{w_i\cdot x_i\cdot y_i}$


Также понадобятся средние взвешенные величины:


$m_n^{wx} = \frac{\sum_{i=1}^n{w_i\cdot x_i}}{\sum_{i=1}^n{w_i}}= \frac{S_n^{wx}}{S_n^w}$


$m_n^{wy} = \frac{\sum_{i=1}^n{w_i\cdot y_i}}{\sum_{i=1}^n{w_i}}=\frac{S_n^{wy}}{S_n^w}$


Наконец, введём обозначения для ненормированных "разбросов" $D_{n}^{wxy}$ и нормированных ковариаций $C_{n}^{wxy}$:


$D_{n}^{wxy} = \sum_{i=1}^{n} w_i (x_i - m_{n}^{wx})(y_i - m_{n}^{wy})$


$C_{n}^{wxy} = \frac{D_{n}^{wxy}}{S_{n}^w} = \frac{\sum_{i=1}^{n} w_i (x_i - m_{n}^{wx})(y_i - m_{n}^{wy})}{\sum_{i=1}^{n}w_{i}}$


3. Вычисление средних


Докажем взвешенный аналог формулы, использованной нами выше для вычисления среднего по методу Уэлфорда. Рассмотрим разность $m_{n+1}^{wx} - m_{n}^{wx}$:


$m_{n+1}^{wx} - m_{n}^{wx} = \frac{S_{n+1}^{wx}}{S_{n+1}^{w}} - \frac{S_{n}^{wx}}{S_{n}^{w}}=\frac{S_{n}^{w}S_{n+1}^{wx} - S_{n}^{wx}S_{n+1}^{w}}{S_{n+1}^{w}S_{n}^{w}}=$


$=\frac{S_{n}^{w}S_{n}^{wx} + w_{n+1}x_{n+1}S_n^w - S_{n}^{wx}S_{n}^{w} - w_{n+1}S_n^{wx}}{S_{n+1}^{w}S_{n}^{w}}=\frac{w_{n+1}x_{n+1}S_n^w - w_{n+1}S_n^{wx}}{S_{n+1}^{w}S_{n}^{w}}$


$=\frac{w_{n+1}}{S_{n+1}^{w}}\Big(x_{n+1} - \frac{S_n^{wx}}{S_n^w}\Big)=\frac{w_{n+1}}{S_{n+1}^{w}}(x_{n+1} - m_n^{wx})$


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


$m_{n+1}^{wx} = m_{n}^{wx} + \frac{x_{n+1} - m_n^{wx}}{n+1}$


Кстати, формулы для "взвешенного" случая позволяют легко реализовывать операции, отличные от добавления ровно одного очередного числа. Например, удаление числа $x$ из множества, по которому вычисляется среднее — это то же, что добавление числа $-x$ с весом $-1$. Добавление сразу нескольких чисел — то же, что добавление одного из среднего с весом, равным количеству этих чисел, и так далее.


4. Вычисление ковариаций


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


$D_{n}^{wxy} = \sum_{i=1}^{n} w_i (x_i - m_n^{wx})(y_i - m_n^{wy}) = S_n^{wxy} - m_n^{wx}S_n^{wy} - S_n^{wx}m_n^{wy}+S_n^wm_n^{wx}m_n^{wy}$


Отсюда уже легко видеть, что


$D_{n}^{wxy} = S_n^{wxy} - \frac{S_n^{wx}S_n^{wy}}{S_n^w}$


Эта формула чрезвычайно удобна, в том числе и для онлайн-алгоритма, однако, если величины $S_n^{wxy}$ и ${S_n^{wx}S_n^{wy}}/{S_n^w}$ окажутся близкими и при этом большими по абсолютному значению, её использование приведёт к существенным вычислительным погрешностям.


Давайте попробуем вывести рекуррентную формулу для $D_n^{wxy}$, в каком-то смысле аналогичную формуле Уэлфорда для средних. Итак:


$ D_{n+1}^{wxy} = S_n^{wxy} + w_{n+1}x_{n+1}y_{n+1} - w_{n+1}x_{n+1}\frac{S_{n+1}^{wy}}{S_{n+1}^{w}} - \frac{S_n^{wx}S_{n+1}^{wy}}{S_{n+1}^w}=$


$= S_n^{wxy} + w_{n+1}x_{n+1}(y_{n+1} - m_{n+1}^{wy}) - \frac{S_n^{wx}S_{n+1}^{wy}}{S_{n+1}^w}$


Рассмотрим последнее слагаемое:


$\frac{S_n^{wx}S_{n+1}^{wy}}{S_{n+1}^w} = \Big(\frac{1}{S_n^w} - \frac{w_{n+1}}{S_n^wS_{n+1}^w}\Big)S_n^{wx}S_{n+1}^{wy}=\frac{S_n^{wx}S_{n+1}^{wy}}{S_n^w}-w_{n+1}m_n^{wx}m_{n+1}^{wy}=$


$=\frac{S_n^{wx}S_{n}^{wy}}{S_n^w}+w_{n+1}y_{n+1}\frac{S_n^{wx}}{S_n^w}-w_{n+1}m_n^{wx}m_{n+1}^{wy}=\frac{S_n^{wx}S_{n}^{wy}}{S_n^w}+w_{n+1}m_n^{wx}\cdot(y_{n+1}-m_{n+1}^{wy})$


Подставим получившееся в выражение для $D_{n+1}^{wxy}$:


$ D_{n+1}^{wxy} = S_n^{wxy} + w_{n+1}x_{n+1}(y_{n+1} - m_{n+1}^{wy}) - \frac{S_n^{wx}S_{n}^{wy}}{S_n^w}-w_{n+1}m_n^{wx}\cdot(y_{n+1}-m_{n+1}^{wy})=$


$= \Big[S_n^{wxy} - \frac{S_n^{wx}S_{n}^{wy}}{S_n^w}\Big] + w_{n+1}(x_{n+1}-m_n^{wx})(y_{n+1} - m_{n+1}^{wy})=$


$=D_{n}^{wxy} + w_{n+1}(x_{n+1}-m_n^{wx})(y_{n+1} - m_{n+1}^{wy})$


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


double WelfordCovariation(const std::vector& x, const std::vector& y) {
    double sumProducts = 0.;
    double xMean = 0.;
    double yMean = 0.;

    for (size_t i = 0; i < x.size(); ++i) {
        xMean += (x[i] - xMean) / (i + 1);
        sumProducts += (x[i] - xMean) * (y[i] - yMean);
        yMean += (y[i] - yMean) / (i + 1);
    }

    return sumProducts / x.size();
}

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


$C_{n+1}^{wxy} = \frac{\sum_{i=1}^{n+1} w_i (x_i - m_{n+1}^{wx})(y_i - m_{n+1}^{wy})}{\sum_{i=1}^{n+1}w_{i}}=\frac{D_{n+1}^{wxy}}{S_{n+1}^w} = $


$=\frac{1}{S_{n+1}^w}\cdot\Big( D_n^{wxy} + w_{n+1}(x_{n+1}-m_n^{wx})(y_{n+1} - m_{n+1}^{wy})\Big)=$


$=\frac{D_n^{wxy}}{S_{n+1}^w}+\frac{w_{n+1}}{S_{n+1}^w}(x_{n+1}-m_n^{wx})(y_{n+1} - m_{n+1}^{wy})$


Рассмотрим первое слагаемое:


$\frac{D_n^{wxy}}{S_{n+1}^w}=\Big(\frac{1}{S_n^w}-\frac{w_{n+1}}{S_{n+1}^wS_n^w}\Big)D_n^{wxy}=\frac{D_n^{wxy}}{S_n^w}\Big(1 - \frac{w_{n+1}}{S_{n+1}^w}\Big)=C_n^{wxy}\Big(1-\frac{w_{n+1}}{S_{n+1}^w}\Big)$


Вернёмся теперь к рассмотрению $C_{n+1}^{wxy}$:


$C_{n+1}^{wxy} = C_n^{wxy}\Big(1-\frac{w_{n+1}}{S_{n+1}^w}\Big) + \frac{w_{n+1}}{S_{n+1}^w}(x_{n+1}-m_n^{wx})(y_{n+1} - m_{n+1}^{wy})$


Это можно переписать, например, так:


$C_{n+1}^{wxy} = C_n^{wxy} + \frac{w_{n+1}}{S_{n+1}^w}\Big((x_{n+1}-m_n^{wx})(y_{n+1} - m_{n+1}^{wy}) - C_n^{wxy}\Big)$


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


5. Экспериментальное сравнение методов


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


  1. "Стандартный" алгоритм, напрямую вычисляющий величины $S_n^{wxy}$, $S_n^{wx}$ и $S_n^{wy}$.
  2. Алгоритм, использующий метод Кэхэна.
  3. Алгоритм, использующий метод Уэлфорда.

Данные в задаче формируются следующим образом: выбираются два числа, $m_x$ и $m_y$ — средние двух выборок. Затем выбираются еще два числа, $d_x$ и $d_y$ — соответственно, отклонения. На вход алгоритмам подаются последовательности чисел вида


$x_i=m_x \pm d_x,$


$y_i=m_y \pm d_y,$


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


$C_{n}^{xy} = \frac{\sum_{i=1}^{n} (x_i - m_x)(y_i - m_y)}{n} = d_x \cdot d_y $


Истинная ковариация константна и не зависит от числа слагаемых, поэтому мы можем вычислять относительную погрешность вычисления для каждого метода на каждой итерации. Так, в текущей реализации $d_x = d_y = 1$, а средние принимают значения 100000 и 1000000.


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



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



Метод Уэлфорда, в свою очередь, и на этих данных демонстрирует идеальную точность!


Заключение


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


class TWelfordMeanCalculator {
private:
    double Mean = 0.;
    size_t Count = 0;
public:
    void Add(const float value) {
        ++Count;
        Mean += (value - Mean) / Count;
    }
    double GetMean() const {
        return Mean;
    }
};

class TWelfordCovariationCalculator {
private:
    size_t Count = 0;
    double MeanX = 0.;
    double MeanY = 0.;
    double SumProducts = 0.;
public:
    void Add(const double x, const double y) {
        ++Count;
        MeanX += (x - MeanX) / Count;
        SumProducts += (x - MeanX) * (y - MeanY);
        MeanY += (y - MeanY) / Count;
    }
    double Covariation() const {
        return SumProducts / Count;
    }
};

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


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


Литература


  1. github.com: Different covariation calculation methods
  2. machinelearning.ru: Сложение большого множества чисел, существенно отличающихся по величине
  3. ru.wikipedia.org: Алгоритм Кэхэна
  4. en.wikipedia.org: Algorithms for calculating variance: Online algorithm
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333426/


Метки:  

Поиск по документации InterSystems с помощью технологий iKnow и iFind

Понедельник, 24 Июля 2017 г. 12:59 + в цитатник
image

В СУБД InterSystems Cach'e есть встроенная технология работы с неструктурированных данными iKnow, а также технология полнотекстового поиска iFind. Решили разобраться с технологией и заодно сделать что-то полезное. В итоге получился DocSearch — Веб приложение для поиска по документации InterSystems, с использованием технологий iKnow и iFind.

Как устроена документация в Cach'e


Документация в Cach'e сделана на технологии Docbook. Для доступа к документации поставляется веб-интерфейс (в том числе с поиском, который не использует ни iFind ни iKnow). Собственно данные статей документации лежат в классах Cach'e, что открывает возможность самостоятельно осуществлять запросы к этим данным, ну и соответственно возможность написать свою утилиту поиска.

Что такое iKnow и iFind:


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


Технология iFind это модуль СУБД Cach'e для выполнения операций полнотекстового поиска по данным классов Cach'e. iFind использует многие функции iKnow для обеспечения интеллектуального текстового поиска. Чтобы использовать iFind в запросах, необходимо описать в классе Cach'e специальный iFind индекс.


Существует три вида индексов iFind, каждый вид индекса предоставляет все функции предыдущего вида, плюс дополнительные функции:

  • Основной индекс (%iFind.Index.Basic): поддерживает поиск слов и словосочетаний.
  • Семантический индекс (%iFind.Index.Semantic): поддерживает поиск объектов iKnow.
  • Аналитический индекс (%iFind.Index.Analytic): поддерживает все функции iKnow в семантическом индексе, а также информацию о пути и близости слов.

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

Код для маппинга в установщике
XData Install [ XMLNamespace = INSTALLER ]
{

// Указываем название области





// Проверяем существует ли такая область




// Создаем область


	
// Создаем базу данных



// Маппируем указанные классы и глобалы в новую область
















 
}


Домен, который нужен нам для работы iKnow, строится по таблице содержащей документацию. Так как источником данных является таблица, будем использовать SQL.Lister. Поле content содержит текст документации, значит укажем его как поле данных. Остальные поля укажем в метаданных.


Код создания домена в установщике
ClassMethod Domain(ByRef pVars, pLogLevel As %String, tInstaller As %Installer.Installer) As %Status
{
	#Include %IKInclude
	#Include %IKPublic
	set ns = $Namespace
	znspace "DOCSEARCH"
	// Создание домена или открытие если он существует
	set dname="DocSearch" 
   	if (##class(%iKnow.Domain).Exists(dname)=1){
	   	write "The ",dname," domain already exists",!
		zn ns
		quit
        }
  	else {	 
  		write "The ",dname," domain does not exist",!
       	set domoref=##class(%iKnow.Domain).%New(dname)
       	do domoref.%Save()
        }
   	set domId=domoref.Id
   	// Lister используется для поиска источников, соответствующих записям в результатах запроса
  	set flister=##class(%iKnow.Source.SQL.Lister).%New(domId)
  	set myloader=##class(%iKnow.Source.Loader).%New(domId)
  	// Построение запроса
	set myquery="SELECT id, docKey, title, bookKey, bookTitle, content, textKey FROM SQLUser.DocBook"
 	set idfld="id"
 	set grpfld="id"
 	// Указываем поля данных и метаданных
  	set dataflds=$LB("content")
  	set metaflds=$LB("docKey", "title", "bookKey", "bookTitle", "textKey")
        //Занесем все данные в Lister
  	set stat=flister.AddListToBatch(myquery,idfld,grpfld,dataflds,metaflds)
        if stat '= 1 {write "The lister failed: ",$System.Status.DisplayError(stat) quit }
        //Запускаем процесс анализа
        set stat=myloader.ProcessBatch()
        if stat '= 1 {
	      quit 
	       }
        set numSrcD=##class(%iKnow.Queries.SourceQAPI).GetCountByDomain(domId)
        write "Done",!
        write "Domain cointains ",numSrcD," source(s)",!
        zn ns
        quit
}


Для поиска по документации мы используем индекс %iFind.Index.Analytic:


Index contentInd On (content) As %iFind.Index.Analytic(LANGUAGE = "en", LOWER = 1, RANKERCLASS = "%iFind.Rank.Analytic");

Где contentInd — название индекса, content — название поля для которого создаем индекс.
Параметр LANGUAGE = «en», указывает язык на котором написан текст
Параметром LOWER = 1, задает нечувствительность к регистру
Параметр RANKERCLASS = "%iFind.Rank.Analytic", позволяет использовать алгоритм ранжирования результатов TF-IDF

После добавления и построения такого индекса, его можно использовать, например в SQL запросах. Общий синтаксис использования iFind в SQL:


SELECT * FROM TABLE WHERE %ID %FIND search_index(indexname,'search_items',search_option)

После создания индекса %iFind.Index.Analytic с такими параметрами генерируются несколько SQL процедур вида — [Название таблицы]_[Название индекса]Название процедуры


В нашем проекте мы используем две из них:

  • DocBook_contentIndRank — Возвращает результат алгоритма ранжирования TF-IDF для запроса
    Синтаксис имеет вид:

    SELECT DocBook_contentIndRank(%ID, ‘SearchString’, ‘SearchOption’) Rank FROM DocBook WHERE %ID %FIND search_index(contentInd,‘SearchString’, ‘SearchOption’)
  • DocBook_contentIndHighlight — возвращает результаты поиска, где искомые слова обрамлены в указанный тег:

    SELECT DocBook_contentIndHighlight(%ID, ‘SearchString’, ‘SearchOption’,’Tags’) Text FROM DocBook WHERE %ID %FIND search_index(contentInd,‘SearchString’, ‘SearchOption’)

Про использование этих процедур я расскажу чуть ниже.

Что в итоге получилось:


  1. Автозаполнение в строке поиска


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

    Происходит этот процесс с помощью iKnow, метода %iKnow.Queries.Entity.GetSimilar


    image

  2. Нечеткий поиск


    Технология iFind поддерживает нечеткий поиск, для нахождения слов, почти соответствующих строке поиска. Реализуется с помощью сравнения расстояния Левенштейна между двумя словами. Расстояние Левенштейна — это минимальное количество односимвольных изменений (вставки, удаления или замены), необходимые для изменения одного слова в другое. Может быть использовано для исправления опечаток, небольших вариаций в письменной форме, различных грамматических форм (единственное и множественное число).


    В SQL запросах iFind, за использование нечеткого поиска отвечает параметр search_option.
    Значение search_option = 3, означает расстояния Левенштейна равное двум.

    Для задания расстояния Левенштейна равное n, следует указать значение search_option = ‘3:n’
    В поиске по документации используется расстояния Левенштейна равное единице, продемонстрируем как это работает:

    Наберем в поиске слово ifind:


    image

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


    image

  3. Сложные запросы


    Благодаря тому, что iFind поддерживает сложные запросы с применением скобок и операторов AND OR NOT, мы реализовали расширенный поиск. В поиске можно указать: слово, словосочетания, любое из нескольких слов, либо не содержащие некоторые слова. Поля можно заполнять как одно или несколько, так и все сразу.


    Например, найдем статьи содержащие слово iknow, словосочетание rest api и содержащие любое из слов domain или UI.


    image

    Видим, что таких статей две:


    image

    Заметим, что во второй статье упоминается Swagger UI, можно добавить в запрос, поиск статьей, не содержащих слово Swagger


    image

    В итоге найдена только одна статья:


    image

  4. Подсветка результатов поиска


    Как уже говорилось выше, использование iFind индекса, создает процедуру DocBook_contentIndHighlight. Используя:


    SELECT DocBook_contentIndHighlight(%ID, 'search_items', '0', '', 0) Text FROM DocBook

    Получаем искомый текст обрамленный в тег



    Это позволяет на фронтенде визуально выделять результаты поиска.


    image

  5. Алгоритм ранжирования результатов


    iFind поддерживает возможность ранжирования результатов по алгоритму TF-IDF. Мера TF-IDF часто используется в задачах анализа текстов и информационного поиска, например, как один из критериев релевантности документа поисковому запросу.


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


    SELECT DocBook_contentIndRank(%ID, ‘SearchString’, ‘SearchOption’) Rank FROM DocBook WHERE %ID %FIND search_index(contentInd,‘SearchString’, ‘SearchOption’)

  6. Интеграция с официальным поиском по документации


    После установки, в официальный поиск по документации добавляется кнопка “Search using iFind”.


    image

    Если заполнено поле Search words, то после нажатия на “Search using iFind”, будет выполнен переход на страницу с результатами поиска для введенного запроса.


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

Установка


  1. Загрузите из последнего релиза с страницы релизов, файл Installer.xml
  2. Импортируйте загруженный файл Installer.xml в область %SYS, скомпилирйте.
  3. В терминале в области %SYS введите следующую команду:

    do ##class(Docsearch.Installer).setup(.pVars)

    Процесс занимает около 15-30 минут из-за процесса построения домена.

После этого, поиск доступен по адресу localhost:[порт]/csp/docsearch/index.html

Демо


Онлайн-демо поиска доступно здесь.

Заключение


В этом проекте демонстрируются интересные и полезные возможности технологий iFind и iKnow, благодаря которым поиск становиться релевантнее.
Критика, замечания, предложения — приветствуются.
Весь исходный код с инсталлятором и инструкцией по установке выложен на гитхаб
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333582/


Метки:  

[Перевод] Создание шейдера дыма на GLSL

Понедельник, 24 Июля 2017 г. 12:32 + в цитатник
image
[Дым на КДПВ несколько сложнее получаемого в туториале.]

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

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

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

Чему мы научимся


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


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

Подготовка


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

Все примеры кода хранятся на CodePen, но их можно также найти в связанном со статьёй репозитории GitHub (там код может быть удобнее читать).

Теория и основы


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

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


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

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


Просмотреть интерактивное демо на CodePen.

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

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

//W = количество столбцов в сетке
//H = количество строк
//f = коэффициент распределения/рассеивания
//Сначала мы копируем сетку в newGrid, чтобы не изменять сетку, потому что выполняем считывание из неё
for(var r=1; rcode>

Этот фрагмент кода является основой алгоритма. Каждая ячейка получает часть значений четырёх соседних ячеек минус её собственное значение, где f — коэффициент меньше 1. Мы умножаем текущее значение ячейки на 4, чтобы оно рассеивалось от высоких к низким значениям.

Чтобы это стало понятно, рассмотрим следующую ситуацию:



Возьмём ячейку посередине (в позиции [1,1] в сетке) и применим указанное выше уравнение рассеивания. Допустим, что f равен 0.1:

0.1 * (100+100+100+100-4*100) = 0.1 * (400-400) = 0

Никакого рассеивания не происходит, потому что все ячейки имеют одинаковые значения!

Рассмотрим тогда ячейку в верхнем левом углу (считаем, что значения всех ячеек за пределами показанной сетки равны 0):

0.1 * (100+100+0+0-4*0) = 0.1 * (200) = 20

Итак, теперь у нас получился чистый прирост 20! Давайте рассмотрим последний случай. После одного временного шага (после применения этой формулы ко всем ячейкам) наша сетка будет выглядеть вот так:



Давайте снова посмотрим на рассеивание в ячейке посередине:

0.1 * (70+70+70+70-4*100) = 0.1 * (280 - 400) = -12

Мы получили чистое уменьшение в 12! Поэтому значения всегда изменяются от больших к меньшим.

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

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

Реализация


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


Нажимайте на кнопки вверху, чтобы увидеть код HTML, CSS и JS.

Наш шейдер прост:

uniform vec2 res;
void main() {
    vec2 pixel = gl_FragCoord.xy / res.xy;
    gl_FragColor = vec4(0.0,0.0,0.0,1.0);
 }

res и pixel сообщают нам координаты текущего пикселя. Мы передаём размеры экрана в res как uniform-переменную. (Пока мы их не используем, но скоро они пригодятся.)

Шаг 1: перемещаем значения между пикселями


Повторю ещё раз то, что мы хотим реализовать:

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

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

Каждый пиксель должен получить немного цвета своих соседей и потерять немного своего.

Теперь это решение можно реализовать. Однако если попробовать сделать это, то мы наткнёмся на фундаментальную проблему…

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

uniform vec2 res;
uniform sampler2D texture;
void main() {
    vec2 pixel = gl_FragCoord.xy / res.xy;

    gl_FragColor = texture2D( tex, pixel );// Это цвет текущего пикселя
    gl_FragColor.r += 0.01;// Инкремент красного компонента 	
 }

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

Понимаете, почему так происходит?

Проблема


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



Мы передаём uniform-переменные и текстуру шейдеру, он делает пиксели немного краснее, отрисовывает их на экране, а затем начинает всё заново. Всё, что мы отрисовываем в шейдере, очищается при следующем шаге отрисовки.

Нам же нужно что-то вроде этого:



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

Фокус с буфером кадра


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

В ThreeJS аналог этой функции — WebGLRenderTarget. Именно его мы будем использовать в качестве промежуточной текстуры для рендеринга. Однако осталось небольшое препятствие: нельзя считывать и рендерить в одну текстуру одновременно. Проще всего обойти это ограничение использованием двух текстур.

Пусть A и B — это две созданные нами текстуры. Тогда способ будет следующим:

  1. Передаём A через шейдер, рендерим в B.
  2. Рендерим B на экран.
  3. Передаём B через шейдер, рендерим в A.
  4. Рендерим A на экран.
  5. Повторяем 1.

Более краткий код будет следующим:

  1. Передаём A через шейдер, рендерим в B.
  2. Рендерим B на экран.
  3. Меняем A и B (то есть переменная A теперь содержит текстуру, находившуюся в B, и наоборот).
  4. Повторяем 1.

На этом всё. Вот реализация этого алгоритма в ThreeJS:


Новый код шейдера находится во вкладке HTML.

Мы по-прежнему видим чёрный экран, с которого и начинали. Шейдер тоже не слишком отличается:

uniform vec2 res; // Ширина и высота экрана
uniform sampler2D bufferTexture; // Входная текстура
void main() {
    vec2 pixel = gl_FragCoord.xy / res.xy;
    gl_FragColor = texture2D( bufferTexture, pixel );
}

Кроме того, что теперь мы добавили эту строку (протестируйте!):

gl_FragColor.r += 0.01;

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

Задача: Что произойдёт, если мы вставим gl_FragColor.r += pixel.x; в примере с буфером кадра, в отличие от первоначального примера? Подумайте немного, почему отличаются результаты, и почему они именно такие.

Шаг 2: получаем источник дыма


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

// Получаем расстояние от этого пикселя до центра экрана
float dist = distance(gl_FragCoord.xy, res.xy/2.0);
if(dist < 15.0){ // Создаём круг радиусом 15 пикселей
	gl_FragColor.rgb = vec3(1.0);
}

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

// Получаем расстояние от этого пикселя до центра экрана
float dist = distance(gl_FragCoord.xy, res.xy/2.0);
if(dist < 15.0){ // Создаём круг радиусом 15 пикселей
	gl_FragColor.rgb += 0.01;
}

Ещё один способ — заменить эту фиксированную точку положением мыши. Мы можем передавать третье значение, сообщающее о том, нажата ли клавиша мыши. Таким образом мы можем создавать дым нажатием левой клавиши. Вот реализация этой возможности:


Нажмите, чтобы создать дым.

Вот как выглядит наш шейдер:

// Ширина и высота экрана
uniform vec2 res; 
// Входная текстура
uniform sampler2D bufferTexture; 
// x,y - это положение. z - это сила/плотность
uniform vec3 smokeSource;

void main() {
    vec2 pixel = gl_FragCoord.xy / res.xy;
    gl_FragColor = texture2D( bufferTexture, pixel );

    // Получаем расстояние от текущего пикселя до источника дыма
    float dist = distance(smokeSource.xy,gl_FragCoord.xy);
    // Создаём дым, когда нажата клавиша мыши
    if(smokeSource.z > 0.0 && dist < 15.0){
    	gl_FragColor.rgb += smokeSource.z;
    }
}

Задача: не забывайте, что ветвление (условные переходы) обычно в шейдерах затратны. Можете переписать шейдер без использования конструкции if? (Решение есть в CodePen.)

Если вы не понимаете, то в предыдущем туториале есть подробное объяснение использования мыши в шейдерах (в части об освещении).

Шаг 3: рассеиваем дым


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

Это выражается примерно так:

// Рассеивание дыма
float xPixel = 1.0/res.x; // Размер единичного пикселя
float yPixel = 1.0/res.y;

vec4 rightColor = texture2D(bufferTexture,vec2(pixel.x+xPixel,pixel.y));
vec4 leftColor = texture2D(bufferTexture,vec2(pixel.x-xPixel,pixel.y));
vec4 upColor = texture2D(bufferTexture,vec2(pixel.x,pixel.y+yPixel));
vec4 downColor = texture2D(bufferTexture,vec2(pixel.x,pixel.y-yPixel));

// Уравнение рассеивания
gl_FragColor.rgb += 
    14.0 * 0.016 * 
	(
		leftColor.rgb + 
		rightColor.rgb + 
		downColor.rgb + 
		upColor.rgb - 
		4.0 * gl_FragColor.rgb
	);

Коэффициент f остаётся прежним. В этом случае у нас есть временной шаг (0.016, то есть 1/60, потому что программа выполняется с частотой 60 fps), и я подбирал разные числа, пока не остановился на значении 14, которое хорошо выглядит. Вот результат:



Ой-ёй, всё зависло!


Это то же самое уравнение рассеивания, которое мы использовали в демо для центрального процессора, но наша симуляция останавливается! В чём причина?

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

float factor = 14.0 * 0.016 * (leftColor.r + rightColor.r + downColor.r + upColor.r - 4.0 * gl_FragColor.r);
// Нам нужно учитывать низкую точность текселов
float minimum = 0.003;
if (factor >= -minimum && factor < 0.0) factor = -minimum;

gl_FragColor.rgb += factor;

Я использую для получения коэффициента компонент r вместо rgb, потому что легче работать с отдельными числами и потому, что все компоненты всё равно имеют одинаковые значения (так как дым белый).

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


Шаг 4: рассеивание дыма вверх


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

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

// Уравнение рассеивания
float factor = 8.0 * 0.016 * 
    (
		leftColor.r + 
		rightColor.r + 
		downColor.r * 3.0 + 
		upColor.r - 
		6.0 * gl_FragColor.r
	);

И вот как выглядит шейдер:


Примечание об уравнении рассеивания


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

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

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

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

Небольшое исправление


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

Чтобы исправить это, мы дадим нижним пикселям находить под собой 0:

// Обрабатываем нижнюю границу
// Эти строки нужно выполнять до функции рассеивания
 if(pixel.y <= yPixel){
 	downColor.rgb = vec3(0.0);
 }

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

Сетка скоростей


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


Этап переноса (адвекции) перемещает плотность по статичному полю скоростей.

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

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

Заключение


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

Для чего могут пригодиться буферы кадров?


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

Ещё один пример использования — реализация шейдеров, требующих нескольких проходов. например blur. Обычно изображение пропускается через шейдер, размывается по оси x, а затем пропускается снова для размывания по оси y.

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

Не бойтесь технических статей


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

Надеюсь, что вы немного больше узнали о шейдерах и статья оказалась полезной.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333718/


Метки:  

Анонс RamblerFront& #2

Понедельник, 24 Июля 2017 г. 12:31 + в цитатник


3 августа на Мансарде Rambler&Co состоится второй внешний RamblerFront& meetup, на котором наши сотрудники поделятся прикладными знаниями в области frontend-разработки.

Темы докладов:

1. Снова ошибки – Денис Денисов (руководитель группы frontend-разработки интерфейсов Rambler&Co)
В коде начинающих программистов обычно много ошибок. Постепенно мы набираемся опыта, код становится надежнее, покрывается тестами, и нам кажется, что ошибки в нашем коде практически исключены. Но фронтенд — это неконтролируемая среда и произойти может что угодно. Небольшой доклад о том, как начать использовать ошибки в свою пользу.

2. TDD адаптивной верстки c применением Galen Framework – Павел Карев (инженер-разработчик клиентских приложений, Rambler Digital Solutions)
Павел расскажет про способы тестирования верстки и сам Galen Framework — что это и как работает, опыт использования на проекте, его плюсы и минусы, а также небольшое сравнение с BackstopJS.

3. Оптимизация изоморфных приложений на React – Михаил Погорельских (руководитель группы frontend-разработки главной страницы Rambler&Co)
Михаил расскажет об оптимизации изоморфного react-приложения на примере обновленного сайта rambler.ru. Также разберем assets, конфигурацию Webpack и Server Side Rendering.

Мероприятие бесплатное, а регистрация обязательна – rambler-co-e-org.timepad.ru/event/543173
С нас пицца и чай!

Начало в 19.00
Место: Варшавское шоссе, д. 9, стр. 1, подъезд №5. Мансарда Rambler&Co

image

Обязательно зарегистрируйтесь и возьмите с собой паспорт, чтобы вас пропустила охрана бизнес-центра!

Приходите, будет интересно!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333834/


Метки:  

КОГДА В РОССИИ ЖДАТЬ 5G

Понедельник, 24 Июля 2017 г. 11:53 + в цитатник

Метки:  

Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1062 1061 [1060] 1059 1058 ..
.. 1 Календарь