...

суббота, 14 марта 2015 г.

Метаоператоры X и Z в Perl 6

Одна из новых идей Perl 6 – метаоператор. Это оператор, который можно скомбинировать с обычным оператором, изменив его поведение. Таких метаоператоров есть несколько штук, но в этой статье мы рассмотрим только X и Z.

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



> say ((1, 2) X ('a', 'b')).perl
((1, "a"), (1, "b"), (2, "a"), (2, "b"))


Однако, запись infix:<X> — это короткая запись метаоператора X, примененного к оператору конкатенации infix:<,> . И действительно, можно написать:



> say ((1, 2) X, (10, 11)).perl
((1, 10), (1, 11), (2, 10), (2, 11))


Что произойдёт, если мы применим Х к другому инфиксному оператору? Например, к infix:<+>




> say ((1, 2) X+ (10, 11)).perl
(11, 12, 12, 13)


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


Это работает со всеми инфиксными операторами. Возьмём объединение строк infix:<~>



> say ((1, 2) X~ (10, 11)).perl
("110", "111", "210", "211")


Или же числовое сравнение infix:<==>



> say ((1, 2) X== (1, 1)).perl
(Bool::True, Bool::True, Bool::False, Bool::False)


Теперь перейдём к метаоператору Z. Если вы уже встречали инфиксный оператор infix:<Z>, который выступает короткой записью оператора «Z,», вы можете догадаться, для чего он нужен. Если программист на Haskell воспринимает infix:<Z> как zip-функцию, то метаоператор Z – это как функция zipWith.



> say ((1, 2) Z, (3, 4)).perl
((1, 3), (2, 4))
> say ((1, 2) Z+ (3, 4)).perl
(4, 6)
> say ((1, 2) Z== (1, 1)).perl
(Bool::True, Bool::False)


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


Также Z – ленивый, поэтому его можно применить к двум бесконечным спискам, и он выдаст столько результатов, сколько вы запросите. X может работать только когда бесконечный список будет слева.


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


Сделать ассоциативный массив из списков ключей и значений? Легко.



my %hash = @keys Z=> @values;


Пройти по двум спискам одновременно:



for @a Z @b -> $a, $b { ... }


Или по трём?



for @a Z @b Z @c -> $a, $b, $c { ... }


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



my @d10 = 1 ... 10;
my @scores = (@d10 X+ @d10) X+ @d10;


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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Перевод] Прощай, MongoDB, здравствуй, PostgreSQL

Наш стартап Olery был основан почти 5 лет назад. Мы начали с единственного продукта, Olery Reputation, который был создан агентством, занимавшимся разработкой на Ruby. Всё это выросло в набор различных продуктов. Сегодня у нас есть ещё Olery Feedback, API для Hotel Review Data, виджеты для вставки на сайты и многое другое.

Всего у нас работает 25 приложений (все на Ruby) – некоторые из них в вебе (Rails или Sinatra), но в основном это фоновые приложения для обработки данных.


Хотя нам есть, чем гордиться, есть у нас одна проблема, которая всё время висела где-то в фоне – база данных. Изначально мы использовали MySQL для важных данных (пользователи, контракты, и т.д.) и MongoDB для хранения обзоров и других данных, которые легко можно было бы восстановить в случае утери. Сначала всё работало неплохо, но по мере роста мы начали испытывать проблемы, в особенности с MongoDB. Некоторые из них возникали в сфере взаимодействия БД с приложениями, некоторые – непосредственно у самой БД.


К примеру, в какой-то момент нам надо было удалить миллион документов из MongoDB, а позже вставить. В результате работа базы застопорилась на несколько часов. Потом нам пришлось запускать repairDatabase. И сама починка тоже заняла несколько часов.



В другой раз мы заметили тормоза и определили, что причиной их стал кластер MongoDB. Но мы так и не смогли разобраться, что именно тормозит в базе. Независимо от того, какие средства для отладки и сбора статистики мы пробовали. Нам пришлось (вот тут я не знаю, как перевести, подскажите в комментариях: " until we replaced the primaries of the cluster "), чтобы быстродействие вернулось в норму.


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


Проблема отсутствия схемы




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

К примеру, у вас может быть набор страниц, в которых приложение ожидает найти поле title типа string. Схема есть, хотя явно и не задана. Проблемы начинаются, если структура данных со временем меняется, а старые данные не переносятся в новую структуру (что довольно трудно сделать в случае бессхемных баз). Допустим, у вас есть такой код:



post_slug = post.title.downcase.gsub(/\W+/, '-')


Это будет работать для всех документов, у которых есть поле title, возвращающее String. Если у документов есть поле с другим именем, или вообще нет строкового поля, это сломается. Для обработки подобных случаев вам надо переписать код:



if post.title
post_slug = post.title.downcase.gsub(/\W+/, '-')
else
# ...
end


Другой способ – задать схему в базе данных. К примеру, Mongoid, популярный MongoDB ODM для Ruby, позволяет это сделать. Но зачем задавать схему через такие инструменты, если можно задать схему в самой базе данных? Это было бы разумно и для повторного использования. Если у вас только одно приложение работает с базой данных, это не страшно. А если их дюжина, то всё это быстро превращается в кавардак.


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


Требования к хорошей БД




И мы приходим к вопросу о том, какой должна быть хорошая БД. Мы в Olery ценим следующее:

— связность

— данные и поведение системы видно извне

— корректность и недвусмысленность

— масштабируемость


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


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


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


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


Уход от MongoDB




Обдумав всё это, мы пошли на поиски замены для MongoDB. Поскольку наши запросы явно подходили под традиционные реляционные БД, мы обратили взоры на двух кандидатов: MySQL и PostgreSQL.

MySQL был первым, в частности потому, что мы его уже использовали в кое-каких случаях. Но и у него есть свои проблемы. К примеру, задав поле как int(11), вы можете вставить туда текст, и MySQL попытается его сконвертировать. Примеры:



mysql> create table example ( `number` int(11) not null );
Query OK, 0 rows affected (0.08 sec)

mysql> insert into example (number) values (10);
Query OK, 1 row affected (0.08 sec)

mysql> insert into example (number) values ('wat');
Query OK, 1 row affected, 1 warning (0.10 sec)

mysql> insert into example (number) values ('what is this 10 nonsense');
Query OK, 1 row affected, 1 warning (0.14 sec)

mysql> insert into example (number) values ('10 a');
Query OK, 1 row affected, 1 warning (0.09 sec)

mysql> select * from example;
+--------+
| number |
+--------+
| 10 |
| 0 |
| 0 |
| 10 |
+--------+
4 rows in set (0.00 sec)


Хотя MySQL и выдаёт предупреждения, предупреждения часто просто игнорируются.


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


Поэтому мы начали присматриваться к PostgreSQL. У неё есть много преимуществ. Например, нельзя вставить текст в числовое поле:



olery_development=# create table example ( number int not null );
CREATE TABLE

olery_development=# insert into example (number) values (10);
INSERT 0 1

olery_development=# insert into example (number) values ('wat');
ERROR: invalid input syntax for integer: "wat"
LINE 1: insert into example (number) values ('wat');
^
olery_development=# insert into example (number) values ('what is this 10 nonsense');
ERROR: invalid input syntax for integer: "what is this 10 nonsense"
LINE 1: insert into example (number) values ('what is this 10 nonsen...
^
olery_development=# insert into example (number) values ('10 a');
ERROR: invalid input syntax for integer: "10 a"
LINE 1: insert into example (number) values ('10 a');


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


Есть и другие интересные особенности, а именно: индекс и поиск, основанный на триграммах, полнотекстовый поиск, поддержка JSON-запросов, поддержка запросов и хранения пар ключ-значение, поддержка pub/sub и многое другое.


А самое важное, у PostgreSQL есть баланс между быстродействием, надёжностью, корректностью и связностью.


Переход на PostgreSQL




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

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

— обновление приложений, которые работают с MongoDB, для работы с PostgreSQL, включая какой-либо рефакторинг

— миграция продакшена на новую БД и размещение на новой платформе


Миграция небольшой части данных



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



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

— замена драйвера/кода/модели MongoDB на код для PostgreSQL

— прогон тестов

— исправление тестов

— повторить пункт 2


Для приложений, работавших не на Rails, мы остановились на использовании Sequel. Для Rails взяли ActiveRecord. Sequel – удобный набор инструментов, поддерживающий почти все особые функции PostgreSQL. В построении запросов он выигрывает у ActiveRecord, хотя местами получается чересчур много текста.


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



SELECT locale,
count(*) AS amount,
(count(*) / sum(count(*)) OVER ()) * 100.0 AS percentage

FROM users

GROUP BY locale
ORDER BY percentage DESC;


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



locale | amount | percentage
--------+--------+--------------------------
en | 2779 | 85.193133047210300429000
nl | 386 | 11.833231146535867566000
it | 40 | 1.226241569589209074000
de | 25 | 0.766400980993255671000
ru | 17 | 0.521152667075413857000
| 7 | 0.214592274678111588000
fr | 4 | 0.122624156958920907000
ja | 1 | 0.030656039239730227000
ar-AE | 1 | 0.030656039239730227000
eng | 1 | 0.030656039239730227000
zh-CN | 1 | 0.030656039239730227000
(11 rows)


Sequel позволяет написать такой запрос на чистом Ruby без строковых фрагментов (которые иногда требуются для ActiveRecord):



star = Sequel.lit('*')

User.select(:locale)
.select_append { count(star).as(:amount) }
.select_append { ((count(star) / sum(count(star)).over) * 100.0).as(:percentage) }
.group(:locale)
.order(Sequel.desc(:percentage))


Если вам не хочется использовать Sequel.lit('*'), можно поступить так:



User.select(:locale)
.select_append { count(users.*).as(:amount) }
.select_append { ((count(users.*) / sum(count(users.*)).over) * 100.0).as(:percentage) }
.group(:locale)
.order(Sequel.desc(:percentage))


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


В планах мы хотим перевести приложения, работающие с Rails, на Sequel. Но пока непонятно, стоит ли это потраченного времени


Миграция рабочих данных



Для этого есть два способа:

— остановить весь проект, мигрировать данные, поднять проект

— мигрировать параллельно с работающим проектом


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


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


План такой:


— перенести критичные данные – пользователи, контракты, и т.д.

— перенести менее критичные данные (которые потом можно пересчитать или восстановить)

— проверить, что всё работает на наборе раздельных серверов

— перенести продакшен на новые сервера

— перенести все критичные данные, которые появились с первого шага


Второй шаг занял сутки. Первый и пятый – по 45 минут.


Заключение




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

image


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


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


image


Наши сборщики данных также ускорились.


image


Разница вышла не такой сильной, но сборщики и не так сильно используют базу данных.


И, наконец, приложение, которое распределяет график работы сборщиков данных (“scheduler”):


image


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


Итак, мы вполне удовлетворены результатами переезда, и скучать по MongoDB не собираемся. Быстродействие отличное, инструменты для работы с базой очень удобные, запросы к данным делать стало гораздо проще, в сравнении с MongoDB. Единственный сервис, который ещё её использует, это Olery Feedback. Он работает на своём отдельном кластере. Но и его мы в будущем также собираемся перевести на PostgreSQL.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Олдскул, хардкор, AY-3-8912. «Железный» чиптюн с последовательным входом


Клона Spectrum 128K, оснащенного музыкальным сопроцессором AY-3-8910 (YM2149F) у меня не было. Был 48K с расширенной клавиатурой и убогим блоком питания, перегревающим внутренности через час-два работы. От этого, помнится. домики посреди моря в Sim City образовывались и другие веселые артефакты. Но к делу данные воспоминания не относятся. Вдохновившись материалом tronix286, я решил восполнить пробел в ретро-образовании и склепать что-нибудь на легендарном (и при этом, легко добываемом и недорогом) чипе.


В ходе изучения различных поделок, идея сформировалась следующая: надо делать модуль с последовательным (UART) входом. Чтобы его уже можно было подключить с минимальными затратами к любому девайсу, добавляя тем самым +146 к чиптюновости. В процессе также было решено освоить пару дополнительных навыков, вроде программирования AVR и изготовления печатных плат с применением фоторезиста.


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




Синтезатор


AY-3-8910 — это большой красивый DIP-чип на 40 ножек. Помимо нужных вещей, там еще два 8-разрядных порта, которые для вывода звука нормальными людьми (не пытающимися на них сколхозить адский аналог Covox) не используются. AY-3-8912 не менее красив, но одним «лишним» портом обделен и упакован уже в DIP28. И еще бывает AY-3-8913, вообще без параллельного порта (DIP24). И это только General Instrument / Microchip. Yamaha клепала еще больше вариаций: от YM2149F (аналог AY-3-8910 с делителем тактовой частоты) до YMZ284 (DIP16, один смикшированный выход каналов). Подробнее о чипах на Wiki (англ.).


Управление


Для загрузки данных используется 8-разрядный параллельный порт. Плюс, три управляющих линии (одну, которая BC2, подтягиваем к плюсу питания). Логика следующая:


1. Исходное состояние — BC1=0, BDIR=0.

2. Устанавливаем на ножках порта AY адрес регистра.

3. BC1=1, BDIR=1. Загрузка адреса. Задержка между включением линий должна составлять не более 50 нс, поэтому всякие медленные подергивания ножек (типа ардуинского DigitalWrite) не годятся, надо, например, PORTC |= 0b00110000;

4. BC1=0, BDIR=0.

5. Устанавливаем на ножках порта AY значение регистра.

6. BC1=0, BDIR=1. Загрузка значения.


Повторяем последовательность 14 раз (для каждого нужного регистра). И все это — 50 раз в секунду. Получаем музыку.


Выбор конкретного железа


В моем случае следующий:

1. AY-3-8912 — был дешевле.

2. Atmega8A (DIP28) — доступно, достаточно выводов.

3. Кварцевый генератор на 4 МГц — для тактирования AY.

4. Счетчик К555ИЕ5 — как делитель частоты для тактирования AY.

5. Кварц на 16 МГц — для Atmega8A.

6. Для подключения к ПК — USB UART на FT232R.



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


1. Если использовать YM2149F, то не нужен счетчик, т.к. в этом чипе есть встроенный делитель частоты на 2.

2. Похоже, что кварц для Atmega тоже не нужен — все прилично работает и от внутреннего генератора на 8 МГц.

3. В теории, можно попробовать вообще избавиться от кварцевого генератора для AY, если поковыряться с аппаратными таймерами и счетчиками Atmega. Но! В этом случае мы сможем тактировать AY только на 2 МГц. А по-хорошему, надо иметь возможность тактирования на 1.7(много цифр) МГц — как это делается в Speccy. У меня кварцевый генератор на 4 МГц стоит в DIP-колодке, чтобы потом его заменить на 3.5(много цифр) МГц.


Выход звука срисован у tronix286, там горстка резисторов и два конденсатора.


Софт


Для вдохновления изучалась вот эта (недо)реализация. Там описан общий принцип работы связки «источник — UART — Atmega — AY», но использование на «меге» загрузчика Arduino в данном случае показалось мне совершенно лишним. Ну и, программа на ПК, написання на C#, мне не понравилась. Шарп здесь примерно так же «нужен», как и Arduino. Формат YM разложен по полочкам здесь.


Прошивка Atmega


Исходный код и hex доступны на гитхабе (ссылка в конце материала), пробегусь просто по основным функциям.


valToPort — запись 8-разрядного значения в «порт», состоящий из половинки порта B и половинки C. Так было удобнее разводить.

sendToAY — запись 8-разрядного значения в регистр AY. Здесь как раз реализована логика, описанная в пункте «Управление».

setup — инициализация портов и UART.

main — зацикленное «получить 16 байт — записать в AY».


Демонстрационный пример на PC



Написан на Python 3 с использованием PySerial. Как и прошивка, лежит на гитхабе. Берет файл 1.ym (несжатый!) из текущей директории, разбирает его и заталкивает в COM6. Ради интереса пример проверен на OS X, работает «из коробки», достаточно только поменять название порта. Подозреваю, что столь же успешно будет работать на Linux, в т.ч. на «малинке».


В выдаче дампа регистров на AY есть один нюанс. Формат YM хранит данные в виде «все значения регистра 0, все значения регистра 1...». Это очень правильно с точки зрения дальнейшего сжатия. Я же работаю с несжатым YM, и мне нужно выдавать пачки байт «регистр 0, регистр 1...». Для PC задача решена в лоб — читаем данные из файла в нужном порядке в большой массив, затем из него последовательно отдаем контроллеру. Когда нужно будет делать «головное устройство» на базе чипа с малым объемом памяти, придется изобретать какие-то буферы.


Итого


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


Имеется и недопойманный баг. Который, наверное, прячется где-то в районе «Windows 10 — Python 3 — UART». Периодически скорость обновления падает с 50 Гц до 20 (осциллоскопировано). Системы в явлении не обнаружено, на другом компьютере глюк не воспроизвелся. Если поймаю когда-нибудь, сделаю UPD.


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


Исходники, разводка платы (SL6), схема модуля (Eagle) на гитхабе.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Из песочницы] Go для системных администраторов. Практические примеры. Часть 0

Здравствуйте, меня зовут Виталий и я обезьяна практик — для меня лучше один раз увидеть и скопировать, чем сто раз прочитать абстрактные руководства. Долгое время я был обычным системным администратором — писал скрипты на CMD/BAT, и даже на sh (при помощи busybox для Windows). Но однажды обычного shell мне стало не хватать, и я решил для себя написать собственный RPC-сервер, но так, чтобы он работал при минимуме системных компонентов, и был понятным, и был многопоточным и содержал минимум строк кода. Java и прочее ООП я отмел, так как для профессионалов, и слишком абстрактно, и надо ставить среду для выполнения на целевой компьютер, и мне же, как админу, её обновлять. Долгое время приглядывался к perl, но я боюсь динамической типизации. В статье я расскажу, как человеку мало знакомому с программированием решить некоторые задачи системного администрирования при помощи Go.



Я предполагаю, что вы осилили «Быстрый старт – программируем на Go под Windows — настройка Environment», имеете опыт написания простейших скриптов. А еще я соврал. На целевой машине может потребоваться Microsoft Visual C++.

Для начала мы попробуем превратить простой скрипт в приложение на Go. Для примера возьмем test.bat:



@echo off
set URL1="http://ift.tt/1LaCeLQ"
set URL2="http://ift.tt/1EdPGWb"
set SAVE_PATH=".\test\test.zip"
echo Первый источник
curl %URL1% -o %SAVE_PATH%
if %errorlevel% NEQ 0 (
echo Загрузка из первого источника закончилась с ошибкой: %errorlevel%
curl %URL2% -o %SAVE_PATH%
)




Минутка любви к Microsoft. Если я хочу проверить, скачался ли файл из второго источника, то я должен использовать GOTO, т. к. внутри IF и FOR %errorlevel% и %time% остаются такими же как перед вызовом IF и FOR.

Примерно так будет выглядеть наш скрипт на Go:



package main

import (
"fmt"
"os"
"os/exec"
)

func main() {
url1 := "http://ift.tt/1LaCeLQ"
url2 := "http://ift.tt/1EdPGWb"
out_file := ".\\test\\test.zip" //В Go бэкслэш используется для экранирования, так что в пути windows его придется удваивать.
err := start_curl(url1, out_file)
if err != nil {
fmt.Printf("Загрузка из первого источника закончилась с ошибкой: %v\r\n", err)
err = start_curl(url2, out_file)
if err != nil {
fmt.Printf("Загрузка из второго источника закончилась с ошибкой: %v\r\n", err)
os.Exit(1)
}
}
fmt.Printf("Загрузка успешно завершена\r\n")
}

func start_curl(url string, out_file string) error {
cmd := exec.Command("curl", url, "-o", out_file)
err := cmd.Run()
return err
}




Запустить мы запустили, но для отладки неплохо было бы увидеть, что выдает curl в stderr/stdout. Кстати, curl все выдает в stderr:

func start_curl(url string, out_file string) error {
cmd := exec.Command("curl", url, "-o", out_file)
//cmd.Stdout = os.Stdout //Закомментировать строку можно можно двумя слэшами.
cmd.Stderr = os.Stderr
err := cmd.Run()
return err
}




При ошибке функция start_curl() возвращает что-то вроде «exit code 7». А нам бы хотелось получить код возврата в виде числа. Мы можем отрезать строку «exit_code » и и преобразовать строку «7» в число7. Для этого придется импортировать пакеты «strings» и «strconv». Но есть более простой, и менее понятный способ:

package main

import (
"fmt"
"os"
"os/exec"
"syscall"
)

func main() {
url1 := "http://ift.tt/1LaCeLQ"
url2 := "http://ift.tt/1EdPGWb"
out_file := ".\\test\\test.zip"
int_err := start_curl(url1, out_file)
if int_err != 0 {
fmt.Printf("Загрузка из первого источника закончилась с ошибкой: %d\r\n", int_err)
int_err = start_curl(url2, out_file)
if int_err != 0 {
fmt.Printf("Загрузка из второго источника закончилась с ошибкой: %d\r\n", int_err)
os.Exit(int_err)
}
}
fmt.Printf("Загрузка успешно завершена\r\n")
}

func start_curl(url string, out_file string) int {
var exit_code int
cmd := exec.Command("curl", url, "-o", out_file)
//cmd.Stdout = os.Stdout
//cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
exit_code = int(err.(*exec.ExitError).Sys().(syscall.WaitStatus).ExitCode)
} else {
exit_code = 0
}
return exit_code
}




На сегодня все. Компилятор соберет нам готовый exe-файл. Бояться большого размера (несколько мегабайт) не нужно. В этот файл будут собраны минимальная среда исполнения и все необходимые пакеты. Потребление памяти приложением на Go раза в два меньше чем у perl или python(если мы конечно говорим о небольших приложениях). Если статья кого-то заинтересовала, в комментариях укажите, какую из тем хотелось бы рассмотреть:

  • работа с текстом (парсинг stdout, кодировки)

  • файлы(информация, поиск, ведение логов)

  • работа с winapi(подключение dll, вызов функций, обработка ответов)

  • работа с adodb(как прочитать данные из базы MSAccess)

  • отправка e-mail средствами Go

  • простой RPC-сервер


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Из песочницы] Игральный кубик на Attiny2313

С недавних пор мы с друзьями плотно подсели на настольные игры, а незадолго до этого я решил попробовать программировать под микроконтроллеры. Чередуя настольные игры с пляской вокруг микроконтроллера (я игрался всё это время с Attiny2313), родилась идея применить на практике небольшие знания, которые приобрёл на начальных этапах работы с этим МК и сделать игральный кубик. Была поставлена примерно следующая задача:


  • Кубик должен отображать информацию по аналогии со своим костяным собратом;

  • Кубик должен выдавать два значения;

  • Кубик должен быть максимально простым в использовании.






Далее начал формализовать задачу.

Итак, было решено из SMD светодиодов выложить два кубика по 7 точек. В каждом кубике есть 4 отдельных группы светодиодов (0 — точка по середине, 1 — левый верхний правый нижний, 2 — правый верхний левый нижний, 3 — правый посередине левый посередине). Таким образом всё замечательно, для управления нам потребуется 8 ног или 1 байт, а это значит что один порт идеально для этого подойдёт.


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


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


Заказывал сопротивления, Attiny2313 в корпусе под SMD и светодиоды; фольгированный текстолит и тактовая кнопка у меня уже были.


Накидал в eagle схемку и развёл плату:



Далее приступил к программированию. Так как групп светодиодов 8, то используем пины PB0-PB7. Создадим массив со значениями для первого кубика:



int digs[6]={
0x01,//1
0x02,//2
0x03,//3
0x06,//4
0x07,//5
0x0E,//6
};




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

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



SIGNAL(INT0_vect) {
sleepTimer=SLEEP_TIMEOUT;
cli();
toggleButton=TRUE;
sei();
}

SIGNAL(TIMER0_COMPA_vect) {
sleepTimer--;

if(toggleButton==TRUE){
timer++;
}

if(timer>40&&timer<250){
if((PIND & (1 << PD2)) !=0){
toggleButton=FALSE;
timer=0;
shuffleDice();
}
}else if(timer>250){
if((PIND & (1 << PD2)) == 0){
setMode();
}
toggleButton=FALSE;
timer=0;
}
}




Результат работы можно увидеть на видео ниже:


Код доступен по ссылке (не вижу смысла его на github выкладывать).


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


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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Перевод] Последовательности в Perl 6 / Rakudo

В Perl 6 введён новый оператор … для задания последовательностей. Вот, как это работает:

my @even-numbers := 0, 2 ... *; # арифметическая последовательность
my @odd-numbers := 1, 3 ... *;
my @powers-of-two := 1, 2, 4 ... *; # геометрическая последовательность


Пример использования:



> my @powers-of-two := 1, 2, 4 ... *; 1;
1
> @powers-of-two[^10]
1 2 4 8 16 32 64 128 256 512






(При задании в REPL геометрической последовательности, которая бесконечна по определению, я поставил в конце строчки «1;». В результате REPL выводит единицу, и не уходит в бесконечный цикл.)

Чтобы ограничить бесконечную «ленивую» последовательность, в примере я использовал запись [^10], что означает «первые десять элементов». При такой записи подсчитанные переменные запоминаются для дальнейшего использования.


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


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



> my @Fibonacci := 0, 1, -> $a, $b { $a + $b } ... *; 1;
1
> @Fibonacci[^10]
0 1 1 2 3 5 8 13 21 34


Часть -> $a, $b { $a + $b } – это стрелочный блок (лямбда-функция), принимающая два аргумента, и возвращающая их сумму. Оператор последовательности вычисляет, сколько аргументов принимает блок, и передаёт нужное количество с конца последовательности для генерации следующего числа.


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



> 1, 1.1 ... 2
1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2
> 1, 1.1 ... 2.01
... шестерёнки Rakudo вращаются, ибо этот список бесконечен ...
> (1, 1.1 ... 2.01)[^14]
1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2 2.1 2.2 2.3


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


Программисты, знакомые с вычислениями с плавающей запятой, наверно ворчат, что нельзя предполагать, будто последовательное добавление 0.1 к числу обязательно приведёт к двойке. В Perl 6 используется Rat-математика, которая обеспечивает точность и работоспособность таких вычислений.


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



> 0, 1, -> $a, $b { $a + $b } ... -> $a { $a > 10000 };
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946


Стрелочный блок -> $a { $a > 10000 } создаёт проверку. Она принимает один аргумент и возвращает true, когда он становится больше 10000.


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



> 0, 1, -> $a, $b { $a + $b } ...^ -> $a { $a > 10000 };
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765


Используя замыкания «чего угодно», эту запись можно переделать следующим образом:



> 0, 1, * + * ...^ * > 10000;
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765


Яснее такая запись, или сложнее – вам решать.


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



> my @Fibonacci := 0, 1, * + * ... *; 1;
1
> @Fibonacci ...^ * > 10000
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
> @Fibonacci[30]
832040


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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


пятница, 13 марта 2015 г.

Магнитная лента — старый конь борозды не портит

Каждый раз когда мы встречаем словосочетание дата-центр, либо же аббревиатуру ЦОД (центр обработки данных), наше сознание моментально «подтягивает из кэша» набор стандартных лекал, которые казалось бы вполне однозначно ассоциируются с этим характерным представителем современной ИТ-инфраструктуры. Просторные помещения, серверные стойки – усеяны брызгами разноцветных светодиодов, гул блоков питания конкурирующий с еще более сильным шумом от вытяжки, что удаляет лишнее тепло из залов, переплетенные пучки кабелей всевозможных диаметров и окрасок, инженеры, рассекающие с важным видом по узким коридорам между стенами, выстроенными из высокотехнологичного оборудования. Что уже говорить о громадных счетах за электричество, это все казалось бы так естественно и безальтернативно. Не стану никого разочаровывать, в общем, так оно и есть, в 99% случаев.


В конце 2014 компания Spectra Logic – одно из подразделений IBM, огласила о создании, пятого поколения ленточных картриджей серии 3592 (тип D), модель 1150. Данная разработка позволяет хранить до 10 ТБ данных, скорость чтения с носителя достигла впечатляющих 360 МБ/с. Более того, данный картридж имеет совместимость со всеми существующими ленточными библиотеками IBM начиная с 90-х. Дата-центр и ленточные носители данных, как увязываются два этих понятия в мире где доминирует HDD и SSD? В начале статьи мы вспомнили о классическом дата-центре, коих действительно абсолютное большинство, но остается тот небольшой процент ИТ-узлов, неотъемлемой составной которых стала, многими из нас уже хорошо подзабытая, магнитная лента.



Технология




Центры обработки данных в которых ставка была сделана на магнитную ленту, как на основной носитель данных, появились конечно же не сегодня. Еще в 1950-х годах на заре зарождения промышленной эксплуатации такого рода носителей, они очень быстро завоевали популярность, и на то были вполне объективные причины. Первая, и главная это соотношение объема размещаемых данных и габаритов носителя. К слову говоря первые ленточные библиотеки вмещали не более 2.5 мбайт данных, но в производстве были существенно проще и дешевле первых HDD решений. Также данная технология позволяла использовать довольно широкий спектр материалов, которые при вполне адекватной цене могли стать для закодированных бинарным кодом данных надежным пристанищем на десятилетия. Собственно говоря так оно десятилетиями и было, огромные бобины с мегабайтами данных были такими же характерными чертами дата-центров 1960-70х годов, коими в ХХІ веке стали HDD и SSD накопители.

Давайте же более детально разберемся, благодаря чему ленточные накопители, такие себе динозавры из времен творения цифрового мира, не только дожили до нашего века, но и остались востребованными для потребителей рынка ИТ-инфраструктуры. Как и в самом начале использования ленточных магнитных накопителей, на данный момент они обладают одним из лучших соотношением цена-объем-компактность-долговечность. Но не стоит забывать и тот факт, что кроме явных плюсов упомянутая технология несет в себе и определенные сложности, которые существенно ограничивают спектр использования ленточных носителей. Очевидно, что недавно представленный компанией Spectra Logic картридж, это детище нашего времени и со своими предшественниками из 50-х имеет схожесть основанную только на самих принципах организации технологии.



Ленточные библиотеки




Пришедшее из прошло сочетание «ленточная библиотека» представляет из себя нечто больше нежели шкаф с хранящимися там систематизированными кассетными блоками. Сейчас на рынке присутствует целый ряд производителей такого рода ИТ-оборудования, крупнейшими из них являются IBM, Dell, HP, Compaq, Fujitsu – реальные флагманы современности в хай-тек индустрии. Как результат, сложность изделий и возможность интегрировать их в самые современные системы достигла настоящих высот. Современные автоматизированные библиотеки полностью исключают человеческое вмешательство в их деятельность, скорость обработки картриджей, благодаря прогрессу механических элементов конструкции и программного продукта, стала приемлемой для организации на основе библиотек автономных бэкап сервисов. Также большинство библиотек, скажем такого производителя как НР, могут быть оснащены внешним интерфейсом SCSI или Fibre Channel, а это в свою очередь позволяет принимать и отдавать существенные объемы данных. Интерфейсы высоко скоростной передачи данных уже давно стали необходимостью для подобного рода решений. До недавней презентации Spectra Logic ленточного картриджа модели 1150, со скоростью считывания информации в 360 МБ/с, широко использовались картриджи с максимальной скоростью в районе 250 МБ/с, что уже само собой было не критично, для переброски больших массивов данных, а также по причине использования в автоматизированных библиотеках нескольких считывающих устройств.

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



Основываясь на сильных и слабых сторонах технической реализации систем, которые создаются на базе ленточных накопителях, они в свою очередь стали идеальным хранилищем для бэкапов. Также нашли они свое применение в исследовательских институтах, стали неотъемлемым спутником «супер компьютеров», порождающих петабайты данных. Взрыв роста телевидения высокой четкости, также сыграло не последнею роль в востребованности «магнитной ленты», тысячи часов емкого видеоматериала создаваемого мультимедийными компаниями не оставили альтернативы кассетным картриджам. Компании, владельцы облачных сервисов также вошли в число клиентов этой технологии. Впечатляющий рост «облаков» поставил данные компании перед реальностью оперировать громадными массивами данных в границах комплексных систем, что базируются на основе множества кластеров, а это в свою очередь дает возможность внедрить самый дешевый на данный момент носитель данных — магнитную ленту. По разным подсчетам 1 ГБ, высоко объемного, ленточного носителя обходится покупателю всего в 4 цента, против 10 центов для аналогичных HDD носителей. Когда же дело доходит до потребления энергии то привычные HHD и SSD просто не конкуренты ленте. «Холодное хранение» данных, и конечно же отсутствие высокого тепловыделения дает право называется автоматизированным библиотекам самыми энергоэффективными комплексными решениями для долговременного размещения данных.


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


Сетевой инженер CERN — Майк Коллин (Micke Collin), отзывается о внедренном узле, основном на ленточной технологии: «В нашем хозяйстве адекватной замене ленты нет. В следствии нашей работы, на базе большего адронного коллайдера, по изучению структуры материи, сталкивая протоны и ионы на огромных скоростях, возникает громадное количество данных. Мало того, что сами эксперименты подразумевают регистрацию всей информации по столкновению миллионов частиц в секунду, так более того мы вынуждены годами сохранять в наших дата-центрах всю эту информацию. С самого начала организации работ, стояла задача минимизировать потери ресурсов на хранение и обработку петабайт данных, полученных в результате многомиллионных затрат. И в этом верным помощником стала магнитная лента». Акцентируя внимание на характерных особенностях ленточных картриджей Майк отметил: «Уникальность ленты состоит и в том, что в случае поломки носителя, он обладает куда большим потенциалом к восстановлению, нежели его конкуренты. Бывали случаи, когда по разным причинам, картриджи выходили из строя и мы теряли куски ленты, но после удаления порушенного участка и склейки уцелевших концов, мы имели доступ к абсолютному большинству информации на картридже, в то же время стыкаясь с неисправностями у SSD и HDD, были прецеденты утери всего массива данных размещенном на диске».



Естественно, технология подразумевающая хранения информации на магнитной ленте не выступает полностью самостоятельным, самодостаточным носителем данных в современных ЦОД. В тоже время она является великолепным дополняющим элементом сетевой инфраструктуры, позволяя экономить существенные средства как при первичных затратах так и в процессе эксплуатации. Развитие этой технологии говорит в том числе и о ее востребованности на рынке ИТ-индустрии, делая ее еще более интересной для владельцев крупных организаций, что в своей деятельности вынуждены взаимодействовать с особо большими объемами информации. Начав свое становление с середины 30-х годов ХХ столетия и сейчас этот способ хранения данных выглядит абсолютно актуальным, кроме того имеет все задатки на дальнейшее успешное существование.



This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Из песочницы] Очень большой Postgres

Так уж случилось, что последнее время приходилось заниматься оптимизацией и масштабированием различных систем. Одной из задач было масштабирование PostgreSQL. Как обычно происходит оптимизация БД? Наверное, в первую очередь смотрят на то, как правильно выбрать оптимальные настройки для работы и какие индексы можно создать. Если обойтись малой кровью не вышло, переходят к наращиванию мощностей сервера, выносу файлов журнала на отдельный диск, балансировке нагрузки, разбиению таблиц на партиции и к всякого рода рефакторингу и перепроектированию модели. И вот уже все идеально настроено, но наступает момент, когда всех этих телодвижения оказывается недостаточно. Что делать дальше? Горизонтальное масштабирование и шардинг данных.





Хочу поделиться опытом развертывания горизонтально масштабируемого кластера на СУБД Postgres-XL.


Postgres-XL — прекрасный инструмент, который позволяет объединить несколько кластеров PostgreSQL таким образом, чтоб они работали как один инстанс БД. Для клиента, который подключается в базе, нет никакой разницы, работает он с единственным инстансом PostgreSQL или с кластером Postgres-XL. Postgres-XL предлагает 2 режима распределения таблиц по кластеру: репликация и шардинг. При репликации все узлы содержат одинаковую копию таблицы, а при шардинге данные равномерно распределяются среди членов кластера. Текущая реализация основана на PostgreSQL-9.2. Так что вам будут доступны почти все фичи версии 9.2.


Терминология




Postgres-XL состоит из трех типов компонентов: глобальный монитор транзакций (GTM), координатор (coordinator) и узел данных (datanode).

GTM — отвечает за обеспечение требований ACID. Ответственен за выдачу идентификаторов. Так как является единой точкой отказа, то рекомендуется подпирать с помощью GTM Standby. Выделение отдельного сервера для GTM является хорошей идеей. Для объединений множественных запросов и ответов от координаторов и узлов данных запущенных на одном сервере имеет смысл настроить GTM-Proxy. Таким образом снижается нагрузка на GTM так как уменьшается общее количество взаимодействий с ним.


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


Узел данных — место где хранятся пользовательские данные и индексы. Связь с узлами данных осуществляется только через координаторы. Для обеспечения высокой доступности можно подпереть каждый из узлов stanby сервером.



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


Схема тестового кластера





Каждый узел это виртуальная машина со скромным аппаратным обеспечением: MemTotal: 501284 kB, cpu MHz: 2604.


Установка




Тут все стандартно: качаем исходники с офсайта, доставляем зависимости, компилируем. Собирал на Ubuntu server 14.10.

$ sudo apt-get install flex bison docbook-dsssl jade iso8879 docbook libreadline-dev zlib1g-dev
$ ./configure --prefix=/home/${USER}/Develop/utils/postgres-xl --disable-rpath
$ make world




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

Настройка GTM




Для обеспечения отказоустойчивости рассмотрим пример с настройкой двух GTM серверов. На обоих серверах создаем рабочий каталог для GTM и инициализируем его.

$ mkdir ~/gtm
$ initgtm -Z gtm -D ~/gtm/




После чего переходим к настройке конфигов:

gtm1


gtm.conf



nodename = 'gtm_master'

listen_addresses = '*'

port = 6666

startup = ACT

log_file = 'gtm.log'






gtm2

gtm.conf



nodename = 'gtm_slave'

listen_addresses = '*'

port = 6666

startup = STANDBY

active_host = 'gtm1'

active_port = 6666

log_file = 'gtm.log'






Сохраняем, стартуем:

$ gtm_ctl start -Z gtm -D ~/gtm/




В логах наблюдаем записи:

LOG: Started to run as GTM-Active.

LOG: Started to run as GTM-Standby.

Настройка GTM-Proxy



$ mkdir gtm_proxy
$ initgtm -Z gtm_proxy -D ~/gtm_proxy/
$ nano gtm_proxy/gtm_proxy.conf




gtm_proxy.conf



nodename = 'gtmproxy1' # имя должно быть уникально

listen_addresses = '*'

port = 6666

gtm_host = 'gtm1' #указываем ip или имя хоста на котором развернут GTM мастер

gtm_port = 6666

log_file = 'gtm_proxy.log'






После правки конфига можно запускать:

$ gtm_ctl start -Z gtm_proxy -D ~/gtm_proxy/


Настройка координаторов



$ mkdir coordinator
$ initdb -D ~/coordinator/ -E UTF8 --locale=C -U postgres -W --nodename coordinator1
$ nano ~/coordinator/postgresql.conf




coordinator/postgresql.conf



listen_addresses = '*'

port = 15432

pooler_port = 16667

gtm_host = '127.0.0.1'

pgxc_node_name = 'coordinator1'






Настройка узла данных



$ mkdir ~/datanode
$ initdb -D ~/datanode/ -E UTF8 --locale=C -U postgres -W --nodename datanode1
$ nano ~/datanode/postgresql.conf




datanode/postgresql.conf



listen_addresses = '*'

port = 25432

pooler_port = 26667

gtm_host = '127.0.0.1'

pgxc_node_name = 'datanode1'






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

Теперь правим pg_hba.conf:

echo "host all all 192.168.1.0/24 trust" >> ~/datanode/pg_hba.conf
echo "host all all 192.168.1.0/24 trust" >> ~/coordinator/pg_hba.conf




Запуск и донастройка




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

$ pg_ctl start -Z datanode -D ~/datanode/ -l ~/datanode/datanode.log
$ pg_ctl start -Z coordinator -D ~/coordinator/ -l ~/coordinator/coordinator.log




Заходим на координатор:

psql -p15432




Выполняем запрос:

select * from pgxc_node;




Запрос показывает как текущей сервер видит наш кластер.

Пример вывода:

node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id
-------------+-----------+-----------+-----------+----------------+------------------+------------
coordinator1 | C | 5432 | localhost | f | f | 1938253334




Эти настройки неверны и их можно смело удалять.

delete from pgxc_node;




Создаем новое отображение нашего кластера:

create node coordinator1 with (type=coordinator, host='192.168.1.151', port=15432);
create node coordinator2 with (type=coordinator, host='192.168.1.152', port=15432);
create node coordinator3 with (type=coordinator, host='192.168.1.161', port=15432);
create node datanode1 with (type=datanode, host='192.168.1.151', primary=true, port=25432);
create node datanode2 with (type=datanode, host='192.168.1.152', primary=false, port=25432);
create node datanode3 with (type=datanode, host='192.168.1.161', primary=false, port=25432);
SELECT pgxc_pool_reload();
select * from pgxc_node;
node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id
--------------+-----------+-----------+---------------+----------------+------------------+-------------
datanode1 | D | 25432 | 192.168.1.151 | t | f | 888802358
coordinator1 | C | 15432 | 192.168.1.151 | f | f | 1938253334
coordinator2 | C | 15432 | 192.168.1.152 | f | f | -2089598990
coordinator3 | C | 15432 | 192.168.1.161 | f | f | -1483147149
datanode2 | D | 25432 | 192.168.1.152 | f | f | -905831925
datanode3 | D | 25432 | 192.168.1.161 | f | f | -1894792127




На остальных узлах нужно выполнить тоже самое.

Узел данных не позволит полностью очистить информацию, но ее можно перезаписать:

psql -p 25432 -c "alter node datanode1 WITH ( TYPE=datanode, HOST ='192.168.1.151', PORT=25432, PRIMARY=true);"




Тестирование кластера




Теперь все настроено и работает. Создадим несколько тестовых таблиц.

CREATE TABLE test1
( id bigint NOT NULL, profile bigint NOT NULL,
status integer NOT NULL, switch_date timestamp without time zone NOT NULL,
CONSTRAINT test1_id_pkey PRIMARY KEY (id)
) to node (datanode1, datanode2);

CREATE TABLE test2
( id bigint NOT NULL, profile bigint NOT NULL,
status integer NOT NULL, switch_date timestamp without time zone NOT NULL,
CONSTRAINT test2_id_pkey PRIMARY KEY (id)
) distribute by REPLICATION;

CREATE TABLE test3
( id bigint NOT NULL, profile bigint NOT NULL,
status integer NOT NULL, switch_date timestamp without time zone NOT NULL,
CONSTRAINT test3_id_pkey PRIMARY KEY (id)
) distribute by HASH(id);

CREATE TABLE test4
( id bigint NOT NULL, profile bigint NOT NULL,
status integer NOT NULL, switch_date timestamp without time zone NOT NULL
) distribute by MODULO(status);




Было создано 4 таблицы с одинаковой структурой, но разной логикой распределения по кластеру.

Данные таблицы test1 будут храниться только на 2х узлах данных — datanode1 и datanode2, а распределятся они будут по алгоритму roundrobin. Остальные таблицы задействуют все узлы. Таблица test2 работает в режиме репликации. Для определения на каком сервере будут храниться данные таблицы test3 используется хеш-функция по полю id, а для определения логики распределения test4 берется модуль по полю status. Попробуем теперь заполнить их:



insert into test1 (id, profile, status, switch_date) select a, round(random()*10000), round(random()*4), now() - '1 year'::interval * round(random() * 40) from generate_series(1,10) a;
insert into test2 (id , profile,status, switch_date) select a, round(random()*10000), round(random()*4), now() - '1 year'::interval * round(random() * 40) from generate_series(1,10) a;
insert into test3 (id , profile,status, switch_date) select a, round(random()*10000), round(random()*4), now() - '1 year'::interval * round(random() * 40) from generate_series(1,10) a;
insert into test4 (id , profile,status, switch_date) select a, round(random()*10000), round(random()*4), now() - '1 year'::interval * round(random() * 40) from generate_series(1,10) a;




Запросим теперь эти данные и посмотрим, как работает планировщик

explain analyze select count(*) from test1;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=27.50..27.51 rows=1 width=0) (actual time=0.649..0.649 rows=1 loops=1)
-> Remote Subquery Scan on all (datanode1,datanode2) (cost=0.00..24.00 rows=1400 width=0) (actual time=0.248..0.635 rows=2 loops=1)
Total runtime: 3.177 ms

explain analyze select count(*) from test2;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
Remote Subquery Scan on all (datanode2) (cost=27.50..27.51 rows=1 width=0) (actual time=0.711..0.711 rows=1 loops=1)
Total runtime: 2.833 ms

explain analyze select count(*) from test3;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=27.50..27.51 rows=1 width=0) (actual time=1.453..1.453 rows=1 loops=1)
-> Remote Subquery Scan on all (datanode1,datanode2,datanode3) (cost=0.00..24.00 rows=1400 width=0) (actual time=0.465..1.430 rows=3 loops=1)
Total runtime: 3.014 ms


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


Подключившись к узлу данных (на порт 25432) можно увидеть как были распределены данные.


Теперь давайте попробуем заполнить таблицы большим объемом данных и сравнить производительность запросов со standalone PostgreSQL.



insert into test3 (id , profile,status, switch_date) select a, round(random()*10000), round(random()*4), now() - '1 year'::interval * round(random() * 40) from generate_series(1,1000000) a;




Запрос в кластере Postgres-XL:

explain analyze select profile, count(status) from test3
where status<>2
and switch_date between '1970-01-01' and '2015-01-01' group by profile;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=34.53..34.54 rows=1 width=12) (actual time=266.319..268.246 rows=10001 loops=1)
-> Remote Subquery Scan on all (datanode1,datanode2,datanode3) (cost=0.00..34.50 rows=7 width=12) (actual time=172.894..217.644 rows=30003 loops=1)
Total runtime: 276.690 ms




Этот же запрос на сервере с PostgreSQL:

explain analyze select profile, count(status) from test
where status<>2
and switch_date between '1970-01-01' and '2015-01-01' group by profile;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=28556.44..28630.53 rows=7409 width=12) (actual time=598.448..600.495 rows=10001 loops=1)
-> Seq Scan on test (cost=0.00..24853.00 rows=740688 width=12) (actual time=0.418..329.145 rows=740579 loops=1)
Filter: ((status <> 2) AND (switch_date >= '1970-01-01 00:00:00'::timestamp without time zone) AND (switch_date <= '2015-01-01 00:00:00'::timestamp without time zone))
Rows Removed by Filter: 259421
Total runtime: 601.572 ms




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

P.S. Надеюсь, данный пост поможет кому-нибудь. Комментарии и дополнения приветствуются! Благодарю за внимание.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


ZeptoLab Code Rush 2015 уже близко

Привет Хабражителям!

В 2014 году мы провели свой первый совместный контест по спортивному программированию совместно с Codeforces, об этом мы писали здесь.


Коротко о том, как это было:


Контест состоял из 6 задач, на решение отводилось 2,5 часа (ознакомиться с задачами прошлого года и даже попробовать свои силы в их решении вы можете здесь).

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



Впервые за всю историю Codeforces в контесте приняли участие одновременно более 2148 человек (зарегистрировалось более 4600 (!) со всего мира. К слову сказать, первые 3 места заняли разработчики из России, что не может не радовать. Кое-кто из них даже приехал забрать призы в офис, где их ждала мини-экскурсия и гвоздь программы: конечно же, игра в гигантский Cut The Rope и наше стандартное корпоративное “озеленение” на входе (озеленением мы называем вручение welcome-kit, полного забавных вещиц нашего корпоративно-зеленого цвета).



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



“Изначально я пришел за леденцами (шутка), но сейчас, если делиться впечатлениями, – в первую очередь важно то, что я могу здесь быть собой до конца. Кроме того, в Зептолаб руководители ставят задачи с учетом способностей каждого разработчика, в том числе передо мной. А когда руководитель тоже “в теме” алгоритмов, мне есть, где развернуться. Получилось, что я стал этаким client-side back-end разработчиком, чего, собственно, и хотелось. Да, я знаю некоторые структуры данных, которые в Зептолабе вряд ли скоро пригодятся (они бы больше пригодились, например, в разработке поисковика); к этой части своих знаний я практически не обращаюсь в ежедневной работе. Зато эти навыки мне полезны на внутренних алгоритмических контестах :) О рабочих моментах: моей последней задачей было улучшение упаковки атласов при подготовке игровых ресурсов – известная NP-трудная задача. Мне удалось добиться существенных успехов: улучшить известные и лучшие на 2013 год по разным метрикам алгоритмы. В результате потребление памяти во всех наших игровых проектах сократилось на мегабайты, и у сборок появился запас по memory-limit. С аппетитом наших художников, я думаю, это ненадолго :) Возможно, мою работу мы опубликуем на одной из ближайших конференций. В целом, навыки спортивного программирования здесь очень ценятся, а этому, по моему опыту, в других компаниях придают недостаточное значение.”


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


4го апреля (в субботу) мы запускаем второй контест совместно с Codeforces.


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



Кстати, кроме как поучаствовав в контесте, такую жилетку больше никак не отхватить. Жилетки крутые.


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


Чемпионат будет проводиться в один раунд. Формат соревнования — по правилам Codeforces.


Раунд будет рейтинговым в системе Codeforces и общим для обоих дивизионов. Участникам, ранее не участвовавшим в чемпионатах Codeforces, рекомендуем заранее зарегистрироваться на сайте (это можно сделать через аккаунты open OpenID, Gmail или ВКонтакте) и разобраться с системой: можно принять виртуальное участие в одном из прошедших соревнований.

Дата и время проведения: 4го апреля 2015 (в субботу), время: 19-30 — 22-00 по Москве.


Для того, чтобы принять участие в ZeptoLab Code Rush 2015, нужно будет зарегистрироваться.

Регистрация откроется не позднее 2 апреля с 12-00 и продлится до 4 апреля, 19-25.


Вопросы о чемпионате, опять же, оставляйте в комментариях на сайте Codeforces.ru.


Пишите код с нами.

Пишите, как мы.

Пишите лучше нас!

Ну и вообще – пишите.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


PCE.js и старое железо в браузере


Доброго дня всем в эту пятницу.


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


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


Для тех, кто не знает, поясняю — это эмулятор разных старых платформ в браузере (думаю скоро Windows XP увидим портированную на кофескрипт).


С момента того поста тут уже появились куча интересного.



http://ift.tt/1EaKEto — Эмулятор Windows 3.0:



http://ift.tt/1EGG1wC — Эмулятор System 7 (Macintosh)

http://ift.tt/1EGFZoh — System 6 тоже есть

http://ift.tt/1fMveFz — здесь больше приложений и игр, тоже System 7.



Занимается этим проектом James Friend.


Лично мне очень было интересно потыкать в System 7, так как у меня был Apple PowerBook 150 — очень мной любимый ноутбук, к сожалению врятли подлежащий восстановлению и с тех просто валяющийся на полке, выбросить просто рука не поднимается.


Всякие ссылочки:

jamesfriend.com.au — Сайт автора эмуляторов

github.com/jsdf/pce — Гитхаб

twitter.com/jsdfco — Твиттер автора


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Туториал по Coub API

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

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


Рабочая версия этого приложения лежит по адресу fantozzi.dev2.workisfun.ru, код приложения из этого туториала можно посмотреть на Гитхабе: http://ift.tt/1Bd4mCI



OAuth




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

Работает авторизация примерно так: приложение со своим уникальным ключом заходит на специальную страницу на coub.com, там Коб спрашивает, согласен ли пользователь дать приложению доступ. Если пользователь разрешает, то Коб возвращает пользователя обратно в приложение, и отдает вместе с запросом токен пользователя, который потом уже используется при всех API-запросах пользователя. То же самое происходит, например, при авторизации через Фейсбук или Твиттер.


Мы будем писать на RoR и для авторизации через OAuth для рельсов все уже давно написано, мы будем использовать для этого гем omniauth-oauth2 и официальный кобовский гем omniauth-coub.


Создание приложения и авторизация




Создаем приложение с красноречивым названием memegenerator и прикручиваем его к Pow (или кто чем пользуется):

$ cd ~/apps/
$ rails new memegenerator
$ ln -s ~/apps/memegenerator/ ~/.pow/memegenerator




Проверяем в браузере, что у нас по адресу memegenerator.dev живет пустое рельсовое приложение.

2. Регистрируем наше новое приложение по адресу http://ift.tt/1xjOj5E



В поле Website указываем урл нашего тестового приложения, в поле Callback URL пишем



http://ift.tt/1xjOj5G

После создания приложения Коб даст нам Application ID и Secret, они нам понадобятся дальше:



3. Устанавливаем гем omniauth-coub:


Gemfile:



gem "omniauth-coub"



$ bundle install




4. Добавляем коб в провайдеры omniauth:

config/initializers/omniauth.rb:



Rails.application.config.middleware.use OmniAuth::Builder do
provider :coub, ENV["COUB_KEY"], ENV["COUB_SECRET"], scope: "logged_in,create"
end




COUB_KEY и COUB_SECRET — это Application ID и Secret из прошлого шага, можно добавить их в ENV переменные или пока для теста вставить строки прямо тут, хотя оставлять в коде ключи нежелательно, ну вы понимаете.

Если у вас Pow, то добавить переменные можно в файле .powenv в корне приложения:


.powenv:



export COUB_KEY="[Application ID]"
export COUB_SECRET="[Secret]"




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

5. Создаем модель пользователя с методом from_omniauth, который создает или находит в базе пользователя по данным, которые передал нам сервер авторизации на Кобе.


Что происходит в этом пункте и в паре следующих пунктов, хорошо объяснено в одном из эпизодов RailsCasts.



$ rails g model user provider:string uid:string auth_token:string name:string
$ rake db:migrate




app/models/user.rb:

class User < ActiveRecord::Base
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.auth_token = auth.credentials.token
user.name = auth.extra.raw_info.name
user.save!
end
end
end




6. Создаем контроллер сессий. Через него мы создаем и удаляем сессию.

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



$ rails g controller sessions




app/controllers/sessions_controller.rb:

class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
cookies.permanent[:auth_token] = user.auth_token
redirect_to root_url
end
def destroy
cookies.delete(:auth_token)
redirect_to root_url
end
def index
end
end




Морда приложения пускай пока поживет в контроллере сессий, поэтому тут метод index.

7. Чтобы иметь доступ к текущему пользователю, добавляем в ApplicationController метод current_user, который ищет пользователя в базе данных, если у нас есть кука с его токеном.


app/controllers/application_controller.rb:



helper_method :current_user
def current_user
@current_user ||= User.find_by_auth_token(cookies[:auth_token]) if cookies[:auth_token]
end




8. Выводим на морде ссылку на логин или показываем текущего пользователя с ссылкой на выход.

app/views/sessions/index.html.erb:



<% if current_user %>
<%= current_user.name %>
<%= link_to "Log out", logout_path, method: :delete %>
<% else %>
<%= link_to "Auth with Coub", "/auth/coub" %>
<% end %>




По пути /auth/coub гем omniauth-oauth2 перебросит на страницу авторизации на coub.com.

9. Прописываем роуты:


config/routes.rb:



Rails.application.routes.draw do
root "sessions#index"
get "/auth/:provider/callback" => "sessions#create"
delete "logout" => "sessions#destroy"
end




С авторизацией все. Заходим на memegenerator.dev, проверяем. Должно выглядеть примерно вот так:

Теперь у нас в базе есть пользователь с токеном, который может делать запросы через Coub API.


Запросы к API




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

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



POST http://ift.tt/1xjOjTm]




Некоторые запросы можно делать и без токена, например, инфа о кобе (без токена доступны только публичные кобы). Это GET запрос, эту ссылку можно открыть просто в браузере:

GET http://ift.tt/1L6kGAx




Все эти запросы хорошо задокументированы, полный список можно посмотреть тут: http://ift.tt/1b7vP3M

Клиент




Каждый раз вручную делать запросы и добавлять токен неудобно, поэтому добавим модели User метод client, через который будем делать запросы:

app/models/user.rb:



def client
@client ||= Faraday.new(:url => "http://coub.com/api/v2/", :params => {:access_token => auth_token})
end




Gemfile:

gem "faraday"



$ bundle install




Запросы мы делаем через Faraday, это HTTP клиент на Ruby.

Запустим консоль, потестим запросы к апи:



$ rails c
> user = User.first
> user.client.get "coubs/4yp6r"




Ответы выдаются в формате JSON, поэтому, если нам хочется прочитать, что вернул сервер, надо ответ распарсить стандартной JSON библиотекой:

> coub_info = JSON.parse(user.client.get("coubs/4yp6r").body)
> coub_info["id"]
=> 9090841




У нас есть айдишник коба, давайте его лайкнем:

> user.client.post "likes?id=#{coub_info["id"]}"


Генерим видео




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

На маке через Homebrew он ставится вот так:



$ brew install ffmpeg --with-freetype




Накладываем текст через ffmpeg фильтром drawtext:

$ ffmpeg -i template.mp4 -vf "drawtext=enable='between(t,1,2)':text=Blah:fontfile=PFDinTextCondPro-XBlack.ttf:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th)" output.mp4


Эта строчка означает:


1. ffmpeg -i template.mp4 -vf: берем видео файла template.mp4. Файл для этого туториала можно скачать вот тут.


2. drawtext=enable: накладываем текст


3. between(t,1,2): с первой по вторую секунду


4. fontfile=PFDinTextCondPro-XBlack.ttf: используем файл с шрифтом в формате TTF


5. text=Blah: пишем текст Blah


6. fontsize=40: размер шрифта


7. fontcolor=white: белого цвета


8. x=(w-tw)/2:y=(h*0.9-th): положение текста в центре внизу (w и h — это размер видео, tw и th — это размер блока с текстом)


9. output.mp4: записываем все в этот файл


Чтобы написать три текста, надо просто через запятую три drawtext написать:



$ ffmpeg -i template.mp4 -vf "drawtext=enable='between(t,1,2)':text=Text1:fontfile=PFDinTextCondPro-XBlack.ttf:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th), drawtext=enable='between(t,3,5)':text=Text2:fontfile=PFDinTextCondPro-XBlack.ttf:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th), drawtext=enable='between(t,6.3,8)':text=Text3:fontfile=PFDinTextCondPro-XBlack.ttf:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th)" output.mp4




В темповой папке можно сложить файл template.mp4 и файл шрифта (можно взять любой TTF из папки со шрифтами) и попробовать в консоли запустить, оно должно сгенерить правильное видео.

Закачиваем видео




Видео у нас есть, теперь надо из него сделать коб.

Коб через API закачивается в три этапа:


1. Сначала инициализируем закачку запросом POST coubs/init_upload, в ответе мы получаем id коба и его permalink.



$ rails c
> user = User.first
> init_response = JSON.parse(user.client.post("coubs/init_upload").body)
> coub_id = init_response["id"]
> permalink = init_response["id"]




2. Закачиваем видео запросом POST coubs/:id/upload_video. Файл передается в теле запроса, в хедере Content-Type надо передать video/mp4:

> user.client.post do |r|
r.url "coubs/#{coub_id}/upload_video"
r.headers["Content-Type"] = "video/mp4"
r.body = File.open("tmp/output.mp4", "r").read
end




Если мы хотим загрузить отдельный саундтрек к кобу, то это можно сделать отдельным запросом coubs/:id/upload_audio. Нам в этот раз этого не нужно, поэтому мы опускаем этот запрос.

3. Финализируем создание коба запросом POST coubs/:id/finalize_upload, в параметрах передаем тайтл, настройки приватности, теги, включен ли звук.



> user.client.post "coubs/#{coub_id}/finalize_upload", title: "Test coub", original_visibility_type: "private", tags: "tag1, tag2, tag3", sound_enabled: true




После закачки коба, он будет какое-то время процесситься: видео на серверах Коба будет конвертироваться в несколько форматов для разных платформ, будут генериться превьюшки и куча всяких таких ресурсоемких вещей. Прогресс конвертирования можно проверить GET запросом coubs/:id/finalize_status. Он отдает примерно такой JSON { percent_done: 20, done: false}.

> user.client.get "coubs/#{coub_id}/finalize_status"




Ок. Мы протестировали это в консоли, теперь все это надо собрать в приложение.

Модель Coub




Создаем модель Coub:

$ rails g model coub user:belongs_to title:string visibility_type:string tags:string permalink:string coub_id:string text1:string text2:string text3:string
$ rake db:migrate




2. Делаем метод generate_video_file, который геренит видео из видео-шаблона и трех текстов, находящихся в полях text1, text2, text3. Шаблон видео и шрифт кладем в ассеты. Готовое видео кладем в папку tmp.

app/models/coub.rb:



def escape_ffmpeg_text(text)
text.to_s.gsub("'", "\\\\\\\\\\\\\\\\\\\\\\\\'").gsub(":", "\\\\\\\\\\\\\\\\:").mb_chars.upcase # Crazy ffmpeg escaping
end
def ffmpeg_drawtext(text, from, to)
font_file = File.join(Rails.root, "app", "assets", "fonts", "PFDinTextCondPro-XBlack.ttf")
"drawtext=enable='between(t,#{from},#{to})':text=#{escape_ffmpeg_text(text)}:fontfile=#{font_file}:fontsize=40:fontcolor=white:x=(w-tw)/2:y=(h*0.9-th)"
end
def generate_video_file
self.video_file = File.join(Rails.root, "tmp", "output-#{Time.now.to_i}.mp4")
template_file = File.join(Rails.root, "app", "assets", "videos", "template.mp4")
`ffmpeg -i #{template_file} -vf \"#{ffmpeg_drawtext(text1, 1, 2)}, #{ffmpeg_drawtext(text2, 3, 5)}, #{ffmpeg_drawtext(text3, 6.3, 8)}\" #{video_file}`
return video_file
end




3. Делаем метод, который в три этапа закачивает видео на Коб:

app/models/coub.rb:



def upload_video
self.title ||= text2
self.visibility_type ||= "private"
self.tags ||= ""

init_response = JSON.parse(client.post("coubs/init_upload").body)
self.coub_id = init_response["id"]
self.permalink = init_response["permalink"]

save
client.post do |r|
r.url "coubs/#{coub_id}/upload_video"
r.headers["Content-Type"] = "video/mp4"
r.body = File.open(video_file, "r").read
end
client.post "coubs/#{coub_id}/finalize_upload",
title: title,
original_visibility_type: visibility_type,
tags: tags,
sound_enabled: true
end
def generate_and_upload_video
generate_video_file
upload_video
end




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

app/models/coub.rb:



belongs_to :user
def client
@client ||= user.client
end




5. Метод url отдает урл коба по пермалинку:

app/models/coub.rb:



def url
"http://ift.tt/1L6kH7B}"
end




Проверим, все ли работает:

$ rails c
> coub = Coub.new
> coub.user = User.first
> coub.text1 = 'Text 1'
> coub.text2 = 'Text 2'
> coub.text3 = 'Text 3'
> coub.tags = 'tag1, tag2, tag3'
> coub.visibility_type = 'unlisted'
> coub.generate_and_upload_video
> coub.url
=> "http://ift.tt/1xjOj5M"




По этому урлу можно зайти и посмотреть на синий экран “Your coub is being processed”.

Осталось сделать для всего этого контроллер:


1. Создаем контроллер coubs. Он будет состоять из двух методов: index (это будет новая морда, вместо sessions#index) и create. При создании коба мы сразу редиректим на него.



$ rails g controller coubs




app/controllers/coubs_controller.rb:

class CoubsController < ApplicationController
def index
end
def create
@coub = Coub.create(coub_params.merge(:user => current_user))
@coub.generate_and_upload_video
redirect_to @coub.url
end
private
def coub_params
params.require(:coub).permit(:text1, :text2, :text3, :visibility_type, :tags)
end
end




2. Перетаскиваем index.html.erb из sessions в coubs и прикручиваем туда форму:

app/views/coubs/index.html.erb:



<% if current_user %>
<p>You’re logged in as <%= current_user.name %>. <%= link_to "Log out", logout_path, method: :delete %></p>
<%= form_for Coub.new, url: {action: "create"} do |f| %>
<%= f.text_field :text1, placeholder: "To me", maxlength: 30, size: 50 %><br />
<%= f.text_field :text2, placeholder: "Your Coub API", maxlength: 30, size: 50 %><br />
<%= f.text_field :text3, placeholder: "Is a piece of shit", maxlength: 30, size: 50 %><br />
<%= f.select :visibility_type, options_for_select(["public", "friends", "unlisted", "private"]) %><br />
<%= f.text_field :tags, placeholder: "first tag, second tag" %><br />
<%= f.submit "Create Coub" %>
<% end %>
<% else %>
<p>Please <a href="/auth/coub">log in via Coub</a>.</p>
<% end %>




Все, теперь заходим в браузер, и проверяем, что все работает:



В этом туториале я описал только принцип работы API. Разумеется, для настоящего приложения надо еще много чего дописать: валидации, проверки ответов API, обработки ошибок, интерфейс нарисовать, тесты. Чуть-чуть допиленная версия этого скрипта лежит вот тут http://ift.tt/1Bd4lPf, там все то же самое, но видео готовится и закачивается асинхронно через delayed_job.


Исходники этого туториала лежат в гитхабе: http://ift.tt/1L6kHnP.


Если есть какие-то вопросы по этому туториалу или по API, пишите на igor@coub.com.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.