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

Поиск сообщений в 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 ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

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

[Из песочницы] Пишем расширения для PHP 7 на C++

Суббота, 12 Августа 2017 г. 10:29 + в цитатник
Мне приходилось писать расширения для того, чтобы воспользоваться функциями C++ библиотек в коде PHP. Ещё, одно тяжёлое расширение портировал с 5й версии на 7ю.

Если загуглить документацию на тему написания расширений для PHP, то, в основном, это будут тексты до 2014 года, актуальные для версии 5. Сам сайт php.net предоставляет обрывчатые и устаревшие сведения, а то, что удаётся найти в их wiki, опять про 5ю версию. Максимум, что удалось найти на офф сайте, это скудный ман по миграции уже написанных расширений.
В итоге, единственным более или менее понятным маном по написанию расширений для меня оказался исходный код PHP, которым я и руководствовался при написании и миграции расширений.

В самом деле, API PHP так поменялся, что даже подробнейшие статьи, такие как Wrapping C++ Classes in a PHP Extension не особенно то помогают при написании расширений под PHP 7.

В данной статье рассматривается работа под Linux, у меня Kubuntu. Для винды нужно писать другие config файлы, а так как в проде винду не ставят и расширять PHP под виндой — дело не благодарное, я в этом не разбирался.

Что нужно


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

Определяем лица на фотографиях


Для определения лиц используем библиотеку OpenCV, тестировалось на версиях >=2.3.1

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

Нужно перейти в папку /ext исходных кодов PHP и от туда выполнить
ext_skel с указанием имени нового расширения:

./ext_skel --extname=phpcv

После этого вновь созданную папку phpcv можно перенести куда-то в более удобное место. Нам от туда нужны папка tests и файлы config.m4, php_phpcv.h и phpcv.c. Файл phpcv.c сразу переименуем в phpcv.cpp.

config.m4


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

Файл представляет собой эдакий bash скрипт с использованием специальных макросов. Макросы эти определены в файлах acinclude.m4 и aclocal.m4 в исходном коде php, и написаны на языке из скобочек и знаков препинания. На самом деле достаточно почитать коменты, которые начинаются со строк «dnl» и будет более или менее понятно, что эти макросы делают.

Удаляем лишнее, правим код под наши нужды.

config.m4

PHP_ARG_ENABLE(phpcv, whether to enable phpcv support,
    [  --enable-phpcv           Enable phpcv support])

if test "$PHP_PHPCV" != "no"; then
    PHP_REQUIRE_CXX()

    SEARCH_PATH="/usr/local /usr /opt/local"
    SEARCH_FOR="/include/opencv2/opencv.hpp"

    if test -r $PHP_PHPCV/$SEARCH_FOR; then
        CV_DIR=$PHP_PHPCV
    else
        AC_MSG_CHECKING([for opencv in default path])
        for i in $SEARCH_PATH ; do
            if test -r $i/$SEARCH_FOR; then
                CV_DIR=$i
                AC_MSG_RESULT(found in $i)
                break
            fi
        done
    fi

    if test -z "$CV_DIR"; then
        AC_MSG_RESULT([not found])
        AC_MSG_ERROR([Please reinstall the OpenCV distribution])
    fi

    AC_CHECK_HEADER([$CV_DIR/include/opencv2/objdetect/objdetect.hpp], [], AC_MSG_ERROR('opencv2/objdetect/objdetect.hpp' header not found))
    AC_CHECK_HEADER([$CV_DIR/include/opencv2/highgui/highgui.hpp], [], AC_MSG_ERROR('opencv2/highgui/highgui.hpp' header not found))

    PHP_ADD_LIBRARY_WITH_PATH(opencv_objdetect, $CV_DIR/lib, PHPCV_SHARED_LIBADD)
    PHP_ADD_LIBRARY_WITH_PATH(opencv_highgui, $CV_DIR/lib, PHPCV_SHARED_LIBADD)
    PHP_ADD_LIBRARY_WITH_PATH(opencv_imgproc, $CV_DIR/lib, PHPCV_SHARED_LIBADD)

    PHP_SUBST(PHPCV_SHARED_LIBADD)
    PHP_NEW_EXTENSION(phpcv, phpcv.cpp, $ext_shared,, -std=c++0x -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
fi



PHP_ARG_ENABLE — настраиваем флаг, с помощью которого расширение можно включить или выключить в процессе сборки PHP из исходных кодов.
PHP_REQUIRE_CXX() — необходимо, если мы собираемся использовать C++
Далее, код на bash для поиска opencv.
AC_CHECK_HEADER — проверяем наличие необходимых нам заголовочных файлов
PHP_ADD_LIBRARY_WITH_PATH — подключаем shared библиотеки
PHP_SUBST — это необходимо для формирования make файла
PHP_NEW_EXTENSION — тут указано имя расширения, перечислены *.cpp файлы, которые участвуют в процессе сборки, указаны флаги компилятора.

php_phpcv.h


php_phpcv.h
#ifndef PHP_PHPCV_H
#define PHP_PHPCV_H

#define PHP_PHPCV_EXTNAME "phpcv"
#define PHP_PHPCV_VERSION "0.2.0"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

extern "C" {
#include "php.h"
#include "ext/standard/info.h"
}

#ifdef ZTS
#include "TSRM.h"
#endif

extern zend_module_entry phpcv_module_entry;
#define phpext_phpcv_ptr &phpcv_module_entry

#if defined(ZTS) && defined(COMPILE_DL_PHPCV)
ZEND_TSRMLS_CACHE_EXTERN();
#endif

#endif	/* PHP_PHPCV_H */



Тут стоит обратить внимание на конструкцию

extern "C" { ... }
Это нужно для совместимости нашего C++ кода с C кодом PHP.

phpcv.cpp


Вот тут, наконец-то, будет C++ код.
Для нахождения лиц будем использовать метод cv::CascadeClassifier::detectMultiScale().

Расширение будет предоставлять единственную функцию, вот её прототип:

/**
 * @see cv::CascadeClassifier::detectMultiScale()
 * @param string $imgPath
 * @param string $cascadePath
 * @param double $scaleFactor
 * @param int $minNeighbors
 *
 * @return array
 */
function cv_detect_multiscale($imgPath, $cascadePath, $scaleFactor, $minNeighbors) {
}

phpcv.cpp
#include "php_phpcv.h"
#include objdetect/objdetect.hpp>
#include highgui/highgui.hpp>
#include imgproc/imgproc.hpp>

PHP_MINFO_FUNCTION(phpcv) {
    php_info_print_table_start();
    php_info_print_table_header(2, "phpcv support", "enabled");
    php_info_print_table_end();
}

PHP_FUNCTION(cv_detect_multiscale) {
    char *imgPath = NULL, *cascadePath = NULL;
    long imgPathLen, cascadePathLen, minNeighbors;
    double scaleFactor, minWidth, minHeight;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssdl", &imgPath, &imgPathLen, &cascadePath, &cascadePathLen, &scaleFactor, &minNeighbors) == FAILURE) {
        RETURN_FALSE;
    }

    // Read Image
    cv::Mat image;
    image = cv::imread(imgPath, CV_LOAD_IMAGE_GRAYSCALE);
    if (image.empty()) {
        RETURN_FALSE;
    }
    equalizeHist(image, image);

    //min size for detected object, discarding objects smaller than this
    minWidth = image.size().width / 10;
    minHeight = image.size().height / 10;

    // Load Face cascade (.xml file)
    cv::CascadeClassifier faceCascade;
    if (!faceCascade.load(cascadePath)) {
        RETURN_FALSE;
    }

    // Detect faces
    std::vector faces;
    faceCascade.detectMultiScale(image, faces, scaleFactor, minNeighbors, 0, cv::Size(minWidth, minHeight));

    array_init(return_value);

    // Build array to return
    for ( int i = 0; i < faces.size(); i++ ) {
        // Now we have: faces[i].x faces[i].y faces[i].width faces[i].height
        zval face;
        array_init(&face);
        add_assoc_long(&face, "x", faces[i].x);
        add_assoc_long(&face, "y", faces[i].y);
        add_assoc_long(&face, "w", faces[i].width);
        add_assoc_long(&face, "h", faces[i].height);

        add_next_index_zval(return_value, &face);
    }
}

const zend_function_entry phpcv_functions[] = {
    PHP_FE(cv_detect_multiscale, NULL)
    PHP_FE_END
};

zend_module_entry phpcv_module_entry = {
    STANDARD_MODULE_HEADER,
    PHP_PHPCV_EXTNAME,
    phpcv_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    PHP_MINFO(phpcv),
    PHP_PHPCV_VERSION,
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_PHPCV
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE();
#endif
ZEND_GET_MODULE(phpcv)
#endif



PHP_MINFO_FUNCTION — добавляет сведения о нашем расширении в вывод phpinfo()
PHP_FUNCTION(cv_detect_multiscale) — код нашей функции. В ней после получения входных параметров с помощью zend_parse_parameters идёт код С++. С помощью библиотеки opencv находим лица и формируем выходной массив с координатами найденных лиц.
zend_function_entry — тут перечисляются функции, которые предоставляются расширением.
zend_module_entry — стандартная конструкция, структура, описывающая наше расширение. Несколько NULL подряд — это вместо методов, которые выполняются при инициализации и shutdown расширения и запроса, нам просто нечего делать во время этих фаз.

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

tests


Тест расширения состоит из кода, который что-то выводит и проверки вывода. Файлы *.phpt с тестами помещаются в папку tests

002.phpt
--TEST--
Test face detection
--SKIPIF--

--FILE--

--EXPECT--
face detection works



Cборка и тестирование


Собрать:

phpize && ./configure && make

Протестировать:

make test

DateTime Pattern Generator


Теперь рассмотрим расширение, которое предоставляет класс, оборачивающий C++ класс.
Добавим недостающий функционал в набор классов расширения intl. Зачем это нужно: https://blog.ksimka.io/a-long-journey-to-formatting-a-date-without-a-year-internationally-with-php/#header. Если коротко, стандартное расширение intl не предоставляет возможности интернационально формировать дату без года, то есть «February 10» или «10 февраля». Это расширение фиксит эту проблему.

В системе должна быть установлена библиотека ICU. В Debian-like системах можно поставить пакет libicu-dev.

config.m4


config.m4

PHP_ARG_ENABLE(intl-dtpg, whether to enable intl-dtpg support,
[  --enable-intl-dtpg           Enable intl-dtpg support])

if test "$PHP_INTL_DTPG" != "no"; then
  PHP_SETUP_ICU(INTL_DTPG_SHARED_LIBADD)
  PHP_SUBST(INTL_DTPG_SHARED_LIBADD)
  PHP_REQUIRE_CXX()

  PHP_NEW_EXTENSION(intl_dtpg, intl_dtpg.cpp, $ext_shared,,-std=c++0x $ICU_INCS -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
fi



На этот раз всё очень лаконично. Так как нам нужна библиотека ICU и расширение intl тоже требует её присутствия, то мы просто позаимствовали макрос PHP_SETUP_ICU из intl расширения.
В PHP_NEW_EXTENSION $ICU_INCS — специфичные для ICU флаги.

intl_dtpg.h


intl_dtpg.h
#ifndef INTL_DTPG_H
#define INTL_DTPG_H

#include 
#include 
#include dtptngen.h>

extern "C" {
#include "php.h"
#include "ext/standard/info.h"
}

extern zend_module_entry intl_dtpg_module_entry;
#define phpext_intl_dtpg_ptr &intl_dtpg_module_entry

#define INTL_DTPG_VERSION "1.0.0"

#ifdef ZTS
#include "TSRM.h"
#endif

typedef struct {
    DateTimePatternGenerator *dtpg;
    UErrorCode status;
    zend_object zo;
} IntlDateTimePatternGenerator_object;

static inline IntlDateTimePatternGenerator_object *php_intl_datetimepatterngenerator_fetch_object(zend_object *obj) {
    return (IntlDateTimePatternGenerator_object *)((char*)(obj) - XtOffsetOf(IntlDateTimePatternGenerator_object, zo));
}

#if defined(ZTS) && defined(COMPILE_DL_INTL_DTPG)
ZEND_TSRMLS_CACHE_EXTERN()
#endif

#endif /* INTL_DTPG_H */



Тут мы определяем структуру IntlDateTimePatternGenerator_object. В ней хранится указатель на объект DateTimePatternGenerator из библиотеки ICU, переменная для хранения статуса и объект zend_object, который представляет собой PHP класс. Это такая обёртка для C++ класса. API PHP оперирует объектом zend_object, а мы завернули его в струкутуру и всегда имеем доступ к тому, что находится «по соседству» с zend_object.

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

intl_dtpg.cpp


Расширение будет предоставлять класс следующей структуры:

class IntlDateTimePatternGenerator
{
    /**
     * @param string $locale
     */
    public function __construct(string $locale) {}

    /**
     * Return the best pattern matching the input skeleton.
     * It is guaranteed to have all of the fields in the skeleton.
     *
     * @param string $skeleton The skeleton is a pattern containing only the variable fields.
     *           For example, "MMMdd" and "mmhh" are skeletons.
     * @return string The best pattern found from the given skeleton.
     */
    public function findBestPattern(string $skeleton) {}
}

intl_dtpg.cpp
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "intl_dtpg.h"
#include ustdio.h>
#include smpdtfmt.h>

zend_class_entry *IntlDateTimePatternGenerator_ce;
zend_object_handlers IntlDateTimePatternGenerator_object_handlers;

/* {{{ IntlDateTimePatternGenerator_objects_dtor */
static void IntlDateTimePatternGenerator_object_dtor(zend_object *object)
{
    zend_objects_destroy_object(object);
}
/* }}} */

/* {{{ IntlDateTimePatternGenerator_objects_free */
void IntlDateTimePatternGenerator_object_free(zend_object *object)
{
    IntlDateTimePatternGenerator_object *dtpgo = php_intl_datetimepatterngenerator_fetch_object(object);

    zend_object_std_dtor(&dtpgo->zo);

    dtpgo->status = U_ZERO_ERROR;
    if (dtpgo->dtpg) {
        delete dtpgo->dtpg;
        dtpgo->dtpg = nullptr;
    }
}
/* }}} */

/* {{{ IntlDateTimePatternGenerator_object_create */
zend_object *IntlDateTimePatternGenerator_object_create(zend_class_entry *ce)
{
    IntlDateTimePatternGenerator_object* intern;

    intern = (IntlDateTimePatternGenerator_object*)ecalloc(1,
                        sizeof(IntlDateTimePatternGenerator_object) + zend_object_properties_size(ce));

    zend_object_std_init(&intern->zo, ce);
    object_properties_init(&intern->zo, ce);
    intern->dtpg = nullptr;
    intern->status = U_ZERO_ERROR;

    intern->zo.handlers = &IntlDateTimePatternGenerator_object_handlers;

    return &intern->zo;
}
/* }}} */

/* {{{ proto void IntlDateTimePatternGenerator::__construct(string $locale)
 * IntlDateTimePatternGenerator object constructor.
 */
PHP_METHOD(IntlDateTimePatternGenerator, __construct)
{
    zend_string *locale;
    zval *object;
    IntlDateTimePatternGenerator_object* dtpg = nullptr;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &locale) == FAILURE) {
        return;
    }

    object = getThis();
    dtpg = php_intl_datetimepatterngenerator_fetch_object(Z_OBJ_P(object));

    dtpg->status = U_ZERO_ERROR;
    dtpg->dtpg = DateTimePatternGenerator::createInstance(Locale(ZSTR_VAL(locale)), dtpg->status);
}
/* }}} */

/* {{{ proto string IntlDateTimePatternGenerator::findBestPattern(string $skeleton)
 * Return the best pattern matching the input skeleton.
 */
PHP_METHOD(IntlDateTimePatternGenerator, findBestPattern)
{
    zend_string *skeleton;
    zval *object;
    IntlDateTimePatternGenerator_object* dtpg = nullptr;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &skeleton) == FAILURE) {
        return;
    }

    object = getThis();
    dtpg = php_intl_datetimepatterngenerator_fetch_object(Z_OBJ_P(object));

    UnicodeString pattern = dtpg->dtpg->getBestPattern(UnicodeString(ZSTR_VAL(skeleton)), dtpg->status);

    std::string s;
    pattern.toUTF8String(s);
    RETURN_STRING(s.c_str());
}
/* }}} */

ZEND_BEGIN_ARG_INFO_EX(arginfo_findBestPattern, 0, 0, 1)
    ZEND_ARG_INFO(0, skeleton)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo___construct, 0, 0, 1)
    ZEND_ARG_INFO(0, locale)
ZEND_END_ARG_INFO()

const zend_function_entry IntlDateTimePatternGenerator_functions[] = {
    PHP_ME(IntlDateTimePatternGenerator, __construct, arginfo___construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    PHP_ME(IntlDateTimePatternGenerator, findBestPattern, arginfo_findBestPattern, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(intl_dtpg)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "IntlDateTimePatternGenerator", IntlDateTimePatternGenerator_functions);
    ce.create_object = IntlDateTimePatternGenerator_object_create;
    IntlDateTimePatternGenerator_ce = zend_register_internal_class(&ce);

    memcpy(&IntlDateTimePatternGenerator_object_handlers, zend_get_std_object_handlers(),
        sizeof IntlDateTimePatternGenerator_object_handlers);
    IntlDateTimePatternGenerator_object_handlers.offset =
                    XtOffsetOf(IntlDateTimePatternGenerator_object, zo);
    IntlDateTimePatternGenerator_object_handlers.clone_obj = NULL; //no clone support
    IntlDateTimePatternGenerator_object_handlers.dtor_obj = IntlDateTimePatternGenerator_object_dtor;
    IntlDateTimePatternGenerator_object_handlers.free_obj = IntlDateTimePatternGenerator_object_free;

    if(!IntlDateTimePatternGenerator_ce) {
        zend_error(E_ERROR, "Failed to register IntlDateTimePatternGenerator class");
        return FAILURE;
    }

    return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(intl_dtpg)
{
    return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(intl_dtpg)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "intl_dtpg support", "enabled");
    php_info_print_table_header(2, "intl_dtpg version", INTL_DTPG_VERSION);
    php_info_print_table_end();
}
/* }}} */

/* {{{ intl_dtpg_functions[]
 */
const zend_function_entry intl_dtpg_functions[] = {
    PHP_FE_END
};
/* }}} */

/* {{{ intl_dtpg_module_entry
 */
zend_module_entry intl_dtpg_module_entry = {
    STANDARD_MODULE_HEADER,
    "intl_dtpg",
    intl_dtpg_functions,
    PHP_MINIT(intl_dtpg),
    PHP_MSHUTDOWN(intl_dtpg),
    NULL,
    NULL,
    PHP_MINFO(intl_dtpg),
    INTL_DTPG_VERSION,
    STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_INTL_DTPG
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(intl_dtpg)
#endif



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

IntlDateTimePatternGenerator_object_dtor — деструктор PHP объекта. Тут нечего делать, вызываем стандартный API.
IntlDateTimePatternGenerator_object_free — освобождение памяти. Важная фаза, нужно сделать всё аккуратно, чтобы не было утечек памяти. Получаем нашу структуру, вызываем деструктор для zend_object, сбрасываем статус и уничтожаем объект С++ класса.
IntlDateTimePatternGenerator_object_create — выделение памяти под новый объект. Код подсмотрен в исходных кодах расширения intl.
Далее определяем методы нашего класса.
PHP_METHOD(IntlDateTimePatternGenerator, __construct) — констуктор, создаётся новый объект DateTimePatternGenerator.
Тут, в zend_parse_parameters, мы получаем строку в виде zend_string объекта, при этом в zend_parse_parameters указывается большая «S». Это нововведение в PHP 7. Но и старый способ с указанием маленькой «s» и получением отдельно C-style строки и её длины тоже работает, как мы видели в предыдущем расширении.
PHP_METHOD(IntlDateTimePatternGenerator, findBestPattern) — тут вызывается метод класса из ICU библиотеки.
Далее следует набор макросов для определения параметров методов класса. По имени параметров этих макросов в файле zend_API.h можно понять, что они значат.
В структуре zend_function_entry указываются методы класса. В макрос PHP_ME передаются ранее определённые наборы параметров и флаги. Эта структура используется при регистрации класса ниже.
PHP_MINIT_FUNCTION(intl_dtpg) — вызывается в момент инициализации расширения. Тут регистрируется наш класс и указываются хендлеры для обслуживания его фаз жизни.
PHP_MSHUTDOWN_FUNCTION(intl_dtpg) — когда нечего делать в момент shutdown, можно просто вернуть SUCCESS, а можно и не указывать и ниже, в zend_module_entry, указать NULL.
PHP_MINFO_FUNCTION(intl_dtpg) — добавляет информацию о расширении в вывод phpinfo()
Снова zend_function_entry, но в этот раз пустая — мы не определяем никаких функций в этом расширении.
zend_module_entry — тут мы указываем методы для инициализации и shutdown нашего расширения, другие два NULL это про request, мы ничего не делаем в момент инициализации и shutdown запроса.

tests


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

001.phpt
--TEST--
Check for intl_dtpg presence
--SKIPIF--

--FILE--

--EXPECT--
d MMMM;MMMM d



Сборка и тестирование



phpize && ./configure && make
make test


valgrind — проверяем расширения на утечки памяти


Проверим наше расширение intl_dtpg на утечки памяти. Для этого создадим в папке расширения тестовый ini файл:

test-php.ini
extension=modules/intl_dtpg.so


и тестовый php файл:
test.php

https://habrahabr.ru/post/335478/


Метки:  

[Перевод] Практический бизнес онтологии: рассказ c передовой

Пятница, 11 Августа 2017 г. 23:34 + в цитатник


Перевод поста Стивена Вольфрама (Stephen Wolfram) "The Practical Business of Ontology: A Tale from the Front Lines".

Философия химических веществ


«Мы только должны решить: химикат — ближе к городу или к числу?» Я провел вчера свой день — как и большинство дней последних 30 лет — разрабатывая новые функции языка Wolfram. И вчера днем на одном из моих собраний была динамичная дискуссия о том, как расширить возможности языка в химии.

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

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

Мы построили целую систему на языке Wolfram для обработки того, что мы называем объектами . Объект может быть городом (например, Нью-Йорк), или фильмом, или планетой, или зиллионом других вещей. У объекта есть какое-то имя («Нью-Йорк»). И он имеет определенные свойства (например, население, площадь, дата основания, ...).

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

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

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

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

На каком-то уровне это путаное различие. Потому что мы можем думать, что зная молекулярную структуру, мы знаем все — это просто вопрос расчета. И некоторые свойства — такие как молярная масса — в основном тривиальны для расчета по молекулярной структуре. Но другие – например, температура плавления — очень далеки от тривиальных.

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

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

По крайней мере, атомы должны быть в порядке


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

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

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

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

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

Чтобы указать химический элемент, вам просто нужно указать его атомный номер Z. А затем учебники подскажут вам, что для указания конкретного изотопа вам просто нужно указать сколько нейтронов он содержит. Но это игнорирует неожиданный случай тантала. Потому что одна из естественных форм тантала (180mTa) на самом деле представляет собой возбужденное состояние ядра тантала, которое является очень стабильным. И чтобы правильно это определить, вы должны указать его уровень возбуждения, а также количество нейтронов.

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

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

Мы перешли от молекул к атомам к ядрам, так почему бы не говорить и об элементарных частицах? Что ж, это усложняет ситуацию. Да, есть известные частицы, такие как электроны и протоны — о которых довольно легко говорить — и они легко представлены объектами на языке Wolfram. Но есть и множество других частиц. Некоторые из них — такие, как ядра — довольно легко охарактеризовать. Вы можете сказать такие вещи, как: «Это особое возбужденное состояние системы c-кварк-анти-c-кварк» или что-то в этом роде. Но в физике частиц речь идет о квантовой теории поля, а не только о квантовой механике. И нельзя просто «подсчитать элементарные частицы»; также нужно иметь дело с возможностью виртуальных частиц и т. д. И в конечном итоге вопрос о том, какие частицы могут существовать — очень сложный, полный вычислительной неприводимостью. (Например, какие стабильные состояния могут быть в глюонном поле, это гораздо более сложный вопрос, похожий на задачу замощения, о которой я упоминал в связи с точками плавления.)

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

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

Пространство возможных химических веществ


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

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

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

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

Однако, любой уважающий себя объект будет иметь свое собственное имя. Какое было бы это имя? На языке Wolfram это может быть только граф, который представляет структуру. Но, возможно, хотелось бы чего-то похожего на обычное текстовое имя — на строку. У нас всегда есть способ IUPAC для названий химических веществ с именами, например 1,1'-{[3-(dimethylamino)propyl]imino}bis-2-propanol. Также есть более удобная для компьютера версия SMILES: CC(CN(CCCN©C)CC©O)O. И какой бы ни был граф, он всегда может генерировать одну из этих строк для ее представления.

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

Что такое химикат в конце?


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

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

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

Но давайте поговорим о некоторых других случаях и аналогиях. Может быть, мы должны просто рассматривать все как объект. Как и любое целое число может быть объектом. Да, их бесконечное число. Но по крайней мере ясно, какие имена им следует давать. С действительными числами вещи уже в беспорядке. Например, уже нет такой уникальности, как с целыми числами: 0.99999… на самом деле то же самое, что и 1.00000 ..., но написано по-разному.

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

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

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

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

Но, очевидно, там должна быть связь. Для конкретного химического объекта одним из свойств будет граф, представляющий структуру молекулы. И, имея граф, нужна какая-то функция ChemicalIdentify, которая, подобно GeoIdentify или, возможно, ImageIdentify, попытается идентифицировать по графу, какой химический объект (если таковой имеется) имеет молекулярную структуру, соответствующую этому графу.

Философия встречает химию встречает математику встречает физику...


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

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

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

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

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

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

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

https://habrahabr.ru/post/335476/


Как попасть в ТОП: PR ДО релиза и в случае провала

Пятница, 11 Августа 2017 г. 23:15 + в цитатник
Сейчас сложно себе представить жизнь современного человека без смартфона, а смартфон уже совсем немыслим без мобильных приложений, число которых перевалило за полтора миллиона в App Store и Google Play. Поэтому часто пользователи даже и не подозревают, что уже разработано и опубликовано самое лучшее мобильное приложение.


— Так вы не читаете «Подиум».
— Нет.
— И никогда не слышали обо мне?
— Нет.


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


— Кто это?

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


— Я еще только учусь разбираться во всех этих шмотках…
— «В этих шмотках»?


Ведите блог


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

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

Соответствуйте


Если Вы делаете приложение для fashion-блогеров, снимите растянутый свитер. Создаете продукт для спортсменов? Сделайте хоть раз зарядку. По уму необходимо иметь представителя, который и будет соответствовать Вашему приложению, рекламное лицо, так сказать. Либо иметь отдельный рабочий аккаунт в социальных сетях.


— Так, теперь — Шанель. Необходима Шанель.

Преобразите свой профиль или создайте рабочий аккаунт:

– Аватарка/фото профиля


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

– Статус/шапка в твиттере/описание в инстаграме/обложка в фейсбуке


Это такой же рекламный канал. Оставьте там ссылке на Ваше приложение – QR-код или укажите ссылки на группы в соц.сетях. Креативное оформление здесь приветствуется.

– Подпишитесь на соответствующие группы;


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

– Контактные данные


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


— Не подскажете, как пишется «Габбана»? Алло?

– Настройки приватности


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

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

Ориентируйтесь на ЦА


Нет тонкой грани между «мне нравится» и «действительно нужно\красиво». Это довольно толстая линия и называется она – профессионализм.

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

Общайтесь


Общайтесь онлайн. Это не значит спамить под каждым постов или вступать в распри в комментариях. Если Вы прочитали/увидели что-то годное – похвалите автора. Есть вопросы? Задайте их в комментариях. Можете ответить на чей-то вопрос? Отвечайте.

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

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


— Конечно, двигайтесь как можно медленней. Вы знаете, как я это люблю.

Не забывайте о времени


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

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


— Так, внимание. Боевая готовность!

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

– Истории неудач


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

– Блог


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

Заключение


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

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

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

https://habrahabr.ru/post/335474/


[recovery mode] IOTV — простой HTTP протокол для работы с сообщениями и командами IOT объектов в сервисе VIALATM

Пятница, 11 Августа 2017 г. 23:01 + в цитатник


Несколько разрработчиков, использующих сервис VIALATM для работы с IOT объектами, обратились ко мне с просьбой реализовать простой http протокол. Ранее они использовали MQTT протокол, но в силу каких-то причин им было необходимо более простое решение. Протокол реализован. В этой статье его краткое описание.


Для поддержки портокола IOTV в сервисе зарезервирован порт 7746 (для работы по протоклу https следеут использовать порт — 7745).

HTTP header


Все запросы по протоколу IOTV должны содержать в заголовке (http header) атрибут «iotv-user». Если в настройках учетной записи «IOTV password» установлен, атрибут «iotv-password» в заголвке должен совпадать с этим значением, в противном случае он может быть опущен.



Обязательный атрибут


Все запросы должны содеражать обязательный атрибут«root».



Дополнительные атрибуты


К запросам может быть добавлен атрибут «time». Он должен быть установлен в формате UNIX STAMP (количество секунд с 1 января 1970 года). Если этот атрибут опущен, то временем события считается время поступления запроса на сервер. Все прочие возможные атрибуты зависят от того, как определен объект IOT в сервисе. В ответ на запрос возвращаются текущие значения всех атрибутов объекта

Примеры


GET
Request: vialatm.com:7746/?root=HOME&A1=5&B1=12
Response: A1=5&B1=12&C1=14

POST
Request: vialatm.com:7746/

JSON
Data: {«A1»:«12»,«root»:«HOME»,«B1»:«44»}
Response: {«A1»:«12»,«B1»:«44»,«C2»:«12»}
XML
Data: 73HOME87
Response: 738788
POST FORM
Data: A1=543&root=HOME&B1=12&
Response: command=12.4&A1=543&B1=12&C1=14

Команды в ответ на запросы


Для IOT объектов можно определить команды



В этом случае, когда задается команда для объекта, она посылается в ответе на IOTV запрос:



Примеры ответов на запросы в этих случаях:

GET
Request: vialatm.com:7746/?root=HOME&A1=5&B1=12
Response: command=12.4&A1=5&B1=12&C1=14

POST
Request: vialatm.com:7746/

JSON
Data: {«A1»:«12»,«root»:«HOME»,«B1»:«44»}
Response: {«command»:«12.4»,«A1»:«12»,«B1»:«44»,«C2»:«12»}
XML
Data: 73HOME87
Response: 12.4738788
POST FORM
Data: A1=543&root=HOME&B1=12&
Response: command=12.4&A1=543&B1=12&C1=14
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335468/


Метки:  

Security Week 32: В репозиторий npm проник шпион, Disney запретят следить за детьми, Juniper запатчил годовалый баг

Пятница, 11 Августа 2017 г. 22:37 + в цитатник
JS-разработчики порой творят друг с другом страшные вещи. Нет бы мирно кодить и радоваться каждому коммиту! Но в ряды травоядных мирных программистов затесался злодей, подкинувший в репозиторий npm пачку вредоносных пакетов. npm – стандартный менеджер пакетов в Node.js, и обладает облачным репозиторием, полным всяких полезнейших пакетов.

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

Один из пользователей таки заглянул в crossenv и побежал в твиттер бить тревогу. Как оказалось, этот пакет содержит скрипт, который добывает из переменных окружения важную информацию (например, учетные данные от npm), кодирует ее в строку, и отправляет POST-запросом на сервер npm.hacktask.net.



Всего злодейский пакет скачан 700 раз, но большинство из этих скачиваний – расползание по зеркалам. Компания npm считает, что реальных установок было не более 50. Но это только для одного, самого популярного пакета, всего шустрый HuskTask залил гораздо больше:

Вредные пакеты
babelcli: 42
cross-env.js: 43
crossenv: 679
d3.js: 72
fabric-js: 46
ffmepg: 44
gruntcli: 67
http-proxy.js: 41
jquery.js: 136
mariadb: 92
mongose: 196
mssql-node: 46
mssql.js: 48
mysqljs: 77
node-fabric: 87
node-opencv: 94
node-opensl: 40
node-openssl: 29
node-sqlite: 61
node-tkinter: 39
nodecaffe: 40
nodefabric: 44
nodeffmpeg: 39
nodemailer-js: 40
nodemailer.js: 39
nodemssql: 44
noderequest: 40
nodesass: 66
nodesqlite: 45
opencv.js: 40
openssl.js: 43
proxy.js: 43
shadowsock: 40
smb: 40
sqlite.js: 48
sqliter: 45
sqlserver: 50
tkinter: 45


Всем жертвам опечаток, подключивших пакеты из списка, настоятельно рекомендуется сменить пароли от npm. HuskTusk забанен, все его пакеты из репозитория вычищены. По всем остальным пакетам быстренько пробежался Адам Болдуин из LiftSecurity, и не нашел ничего похожего на злодейский скрипт. Зачем это нужно было злоумышленнику – остается только догадываться. Об актах бессмысленного вандализма в node.js-проектах пока не сообщается.

Disney обвинили в незаконном сборе личной информации о детях

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

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

Корень зла, по мнению Аманды Рашинг, кроется в мобильных приложениях вроде Disney Priness Palace Pets (всего в иске указано около 50 наименований). В этой милой игрушке надо купать, стричь, наряжать и всячески развлекать виртуальных домашних зверюшек. Ну и остальные примерно такие же.

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

Все относительно невинно, но в США действует закон COPPA, специально против этого.
Children's Online Privacy Protection Act (COPPA) постулирует простую вещь – перед тем, как собирать хоть какую информацию о ребенке младше 13, изволь сначала получить разрешение у родителей. По букве закона, обезличенной информации это тоже касается. На практике принятие COPPA привело к запрету детям этого возраста регистрироваться на большинстве веб-сайтов – никому не нужны лишние проблемы. Ну и таргетирование рекламы становится, мягко говоря, затруднительным.

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

В оборудовании juniper нашли серьезные уязвимости

Новость. Посмотрев, как исследователи гнобят Cisco, наперегонки находя в их девайсах дыры, Juniper сыграл на опережение, известив мир о серьезном баге в своих продуктах. Там, оказывается, имеется серьезная уязвимость в библиотеке GD из PHP версии 4.3 и выше. Библиотека графическая, а вот баг критический, ибо позволяет командовать устройством без аутентификации.

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

На самом деле баг очень несвежий, проблему в libgd 2.1.1 обнаружили уже год назад, и HP Enterprise, Red Hat, Fedora, Debian давно запатчились. Вот и Juniper подтянулись. Лучше, конечно, поздно, чем никогда.

Пользователям уязвимых устройств, а это роутеры серий T и MAX, и а также коммутаторы четырех моделей, советуют обновить софт. Или же можно отключить все сервисы, использующие PHP-скрипты, вроде J-Web и XNM-SSL. И еще используйте списки доступа (в любой непонятной ситуации используйте списки доступа).

Древности


«Kuku-448»

Нерезидентный очень опасный вирус, поражает .COM-файлы при запуске инфицированного файла, записываясь в их начало. В зависимости от времени (с вероятностью 1/8) «убивает» файлы. «Убитый» файл при старте расшифровывает и в большом количестве выводит на экран разноцветные надписи «Kuku!», при этом компьютер не реагирует на клавиатуру.

Цитата по книге «Компьютерные вирусы в MS-DOS» Евгения Касперского. 1992 год. Страницa 73.

Disclaimer: Данная колонка отражает лишь частное мнение ее автора. Оно может совпадать с позицией компании «Лаборатория Касперского», а может и не совпадать. Тут уж как повезет.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335470/


Метки:  

[Перевод] Создаем самодостаточный Docker-кластер

Пятница, 11 Августа 2017 г. 20:30 + в цитатник

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


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


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


self-sufficient-system


Система с самовосстанавливающимися и самоадаптирующимися сервисами


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


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


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


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


Роль разработчика в системе


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


Я назначил разработчика главным героем в этой драме. Я намеренно отказался от слова “кодер”. Разработчик — это любой человек, который работает над проектом разработки софта. Он может быть программистом, тестировщиком, гуру операций или scrum-мастером — это всё не важно. Я помещаю всех этих людей в группу под названием разработчик. В результате своей работы они должны разместить некий код в репозиторий. Пока его там нет, его будто бы и не существует. Не важно, располагается ли он на вашем компьютере, в ноутбуке, на столе или на маленьком кусочке бумаги, прикрепленном к почтовому голубю. С точки зрения системы этот код не существует, до тех пор пока он не попадет в репозиторий. Я надеюсь, что этот репозиторий Git, но, по идее, это может быть любое место, где вы можете хранить что-нибудь и отслеживать версии.


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


developer-commits-code


Разработчик отправляет код в репозиторий


Посмотрим, что происходит, когда код отправляется в репозиторий.


Роль непрерывного развертывания в системе


Процесс непрерывного развертывания полностью автоматизирован. Никаких исключений. Если ваша система не автоматизирована, то у вас нет непрерывного развертывания. Возможно, вам понадобится вручную деплоить в продакшн. Если вы вручную нажимаете на одну единственную кнопку, на которой жирным написано “deploy”, то ваш процесс является непрерывной доставкой. Такое я могу понять. Такая кнопка может потребоваться с точки зрения бизнеса. И все же уровень автоматизации в таком случае такой же, как и при непрерывном развертывании. Вы здесь только принимаете решения. Если требуется делать что-то еще вручную, то вы либо выполняете непрерывную интеграцию, либо, что более вероятно, делаете что-то такое, в чьем названии нет слова “непрерывный”.


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


Как только репозиторий получает commit или pull request, срабатывает Web hook, который в свою очередь отправляет запрос инструменту непрерывного развертывания для запуска процесса непрерывного развертывания. В нашем случае этим инструментом является Jenkins. Запрос запускает поток всевозможных задач по непрерывному развертыванию. Он проверяет код и проводит модульные тесты. Он создает образ и пушит в регистр. Он запускает функциональные, интеграционные, нагрузочные и другие тесты — те, которым требуется рабочий сервис. В самом конце процесса (не считая тестов) отправляется запрос планировщику, чтобы тот развернул или обновил сервис в кластере. Среди прочих планировщиков мы выбираем Docker Swarm.


jenkins-deployment


Развертывание сервиса через Jenkins


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


Роль конфигурации сервисов в системе


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


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


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


Есть хороший инструмент, который находит информацию о сервисе и распространяет ее по всей системе, — это Docker Flow Swarm Listener (DFSL). Можете воспользоваться любым другим решением или создать свое собственное. Конечная цель этого и любого другого такого инструмента — прослушивать события Docker Swarm. Если у сервиса есть особый набор ярлыков, приложение получит информацию, как только вы установите или обновите сервис. После чего оно передаст эту информацию всем заинтересованным сторонам. В данном случае это Docker Flow Proxy (DFP, внутри которого есть HAProxy) и Docker Flow Monitor (DFM, внутри есть Prometheus). В результате у обоих всегда будет последняя актуальная конфигурация. У Proxy есть путь ко всем публичным сервисам, тогда как у Prometheus есть информация об экспортерах, оповещениях, адресе Alertmanager-а и других вещах.


system-reconfig


Реконфигурация системы через Docker Flow Swarm Listener


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


Роль Proxy в системе


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


Благодаря Docker-у некоторые аспекты прокси теперь устарели. Больше не нужно балансировать нагрузку. Сеть Docker Overlay делает это за нас. Больше не нужно поддерживать IP-ноды, на которых хостятся сервисы. Service discovery делает это за нас. Все, что требуется от прокси, — это оценить заголовки и переправить запросы, куда следует.


Поскольку Docker Swarm всегда использует скользящие обновления, когда изменяется какой-либо аспект сервиса, процесс непрерывного развертывания не должен становиться причиной простоя. Чтобы это утверждение было верным, должны выполняться несколько требований. Должно быть запущено по крайней мере две реплики сервиса, а лучше еще больше. Иначе, если реплика будет только одна, простой неизбежен. На минуту, секунду или миллисекунду — неважно.


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


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


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


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


Мы используем Docker Flow Proxy (DFP), который добавляет нужный уровень динамизма поверх HAProxy.


request-flow


Путь запроса к назначенному сервису


Следующая роль, которую мы обсудим, связана со сбором метрик.


Роль метрик в системе


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


Самоадаптирующейся системе надо собирать данные, хранить их и применять к ним разные действия. Не будем обсуждать, что лучше — отправка данных или их сбор. Но поскольку мы используем Prometheus для хранения и оценки данных, а также для генерации оповещений, то мы будем собирать данные. Эти данные доступны от экспортеров. Они могут быть общими (например, Node Exporter, cAdvisor и т.д.) или специфичными по отношению к сервису. В последнем случае сервисы должны выдавать метрики в простом формате, который ожидает Prometheus.


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


data-collection


Сбор данных и оповещений


Роль оповещений в системе


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


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


self-adapt


Уведомление системы для самоадаптации


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


Когда вы начинаете проектировать самовосстанавливающуюся систему, большинство оповещений попадут в категорию “неожиданное”. Вы не можете предугадать все ситуации. Единственное, что вы можете в данном случае — это убедиться, что неожиданное случается только один раз. Когда вы получаете уведомление, ваша первая задача — адаптировать систему вручную. Вторая — улучшить правила в Alertmanager и Jenkins, чтобы когда ситуация повторится, система могла бы справиться с ней автоматически.


human-notification


Уведомление для человека, когда случается что-то неожиданное


Настроить самоадаптирующуюся систему тяжело, и эта работа бесконечная. Ее постоянно нужно улучшать. А как насчет самовосстановления? Так ли сложно достичь и его?


Роль планировщика в системе


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


Реплики могут выходить из строя, они могут быть убиты и они могут находиться внутри нездорового нода. Это все не так важно, поскольку Swarm следит за тем, чтобы они перезапускались при необходимости и (почти) всегда нормально работали. Если все наши сервисы масштабируемы и на каждом из них запущено хотя бы несколько реплик, простоя никогда не будет. Процессы самовосстановления внутри Docker-а сделают процессы самоадаптации легко доступными. Именно комбинация этих двух элементов делает нашу систему полностью автономной и самодостаточной.


Проблемы начинают накапливаться, когда сервис нельзя масштабировать. Если у нас не может быть нескольких реплик сервиса, Swarm не сможет гарантировать отсутствие простоев. Если реплика выйдет из строя, она будет перезапущена. Впрочем, если это единственная доступная реплика, период между аварией и повторным запуском превращается в простой. У людей все точно так же. Мы заболеваем, лежим в постели и через какое-то время возвращаемся на работу. Если мы единственный работник в этой компании и нас некому подменить, пока мы отсутствуем, то это проблема. То же применимо и к сервисам. Для сервиса, который хочет избежать простоев, необходимо как минимум иметь две реплики.


docker-swarm


Docker Swarm следит, чтобы не было простоев


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


Роль кластера в системе


В конце концов все, что мы делаем, находится внутри одного и более кластеров. Больше не существует индивидуальных серверов. Не мы решаем, что куда направить. Это делают планировщики. С нашей (человеческой) точки зрения самый маленький объект — это кластер, в котором собраны ресурсы типа памяти и CPU.


everything-is-a-cluster


Все является кластером

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

https://habrahabr.ru/post/335464/


Автономный способ обхода DPI и эффективный способ обхода блокировок сайтов по IP-адресу

Пятница, 11 Августа 2017 г. 19:11 + в цитатник
Провайдеры Российской Федерации, в большинстве своем, применяют системы глубокого анализа трафика (DPI, Deep Packet Inspection) для блокировки сайтов, внесенных в реестр запрещенных. Не существует единого стандарта на DPI, есть большое количество реализации от разных поставщиков DPI-решений, отличающихся по типу подключения и типу работы.

Существует два распространенных типа подключения DPI: пассивный и активный.

Пассивный DPI

Пассивный DPI — DPI, подключенный в провайдерскую сеть параллельно (не в разрез) либо через пассивный оптический сплиттер, либо с использованием зеркалирования исходящего от пользователей трафика. Такое подключение не замедляет скорость работы сети провайдера в случае недостаточной производительности DPI, из-за чего применяется у крупных провайдеров. DPI с таким типом подключения технически может только выявлять попытку запроса запрещенного контента, но не пресекать ее. Чтобы обойти это ограничение и заблокировать доступ на запрещенный сайт, DPI отправляет пользователю, запрашивающему заблокированный URL, специально сформированный HTTP-пакет с перенаправлением на страницу-заглушку провайдера, словно такой ответ прислал сам запрашиваемый ресурс (подделывается IP-адрес отправителя и TCP sequence). Из-за того, что DPI физически расположен ближе к пользователю, чем запрашиваемый сайт, подделанный ответ доходит до устройства пользователя быстрее, чем настоящий ответ от сайта.

Выявляем и блокируем пакеты пассивного DPI

Поддельные пакеты, формируемые DPI, легко обнаружить анализатором трафика, например, Wireshark.
Пробуем зайти на заблокированный сайт:
Wireshark

Мы видим, что сначала приходит пакет от DPI, с HTTP-перенаправлением кодом 302, а затем настоящий ответ от сайта. Ответ от сайта расценивается как ретрансмиссия и отбрасывается операционной системой. Браузер переходит по ссылке, указанной в ответе DPI, и мы видим страницу блокировки.

Рассмотрим пакет от DPI подробнее:
image

HTTP/1.1 302 Found
Connection: close
Location: http://warning.rt.ru/?id=17&st=0&dt=195.82.146.214&rs=http%3A%2F%2Frutracker.org%2F

В ответе DPI не устанавливается флаг «Don't Fragment», и в поле Identification указано 1. Серверы в интернете обычно устанавливают бит «Don't Fragment», и пакеты без этого бита встречаются нечасто. Мы можем использовать это в качестве отличительной особенности пакетов от DPI, вместе с тем фактом, что такие пакеты всегда содержат HTTP-перенаправление кодом 302, и написать правило iptables, блокирующее их:
# iptables -A FORWARD -p tcp --sport 80 -m u32 --u32 "0x4=0x10000 && 0x60=0x7761726e && 0x64=0x696e672e && 0x68=0x72742e72" -m comment --comment "Rostelecom HTTP" -j DROP

Что это такое? Модуль u32 iptables позволяет выполнять битовые операции и операции сравнения над 4-байтовыми данными в пакете. По смещению 0x4 хранится 2-байтное поле Indentification, сразу за ним идут 1-байтные поля Flags и Fragement Offset.
Начиная со смещения 0x60 расположен домен перенаправления (HTTP-заголовок Location).
Если Identification = 1, Flags = 0, Fragment Offset = 0, 0x60 = «warn», 0x64 = «ing.», 0x68 = «rt.ru», то отбрасываем пакет, и получаем настоящий ответ от сайта.

В случае с HTTPS-сайтами, DPI присылает TCP Reset-пакет, тоже с Identification = 1 и Flags = 0.

Активный DPI

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

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

Изучаем стандарт HTTP

Типичные HTTP-запросы в упрощенном виде выглядят следующим образом:
GET / HTTP/1.1
Host: habrahabr.ru
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/50.0
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Запрос начинается с HTTP-метода, затем следует один пробел, после него указывается путь, затем еще один пробел, и заканчивается строка протоколом и переносом строки CRLF.
Заголовки начинаются с большой буквы, после двоеточия ставится символ пробела.

Давайте заглянем в последнюю версию стандарта HTTP/1.1 от 2014 года. Согласно RFC 7230, HTTP-заголовки не зависят от регистра символов, а после двоеточия может стоять произвольное количество пробелов (или не быть их вовсе).
   Each header field consists of a case-insensitive field name followed
   by a colon (":"), optional leading whitespace, the field value, and
   optional trailing whitespace.

     header-field   = field-name ":" OWS field-value OWS

     field-name     = token
     field-value    = *( field-content / obs-fold )
     field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
     field-vchar    = VCHAR / obs-text

     obs-fold       = CRLF 1*( SP / HTAB )
                    ; obsolete line folding

OWS — опциональный один или несколько символов пробела или табуляции, SP — одинарный символ пробела, HTAB — табуляция, CRLF — перенос строки и возврат каретки (\r\n).

Это значит, что запрос ниже полностью соответствует стандарту, его должны принять многие веб-серверы, придерживающиеся стандарта:
GET / HTTP/1.1
hoSt:habrahabr.ru
user-agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/50.0
Accept-Encoding:           gzip, deflate, br
coNNecTion:	keep-alive      <- здесь символ табуляции между двоеточием и значением

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

Старый стандарт, RFC 2616, рекомендует снисходительно парсить запросы и ответы сломанных веб-северов и клиентов, и корректно обрабатывать произвольное количество пробелов в самой первой строке HTTP-запросов и ответов в тех местах, где требуется только один:
Clients SHOULD be tolerant in parsing the Status-Line and servers tolerant when parsing the Request-Line. In particular, they SHOULD accept any amount of SP or HT characters between fields, even though only a single SP is required.
Этой рекомендации придерживаются далеко не все веб-серверы. Из-за двух пробелов между методом и путем ломаются некоторые сайты.

Спускаемся на уровень TCP

Соединение TCP начинается с SYN-запроса и SYN/ACK-ответа. В запросе клиент, среди прочей информации, указывает размер TCP-окна (TCP Window Size) — количество байт, которые он готов принимать без подтверждения передачи. Сервер тоже указывает это значение. В интернете используется значение MTU 1500, что позволяет отправить до 1460 байтов данных в одном TCP-пакете.
Если сервер указывает размер TCP-окна менее 1460, клиент отправит в первом пакете данных столько, сколько указано в этом параметре.

Если сервер пришлет TCP Window Size = 2 в SYN/ACK-пакете (или мы его изменим на это значение на стороне клиента), то браузер отправит HTTP-запрос двумя пакетами:

Пакет 1:
GE
Пакет 2:
T / HTTP/1.1
Host: habrahabr.ru
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/50.0
Accept-Encoding: gzip, deflate, br
Connection: keep-alive


Используем особенности HTTP и TCP для обхода активного DPI

Многие решения DPI ожидают заголовки только в стандартном виде.
Для блокировки сайтов по домену или URI, они ищут строку "Host: " в теле запроса. Стоит заменить заголовок «Host» на «hoSt» или убрать пробел после двоеточия, и перед вами открывается запрошенный сайт.
Не все DPI можно обмануть таким простым трюком. DPI некоторых провайдеров корректно анализируют HTTP-заголовки в соответствии со стандартом, но не умеют собирать TCP-поток из нескольких пакетов. Для таких DPI подойдет «фрагментирование» пакета, путем искусственного уменьшения TCP Window Size.

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

Программа для обхода DPI

Я написал программу для обхода DPI под Windows: GoodbyeDPI.
Она умеет блокировать пакеты с перенаправлением от пассивного DPI, заменять Host на hoSt, удалять пробел между двоеточием и значением хоста в заголовке Host, «фрагментировать» HTTP и HTTPS-пакеты (устанавливать TCP Window Size), и добавлять дополнительный пробел между HTTP-методом и путем.
Преимущество этого метода обхода в том, что он полностью автономный: нет внешних серверов, которые могут заблокировать.

По умолчанию активированы опции, нацеленные на максимальную совместимость с провайдерами, но не на скорость работы. Запустите программу следующим образом:
goodbyedpi.exe -1 -a
Если заблокированные сайты стали открываться, DPI вашего провайдера можно обойти.
Попробуйте запустить программу с параметром -2 и зайти на заблокированный HTTPS-сайт. Если все продолжает работать, попробуйте режим -3 и -4 (наиболее быстрый).
Некоторые провайдеры, например, Мегафон и Yota, не пропускают фрагментированные пакеты по HTTP, и сайты перестают открываться вообще. С такими провайдерами используйте опцию -3 -a

Эффективное проксирование для обхода блокировок по IP

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

ReQrypt

Долгое время разработка была заморожена из-за того, что автор не мог найти сервер с возможностью спуфинга. Спуфинг IP-адресов часто используется для амплификации атак через DNS, NNTP и другие протоколы, из-за чего он запрещен у подавляющего большинства провайдеров. Но сервер все-таки был найден, хоть и не самый удачный. Разработка продолжается.

Заключение и TL;DR

GoodbyeDPI — программа под Windows, позволяющая обходить пассивные и активные DPI. Просто скачайте и запустите ее, и заблокированные сайты станут снова доступны.
Для Linux есть аналогичная программа — zapret.

Используйте кроссплатформенную программу ReQrypt, если ваш провайдер блокирует сайты по IP-адресу.

Определить тип блокировки сайтов можно программой Blockcheck. Если в тестах DPI вы видите, что сайты открываются, или видите строку «обнаружен пассивный DPI», то GoodbyeDPI вам поможет. Если нет, используйте ReQrypt.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335436/


Метки:  

Пятничное: к сообществу

Пятница, 11 Августа 2017 г. 19:10 + в цитатник

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


Спасибо за внимание.

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

https://habrahabr.ru/post/335460/


Метки:  

Метод оптимизации Trust-Region DOGLEG. Пример реализации на Python

Пятница, 11 Августа 2017 г. 19:03 + в цитатник


Trust-region метод (TRM) является одним из самых важных численных методов оптимизации в решении проблем нелинейного программирования (nonlinear programming problems). Метод базируется на определении региона вокруг лучшего решения, в котором квадратичная модель аппроксимирует целевую функцию.

Методы линейного поиска (line search) и методы trust-region генерируют шаги с помощью аппроксимации целевой функции квадратичной моделью, но использую они эту модель по-разному. Линейный поиск использует её для получения направления поиска и дальнейшего нахождения оптимального шага вдоль направления. Trust-region метод определяет область (регион) вокруг текущей итерации, в котором модель достаточно аппроксимирует целевую функцию. В целях повышения эффективности направление и длина шага выбираются одновременно.

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

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

В итоге получаем приблизительно такую картину:


Алгоритм


Шаг №1


Trust-region метод использует квадратичную модель. На каждой итерации шаг вычисляется путем решения следующей квадратичной задачи (subproblem):

$\min_{p\in R^n}\ m_k(p) = f_k + p^T g_k + \frac 1 2 p^T B_kp, \ \\ s.t. |p|\leq\Delta_k,$


где $f_k = f(x_k), g_k = \nabla f(x_k), B_k = \nabla^2f(x_k)$;
$\Delta_k>0$ — trust-region радиус;

Таким образом trust-region требует последовательного вычисления аппроксимаций квадратичной модели в которых целевая функция и условие (которое может быть записано $p^Tp\le\Delta_k^2$) тоже квадратично. Когда гессиан ($B_k$) положительно определен и $|B_k^{-1}\nabla f_k|\le \Delta_k$ решение легко определить — это безусловный минимум $p_k^B = -B_k^{-1}\nabla f_k$. В данном случае $p_k^B$ называют полным шагом. Решение не так очевидно в других случаях, однако поиск его не стоит слишком дорого. Нам необходимо найти лишь приблизительное решение, чтобы получить достаточную сходимость и хорошее поведение алгоритма на практике.

Существует несколько стратегий аппроксимации квадратичной модели, среди которых следующие:

Алгоритм Cauchy point

Концепция метода схожа с логикой работы алгоритма наискорейшего спуска (steepest descent). Точка Cauchy лежит на градиенте, который минимизирует квадратичную модель при условии наличия шага в trust-region. Посредством последовательного нахождения точек Cauchy, может быть найден локальный минимум. Метод имеет неэффективную сходимость подобно методу наискорейшего спуска.

Алгоритм Steihaug

Метод носит название его исследователя — Steihaug. Представляет из себя модифицированный метод сопряженных градиентов (conjugate gradient approach). Для каждого шага алгоритм определяет итеративную технику, которая практически соответствует стандартной процедуре метода сопряженных градиентов за исключением двух дополнительных условий окончания алгоритма: если следующий шаг находится вне trust-region, если встречается нулевое или отрицательное направление.

Алгоритм Dogleg

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

Шаг №2


Первая проблема, которая возникает при определении trust-region алгоритма — это выбор стратегии для поиска оптимального trust-region радиуса $\Delta_k$ на каждой итерации. Выбор основывается на сходстве функции $m_k$ и целевой функции $f$ на предыдущей итерации. После нахождения $p_k$ мы определяем следующее соотношение:

$\rho_k = \frac{actual\ reduction}{predicted \ reduction} = \frac {f(x_k) - f(x_k + p_k)}{m_k(0) - m_k(p_k)}$


Знаменатель всегда должен быть положительно определен, поскольку шаг $p_k$ получается путем минимизации квадратичной модели $m_k$ по региону, который включает в себя шаг $p=0$. Отношение используется для определения преемственности шага и последующего обновления trust-region радиуса.

Если $\rho_k < 0$ или $\rho_k \approx0$ мы уменьшаем размер trust-region области.

Если $\rho_k\approx1$, тогда модель хорошо соответствует целевой функции. В данном случае следует расширить trust-region на следующей итерации.

В других случаяx trust-region остается неизменным.

Шаг №3


Следующий алгоритм описывает процесс:

Определяем стартовую точку $x_0$, максимальный trust-region радиус $\stackrel{-}{\Delta}$, начальный trust-region радиус $\Delta_0 = \in(0, \stackrel{-}{\Delta})$ и константу $\eta\in [0, \frac{1}{4})$

for k = 0, 1, 2,… пока $x_k$ не оптимум.

Решаем:

$\min_{p\in R^n}\ m_k(p) = f_k + p^T g_k + \frac 1 2 p^T B_kp \ \\ s.t. |p|\leq\Delta_k$


где $p_k$-решение.
Вычисляем отношение:

$\rho_k = \frac {f(x_k) - f(x_k + p_k)}{m_k(0) - m_k(p_k)}$


Обновляем текущую точку:

$$display$$x_{k+1} = \begin{equation*} \begin{cases} x_k + p_k & if\ \rho_k>\eta,\\ x_k&\text{otherwise.} \end{cases} \end{equation*}$$display$$


Обновляем trust-region радиус:

$$display$$\Delta_{k+1}=\begin{equation*} \begin{cases} \frac{1}{4}\Delta_k & if\ \rho_k<\frac{1}{4},\\ min(2\Delta_k, \stackrel{-}{\Delta})&if\ \rho_k>\frac{3}{4}\ and\ |p_k|=\Delta_k,\\ \Delta_k&otherwise. \end{cases} \end{equation*}$$display$$


Алгоритм в развернутом виде



Заметьте, что радиус увеличивается только если $|p_k|$ достигает границы trust-region. Если шаг остается строго в регионе, тогда текущее значение не влияет на работу алгоритма и нет необходимости изменять значение радиуса на следующей итерации.

Алгоритм Dogleg


Метод начинается с проверки эффективности trust-region радиуса в решении $p^*(\Delta)$ квадратичной модели $m(p)$. Когда $B$ положительна определена, как уже было отмечено, оптимальным решением будет полный шаг $p^B = -B^{-1}g$. Когда эта точка может быть найдена, очевидно, она будет являться решением.

$p^*(\Delta) = p^B, \ \Delta \geq|p^B|.$



Когда $\Delta_k$ весьма мало, ограничение $|p|\leq\Delta_k$ гарантирует, что квадратный член в модели $m(p)$ оказывает небольшое влияние на решение. Реальное решение $p(\Delta)$ аппроксимируется также, как если бы мы оптимизировали линейную функцию $f + g^Tp$ при условии $|p|\leq\Delta$, тогда:

$p^*(\Delta) \approx -\Delta\frac{g}{|g|}$


Когда $\Delta$ достаточно мало.

Для средних значений $\Delta$ решение $p^*(\Delta)$, как правило, следует за криволинейной траекторией, как показано на картинке:



Dogleg метод аппроксимирует криволинейную траекторию $p^*(\Delta)$ линией состоящей из двух прямыx. Первый отрезок начинается с начала и простирается вдоль направления наискорейшего спуска (steepest descent direction) и определяется следующим образом:

$p^U = -\frac{g^Tg}{g^TBg}g$


Второй начинается с $p^U$ и продолжается до $p^B$.

Формально мы обозначим траекторию $\tilde p(\tau)$, где $\tau \in [0, 2]$;

$$display$$\tilde p(\tau) = \begin{equation*} \begin{cases} \tau p^U, &0\leq\tau\leq1,\\\ p^U + (\tau - 1)(p^B-p^U), &1\leq\tau\leq2. \end{cases} \end{equation*}$$display$$


Для поиска $\tau$ необходимо решить квадратное уравнение, следующего вида:

$|p^U + \tau*(p^B - p^U)|^2 = \Delta^2$


$(p^U)^2 + 2\tau(p^B-p^U)p^U + \tau^2(p^B-p^U)^2=\Delta^2 $


Находим дискриминант уравнения:

$D = 4(p^B-p^U)^2(p^U)^2 - 4(p^B-p^U)^2((p^U)^2 - \Delta^2)$


$\sqrt{D} = 2(p^B-p^U)\Delta$


Корень уравнения равен:

$\tau = \frac{-2(p^B-p^U)p^U + 2(p^B - p^U)\Delta}{2(p^B-p^U)^2} = \frac{\Delta-p^U}{p^B-p^U}$


Dogleg метод выбирает $p_k$, чтобы минимизировать модель $m$ вдоль этого пути. В действительности нет необходимости выполнять поиск, поскольку dogleg путь пересекает границу trust-region только один раз и точка пересечения может быть найдена аналитически.

Пример


С использованием алгоритма trust-region (dogleg) оптимизировать следующую функцию (функция Розенброка):

$f(x, y) = (1-x)^2 + 100(y-x^2)^2$


Находим градиент, якобиан и гессиан функции:

$\frac{\partial f}{\partial x} = -400(y-x^2)x - 2 + 2x$

$\frac{\partial f}{\partial y} = 200y - 200x^2$

$\frac{\partial^2 f}{\partial x \partial x} = 1200x^2 - 400y + 2$

$\frac{\partial^2 f}{\partial x \partial y} = \frac{\partial^2 f}{\partial y \partial x} = -400x$

$\frac{\partial^2 f}{\partial y \partial y} = 200$

Инициализируем необходимые переменные для работы алгоритма:
$\Delta_k$ = 1.0,
$\stackrel{-}{\Delta}$ = 100.0,
$x_k = x_0 = [5, 5]$,
$gtol = 0.15$ (необходимая точность),
$\eta =0.15$.

Итерация 1

Находим оптимальное решение квадратичной модели $p_k$:

Поскольку $p^U = [-1.4226, 0.1422]$ и $|p^U| > \Delta_k$

Следовательно:

$p_k = \frac{\Delta_kp^U}{|p^U|} = [-1.4226, 0.1422]$



Вычисляем $\rho_k$:

actual reduction = $f(x_k) - f(x_k + p_k) = 28038.11$

predicted reduction = $m_k(0) - m_k(p_k) = 26146.06$

$\rho_k = \frac{28038.11}{26146.06} = 1.0723$



$\Delta_k$ — остается неизменным.

Обновляем $x_k$:

$x_k = x_k + p_k = [4.004, 5.099]$



Итерация 2

Находим оптимальное решение квадратичной модели $p_k$:

Поскольку $p^U = [-1.0109 0.1261]$ и $|p^U| > \Delta_k$.

Следовательно:

$p_k = \frac{\Delta_kp^U}{|p^U|} = [-0.9923 0.1238]$



Вычисляем $\rho_k$:

actual reduction = $f(x_k) - f(x_k + p_k) = 10489.43$

predicted reduction = $m_k(0) - m_k(p_k) = 8996.73$

$\rho_k = \frac{10489.43}{8996.73} = 1.1659$



Т.к. $\rho_k > 0.75$ и $|p_k|=\Delta_k$:

$\Delta_k = min(2\Delta_k, \stackrel{-}{\Delta_k}) = 2.0$



Обновляем $x_k$:

$x_k = x_k + p_k = [ 3.01, 5.22]$



Итерация 3

Находим оптимальное решение квадратичной модели $p_k$:

$p_k = p^U + \tau * (p^B-p^U) = [-0.257, 1.983]$



где $\tau = 0.5058$.

Вычисляем $\rho_k$:

actual reduction = $f(x_k) - f(x_k + p_k) = 1470.62$

predicted reduction = $m_k(0) - m_k(p_k) = 1424.16$

$\rho_k = \frac{1470.62}{1424.16} = 1.0326$



$\Delta_k$ — остается прежним.

Обновляем $x_k$:

$x_k = x_k + p_k = [2.7551, 7.2066]$



Алгоритм продолжается до тех пока $|g_k|<$gtol или не произведено заданное количество итераций.

Таблица результатов работы алгоритма для функции Розенброка:


k $p_k$ $\rho_k$ $\Delta_k$ $x_k$
0 - - 1.0 [5, 5]
1 [-0.9950, 0.0994] 1.072 1.0 [4.0049, 5.0994]
2 [-0.9923, 0.1238] 1.1659 2.0 [3.0126, 5.2233]
3 [-0.2575, 1.9833] 1.0326 2.0 [2.7551, 7.2066]
4 [-0.0225, 0.2597] 1.0026 2.0 [ 2.7325, 7.4663]
5 [-0.3605, -1.9672] -0.4587 0.5 [2.7325, 7.4663]
6 [-0.0906, -0.4917] 0.9966 1.0 [ 2.6419, 6.9746]
7 [-0.1873, -0.9822] 0.8715 2.0 [ 2.4546, 5.9923]
8 [-0.1925, -0.9126] 1.2722 2.0 [ 2.2620, 5.0796]
9 [-0.1499, -0.6411] 1.3556 2.0 [ 2.1121, 4.4385]
10 [-0.2023, -0.8323] 1.0594 2.0 [ 1.9097, 3.6061]
11 [-0.0989, -0.3370] 1.2740 2.0 [ 1.8107, 3.2690]
12 [-0.2739, -0.9823] -0.7963 0.25495 [ 1.8107, 3.2690]
13 [-0.0707, -0.2449] 1.0811 0.5099 [ 1.7399, 3.0240]
14 [-0.1421, -0.4897] 0.8795 1.0198 [1.5978, 2.5343]
15 [-0.1254, -0.3821] 1.3122 1.0198 [ 1.4724, 2.1522]
16 [-0.1138, -0.3196] 1.3055 1.0198 [ 1.3585, 1.8326]
17 [-0.0997, -0.2580] 1.3025 1.0198 [ 1.2587, 1.5745]
18 [-0.0865, -0.2079] 1.2878 1.0198 [ 1.1722, 1.3666]
19 [-0.0689, -0.1541] 1.2780 1.0198 [ 1.1032, 1.2124]
20 [-0.0529, -0.1120] 1.2432 1.0198 [ 1.0503, 1.1004]
21 [-0.0322, -0.0649] 1.1971 1.0198 [1.0180, 1.0354]
22 [-0.0149, -0.0294] 1.1097 1.0198 [ 1.0031, 1.0060]
23 [-0.0001, -0.0002] 1.0012 1.0198 [ 1.00000024, 1.00000046]
24 [-2.37065e-07, -4.56344e-07] 1.0000 1.0198 [ 1.0, 1.0]


Аналитически находим минимум функции Розенброка, он достигается в точке $[1, 1]$. Таким образом можно убедиться в эффективности алгоритма.

Пример реализации на Python


Алгоритм реализован с использованием библиотеки numpy. В примере наложено ограничение на количество итераций.

#!/usr/bin/python
# -*- coding: utf-8 -*-
import numpy as np
import numpy.linalg as ln
from math import sqrt

def f(x):
    return (1-x[0])**2 + 100*(x[1]-x[0]**2)**2

def jac(x):
    return np.array([-400*(x[1] - x[0]**2)*x[0] - 2 + 2*x[0], 200*x[1] - 200*x[0]**2])

def hess(x):
    return np.array([[1200*x[0]**2 - 400*x[1]+2, -400*x[0]], [-400*x[0], 200]])


def dogleg_update(Hk, gk, Bk, trust_radius):

    # Calculate the full step and its norm.
    pB = -np.dot(Hk, gk)
    norm_pB = sqrt(np.dot(pB, pB))

    # Test if the full step is within the trust region.
    if norm_pB <= trust_radius:
        return pB

    # Calculate pU.
    pU = - (np.dot(gk, gk) / np.dot(gk, np.dot(Bk, gk))) * gk
    dot_pU = np.dot(pU, pU)
    norm_pU = sqrt(dot_pU)

    # Test if the step pU exits the trust region.
    if norm_pU >= trust_radius:
        return trust_radius * pU / norm_pU

    # Find the solution to the scalar quadratic equation.
    pB_pU = pB - pU
    dot_pB_pU = np.dot(pB_pU, pB_pU)
    dot_pU_pB_pU = np.dot(pU, pB_pU)
    fact = dot_pU_pB_pU**2 - dot_pB_pU * (dot_pU - trust_radius**2)
    tau = (-dot_pU_pB_pU + sqrt(fact)) / dot_pB_pU
    
    # Decide on which part of the trajectory to take.
    return pU + tau * pB_pU
    

def trust_region_dogleg(func, jac, hess, x0, initial_trust_radius=1.0,
                        max_trust_radius=100.0, eta=0.15, gtol=1e-4,
                        maxiter=400):
    xk = x0
    trust_radius = initial_trust_radius
    k = 0
    while True:
        
        # Initialize the search
        gk = jac(xk)
        Bk = hess(xk)
        Hk = np.linalg.inv(Bk)
        
        # Solve the sub-problem.
        pk = dogleg_update(Hk, gk, Bk, trust_radius)
       
        act_red = func(xk) - func(xk + pk)

        pred_red = -(np.dot(gk, pk) + 0.5 * np.dot(pk, np.dot(Bk, pk)))
        
        # Evaluate the ratio
        rhok = act_red / pred_red
        if pred_red == 0.0:
            rhok = 1e99
        else:
            rhok = act_red / pred_red
        norm_pk = sqrt(np.dot(pk, pk))
        
        # Update the trust radius according to the actual/predicted ratio
        if rhok < 0.25:
            trust_radius = 0.25 * norm_pk
        else: 
            if rhok > 0.75 and norm_pk == trust_radius:
                trust_radius = min(2.0*trust_radius, max_trust_radius)
            else:
                trust_radius = trust_radius
        
        if rhok > eta:
            xk = xk + pk
        else:
            xk = xk
            
        # Check if the gradient is small enough to stop
        if ln.norm(gk) < gtol:
            break
        
        # Check if we have looked at enough iterations
        if k >= maxiter:
            break
        k = k + 1
    return xk
    
result = trust_region_dogleg(f, jac, hess, [5, 5])
print("Result of trust region dogleg method:")
print(result)
print("Value of function at a point:")
print(f(result))


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

О всех неточностях сообщайте, пожалуйста, в личных сообщениях.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335224/


Обучающий проект: ToDo веб приложения на Srping и ReactJS

Пятница, 11 Августа 2017 г. 18:29 + в цитатник
Srping и ReactJS

Приложение будет предназначено для людей, нуждающихся в удобном средстве организации личных целей и задач. Типичное приложение типа todo-list, но с одной особенностью, которая впрочем понятна из названия. Организация задач будет возможна не только в виде списка, но и в виде дерева подзадач. Tree > List!


Проект будет транслироваться с нуля, то есть вплоть до установки необходимого окружения для разработки (JDK, Eclipse, Atom, Node.js) и подключения необходимых зависимостей (Spring, Hibernate, ReactJS, Redux). Если вы хотели увидеть, как используются следующие технологии: Java, Spring, Hibernate, JavaScript, ReactJS, Redux, то спешу вас обрадовать, у вас будет такая возможность! Конечно, это не весь список, но это то на чем будет делаться акцент.

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

Данный подход отличается от простого всестороннего изучения технологий по документации или книгам и он не претендует на всеобъемлемость. Применение больших технологий зачастую схоже с принципом Парето: 20% использования технологии дает 80% результата, а поэтому можно считать, что даже в небольшом проекте, есть хорошая возможность понять суть технологии. И если вы готовы, то давайте вместе пройдем этот путь по разработке простого приложения ToDo Tree с серьёзными промышленными технологиями под капотом. Так что жду всех желающих на стриме!

Необходимые навыки?

  • HTML/CSS
  • SQL
  • Java (желательно 8)
  • JavaScript (желательно 6)

Изучаемые технологии?

  • Srping
  • Hibernate
  • ReactJS + Redux

Какова целевая аудитория?

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

Когда будут проходить трансляции?

Каждую пятницу в 20-00

План разработки ToDo на Srping и ReactJS:


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

Этап 1:

  • Настройка окружения (под Linux)

Этап 2:

  • Разработка страницы регистрации

Этап 3:

  • Разработка страницы создания/редактирования задачи

Этап 4

  • Разработка страницы списка задач

Этап 5

  • Разработка страницы фильтрации и сортировки


Обучающий проект будет проходит в онлайн режиме на следующем канале, там же можно будет задать интересующие вопросы в чате. Записи всех трансляций доступны на странице канала в разделе «Project Playlist».
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335452/


Метки:  

[Из песочницы] Уменьшение размера файла сборки Android в Unity

Пятница, 11 Августа 2017 г. 18:16 + в цитатник
Размер сборки — важная характеристика мобильного приложения. Если приложение весит много, оно первым будет удалено при чистке. Также меньший размер может ускорить запуск, установку, скачивание.

Даже пустой проект в Unity весит очень много. Пустой проект под Android с настройками по умолчанию в Unity 2017.1 весит 21637 КБ. Однако его можно очень легко уменьшить до 11952\12412 КБ, указав платформу для компиляции (ARMv7 и x86 соответственно).

По аналогии с этим, можно еще попробовать еще немного уменьшить вес, выбрав Graphic API. Если выбрать OpenGLES2 вместо Auto Graphics API, можно сэкономить еще 236 КБ (11716 вместо 11952). Выгода незначительна и возможна потеря в производительности, так что этого делать я не рекомендую.

Теперь поговорим о содержимом проекта. Рассмотрим 2D игру с большим количеством спрайтов.
Есть вероятность, что многие спрайты будут симметричными по одной или нескольким осям.

Давайте проверим, есть ли автоматическое сжатие на этот случай: скомпилируем сцену с выставленным Sprite Renderer с одной текстурой, например, этой.



ARMv7 билд увеличился с 11952 КБ 12046 КБ, прибавка от пустого билда составляет 94 КБ.

Теперь подготовим половину текстуры:



Поставим два Sprite Renderer с одинаковой позицией, у правого выставим Flip X для отзеркаливания, в настройках Sprite Import Settings укажем Pivot Right для совмещения зеркальных половин. Должен получиться такой же круг как и был раньше. Скомпилируем, посмотрим размер: 12000 КБ, то есть прибавка почти в два раза меньше (48 КБ против 94). Если и есть какое то специальное сжатие, то по умолчанию оно неэффективно.

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

Найдем стандартный шейдер Unity для Sprite Renderer.

Sprites/Default
Shader "Sprites/Default"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    }

    SubShader
    {
        Tags
        { 
            "Queue"="Transparent" 
            "IgnoreProjector"="True" 
            "RenderType"="Transparent" 
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Fog { Mode Off }
        Blend One OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile DUMMY PIXELSNAP_ON
            #include "UnityCG.cginc"

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
            };

            fixed4 _Color;

            v2f vert(appdata_t IN)
            {
                v2f OUT;
                OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
                OUT.texcoord = IN.texcoord;
                OUT.color = IN.color * _Color;
                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif

                return OUT;
            }

            sampler2D _MainTex;

            fixed4 frag(v2f IN) : SV_Target
            {
                fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
                c.rgb *= c.a;
                return c;
            }
        ENDCG
        }
    }
}


Начнем с того, что убедимся, что все работает.
Создаем шейдер, копируем в него код, меняем название в коде на Sprites/HorizontalSymmetry.
Теперь нужно создать материал и выбрать наш шейдер.
Попробуем назначить на Sprite Renderer наш материал. Должен выглядеть как раньше.

Теперь разберем шейдер. Вся магия происходит тут:

fixed4 frag(v2f IN) : SV_Target
{
//tex2D возвращает цвет текстуры с заданными координатами.
//_MainTex - используемая текстура, IN.texcoord - текущие координаты в формате 0..1
      fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
//Умножение r,g,b полученного цвета на a. 
      c.rgb *= c.a;
      return c;
}

Это функция, которая должна вернуть цвет пикселя в указанной точке. Нам даны позиция, текстура, цвет для смешения. Не стоит бояться fixed4: это просто тип данных с 4 float: r,g,b,a.
В первой строчке мы получаем цвет текстуры и после этого умножаем на некий цвет IN.color. Этот цвет — это параметр шейдера, его можно изменить в Sprite Renderer/Color.

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

fixed4 frag(v2f IN) : SV_Target
{            	            	            	
      fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
      c.rgb=0.5;
      c.a = 0.0;
      return c;
}

Получаем прозрачную серую текстуру. При rgb = 1 и a = 0 будет непрозрачная белая, при rgb = 0 и a = 0 полностью прозрачная, rgb = 0 и a = 1 будет черным непрозрачным.

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

Эту задачу можно решить в лоб:

fixed4 frag(v2f IN) : SV_Target
{
//Запоминаем в новой переменной координаты пикселя
      fixed2 nIn = IN.texcoord;
//Приводим их к форме 0...2
      nIn.x = nIn.x*2;
//Если значение больше одного, то дает 1..0
      if (nIn.r>1)
            nIn.r = 2-nIn.
//Используем новые координаты текстуры
      fixed4 c = tex2D(_MainTex, nIN.texcoord) * IN.color;
//Умножаем r,g,b полученного цвета на a. 
      c.rgb *= c.a;
      return c;
}

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

fixed4 frag(v2f IN) : SV_Target
{            	
//Получаем необходимые координаты
      IN.texcoord.x = 1-abs(2*IN.texcoord.x-1);
//Получаем цвет по новым координатам
      fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
//Умножаем r,g,b полученного цвета на a. 
      c.rgb *= c.a;
      return c;
}

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

Иногда могут быть артефакты прозрачности, связанные с тем, что спрайт рисуется по умолчанию по определенному контуру (мешу). Лечится так: Sprite/Import Settings/Mesh type = Full Rect.

Этот способ уже теоретически способен сократить размер используемых текстур в 4 раза.
Проверим, как поведет себя билд при четверти спрайта (используя шейдер двойной симметрии).
Размер билда — 11978 КБ против 12000 (половина спрайта). Напомню, что пустой проект весил 11952 КБ. То есть, опять получилось уменьшение прибавки почти в два раза (в 3.6 от изначального круга без оптимизации)

Однако, это не предел. В моей игре использовалось большое количество шайб, обладающей радиальной симметрией. Это означает, что достаточно иметь всего одну полоску, для того чтобы задать весь круг! Причем, половинную полоску (радиус, не диаметр).

Подготовим текстуру:


Теперь дело за шейдером. Наша задача — сделать из линии круг.

Можно поступить так: найти удаленность текущей точки от центра и использовать координату текстуры с позицией (1-distance*2, 0). Умножение на два происходит потому что максимальное расстояние от центра будет 0.5, не 1. Вычитаем из единицы потому что текстура подготовлена слева (край круга) направо (центр круга).

Пример реализации:

fixed4 frag(v2f IN) : SV_Target
{            	
      fixed2 nIn = IN.texcoord;
      nIn.x = nIn.x-0.5;
      nIn.y = nIn.y-0.5;
      float dist = sqrt(nIn.x*nIn.x+nIn.y*nIn.y);            
      IN.texcoord.x = 1-dist*2;
      fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
      c.rgb *= c.a;
      return c;
}

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

Создаем материал, поставим его в Sprite Renderer, поставим Sprite = line, смотрим.
Изображение будет очень узким, так что нужно растянуть спрайт (выставить большое значение Trasnform.Scale.y). Должен получиться исходный круг\овал.

Проверим размер билда с новым шейдером и кругом из полоски: получилось 11957 КБ. То есть прибавка от пустого проекта составляет всего лишь 5 КБ, и это включая размер шейдера.

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

https://habrahabr.ru/post/335454/


Метки:  

Как настроить командную работу и сохранять спокойствие в чатах Телеграма, если всё горит, и все в аду

Пятница, 11 Августа 2017 г. 17:03 + в цитатник
У нас беспокойная работа — с сайтами, которые мы поддерживаем, постоянно что-то происходит, и на любую аварию мы должны среагировать за 15 минут — все это в режиме 24/7, семь дней в неделю. Задачи для админов невозможно запланировать — сложно представить себе такой план на неделю: случится 25 аварий, и мы их устраним одну за другой. О том, как мы пытаемся с этим жить, я и хочу рассказать.

image

Что такое авария? Может закончиться место на диске (или похоже, скоро закончится), может увеличиться время ответа. Если мы говорим, что среагируем за 15 минут, это означает, что дежурный администратор должен сделать это за 7-8 минут. За это время он получает оповещение, подтверждает, что принял, расследует его и в случае необходимости начинает чинить.

image

Сейчас таких оповещений от 100 до 500 в час на человека. Днем их бывает больше, ночью меньше, в «черную пятницу» их безумно много, потому что интернет-магазины почему-то любят начать «черную пятницу» на день раньше каждый год, и не всегда это планируется заранее (каждый раз они говорят, что не будут вообще в ней участвовать; один наш клиент как-то запустил рассылку на 300 тыс. человек через полчаса после того, как сказал, что в этой «черной пятнице» не участвует, и мы получили в итоге порядка 900 алертов в час на трех дежурных админов).

Основная работа по поддержке у нас идет через чаты (в среднем за десять минут могут прийти уведомления в десять чатов). Ни тикетная система, ни клиент не должен говорить, что что-то упало, это мы должны заметить первыми и сказать клиенту. Это выглядит так:
image
или так:
image

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

Сейчас у нас в среднем 90 активных чатов каждый день, и общение в них неравномерно по времени:
image

В чатах возникает много задач, и за ними трудно уследить, их очень просто потерять. Оповещений очень много, на них надо реагировать вовремя — иначе мы нарушим SLA. Ну и в конце концов, когда у вас получается порядка 500 алертов в час, вы немножко устаете, потому что нужно с этим как-то жить.

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

Почему нас не устроил Телеграм, или любой другой мессенджер, как он есть? Никто из разработчиков не думает, что у человека может быть 80 чатов одновременно. Слева в окне мессенджера показывается малое количество чатов — не то, которое нам нужно для нашей работы. В обычной жизни, по сравнению с нами, человек пишет довольно редко. У нас же в пики постоянно меняется очередность чатов, между ними как минимум неудобно переключаться (ну легко что-то потерять). По списку часто непонятно, где мы не успели ответить или еще не отвечали, и где приближается время, что нужно срочно ответить.

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

Стали отслеживать SLA по тому, где в чате последним написал клиент, и стали подсвечивать чаты, где нужно ответить нам.
image

Сгруппировали чаты по необходимости ответа, добавили функцию отметить все чаты как прочитанные. Зачем это сделали? У нас за день накапливается, если сотрудник не дежурил, порядка 150 чатов, в которых что-то писали — это уже неактуально.

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

Если клиент просит сделать что-то в чате, дежурный по чатам правым кликом создает из этого диалога таск в Битрикс24 (его мы тоже переписали для себя — взяли за основу «Битрикс 24 в коробке», он написан на php, у него есть тикеты, API).
image

Клиент может внести в тикет какие-то дополнительные данные, отслеживать выполнение, там же ведется дальнейшая работа.
image

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

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

Скачать наш клиент Телеграма для Mac OS можно тут, а для Windows — тут.

image

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

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

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

https://habrahabr.ru/post/335446/


Метки:  

[Из песочницы] Блокировка операций как защита от вредоносных программ

Пятница, 11 Августа 2017 г. 16:39 + в цитатник


Не так давно в сети появился доклад инженера Google Даррена Билби «Защита Гибсона в эпоху Просвещения», посвящённый фейлу антивирусов и другим бесполезным методам информационной безопасности.

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

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

Что, по сути, нам предлагает классический антивирус? В подавляющем большинстве сканер файлов для поиска вируса по сигнатуре и эвристический анализатор для файлов, против которых бессилен сканер (например, сжатых или зашифрованных). У каждого из этих методов свои достоинства и недостатки, но в общем Билби прав, на протяжении многих лет используется устаревшая технология, а с другой стороны и предложить что-то принципиально новое тоже достаточно сложно. Но можно. К примеру, за последнее десятилетие получили развитие «песочницы», но особого распространения по определенным причинам не снискали, возможно из-за сложностей в обслуживании, ведь массовое решение должно быть простым как сковородка и надежным как лом. Все гениальное просто, хоть и не все простое – гениально.

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

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

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

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

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

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

Сегодня я хочу познакомить вас с наиболее интересными на мой взгляд продуктами, удовлетворяющим описанным выше требованиям, это VoodooShield от американской компании VoodooSoft, SecureAPlus от сингапурской SecureAge Technology, а также российский Defendset от красноярской компании ВТБ. Первоначально мне удалось найти еще одну российскую программу – питерскую «Систему защиты Панцирь», к сожалению демоверсию «Панциря» скачать не удалось, разработчики требуют отправить специальную форму на бланке организации, с печатью фирмы и подписью ответственного лица. Поскольку у меня нет ни организации, ни бланков, ни печати, то в обзор этот продукт включить не удалось, а жаль. Возможно в будущем удастся раздобыть эту утилиту, ну а пока давайте посмотрим на сегодняшних участников и поизучаем, что они собой представляют, а главное – как справляется с заявленными задачами.

VoodooShield


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

Продукт представлен в двух версиях – бесплатной, с ограниченными возможностями настройки и полнофункциональной, по цене 19,99$ в год. Сайт продукта: https://voodooshield.com

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



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

Окно выбора режима работы


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

Инструкция по работе


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

Управляющий виджет


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

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

Базовые настройки


Расширенные настройки


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

Настройка приложений


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

Белый список


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

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

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

Уведомление о блокировке


Нажатие на уведомление открывает панель выбора действия – заблокировать либо запустить в локальной или интернет-песочнице Cuckoo.

Выбор действия


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

Сообщение об ошибке


Запускаем защиту еще раз и пробуем установить браузер. При выборе действия выбираем запуск в песочнице Cuckoo.

Песочница Cuckoo


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

Результат анализа


Все действия фиксируются в логе.

Лог событий


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

Лог командной строки


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

SecureAPlus


SecureAPlus продвинутый антивирус от компании SecureAge. Приложение сочетает в себе инструменты для мониторинга и обнаружения угроз в режиме реального времени с функциями анализа подозрительных объектов и использованием аж 12 антивирусных движков сторонних разработчиков.

При установке продукта разработчики щедро предлагают бесплатную годовую лицензию, но по прошествии года за лицензию придется платить 2$ в месяц или 22,5$ за год. Сайт продукта: https://www.secureaplus.com



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

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

Контекстное меню приложения


SecureAPlus может функционировать в следующих режимах — «Взаимодействие», «Блокировка» и «Доверять всем». Названия отражают суть выбранного режима – «Взаимодействие» каждый раз спрашивает у пользователя доверять или нет неизвестному приложению, «Блокировка» автоматически блокирует неизвестные подозрительные объекты, которые не попали в белый список, «Доверять всем» разрешает работу любых процессов.

Выбор режима


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

Выбор интервала отключения


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

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

Расширенные установки


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

Настройки белого списка


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

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

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

Универсальный антивирус


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

Выбор действия


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

Информация об угрозе


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

Сведения об угрозе


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

Defendset


Defendset — проактивное решение блокировки несанкционированных операций над файлами, работающее в операционных системах Windows 7/8/10 и Windows Server 2008/2012/2016.

Стоимость лицензии зависит от сферы ее применения. Домашняя некоммерческая или персональная лицензия – 320 руб., корпоративная лицензия для организаций – 970 руб., серверная – 4600 руб. Цены указаны за один год использования. Сайт продукта: https://defendset.ru

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

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



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

Окно настроек


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

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

Настройки правила


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

Настройка путей


Выбор типов наблюдаемых файлов, все по аналогии с каталогами.

Настройка масок


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

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


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

Уведомление о блокировке


Для дальнейших тестов я сделал новое правило – в корневой папке Test запретил все операции над любыми файлами.

Тестовое правило


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

Блокировка создания файла


Копирование скачанного ранее браузера в тестовую папку так же благополучно предотвращается.

Блокировка копирования


Для проведения дальнейших экспериментов временно отключаю защиту и создаю в Test вордовский документ. Снова активизирую Defendset и пробую открыть файл.

Блокировка открытия


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

Блокировка запуска


Какое-либо изменение (редактирование, переименование, перемещение) и удаление файла при включенной защите так же невозможно.

Блокировка удаления


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

Журнал событий


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

Настройка мониторинга


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

Уведомление о событии


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

Настройка версионирования


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

Правило для сохранения версий


Создаю текстовый файлик со словом Привет внутри. Уведомление при записи говорит что копия успешно создана.

Запись первой версии


Добавляю строчку и записываю еще раз.

Запись второй версии


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

Созданные версии


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

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

Журнал событий


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

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

Выводы


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

https://habrahabr.ru/post/335448/


Метки:  

Обзор мобильного рынка Турции

Пятница, 11 Августа 2017 г. 16:31 + в цитатник


Наши друзья и партнеры Unilead News выпустили подробный отчет о мобильном рынке в Турции.
Мы сделали выдержку из отчета.

Особенности аудитории
Турция отличается молодым средним возрастом населения — 28 лет. Аудитория огромна, т.к. проникновение мобильных телефонов составляет 96%, почти половина населения владеет смартфонами. Как отмечает Кандаш Демир, Head of performance
marketing компании adXclusive, активные молодые пользователи правят мобильным рынком Турции.

Потребление контента
Страна занимает третье место по Европе по потреблению медиаконтента в мессенджерах. Netflix запустился в прошлом году, и приложения по предоставлению контента набирают огромную популярность. По данным исследования Mobile Ecosystem Forum (MEF), мобильные пользователи Турции особенно любят музыку, игры, социальные сети, и фото/видео — 64% пользователей используют приложения социальных сетей, 63% — игры, 62% — приложения для воспроизведения музыки, 61% — фото-и видеоприложения. Несмотря на быстрое развитие мобайла, в стране все еще доминирует телевидение: аудитория здесь намного шире, чем в других странах, поэтому стоит учитывать этот фактор и комбинировать его с диджиталом при планировании стратегии.

Перспективы развития
В тренде — мобильный банкинг (!), 57% мобильных юзеров пользуются услугами мобильных банков. В отчете есть красивые скриншоты мобильных банков Турции и интересная статистика. Кроме банкинга развивается ниша mCommerce: здесь рынок пока не так развит, однако все больший процент аудитории совершает покупки (52% по сравнению с общемировым показателем в 78%). Также эксперты советуют обратить внимание на сегмент здоровья — приложения для диагностики или доступа к лечению использует 13% пользователей. Рынок чувствителен к ценам и скидкам, популярны дискаунтеры — это открывает путь к in-app покупкам и работе с онлайн-магазинами.

Проблемы
Строгие законы о защите персональных данных ведут к юридическим проблемам при сборе данных и повышенной обеспокоенности пользователей безопасностью в интернете. Также бренды соревнуются за лучшую адапцию/локализацию стратегии для рынка и формируют уникальные предложения с космическими скоростями: международные кампании не работают за счет культурных особенностей. Аудитория сильно сегментирована в плане благосостояния, в том числе, в пределах одной возрастной группы — это тоже стоит учитывать при выходе на рынок. Помимо этого, турецкие компании в среднем тратят только 10% маркетингового бюджета на мобайл, 79% рекламодателей все еще вкладывается в телевидение. Эксперты полагают, что в ближайшее время расстановка сил изменится, и расходы на мобильную рекламу будут расти.

Бонусом к статистическим данным, комментариям экспертов и выжимкам из разных отчетов (например, отчета MMA) идет подробная инструкция по открытию бизнеса в Турции (стр. 12). Полный отчет можно найти по ссылкам:
Мобильная версия (pdf)
Десктоп-версия (pdf)
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335424/


Метки:  

Учим робота готовить пиццу. Часть 1: Получаем данные

Пятница, 11 Августа 2017 г. 16:19 + в цитатник


Автор изображения: Chuchilko


Не так давно, после завершения очередного конкурса на Kaggle — вдруг возникла идея попробовать сделать тестовое ML-приложение.
Например, такое: "помоги роботу сделать пиццу".


Разумеется, основная цель этого ровно та же — изучение нового.


Захотелось разобраться, как работают генеративные нейронные сети (Generative Adversarial Networks — GAN).


Ключевой идеей было обучить GAN, который по выбранным ингредиентам сам собирает картинку пиццы.


Ну что ж, приступим.


Начало


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


И тут я подумал — а почему бы не дёрнуть данные с сайта Додо-пиццы.


Disclaimer

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


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


  1. получить с сайта нужные данные

Загрузка данных


Так как вся нужная нам информация доступна на сайте Додо-пиццы, применим так называемый парсинг сайтов (он же — Web Scraping).


Здесь нам поможет статья: Web Scraping с помощью python.


И всего две библиотеки:


import requests
import bs4

Открываем сайт додо-пиццы, щелкаем в браузере "Просмотреть код" и находим элемент с нужными данными.


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

Это окошко появляется в результате GET-запроса, который можно эмулировать, передав нужные заголовки:


headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
    'Referer': siteurl,
    'x-requested-with': 'XMLHttpRequest'
}
res = requests.get(siteurl, headers = headers)

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


Сразу же можно обратить внимание, что статический контент распространяется через CDN akamaihd.net


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


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


  • получение списка городов (на тот момент — 146)
  • получение списка пицц (для Города)
  • получение информации о пицце
  • загрузка фотографий пиццы

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


Что хорошо в программировании скриптов автоматизации — их можно запустить и откинувшись на списку кресла наблюдать за их работой...


На выходе получилось всего 20 пицц.
На каждую пиццу получается по 3 картинки. Нас интересует только третья картинка, на которой есть вид пиццы сверху.


Разумеется, после получения картинок, их нужно дополнительно обработать — вырезать и отцентровать пиццу.
Думаю, это не должно составить особых проблем, так как все картинки одинаковые — 710х380.


Обработка данных


После scraping-а сайта, получили привычные по kaggle — csv-файл с данными (и директории с картинками).
Настала пора изучить пиццы.


Подключаем необходимые библиотеки.


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline 

import seaborn as sns

np.random.seed(42)

import cv2
import os
import sys

df = pd.read_csv('pizzas.csv', encoding='cp1251')
print(df.shape)

(20, 13)

df.info()


RangeIndex: 20 entries, 0 to 19
Data columns (total 13 columns):
city_name         20 non-null object
city_url          20 non-null object
pizza_name        20 non-null object
pizza_eng_name    20 non-null object
pizza_url         20 non-null object
pizza_contain     20 non-null object
pizza_price       20 non-null int64
kiloCalories      20 non-null object
carbohydrates     20 non-null object
proteins          20 non-null object
fats              20 non-null object
size              20 non-null int64
weight            20 non-null object
dtypes: int64(2), object(11)
memory usage: 2.1+ KB

df.head()


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight
0 Калининград /Kaliningrad Двойная пепперони double-pepperoni https://dodopizza.ru/Kaliningrad/Product/doubl... Томатный соус, двойная порция пепперони и увел... 395 257,52 26,04 10,77 12,11 25 470±50
1 Калининград /Kaliningrad Крэйзи пицца crazy-pizza https://dodopizza.ru/Kaliningrad/Product/crazy... Томатный соус, увеличенные порции цыпленка и п... 395 232,37 31,33 9,08 7,64 25 410±50
2 Калининград /Kaliningrad Дон Бекон pizza-don-bekon https://dodopizza.ru/Kaliningrad/Product/pizza... Томатный соус, бекон, пепперони, цыпленок, кра... 395 274 25,2 9,8 14,8 25 454±50
3 Калининград /Kaliningrad Грибы и ветчина gribvetchina https://dodopizza.ru/Kaliningrad/Product/gribv... Томатный соус, ветчина, шампиньоны, моцарелла 315 189 23,9 9,3 6,1 25 370±50
4 Калининград /Kaliningrad Пицца-пирог pizza-pirog https://dodopizza.ru/Kaliningrad/Product/pizza... Сгущенное молоко, брусника, ананасы 315 144,9 29,8 2,9 2,7 25 420±50


Приведём данные к более удобному виду.


df['kiloCalories'] = df.kiloCalories.apply(lambda x: x.replace(',','.'))
df['carbohydrates'] = df.carbohydrates.apply(lambda x: x.replace(',','.'))
df['proteins'] = df.proteins.apply(lambda x: x.replace(',','.'))
df['fats'] = df.fats.apply(lambda x: x.replace(',','.'))
df['weight'], df['weight_err'] = df['weight'].str.split('±', 1).str

df['kiloCalories'] = df.kiloCalories.astype('float32')
df['carbohydrates'] = df.carbohydrates.astype('float32')
df['proteins'] = df.proteins.astype('float32')
df['fats'] = df.fats.astype('float32')
df['weight'] = df.weight.astype('int64')
df['weight_err'] = df.weight_err.astype('int64')

df.head()


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err
0 Калининград /Kaliningrad Двойная пепперони double-pepperoni https://dodopizza.ru/Kaliningrad/Product/doubl... Томатный соус, двойная порция пепперони и увел... 395 257.519989 26.040001 10.77 12.11 25 470 50
1 Калининград /Kaliningrad Крэйзи пицца crazy-pizza https://dodopizza.ru/Kaliningrad/Product/crazy... Томатный соус, увеличенные порции цыпленка и п... 395 232.369995 31.330000 9.08 7.64 25 410 50
2 Калининград /Kaliningrad Дон Бекон pizza-don-bekon https://dodopizza.ru/Kaliningrad/Product/pizza... Томатный соус, бекон, пепперони, цыпленок, кра... 395 274.000000 25.200001 9.80 14.80 25 454 50
3 Калининград /Kaliningrad Грибы и ветчина gribvetchina https://dodopizza.ru/Kaliningrad/Product/gribv... Томатный соус, ветчина, шампиньоны, моцарелла 315 189.000000 23.900000 9.30 6.10 25 370 50
4 Калининград /Kaliningrad Пицца-пирог pizza-pirog https://dodopizza.ru/Kaliningrad/Product/pizza... Сгущенное молоко, брусника, ананасы 315 144.899994 29.799999 2.90 2.70 25 420 50


Учитывая, что пищевая ценность продукта приводится в расчёте на 100 грамм, то для лучшего понимания — домножим их на массу пиццы.


df['pizza_kiloCalories'] = df.kiloCalories * df.weight / 100
df['pizza_carbohydrates'] = df.carbohydrates * df.weight / 100
df['pizza_proteins'] = df.proteins * df.weight / 100
df['pizza_fats'] = df.fats * df.weight / 100

df.describe()


pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
count 20.00000 20.000000 20.000000 20.000000 20.00000 20.0 20.000000 20.0 20.000000 20.000000 20.000000 20.000000
mean 370.50000 212.134491 25.443501 8.692500 8.44250 25.0 457.700000 50.0 969.942043 115.867950 39.857950 38.736650
std 33.16228 34.959122 2.204143 1.976283 3.20358 0.0 43.727746 0.0 175.835991 8.295421 9.989803 15.206275
min 315.00000 144.899994 22.100000 2.900000 2.70000 25.0 370.000000 50.0 608.579974 88.429999 12.180000 11.340000
25% 367.50000 188.250000 23.975000 7.975000 6.05000 25.0 420.000000 50.0 858.525000 113.010003 35.625000 28.159999
50% 385.00000 212.500000 24.950000 9.090000 8.20000 25.0 460.000000 50.0 966.358490 114.779002 39.580000 35.930001
75% 395.00000 235.527496 26.280001 9.800000 9.77500 25.0 485.000000 50.0 1095.459991 120.597001 45.707500 47.020001
max 395.00000 274.000000 31.330000 12.200000 14.80000 25.0 560.000000 50.0 1243.960000 128.453000 60.999999 68.080001


Настала пора узнать, какие же пиццы самые-самые...


Итак, самая калорийная пицца


df[df.pizza_kiloCalories == np.max(df.pizza_kiloCalories)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
2 Калининград /Kaliningrad Дон Бекон pizza-don-bekon https://dodopizza.ru/Kaliningrad/Product/pizza... Томатный соус, бекон, пепперони, цыпленок, кра... 395 274.0 25.200001 9.8 14.8 25 454 50 1243.96 114.408003 44.492001 67.192001


Самая жирная пицца:


df[df.pizza_fats == np.max(df.pizza_fats)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
14 Калининград /Kaliningrad Мясная myasnaya-pizza https://dodopizza.ru/Kaliningrad/Product/myasn... Томатный соус, охотничьи колбаски, бекон, ветч... 395 268.0 24.200001 9.1 14.8 25 460 50 1232.8 111.320004 41.860002 68.080001


Самая богатая углеводами:


df[df.pizza_carbohydrates == np.max(df.pizza_carbohydrates)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
1 Калининград /Kaliningrad Крэйзи пицца crazy-pizza https://dodopizza.ru/Kaliningrad/Product/crazy... Томатный соус, увеличенные порции цыпленка и п... 395 232.369995 31.33 9.08 7.64 25 410 50 952.71698 128.453 37.228 31.323999


Самая богатая белками:


df[df.pizza_proteins == np.max(df.pizza_proteins)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
7 Калининград /Kaliningrad Гавайская gavayskaya-pizza https://dodopizza.ru/Kaliningrad/Product/gavay... Томатный соус, ананасы, цыпленок, моцарелла 315 216.0 25.0 12.2 7.4 25 500 50 1080.0 125.0 60.999999 37.0


Самая тяжёлая по весу пицца:


df[df.weight == np.max(df.weight)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
8 Калининград /Kaliningrad Додо pizza-dodo https://dodopizza.ru/Kaliningrad/Product/pizza... Томатный соус, говядина (фарш), ветчина, пеппе... 395 203.899994 22.1 8.6 8.9 25 560 50 1141.839966 123.760002 48.160002 49.839998


Самая лёгкая по весу пицца:


df[df.weight == np.min(df.weight)]


city_name city_url pizza_name pizza_eng_name pizza_url pizza_contain pizza_price kiloCalories carbohydrates proteins fats size weight weight_err pizza_kiloCalories pizza_carbohydrates pizza_proteins pizza_fats
3 Калининград /Kaliningrad Грибы и ветчина gribvetchina https://dodopizza.ru/Kaliningrad/Product/gribv... Томатный соус, ветчина, шампиньоны, моцарелла 315 189.0 23.9 9.3 6.1 25 370 50 699.3 88.429999 34.410001 22.57


Получаем названия пицц


pizza_names = df['pizza_name'].tolist()
pizza_eng_names = df['pizza_eng_name'].tolist()
print( pizza_eng_names )

['double-pepperoni', 'crazy-pizza', 'pizza-don-bekon', 'gribvetchina', 'pizza-pirog', 'pizza-margarita', 'syrnaya-pizza', 'gavayskaya-pizza', 'pizza-dodo', 'pizza-chetyre-sezona', 'ovoshi-i-griby', 'italyanskaya-pizza', 'meksikanskaya-pizza', 'morskaya-pizza', 'myasnaya-pizza', 'pizza-pepperoni', 'ranch-pizza', 'pizza-syrnyi-cyplenok', 'pizza-cyplenok-barbekyu', 'chizburger-pizza']

Получаем путь до картинок — нас интересует 3-я картинка (вид сверху)


image_paths = []
for name in pizza_eng_names:
    path = os.path.join(name, name+'3.jpg')
    image_paths.append(path)
print(image_paths)

['double-pepperoni\\double-pepperoni3.jpg', 'crazy-pizza\\crazy-pizza3.jpg', 'pizza-don-bekon\\pizza-don-bekon3.jpg', 'gribvetchina\\gribvetchina3.jpg', 'pizza-pirog\\pizza-pirog3.jpg', 'pizza-margarita\\pizza-margarita3.jpg', 'syrnaya-pizza\\syrnaya-pizza3.jpg', 'gavayskaya-pizza\\gavayskaya-pizza3.jpg', 'pizza-dodo\\pizza-dodo3.jpg', 'pizza-chetyre-sezona\\pizza-chetyre-sezona3.jpg', 'ovoshi-i-griby\\ovoshi-i-griby3.jpg', 'italyanskaya-pizza\\italyanskaya-pizza3.jpg', 'meksikanskaya-pizza\\meksikanskaya-pizza3.jpg', 'morskaya-pizza\\morskaya-pizza3.jpg', 'myasnaya-pizza\\myasnaya-pizza3.jpg', 'pizza-pepperoni\\pizza-pepperoni3.jpg', 'ranch-pizza\\ranch-pizza3.jpg', 'pizza-syrnyi-cyplenok\\pizza-syrnyi-cyplenok3.jpg', 'pizza-cyplenok-barbekyu\\pizza-cyplenok-barbekyu3.jpg', 'chizburger-pizza\\chizburger-pizza3.jpg']

Загружаем картинки


images = []
for path in image_paths:
    print('Load image:', path)
    image = cv2.imread(path)
    if image is not None:
        images.append(image)
    else:
        print('Error read image:', path)

Load image: double-pepperoni\double-pepperoni3.jpg
Load image: crazy-pizza\crazy-pizza3.jpg
Load image: pizza-don-bekon\pizza-don-bekon3.jpg
Load image: gribvetchina\gribvetchina3.jpg
Load image: pizza-pirog\pizza-pirog3.jpg
Load image: pizza-margarita\pizza-margarita3.jpg
Load image: syrnaya-pizza\syrnaya-pizza3.jpg
Load image: gavayskaya-pizza\gavayskaya-pizza3.jpg
Load image: pizza-dodo\pizza-dodo3.jpg
Load image: pizza-chetyre-sezona\pizza-chetyre-sezona3.jpg
Load image: ovoshi-i-griby\ovoshi-i-griby3.jpg
Load image: italyanskaya-pizza\italyanskaya-pizza3.jpg
Load image: meksikanskaya-pizza\meksikanskaya-pizza3.jpg
Load image: morskaya-pizza\morskaya-pizza3.jpg
Load image: myasnaya-pizza\myasnaya-pizza3.jpg
Load image: pizza-pepperoni\pizza-pepperoni3.jpg
Load image: ranch-pizza\ranch-pizza3.jpg
Load image: pizza-syrnyi-cyplenok\pizza-syrnyi-cyplenok3.jpg
Load image: pizza-cyplenok-barbekyu\pizza-cyplenok-barbekyu3.jpg
Load image: chizburger-pizza\chizburger-pizza3.jpg

Посмотрим на картинку


def plot_img(img):
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    plt.imshow(img_rgb)

print(images[0].shape)
plot_img(images[0])

(380, 710, 3)


Пиццы располагаются в одной и той же области — вырезаем


pizza_imgs = []
for img in images:
    y, x, height, width = 0, 165, 380, 380
    pizza_crop = img[y:y+height, x:x+width]
    pizza_imgs.append(pizza_crop)
print(pizza_imgs[0].shape)
print(len(pizza_imgs))
plot_img(pizza_imgs[0])

(380, 380, 3)
20


Посмотрим все фотографии


fig = plt.figure(figsize=(12,15))
for i in range(0, len(pizza_imgs)):
    fig.add_subplot(4,5,i+1)
    plot_img(pizza_imgs[i])


Пицца четыре сезона явно выбивается по своей структуре, так как, по сути, состоит из четырёх разных пицц.


Изучим ингредиенты


def split_contain(contain):
    lst = contain.split(',')
    print(len(lst),':', lst)

for i, row in df.iterrows():
    split_contain(row.pizza_contain)

2 : ['Томатный соус', ' двойная порция пепперони и увеличенная порция моцареллы']
4 : ['Томатный соус', ' увеличенные порции цыпленка и пепперони', ' моцарелла', ' кисло-сладкий соус']
6 : ['Томатный соус', ' бекон', ' пепперони', ' цыпленок', ' красный лук', ' моцарелла']
4 : ['Томатный соус', ' ветчина', ' шампиньоны', ' моцарелла']
3 : ['Сгущенное молоко', ' брусника', ' ананасы']
4 : ['Томатный соус', ' томаты', ' увеличенная порция моцареллы', ' орегано']
4 : ['Томатный соус', ' брынза', ' увеличенная порция сыра моцарелла', ' орегано']
4 : ['Томатный соус', ' ананасы', ' цыпленок', ' моцарелла']
9 : ['Томатный соус', ' говядина (фарш)', ' ветчина', ' пепперони', ' красный лук', ' маслины', ' сладкий перец', ' шампиньоны', ' моцарелла']
8 : ['Томатный соус', ' пепперони', ' ветчина', ' брынза', ' томаты', ' шампиньоны', ' моцарелла', ' орегано']
9 : ['Томатный соус', ' брынза', ' маслины', ' сладкий перец', ' томаты', ' шампиньоны', ' красный лук', ' моцарелла', ' базилик']
6 : ['Томатный соус', ' пепперони', ' маслины', ' шампиньоны', ' моцарелла', ' орегано']
8 : ['Томатный соус', ' халапеньо', ' сладкий перец', ' цыпленок', ' томаты', ' шампиньоны', ' красный лук', ' моцарелла']
6 : ['Томатный соус', ' креветки', ' маслины', ' сладкий перец', ' красный лук', ' моцарелла']
5 : ['Томатный соус', ' охотничьи колбаски', ' бекон', ' ветчина', ' моцарелла']
3 : ['Томатный соус', ' пепперони', ' увеличенная порция моцареллы']
6 : ['Соус Ранч', ' цыпленок', ' ветчина', ' томаты', ' чеснок', ' моцарелла']
4 : ['Сырный соус', ' цыпленок', ' томаты', ' моцарелла']
6 : ['Томатный соус', ' цыпленок', ' бекон', ' красный лук', ' моцарелла', ' соус Барбекю']
7 : ['Сырный соус', ' говядина', ' бекон', ' соленые огурцы', ' томаты', ' красный лук', ' моцарелла']

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


  • "двойная порция"
  • "увеличенная порция"

При этом, после модификаторов может идти перечисление ингредиетов через союз И.


Гипотезы:


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

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


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


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


def split_contain2(contain):
    lst = contain.split(',')
    #print(len(lst),':', lst)
    for i in range(len(lst)):
        item = lst[i]
        item = item.replace('увеличенная порция', '')
        item = item.replace('увеличенные порции', '')
        item = item.replace('сыра моцарелла', 'моцарелла')
        item = item.replace('моцареллы', 'моцарелла')
        item = item.replace('цыпленка', 'цыпленок')
        and_pl = item.find(' и ')
        if and_pl != -1:
            item1 = item[0:and_pl]
            item2 = item[and_pl+3:]
            item = item1
            lst.insert(i+1, item2.strip())
        double_pl = item.find('двойная порция ')
        if double_pl != -1:
            item = item[double_pl+15:]
            lst.insert(i+1, item.strip())
        lst[i] = item.strip()
    # last one
    for i in range(len(lst)):
        lst[i] = lst[i].strip()
    print(len(lst),':', lst)
    return lst

ingredients = []
ingredients_count = []
for i, row in df.iterrows():
    print(row.pizza_name)
    lst = split_contain2(row.pizza_contain)
    ingredients.append(lst)
    ingredients_count.append(len(lst))
ingredients_count

Двойная пепперони
4 : ['Томатный соус', 'пепперони', 'пепперони', 'моцарелла']
Крэйзи пицца 
5 : ['Томатный соус', 'цыпленок', 'пепперони', 'моцарелла', 'кисло-сладкий соус']
Дон Бекон
6 : ['Томатный соус', 'бекон', 'пепперони', 'цыпленок', 'красный лук', 'моцарелла']
Грибы и ветчина
4 : ['Томатный соус', 'ветчина', 'шампиньоны', 'моцарелла']
Пицца-пирог
3 : ['Сгущенное молоко', 'брусника', 'ананасы']
Маргарита
4 : ['Томатный соус', 'томаты', 'моцарелла', 'орегано']
Сырная
4 : ['Томатный соус', 'брынза', 'моцарелла', 'орегано']
Гавайская
4 : ['Томатный соус', 'ананасы', 'цыпленок', 'моцарелла']
Додо
9 : ['Томатный соус', 'говядина (фарш)', 'ветчина', 'пепперони', 'красный лук', 'маслины', 'сладкий перец', 'шампиньоны', 'моцарелла']
Четыре сезона
8 : ['Томатный соус', 'пепперони', 'ветчина', 'брынза', 'томаты', 'шампиньоны', 'моцарелла', 'орегано']
Овощи и грибы
9 : ['Томатный соус', 'брынза', 'маслины', 'сладкий перец', 'томаты', 'шампиньоны', 'красный лук', 'моцарелла', 'базилик']
Итальянская
6 : ['Томатный соус', 'пепперони', 'маслины', 'шампиньоны', 'моцарелла', 'орегано']
Мексиканская
8 : ['Томатный соус', 'халапеньо', 'сладкий перец', 'цыпленок', 'томаты', 'шампиньоны', 'красный лук', 'моцарелла']
Морская
6 : ['Томатный соус', 'креветки', 'маслины', 'сладкий перец', 'красный лук', 'моцарелла']
Мясная
5 : ['Томатный соус', 'охотничьи колбаски', 'бекон', 'ветчина', 'моцарелла']
Пепперони
3 : ['Томатный соус', 'пепперони', 'моцарелла']
Ранч пицца
6 : ['Соус Ранч', 'цыпленок', 'ветчина', 'томаты', 'чеснок', 'моцарелла']
Сырный цыплёнок
4 : ['Сырный соус', 'цыпленок', 'томаты', 'моцарелла']
Цыплёнок барбекю
6 : ['Томатный соус', 'цыпленок', 'бекон', 'красный лук', 'моцарелла', 'соус Барбекю']
Чизбургер-пицца
7 : ['Сырный соус', 'говядина', 'бекон', 'соленые огурцы', 'томаты', 'красный лук', 'моцарелла']

[4, 5, 6, 4, 3, 4, 4, 4, 9, 8, 9, 6, 8, 6, 5, 3, 6, 4, 6, 7]

Посмотрим минимальное и максимальное количество ингредиентов


min_count = np.min(ingredients_count)
print('min:', min_count)
max_count = np.max(ingredients_count)
print('max:', max_count)

min: 3
max: 9

print('min:', np.array(pizza_names)[ingredients_count == min_count] )
print('max:', np.array(pizza_names)[ingredients_count == max_count] )

min: ['Пицца-пирог' 'Пепперони']
max: ['Додо' 'Овощи и грибы']

Интересно, больше всего ингредиентов (9 штук) в пиццах: Додо и Овощи и грибы.


Заполним табличку ингредиентов.


df_ingredients = pd.DataFrame(ingredients)
df_ingredients.fillna(value='0', inplace=True)
df_ingredients


0 1 2 3 4 5 6 7 8
0 Томатный соус пепперони пепперони моцарелла 0 0 0 0 0
1 Томатный соус цыпленок пепперони моцарелла кисло-сладкий соус 0 0 0 0
2 Томатный соус бекон пепперони цыпленок красный лук моцарелла 0 0 0
3 Томатный соус ветчина шампиньоны моцарелла 0 0 0 0 0
4 Сгущенное молоко брусника ананасы 0 0 0 0 0 0
5 Томатный соус томаты моцарелла орегано 0 0 0 0 0
6 Томатный соус брынза моцарелла орегано 0 0 0 0 0
7 Томатный соус ананасы цыпленок моцарелла 0 0 0 0 0
8 Томатный соус говядина (фарш) ветчина пепперони красный лук маслины сладкий перец шампиньоны моцарелла
9 Томатный соус пепперони ветчина брынза томаты шампиньоны моцарелла орегано 0
10 Томатный соус брынза маслины сладкий перец томаты шампиньоны красный лук моцарелла базилик
11 Томатный соус пепперони маслины шампиньоны моцарелла орегано 0 0 0
12 Томатный соус халапеньо сладкий перец цыпленок томаты шампиньоны красный лук моцарелла 0
13 Томатный соус креветки маслины сладкий перец красный лук моцарелла 0 0 0
14 Томатный соус охотничьи колбаски бекон ветчина моцарелла 0 0 0 0
15 Томатный соус пепперони моцарелла 0 0 0 0 0 0
16 Соус Ранч цыпленок ветчина томаты чеснок моцарелла 0 0 0
17 Сырный соус цыпленок томаты моцарелла 0 0 0 0 0
18 Томатный соус цыпленок бекон красный лук моцарелла соус Барбекю 0 0 0
19 Сырный соус говядина бекон соленые огурцы томаты красный лук моцарелла 0 0


df_ingredients.describe()


0 1 2 3 4 5 6 7 8
count 20 20 20 20 20 20 20 20 20
unique 4 13 10 12 6 7 4 4 3
top Томатный соус пепперони пепперони моцарелла 0 0 0 0 0
freq 16 4 3 5 8 10 15 16 18


Как и ожидалось — самый используемый соус — томатный. Стандартный рецепт — состоит из 4 ингредиентов.


Забавно, что образовался новый рецепт для пиццы.


Посмотрим сколько раз встречается тот или иной ингредиент:


df_ingredients.stack().value_counts()

0                     69
моцарелла             19
Томатный соус         16
пепперони              8
томаты                 7
цыпленок               7
красный лук            7
шампиньоны             6
ветчина                5
сладкий перец          4
бекон                  4
орегано                4
маслины                4
брынза                 3
ананасы                2
Сырный соус            2
чеснок                 1
кисло-сладкий соус     1
базилик                1
соус Барбекю           1
креветки               1
халапеньо              1
Сгущенное молоко       1
соленые огурцы         1
говядина (фарш)        1
охотничьи колбаски     1
брусника               1
говядина               1
Соус Ранч              1
dtype: int64

Опять же: моцарелла, Томатный соус, пепперони.


df_ingredients.stack().value_counts().drop('0').plot.pie()



Теперь закодируем ингредиенты.


from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder

ingredients_full = df_ingredients.values.tolist()

# flatten lists
flat_ingredients = [item for sublist in ingredients_full for item in sublist]
print(flat_ingredients)
print(len(flat_ingredients))

np_ingredients = np.array(flat_ingredients)
#print(np_ingredients)

labelencoder = LabelEncoder()
ingredients_encoded = labelencoder.fit_transform(np_ingredients)
print(ingredients_encoded)

label_max = np.max(ingredients_encoded)
print('max:', label_max)

['Томатный соус', 'пепперони', 'пепперони', 'моцарелла', '0', '0', '0', '0', '0', 'Томатный соус', 'цыпленок', 'пепперони', 'моцарелла', 'кисло-сладкий соус', '0', '0', '0', '0', 'Томатный соус', 'бекон', 'пепперони', 'цыпленок', 'красный лук', 'моцарелла', '0', '0', '0', 'Томатный соус', 'ветчина', 'шампиньоны', 'моцарелла', '0', '0', '0', '0', '0', 'Сгущенное молоко', 'брусника', 'ананасы', '0', '0', '0', '0', '0', '0', 'Томатный соус', 'томаты', 'моцарелла', 'орегано', '0', '0', '0', '0', '0', 'Томатный соус', 'брынза', 'моцарелла', 'орегано', '0', '0', '0', '0', '0', 'Томатный соус', 'ананасы', 'цыпленок', 'моцарелла', '0', '0', '0', '0', '0', 'Томатный соус', 'говядина (фарш)', 'ветчина', 'пепперони', 'красный лук', 'маслины', 'сладкий перец', 'шампиньоны', 'моцарелла', 'Томатный соус', 'пепперони', 'ветчина', 'брынза', 'томаты', 'шампиньоны', 'моцарелла', 'орегано', '0', 'Томатный соус', 'брынза', 'маслины', 'сладкий перец', 'томаты', 'шампиньоны', 'красный лук', 'моцарелла', 'базилик', 'Томатный соус', 'пепперони', 'маслины', 'шампиньоны', 'моцарелла', 'орегано', '0', '0', '0', 'Томатный соус', 'халапеньо', 'сладкий перец', 'цыпленок', 'томаты', 'шампиньоны', 'красный лук', 'моцарелла', '0', 'Томатный соус', 'креветки', 'маслины', 'сладкий перец', 'красный лук', 'моцарелла', '0', '0', '0', 'Томатный соус', 'охотничьи колбаски', 'бекон', 'ветчина', 'моцарелла', '0', '0', '0', '0', 'Томатный соус', 'пепперони', 'моцарелла', '0', '0', '0', '0', '0', '0', 'Соус Ранч', 'цыпленок', 'ветчина', 'томаты', 'чеснок', 'моцарелла', '0', '0', '0', 'Сырный соус', 'цыпленок', 'томаты', 'моцарелла', '0', '0', '0', '0', '0', 'Томатный соус', 'цыпленок', 'бекон', 'красный лук', 'моцарелла', 'соус Барбекю', '0', '0', '0', 'Сырный соус', 'говядина', 'бекон', 'соленые огурцы', 'томаты', 'красный лук', 'моцарелла', '0', '0']
180
[ 4 20 20 17  0  0  0  0  0  4 26 20 17 13  0  0  0  0  4  7 20 26 14 17  0
  0  0  4 10 28 17  0  0  0  0  0  1  8  5  0  0  0  0  0  0  4 24 17 18  0
  0  0  0  0  4  9 17 18  0  0  0  0  0  4  5 26 17  0  0  0  0  0  4 12 10
 20 14 16 21 28 17  4 20 10  9 24 28 17 18  0  4  9 16 21 24 28 14 17  6  4
 20 16 28 17 18  0  0  0  4 25 21 26 24 28 14 17  0  4 15 16 21 14 17  0  0
  0  4 19  7 10 17  0  0  0  0  4 20 17  0  0  0  0  0  0  2 26 10 24 27 17
  0  0  0  3 26 24 17  0  0  0  0  0  4 26  7 14 17 23  0  0  0  3 11  7 22
 24 14 17  0  0]
max: 28

Получается, что для приготовления, используется целых 27 ингредиентов.


for label in range(label_max):
    print(label, labelencoder.inverse_transform(label))

0 0
1 Сгущенное молоко
2 Соус Ранч
3 Сырный соус
4 Томатный соус
5 ананасы
6 базилик
7 бекон
8 брусника
9 брынза
10 ветчина
11 говядина
12 говядина (фарш)
13 кисло-сладкий соус
14 красный лук
15 креветки
16 маслины
17 моцарелла
18 орегано
19 охотничьи колбаски
20 пепперони
21 сладкий перец
22 соленые огурцы
23 соус Барбекю
24 томаты
25 халапеньо
26 цыпленок
27 чеснок

lb_ingredients = []
for lst in ingredients_full:
    lb_ingredients.append(labelencoder.transform(lst).tolist())
#lb_ingredients = np.array(lb_ingredients)
lb_ingredients

[[4, 20, 20, 17, 0, 0, 0, 0, 0],
 [4, 26, 20, 17, 13, 0, 0, 0, 0],
 [4, 7, 20, 26, 14, 17, 0, 0, 0],
 [4, 10, 28, 17, 0, 0, 0, 0, 0],
 [1, 8, 5, 0, 0, 0, 0, 0, 0],
 [4, 24, 17, 18, 0, 0, 0, 0, 0],
 [4, 9, 17, 18, 0, 0, 0, 0, 0],
 [4, 5, 26, 17, 0, 0, 0, 0, 0],
 [4, 12, 10, 20, 14, 16, 21, 28, 17],
 [4, 20, 10, 9, 24, 28, 17, 18, 0],
 [4, 9, 16, 21, 24, 28, 14, 17, 6],
 [4, 20, 16, 28, 17, 18, 0, 0, 0],
 [4, 25, 21, 26, 24, 28, 14, 17, 0],
 [4, 15, 16, 21, 14, 17, 0, 0, 0],
 [4, 19, 7, 10, 17, 0, 0, 0, 0],
 [4, 20, 17, 0, 0, 0, 0, 0, 0],
 [2, 26, 10, 24, 27, 17, 0, 0, 0],
 [3, 26, 24, 17, 0, 0, 0, 0, 0],
 [4, 26, 7, 14, 17, 23, 0, 0, 0],
 [3, 11, 7, 22, 24, 14, 17, 0, 0]]

onehotencoder = OneHotEncoder(sparse=False)
ingredients_onehotencoded = onehotencoder.fit_transform(ingredients_encoded.reshape(-1, 1))
print(ingredients_onehotencoded.shape)
ingredients_onehotencoded[0]

(180, 29)

array([ 0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.])

Теперь у нас есть данные с которыми мы можем работать.


Автоэнкодер


Попробуем загрузить фотографии пиц (вид сверху) и попробуем натренировать простой сжимающий автоэнкодер.


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline 

import seaborn as sns

np.random.seed(42)

import cv2
import os
import sys

import load_data
import prepare_images

Загружаем фотографии пицц


pizza_eng_names, pizza_imgs = prepare_images.load_photos()

Read csv...
(20, 13)

RangeIndex: 20 entries, 0 to 19
Data columns (total 13 columns):
city_name         20 non-null object
city_url          20 non-null object
pizza_name        20 non-null object
pizza_eng_name    20 non-null object
pizza_url         20 non-null object
pizza_contain     20 non-null object
pizza_price       20 non-null int64
kiloCalories      20 non-null object
carbohydrates     20 non-null object
proteins          20 non-null object
fats              20 non-null object
size              20 non-null int64
weight            20 non-null object
dtypes: int64(2), object(11)
memory usage: 2.1+ KB
None
['double-pepperoni', 'crazy-pizza', 'pizza-don-bekon', 'gribvetchina', 'pizza-pirog', 'pizza-margarita', 'syrnaya-pizza', 'gavayskaya-pizza', 'pizza-dodo', 'pizza-chetyre-sezona', 'ovoshi-i-griby', 'italyanskaya-pizza', 'meksikanskaya-pizza', 'morskaya-pizza', 'myasnaya-pizza', 'pizza-pepperoni', 'ranch-pizza', 'pizza-syrnyi-cyplenok', 'pizza-cyplenok-barbekyu', 'chizburger-pizza']
['double-pepperoni\\double-pepperoni3.jpg', 'crazy-pizza\\crazy-pizza3.jpg', 'pizza-don-bekon\\pizza-don-bekon3.jpg', 'gribvetchina\\gribvetchina3.jpg', 'pizza-pirog\\pizza-pirog3.jpg', 'pizza-margarita\\pizza-margarita3.jpg', 'syrnaya-pizza\\syrnaya-pizza3.jpg', 'gavayskaya-pizza\\gavayskaya-pizza3.jpg', 'pizza-dodo\\pizza-dodo3.jpg', 'pizza-chetyre-sezona\\pizza-chetyre-sezona3.jpg', 'ovoshi-i-griby\\ovoshi-i-griby3.jpg', 'italyanskaya-pizza\\italyanskaya-pizza3.jpg', 'meksikanskaya-pizza\\meksikanskaya-pizza3.jpg', 'morskaya-pizza\\morskaya-pizza3.jpg', 'myasnaya-pizza\\myasnaya-pizza3.jpg', 'pizza-pepperoni\\pizza-pepperoni3.jpg', 'ranch-pizza\\ranch-pizza3.jpg', 'pizza-syrnyi-cyplenok\\pizza-syrnyi-cyplenok3.jpg', 'pizza-cyplenok-barbekyu\\pizza-cyplenok-barbekyu3.jpg', 'chizburger-pizza\\chizburger-pizza3.jpg']
Load images...
Load image: double-pepperoni\double-pepperoni3.jpg
Load image: crazy-pizza\crazy-pizza3.jpg
Load image: pizza-don-bekon\pizza-don-bekon3.jpg
Load image: gribvetchina\gribvetchina3.jpg
Load image: pizza-pirog\pizza-pirog3.jpg
Load image: pizza-margarita\pizza-margarita3.jpg
Load image: syrnaya-pizza\syrnaya-pizza3.jpg
Load image: gavayskaya-pizza\gavayskaya-pizza3.jpg
Load image: pizza-dodo\pizza-dodo3.jpg
Load image: pizza-chetyre-sezona\pizza-chetyre-sezona3.jpg
Load image: ovoshi-i-griby\ovoshi-i-griby3.jpg
Load image: italyanskaya-pizza\italyanskaya-pizza3.jpg
Load image: meksikanskaya-pizza\meksikanskaya-pizza3.jpg
Load image: morskaya-pizza\morskaya-pizza3.jpg
Load image: myasnaya-pizza\myasnaya-pizza3.jpg
Load image: pizza-pepperoni\pizza-pepperoni3.jpg
Load image: ranch-pizza\ranch-pizza3.jpg
Load image: pizza-syrnyi-cyplenok\pizza-syrnyi-cyplenok3.jpg
Load image: pizza-cyplenok-barbekyu\pizza-cyplenok-barbekyu3.jpg
Load image: chizburger-pizza\chizburger-pizza3.jpg
Cut pizza from images...
(380, 380, 3)
20

def plot_img(img):
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    plt.imshow(img_rgb)

plot_img(pizza_imgs[0])


Замечательно, что пицца обладает осевой симметрией — для аугментации, её можно будет вращать вокруг центра, а так же отражать по вертикали.


img_flipy = cv2.flip(pizza_imgs[0], 1)
plot_img(img_flipy)


Результат поворота вокруг центра на 15 градусов:


img_rot15 = load_data.rotate(pizza_imgs[0], 15)
plot_img(img_rot15)


Сделаем аугментацию для первой пиццы в списке (предварительно уменьшив до размеров: 56 на 56) — вращение вокруг оси на 360 градусов с шагом в 1 градус и отражением по вертикали.


channels, height, width = 3, 56, 56

lst0 = load_data.resize_rotate_flip(pizza_imgs[0], (height, width))
print(len(lst0))

720

plot_img(lst0[0])


Попробуем автоэнкодер


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


image_list = lst0
image_list = np.array(image_list, dtype=np.float32)
image_list = image_list.transpose((0, 3, 1, 2))
image_list /= 255.0
print(image_list.shape)

(720, 3, 56, 56)

x_train = image_list[:600]
x_test = image_list[600:]
print(x_train.shape, x_test.shape)

(600, 3, 56, 56) (120, 3, 56, 56)

from keras.models import Model
from keras.layers import Input, Dense, Flatten, Reshape
from keras.layers import Conv2D, MaxPooling2D, UpSampling2D

from keras import backend as K
#For 2D data (e.g. image), "channels_last" assumes (rows, cols, channels) while "channels_first" assumes  (channels, rows, cols).
K.set_image_data_format('channels_first')

Using Theano backend.

Создаём автоэнкодер


def create_deep_conv_ae(channels, height, width):
    input_img = Input(shape=(channels, height, width))

    x = Conv2D(16, (3, 3), activation='relu', padding='same')(input_img)
    x = MaxPooling2D(pool_size=(2, 2), padding='same')(x)
    x = Conv2D(8, (3, 3), activation='relu', padding='same')(x)
    encoded = MaxPooling2D(pool_size=(2, 2), padding='same')(x)

    # at this point the representation is (8, 14, 14)

    input_encoded = Input(shape=(8, 14, 14))
    x = Conv2D(8, (3, 3), activation='relu', padding='same')(input_encoded)
    x = UpSampling2D((2, 2))(x)
    x = Conv2D(16, (3, 3), activation='relu', padding='same')(x)
    x = UpSampling2D((2, 2))(x)
    decoded = Conv2D(channels, (3, 3), activation='sigmoid', padding='same')(x)

    # Models
    encoder = Model(input_img, encoded, name="encoder")
    decoder = Model(input_encoded, decoded, name="decoder")
    autoencoder = Model(input_img, decoder(encoder(input_img)), name="autoencoder")
    return encoder, decoder, autoencoder

c_encoder, c_decoder, c_autoencoder = create_deep_conv_ae(channels, height, width)
c_autoencoder.compile(optimizer='adam', loss='binary_crossentropy')

c_encoder.summary()
c_decoder.summary()
c_autoencoder.summary()

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 3, 56, 56)         0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 16, 56, 56)        448       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 16, 28, 28)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 8, 28, 28)         1160      
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 8, 14, 14)         0         
=================================================================
Total params: 1,608
Trainable params: 1,608
Non-trainable params: 0
_________________________________________________________________
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         (None, 8, 14, 14)         0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 8, 14, 14)         584       
_________________________________________________________________
up_sampling2d_1 (UpSampling2 (None, 8, 28, 28)         0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 16, 28, 28)        1168      
_________________________________________________________________
up_sampling2d_2 (UpSampling2 (None, 16, 56, 56)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 3, 56, 56)         435       
=================================================================
Total params: 2,187
Trainable params: 2,187
Non-trainable params: 0
_________________________________________________________________
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 3, 56, 56)         0         
_________________________________________________________________
encoder (Model)              (None, 8, 14, 14)         1608      
_________________________________________________________________
decoder (Model)              (None, 3, 56, 56)         2187      
=================================================================
Total params: 3,795
Trainable params: 3,795
Non-trainable params: 0
_________________________________________________________________

c_autoencoder.fit(x_train, x_train,
                epochs=20,
                batch_size=16,
                shuffle=True,
                verbose=2,
                validation_data=(x_test, x_test))

Train on 600 samples, validate on 120 samples
Epoch 1/20
10s - loss: 0.5840 - val_loss: 0.5305
Epoch 2/20
10s - loss: 0.4571 - val_loss: 0.4162
Epoch 3/20
9s - loss: 0.4032 - val_loss: 0.3956
Epoch 4/20
8s - loss: 0.3884 - val_loss: 0.3855
Epoch 5/20
10s - loss: 0.3829 - val_loss: 0.3829
Epoch 6/20
11s - loss: 0.3808 - val_loss: 0.3815
Epoch 7/20
9s - loss: 0.3795 - val_loss: 0.3804
Epoch 8/20
8s - loss: 0.3785 - val_loss: 0.3797
Epoch 9/20
10s - loss: 0.3778 - val_loss: 0.3787
Epoch 10/20
10s - loss: 0.3771 - val_loss: 0.3781
Epoch 11/20
9s - loss: 0.3764 - val_loss: 0.3779
Epoch 12/20
8s - loss: 0.3760 - val_loss: 0.3773
Epoch 13/20
9s - loss: 0.3756 - val_loss: 0.3768
Epoch 14/20
10s - loss: 0.3751 - val_loss: 0.3766
Epoch 15/20
10s - loss: 0.3748 - val_loss: 0.3768
Epoch 16/20
9s - loss: 0.3745 - val_loss: 0.3762
Epoch 17/20
10s - loss: 0.3741 - val_loss: 0.3755
Epoch 18/20
9s - loss: 0.3738 - val_loss: 0.3754
Epoch 19/20
11s - loss: 0.3735 - val_loss: 0.3752
Epoch 20/20
8s - loss: 0.3733 - val_loss: 0.3748


#c_autoencoder.save_weights('c_autoencoder_weights.h5')
#c_autoencoder.load_weights('c_autoencoder_weights.h5')

Посмотрим на результаты работы автоэнкодера


n = 5
imgs = x_test[:n]
encoded_imgs = c_encoder.predict(imgs, batch_size=n)
decoded_imgs = c_decoder.predict(encoded_imgs, batch_size=n)

def get_image_from_net_data(data):
    res = data.transpose((1, 2, 0))
    res *= 255.0
    res = np.array(res, dtype=np.uint8)
    return res

#image0 = get_image_from_net_data(decoded_imgs[0])
#plot_img(image0)

fig = plt.figure()
j = 0
for i in range(0, len(imgs)):
    j += 1
    fig.add_subplot(n,2,j)
    plot_img( get_image_from_net_data(imgs[i]) )
    j += 1
    fig.add_subplot(n,2,j)
    plot_img( get_image_from_net_data(decoded_imgs[i]) )


Продолжение следует...


Ссылки


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

https://habrahabr.ru/post/335444/


Метки:  

История торговых кассовых аппаратов

Пятница, 11 Августа 2017 г. 15:55 + в цитатник
Современную торговлю невозможно представить без использования контрольно-кассовой техники. Кассы с нами повсюду: в супермаркетах, кафе, на заправках и на почте. С 1 июля 2017 года кассовые аппараты обязательно должны использоваться даже при оплате товаров в интернет-магазинах. А кто и когда первым придумал вести учет финансовых поступлений при помощи кассы? Вся история торговых кассовых аппаратов – в нашем материале.




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


1871-1884: первые кассы Джеймса Якоба Ритти


Первым человеком, который задумался о необходимости контроля денежных поступлений, был Джеймс Якоб Ритти. Будущий изобретатель кассового аппарата в 1871 году открыл в городе Дайтон (штат Огайо) бар под названием Pony House. Несмотря на немалое число посетителей, денег бизнес не приносил, так как персонал заведения постоянно утаивал выручку от владельца. Решить проблему увольнением нечистых на руку продавцов не получалось – с новыми людьми происходило то же самое.


Джеймс Ритти – изобретатель первого кассового аппарата

Решение пришло неожиданно, во время морского путешествия Ритти из США в Европу. Джеймс зашел в машинное отделение корабля и увидел там тахометр – круглый датчик, который отсчитывал число оборотов гребного вала. Глядя на этот датчик, Джеймс подумал, что можно собрать похожее устройство, которое бы точно так же отсчитывало деньги, принимаемые от клиентов Pony House. Вернувшись домой, воодушевленный Джеймс сконструировал прототип кассового аппарата.

Это устройство было совершенно не похоже на привычные для нас кассы. Для индикации суммы дохода использовался круглый циферблат, из-за чего устройство издалека можно было принять за обычные часы в деревянном корпусе. Тем более что у первой кассы, как и у часов, были стрелки: длинная («минутная») показывала центы, а короткая («часовая») отображала доллары. Под этим циферблатом располагались кнопки, каждая из которых соответствовала цене на тот или иной товар. Например, если посетитель приобретал выпивку на 35 центов, кассир должен был нажать на кнопку с этим значением, после чего счетчик корректировал положение стрелок на циферблате.


Первая рабочая модель кассового аппарата

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



Чтобы приучить посетителей заведения к кассе, Ритти повесил на устройство колокольчик, который издавал звон после расчета посетителя. Эта модель получила прозвище «Неподкупный кассир Ритти».

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

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

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



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


Джеймс Ритти с братом создают первые образцы кассовых аппаратов

Первые кассы современности: «Национальная кассовая компания» Джона Паттерсона


В 1884 году права на изготовление кассовых аппаратов выкупил Джон Паттерсон, которого по праву называют человеком, создавшим современную технологию продаж. Получив патент на производство перспективного устройства, бизнесмен создал компанию The National Cash Register Company (которая, кстати, и сегодня выпускает POS-оборудование).

В 1906 году в NCR был изобретен первый кассовый аппарат на электромоторе с кнопочным приводом. Его создал один из самых талантливых инженеров NCR – Чарльз Кеттеринг, который всего за пять лет работы в компании получил более двадцати патентов на разные изобретения. Команда инженеров NCR вносила все новые усовершенствования в кассовые аппараты, и именно под началом Джона Паттерсона эти устройства стали такими, какими мы их привыкли видеть.


Классический кассовый аппарат фирмы The National Cash Register Company

Когда Паттерсон занялся кассовыми аппаратами, ему пришлось влезть в долги и полагаться на свою интуицию предпринимателя. Но он твердо верил в то, что за кассовыми аппаратами будущее. Интересно, что Паттерсон не навязывал покупателям сами кассовые аппараты, а работал над созданием потребности в кассовых чеках (то есть по сути продавал не сам продукт, а выгоду от его использования). Для продавцов NCR был написан целый учебник, который они обязаны были выучить наизусть, прежде чем идти к потенциальным клиентам. В итоге с 1884 по 1911 год в мире было продано более миллиона кассовых аппаратов, а к 1917 году The National Cash Register Company контролировала около 95% рынка.

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

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

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


Альтернативный тип кассового аппарата от Simplex Cash Register

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

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

Впрочем, Паттерсон не боялся судебных процессов и активно спорил в суде с конкурентами. Забавный случай произошел в 1894 году. Обратив внимание на растущую популярность кассовых устройств, некий Майкл Хайнц из Детройта создал компанию Heintz Cash Register, которая выпустила свой кассовый аппарат. Отличительной особенностью этой модели было то, что вместо привычного звона колокольчика об окончании операции сообщала… кукушка. Да-да, механическая кукушка, которая высовывала голову из кассы и куковала.



Мимо такого Паттерсон пройти не мог. Он подал иск на Хайнц Кэш Реджистер, обвинив конкурента в нарушении авторских прав. В суде представители «кассы с кукушкой» заявили, что сделали принципиально новый продукт, заменив классический колокольчик на птичку. Но Джон настаивал на том, что это не так, а в доказательство предъявил текст оригинального патента. И действительно в патенте Джеймса Ритти не был прописан именно колокольчик — в нем указывалось «звуковое устройство для оповещения». Решение суда постановило заставить кукушку замолчать навсегда. Хотя, наверное, было бы забавно услышать на кассе, что твои деньги «ку-ку».

Кассовые аппараты в СССР


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



Более того, в стране даже толком не было мест, где можно было бы отремонтировать поврежденную импортную технику. В 1923 году открылось единственное на тот момент предприятие по ремонту пишущих машин, счётных и кассовых аппаратов – «Бюро точной механики 1-го МГУ».

Первые более или менее удачные варианты кассовых аппаратов отечественного производства, такие как А1Т или линейка устройств КИМ, появились во второй половине прошлого века. Изначально эти модели были исключительно механическими и приводились в действие поворотной ручкой. Позже их конструкция стала электромеханической, они работали от обычной сети. Но даже когда во второй половине 70-х в универмагах и продуктовых магазинах появились электрифицированные образцы, на боковой стенке кассового аппарата все еще можно было видеть ручку «для завода», которая использовалась в экстренных случаях при отсутствии электричества.


Кассовый аппарат КИМ-2

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

Ранние модели советских касс имели ограниченный набор регистров. Каждый регистр вводился своим набором цифр. Для десятков рублей использовался первый вертикальный столбец кнопок от «1» до «9», для единиц рублей – второй вертикальный столбец и т.д. Цифры «ноль» не было вообще, вместо нее ставился крест – это была своеобразная защита от подделок чека. Также на клавиатуре находился короткий столбец кнопок – «1», «2», «3», «4». Это были номера отделов, для которых выбивался товар.

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

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


Модель КИМ-3-СП

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


Контрольно-кассовый аппарат «Ока 4401»

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

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

В восьмидесятых же появились и первые электронные кассовые аппараты. Наиболее популярной среди них была Искра-302А. Она была похожа на гигантский калькулятор и имела встроенную память на магнитных сердечниках. Такая касса часто использовалась в Сбербанке и на почте.


Советский кассовый аппарат Искра-302А

Наше время


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

  • корпуса, внутри которого должны быть часы реального времени.
  • фискального накопителя (ФН) – криптографического средства защиты фискальных данных, которое записывает данные в некорректируемом виде, хранит их и передает в контролирующие органы. Именно передача накопленных данных является отличием ФН от ЭКЛЗ (электронной защищенной контрольной ленты), которая использовалась в более ранних версиях кассовых аппаратов для некорректируемого накопления информации обо всех оформленных на устройстве платежных документах и отчетах закрытия смены.
  • устройства для печати чеков. Впрочем, в эпоху онлайн-расчетов печать бумажного чека для некоторых видов торговли уже необязательна. Поэтому сегодня уже есть онлайн-кассы, которые генерируют только электронный чек и не печатают его на бумаге.


Для того чтобы разобраться в разнообразии современных касс, можно ориентироваться на буквенные обозначения в названиях моделей:
  • «ФС» – кассы только для расчетов в интернете (не содержат внутри корпуса устройство для печати);
  • «ФА» – кассы только для встраивания в автоматические устройства (вендинг, платежные терминалы);
  • «ФБ» – автоматизированные системы БСО;
  • «Ф» – все остальные, которые могут применяться в любом из вариантов.


Современные онлайн-кассы для торговых точек обычно оснащаются встроенным модемом, обеспечивающим онлайн-передачу данных в ФНС, слотом для SIM-карты, влагозащищенной клавиатурой, Li-ion аккумулятором (на случай перебоев с электропитанием).


Касса АТОЛ 90Ф

Есть и портативные кассы, предназначенные специально для курьеров. Они более легкие и компактные (иногда – весом до 300 грамм), могут в течение длительного времени работать автономно, оснащены интерфейсами передачи данных Bluetooth и Wi-Fi.

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


Многофункциональный POS-терминал «АТОЛ Магазин у дома»

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

В итоге


Контрольно-кассовые аппараты, выпущенные за последние сто пятьдесят лет, сильно отличаются внешне и функционально. Но есть кое-что, что объединяет первые кассы Джеймса Ритти и современные POS-терминалы. Это – простота в обращении.

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

https://habrahabr.ru/post/335362/


Метки:  

Digest MBLTdev — свежак для iOS-разработчиков

Пятница, 11 Августа 2017 г. 15:55 + в цитатник


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



The Ultimate Guide to Branch Products
Если вы что-то слышали про Branch, то, возможно, знаете, что они предоставляют систему дип линков. На самом деле у Branch сейчас есть целый арсенал продуктов для любой стадии развития приложения.
BRANCH.APP.LINK

HomePod firmware provides detailed look at iPhone 8 screen layout
Скандалы, интриги, расследования. Вопрос один: это вообще законно? Скоро узнаем. Ждать осталось недолго.
9TO5MAC.COM

Apple releases fourth iOS 11 public beta for iPhone and iPad
Паблик бета 4 в бою. Хуже не стало. И это хорошо.
9TO5MAC.COM

Неделя для подачи доклада на MBLTdev 2017
Точнее, 9 дней. Но дедлайн близко (ждём заявки до 20.08).
MBLTDEV.RU

iOS.Ninja
Новый канал для iOS-разработчиков, объединённый. Если вдруг подзабылось, каналов целый ворох на http://ios-channels.ru.
T.ME



Не CoreML единым
Нейросеть на Swift, реализующая XOR, но без использования CoreML, а через BNNS.
COCOA-BEANS.RU

MAChineLearning
Ещё один заход по ML. Специально для macOS-разработчиков. Вторую неделю подряд появляется что-то специфическое для macOS. Так, глядишь, и синхронизацию CoreData через iCloud починят.
GITHUB.COM

Disk
Предлагается абстракция над некоторыми дисковыми операциями.
GITHUB.COM

Managing view controller complexity in tvOS and iOS projects
В процессе работы над своим AVPlayerViewController ребята сделали правильные выводы насчёт архитектуры. Стейт надо изолировать!
MEDIUM.COM

Enforce Exclusive Access to Memory (Swift-evolution)
Concurrency — это непросто. Если вам интересно узнать, что такое overlapping accesses, чем он плох и как с ним предлагают жить, читайте этот proposal.
GITHUB.COM

UI-тесты для iOS
Мобильные проекты обычно небольшие и недолго живущие, люди уже начинают забывать, как выглядит Objective-C, а некоторые его даже не видели. И вот прекрасная статья про проект, которому уже 6 лет. 99% кода написано на Objective-C, много чего подкручено это здорово! Конечно, такую кодовую базу надо покрывать тестами, и парни поделились тем, как они тестируют UI. Если коротко, то это XCTest и добавление JSON с описанием UI-элементов в accessibilityValue.
HABRAHABR.RU

TamTam: как мы делали новый мессенджер
Немного про то, как устроен мессенджер от Одноклассников.
HABRAHABR.RU



Sequel Pro
А вот немножечко UI для работы с mySQL, MariaDB. Можно, конечно, и образ с сайта скачать, но из исходников-то правомернее будет.
GITHUB.COM

SQLite.viewer
Поднимает сервер который позволяет работать с SQLite базой приложения из браузера. Напомнило PonyDebugger.
GITHUB.COM



App Store Insights
Немного занимательных фактов о магазинах приложений.
BLOG.APPFIGURES.COM

Digest MBLTDEV — это собрание самой полезной и свежей информации для iOS-разработчиков с просторов мирового интернета. Выпуск выходит каждую пятницу. Подписка бесплатная. И никакого спама, честно!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335438/


Мониторинг как сервис: модульная система для микросервисной архитектуры

Пятница, 11 Августа 2017 г. 15:50 + в цитатник
Сегодня на нашем проекте, помимо монолитного кода, функционируют десятки микросервисов. Каждый из них требует того, чтобы его мониторили. Делать это в таких объемах силами DevOps проблематично. Мы разработали систему мониторинга, которая работает как сервис для разработчиков. Они могут самостоятельно писать метрики в систему мониторинга, пользоваться ими, строить на их основании дашборды, прикручивать к ним алерты, которые будут срабатывать при достижении пороговых значений. С DevOps — только инфраструктура и документация.
Этот пост — расшифровка моего выступления с нашей секции на РИТ++. Многие просили нас сделать текстовые версии докладов оттуда. Если вы были на конференции или смотрели видео, то не найдете ничего нового. А всем остальным — добро пожаловать под кат. Расскажу, как мы пришли к такой системе, как она работает и как мы планируем её обновлять.



Прошлое: схемы и планы


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



У нас существовало порядка 24 узлов, которые отвечали за мониторинг. Здесь есть целая пачка различных кронов, скриптов, демонов, которые что-то где-то каким-то образом мониторят, отправляют сообщения, выполняют функции. Мы подумали, что чем дальше, тем менее такая система будет жизнеспособна. Развивать её нет смысла: слишком громоздкая.
Мы решили выбрать те элементы мониторинга, которые мы оставим и будем развивать, и те, от каких откажемся. Их оказалось 19. Остались только графиты, агрегаторы и Grafana в качестве дашборда. Но как же будет выглядеть новая система? Вот так:



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

Стандарт: Мониторинг 2.0


Так выглядели планы в 2015. Но нам надо было готовить не только инфраструктуру и сам сервис, но и документацию к нему. Мы для себя разработали корпоративный стандарт, который назвали мониторинг 2.0. Какие требования были к системе?
  • постоянная доступность;
  • интервал хранения метрик = 10 секунд;
  • структурированное хранение метрик и дашбордов;
  • SLA > 99,99%
  • cбор ивентовых метрик по UDP (!).

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



Каждый из префиксов носит какое-то свойство. Есть метрики по серверам, сетям, контейнерам, ресурсам, приложениям и так далее. Реализована четкая, строгая, типизированная фильтрация, где мы принимаем метрики первого уровня, а остальные просто дропаем. Вот как мы планировали эту систему в 2015 году. Что же в настоящем?

Настоящее: схема взаимодействия компонентов мониторинга


В первую очередь мы мониторим аппликейшны: наш PHP-код, приложения и микросервисы — словом, все, что пишут наши разработчики. Все аппликейшны через UDP отправляют метрики в агрегатор Brubeck (statsd, переписанный на С). Он оказался самым быстрым по итогам синтетических тестов. И он отправляет уже агрегированные метрики в Graphite через TCP.

У него есть такой тип метрик, как таймеры. Это очень удобная штука. Например, на каждое соединение пользователя с сервисом вы отправляете в Brubeck метрику с responce time. Пришел миллион ответов, а агрегатор выдал всего 10 метрик. У вас есть количество пришедших людей, максимальное, минимальное и среднее время отклика, медиана и 4 персентиля. Потом данные передаются в Graphite и мы видим их все вживую.

Также у нас есть агрегация для метрик по железу, софту, системных метрик и нашей старой системы мониторинга Munin (она работала у нас до 2015 года). Все это мы собираем через C'ишный демон CollectD (в него вшита целая пачка различных плагинов, он умеет опрашивать все ресурсы хостовой системы, на которой он установлен, просто укажите в конфигурации, куда писать данные) и пишем через него данные в Graphite. Также он поддерживает плагины python и shell скрипты, так что вы можете писать свои кастомные решения: CollectD будет собирать эти данные с локального или удаленного хоста (предположим, есть Curl) и отправлять их в Graphite.

Дальше все метрики, которые мы собрали, отправляем в Carbon-c-relay. Это решение Carbon Relay от Graphite, доработанное на C. Это роутер, который собирает в себе все метрики, которые мы отправляем с наших агрегаторов, и маршрутизирует их по нодам. Также на стадии маршрутизации он проверяет валидность метрик. Они, во-первых, должны соответствовать той схеме с префиксами, которую я показал раньше и, во-вторых, валидны для графита. Иначе они дропаются.

Потом Carbon-c-relay отправляет метрики в кластер Graphite. Мы используем в качестве основного хранилища метрик Carbon-cache, переписанные на Go. Go-carbon по причине его многопоточности намного превосходит по производительности Carbon-cache. Он принимает данные в себя и записывает их на диски с помощью пакета whisper (стандартный, написан на python). Для того, чтобы прочитать данные с наших хранилищ, мы используем Graphite API. Он работает намного быстрее, чем стандартный Graphite WEB. Что происходит с данными дальше?

Они идут в Grafana. В качестве основного источника данных мы используем наши кластеры графитов, плюс у нас есть Grafana как веб-интерфейс, для отображения метрик, построения дэшбордов. На каждый свой сервис разработчики заводят собственный дэшборд. Далее они строят по ним графики, на которых отображаются метрики, которые они пишут со своих приложений. Помимо Grafana у нас есть еще SLAM. Это питонячий демон, который считает SLA на основании данных из графита. Как я уже говорил, у нас есть несколько десятков микросервисов, у каждого из которых есть свои требования. С помощью SLAM мы ходим в документацию и сравниваем её с тем что есть в Graphite и сравниваем, насколько требования соответствуют доступности наших сервисов.

Идем далее: алертинг. Он организован с помощью сильной системы — Moira. Она независимая потому, что у нее под капотом — свой собственный Graphite. Разработана ребятами из СКБ контура, написана на python и Go, полностью опенсорсная. Moira получает в себя весь тот же поток, что уходит в графиты. Если по какой-то причине у вас умрет хранилище, то ваш алертинг будет работать.

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

Еще мы к ней прикрутили корпоративный LDAP, с помощью которого каждый пользователь корпоративной системы может создавать для себя нотификации по существующим (или вновь созданным) триггерам. Так как Moira содержит в себе Graphite, она поддерживает все его функции. Поэтому вы сначала берете строчку и копируете ее в Grafana. Смотрите, как отображаются данные на графиках. А потом берете эту же строчку и копируете ее в Moira. Обвешиваете ее лимитами и получаете на выходе алертинг. Чтобы все это делать, вам не нужны никакие специфические знания. Moira умеет алертить по смс, email, в Jira, Slack… Также она поддерживает выполнение кастомных скриптов. Когда у нее случается триггер, и она подписана на кастомный скрипт или бинарник, она его запускает, и отдает на stdin этому бинарнику JSON. Соответственно, ваша программа должна его распарсить. Что вы будете с этим JSONом делать — решайте сами. Хотите — отправляйте в Telegram, хотите — открывайте таски в Jira, делайте что угодно.

У нас для алертинга используется ещё и собственная разработка — Imagotag. Мы адаптировали панель, которая применяется обычно для электронных ценников в магазинах, под наши задачи. Мы вывели на нее триггеры из Moira. Там указано, в каком они состоянии, когда произошли. Часть ребят из разработки отказались от уведомлений в Slack и в почту в пользу вот этой панельки.



Ну и так как мы — прогрессивная компания, то замониторили в этой системе еще и Kubernetes. Включили его в систему с помощью Heapster, который мы установили в кластер, он собирает данные и отправляет их в Graphite. В итоге схема выглядит вот так:



Компоненты мониторинга



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

Graphite:




Carbon-c-relay:


github.com/grobian/carbon-c-relay

Brubeck:


github.com/github/brubeck

Collectd:


collectd.org

Moira:


github.com/moira-alert

Grafana:


grafana.com

Heapster:


github.com/kubernetes/heapster

Статистика


И вот немного цифр о том, как система работает у нас.

Aggregator (brubeck)


Количество метрик: ~ 300 000 / sec
Интервал отправки метрик в Graphite: 30 sec
Использование ресурсов сервера: ~ 6% CPU (речь идет о полноценных серверах); ~ 1Gb DDR; ~ 3 Mbps LAN

Graphite (go-carbon)


Количество метрик: ~ 1 600 000 / min
Интервал обновления метрик: 30 sec
Схема хранения метрик: 30sec 35d, 5min 90d, 10min 365d (дает понимание что происходит с сервисом на продолжительном этапе времени)
Использование ресурсов сервера: ~ 10% CPU; ~ 20Gb DDR; ~ 30 Mbps LAN

Гибкость


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

Вот пример реально возникшей проблемы. Метрика в Graphite — это файл. У него есть название. Имя файла = имя метрики. И есть путь до него. Названия файлов в Linux ограничены 255 символами. А у нас есть (в качестве “внутренних заказчиков”) ребята из отдела баз данных. Они нам говорят: “Мы хотим мониторить наши SQL-запросы. А они — не 255 символов, а 8 МБ каждый. Мы их хотим отображать в Grafana, видеть параметры по этому запросу, а еще лучше, мы хотим видеть топ таких запросов. Будет здорово, если он будет отображаться в реальном времени. А совсем круто было бы запихнуть их в алертинг”.


Пример SQL-запроса взят в качестве примера с сайта postgrespro.ru

Мы поднимаем сервер Redis и нашими Collectd-плагинами, которые ходят в Postgres и берут оттуда все данные, отправляем метрики в Graphite. Но заменяем имя метрики на хэши. Этот же хэш одновременно отправляем в Redis в качестве ключа, и весь SQL-запрос в качестве значения. Нам осталось сделать так, чтобы Grafana умела ходить в Redis и брать эту информацию. Мы открываем Graphite API, т.к. это основной интерфейс взаимодействия всех компонентов мониторинга с графитом, и вписываем туда новую функцию, которая называется aliasByHash() — от Grafana получаем имя метрики, и используем его в запросе к Redis как ключ, в ответ получаем значение ключа, которым является наш “SQL запрос”. Таким образом, мы вывели в Grafana отображение SQL-запроса, который по идее отобразить там было никак нельзя, вместе со статистикой по нему (calls, rows, total_time, ...).

Итоги


Доступность. Наш сервис мониторинга доступен 24 на 7 из любого аппликейшна и любого кода. Если у вас есть доступ к хранилищам, вы можете писать в сервис данные. Язык неважен, решения не важны. Вам нужно только знать как открыть сокет, закинуть туда метрику и закрыть сокет.

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

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

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

К чему мы стремимся?


Все перечисленное ниже — это не просто абстрактные мысли, а то, к чему сделаны хотя бы первые шаги.
  1. Детектор аномалий. Хотим запилить у себя сервис, который будет ходить в наши Graphite-хранилища и каждую метрику проверять по различным алгоритмам. Уже есть алгоритмы, которые мы хотим просматривать, есть данные, мы умеем с ними работать.
  2. Метаданные. У нас много сервисов, со временем они меняются, так же как и люди, которые с ними работают. Постоянно вести документацию вручную — не вариант. Поэтому сейчас в наши микросервисы встраиваются метаданные. Там прописано, кто его разработал, языки, с которыми он взаимодействует, требования по SLA, куда и кому высылать нотификации. При деплое сервиса все данные сущности создаются самостоятельно. В итоге вы получаете две ссылки — одна на триггеры, другая — на дэшборды в Grafana.
  3. Мониторинг в каждый дом. Мы считаем, что подобной системой должны пользоваться все разработчики. В этом случае вы всегда понимаете, где ваш трафик, что с ним происходит, где он падает, где у него слабые места. Если, допустим, придёт нечто и завалит ваш сервис, то вы узнаете об этом не во время звонка от менеджера, а от алерта, и сразу сможете открыть свежие логи и посмотреть, что там произошло.
  4. Высокая производительность. Наш проект постоянно растет, и сегодня в нём обрабатывается около 2 000 000 значений метрик в минуту. Год назад этот показатель составлял 500 000. А рост продолжается, и это значит, что через какое-то время Graphite (whisper) начнет очень сильно нагружать дисковую подсистему. Как я уже говорил, эта система мониторинга довольно универсальна за счёт взаимозаменяемости компонентов. Кто-то специально под Graphite обслуживает и постоянно расширяет свою инфраструктуру, но мы решили пойти другим путем: использовать ClickHouse в качестве хранилища наших метрик. Этот переход практически завершен, и совсем скоро я расскажу поподробнее, как это было сделано: какие были трудности и как они были преодолены, как проходил процесс миграции, опишу выбранные в качестве обвязки компоненты и их конфигурации.

Спасибо за внимание! Задавайте свои вопросы по теме, постараюсь ответить здесь или в следующих постах. Возможно, у кого-то есть опыт построения подобной системы мониторинга или перехода на Clickhouse в сходной ситуации — делитесь им в комментариях.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335410/


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

Пятница, 11 Августа 2017 г. 15:50 + в цитатник
В ответ на мою первую статью «Доступность приложений для пользователей с нарушениями зрения» я получил список вопросов, касающихся доступности сайтов, которые действительно неплохо было бы осветить.

Что желательно сделать на сайте для повышения доступности


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

  • Добавьте атрибут alt к изображениям, кратко, можно сокращённо, описав картинку. Ни в коем случае не используйте надписи вида «img12345» или «image1948372» — они не облегчат, а, наоборот, существенно усложнят навигацию.

  • Если при разметке сайта использовались контейнеры div, добавьте к ним атрибут role, тем самым дав понять скрин-ридеру, что здесь находится меню (role=«navigation»), основное содержимое (role=«main») или подвал сайта (role=«contentinfo»). Список всех возможных значений атрибута role и информацию о его использовании вы можете найти здесь.

  • Если на странице присутствуют несколько контейнеров с одинаковым значением role, используйте атрибут area-label, в значении которого по-русски кратко описывайте предназначение контейнера. Если вы напишете
    something
    , то скрин-ридер прочитает пользователю «главное меню навигация ориентир», что довольно удобно.
    Отличный пример использования адаптивной разметки с помощью role и area-label можно увидеть в исходном коде mail.yandex.ru, в режиме для пользователей программ экранного доступа.

  • При желании добавьте горячие клавиши к значимым ссылкам, например, к элементам главного меню. Это делается с помощью атрибута accesskey=«X», где X — любая буква латинского алфавита.

  • Если вам нужно отобразить какой-либо текст при наведении мыши на ссылку, кнопку или другой элемент, сделайте это с помощью атрибута title, а не при помощи JavaScript.

Чего делать не надо


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

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

  • Использование технологии Flash полностью ломает любую доступность, так как элементы, созданные с помощью неё скрин-ридер даже не распознаёт как отдельные объекты.

  • Правильная разметка — это хорошо, но не злоупотребляйте атрибутами role и area-label, их избыток тоже затрудняет навигацию.

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

Нужна ли сайту отдельная версия для пользователей с нарушениями зрения?


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

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

Где разместить ссылку на адаптированную версию сайта?


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

Как проверить сайт на доступность? существуют ли специальные инструменты?


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

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

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

С какими проблемами вы сталкиваетесь при работе с формами?


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

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

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


Чтобы правильно ответить на этот вопрос, нужно подробнее объяснить, как незрячий пользователь «видит» web-страницу.

Для него страница — это объектная модель, а не картинка. то есть, если визуально можно кнопку, ссылку или обычный текст сделать одинаковыми, то скрин ридер не обманешь, он обязательно произнесёт «ссылка вход», «кнопка вход» или просто «вход». Такая же ситуация  - с похожими буквами латинского и русского алфавитов. Если для обычного пользователя надписи «привет» и «пpивet» при беглом чтении могут показаться одинаковыми, то для скрин-ридера это всегда разные буквы, и читает он их по-разному.

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

Как вы взаимодействуете с рекламой? она бывает доступной?


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

Приведите пример отличного по доступности сайта


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

Почти доступна полная версия vk.com, но присутствует множество грубейших ошибок. В этом нет вины разработчиков, здесь скорее налицо исполнение мечты неопытного пользователя: куча лишней информации, лишних ориентиров, зато не заблудишься. Доступных сайтов много, проще назвать полностью или почти полностью недоступные, например, уже упомянутая мной в первой статье web-версия telegram, официальный сайт сбербанка. Им, конечно, можно пользоваться, но с затруднениями. В эту же категорию можно отнести и сайт ГосУслуги, о котором я тоже уже говорил.

Пользуетесь ли вы настройкой браузера, позволяющей увеличить размер шрифта? часто ли это работает?


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

Используете ли вы голосовые помощники вроде Siri, Alexa, Cortana?


Siri я не использую, потому что у меня не IPhone, про Alexa раньше никогда не слышал, а Cortana в Windows 10, как впрочем и на Windows Phone, недоступна на русском языке. 

Часто ли вы пользуетесь смартфоном? Удобнее ли Мобильные сайты десктопных? Мобильные сайты содержат меньше мусора, является ли это преимуществом?


Смартфоном я пользуюсь постоянно, но просматривать web-сайты предпочитаю на компьютере, потому что, к сожалению, связки TalkBack + Chrome & Firefox на Android крайне нестабильны и неудобны. А мобильные версии сайтов можно использовать и с компьютера, что очень часто спасает от лишнего контента и упрощает навигацию. Так было в vk.com, до тех пор, пока не сделали адаптивную полную версию.

Используете ли вы режим чтения в браузере, который вырезает шелуху?


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

Пытаетесь ли вы сообщать владельцам сайтов о проблемах с доступностью? Помогает ли это?


Здесь работает следующее правило: чем крупнее компания, тем меньше её волнует мнение отдельного пользователя.

к примеру, небольшой интернет-магазин с аудиторией человек в 500 гораздо охотнее ответит на сообщение о проблеме и исправит её, чем такой гигант как Samsung или Sony. 

Доступности ВК добивались всей Россией почти месяц, и, только собрав более 200000 подписей под соответствующей петицией на change.org, мы смогли что-то изменить.

Как вы работаете с сайтами на других языках? Используете ли функции автоперевода в браузере?


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

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

Использованные материалы


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

https://habrahabr.ru/post/335442/


Метки:  

Резервное копирование базы mysql и файлов на удаленный FTP — Python 3

Пятница, 11 Августа 2017 г. 15:40 + в цитатник
Начал изучать волшебный язык Python3 и решил испробовать его в действие на своем маленьком VPS.

На сервере стоит Mysql, Apache, nginx… во общем простой стандартный набор, там же хостятся с два десятка клиентских сайтов.

Каждый день делается резервная копия всех баз и файлов доменнов средствами приметного скрипта #!bin/bash

Я решил использовать Python 3… Вот непосредственно и сам код:


#!/usr/bin/env python3

import subprocess
import datetime
import optparse
import zipfile
import os
import ftplib

class ReturnCode(Exception):
    pass


class NotExist(Exception):
    pass


class RequiredOpts(Exception):
    pass


class BackupUtils:
    __current_date = str(datetime.datetime.now().strftime('%d_%m_%Y'))

    def __init__(self):
        self.ftp = None

    def to_zip(self, file, filename=__current_date + '.zip', append_to_file=False):
        """
        :param file: file or folder for added to archive
        :param filename: output archive filename
        :param append_to_file: if False, will be create new file, True for append in exist file
        :type append_to_file: False
        :type filename: str
        :type file: str
        :return True
        """
        param_zip = 'a' if append_to_file else 'w'
        try:
            with zipfile.ZipFile(filename, param_zip) as zip_file:
                if os.path.isfile(file):
                    zip_file.write(file)
                else:
                    self.add_folder_to_zip(zip_file, file)
            return True
        except IOError as error:
            print('Cannot create zip file, error: {}'.format(error))
            return False

    def add_folder_to_zip(self, zip_file, folder):
        """
        :type folder: str
        :type zip_file: file
        """
        for file in os.listdir(folder):
            full_path = os.path.join(folder, file)
            if os.path.isfile(full_path):
                zip_file.write(full_path)
            elif os.path.isdir(full_path):
                self.add_folder_to_zip(zip_file, full_path)

    def run_backup(self, mysql_user, mysql_pw, db):
        """
        :type db: str
        :type mysql_pw: str
        :type mysql_user: str
        :return string - dump filename
        """
        try:
            dump = 'dump_' + db + '_' + self.__current_date + '.sql'
            # return dump
            p = subprocess.Popen(
                'mysqldump -u' + mysql_user + ' -p' + mysql_pw + ' --databases ' + db + ' > ' + dump,
                shell=True)
            # Wait for completion
            p.communicate()
            # Check for errors
            if p.returncode != 0:
                raise ReturnCode
            print('Backup done for', db)
            return dump
        except:
            print('Backup failed for ', db)

    def parse_options(self):
        parser = optparse.OptionParser(usage="""\
        %prog -u USERNAME -p PASSWORD -d DATABASE -D /path/for/domain/ -f BACKUP_FILE_NAME
        Required Username, Password, Database name and path for Domain folder
        If you want copy backup to remote ftp, use options:
        %prog -u USERNAME -p PASSWORD -d DATABASE -D /path/for/domain/ -f BACKUP_FILE_NAME --ftp-host HOST --ftp-user USERNAME --ftp-password PASSWORD --ftp-folder FOLDER
        If you want delete archives from ftp, add options: --ftp-delete-old --ftp-delete-day N (not required, 3 days default) 
        """, conflict_handler="resolve")
        parser.add_option("-u", "--username", dest="username",
                          help=("Username of database "
                                "[default: %default]"))
        parser.add_option("-p", "--password", dest="password",
                          help=("Password of database "
                                "[default: %default]"))
        parser.add_option("-d", "--database", dest="database",
                          help=("Database name "
                                "[default: %default]"))
        parser.add_option("-D", "--domain", dest="domain",
                          help=("Domain folder for backup "
                                "[default: %default]"))
        parser.add_option("-f", "--filename", dest="filename",
                          help=("Backup file name "
                                "[default: %default]"))
        parser.add_option("--ftp-host", dest="host",
                          help=("Ftp host "
                                "[default: %default]"))
        parser.add_option("--ftp-user", dest="ftpuser",
                          help=("Ftp username "
                                "[default: %default]"))
        parser.add_option("--ftp-password", dest="ftppassword",
                          help=("Ftp password "
                                "[default: %default]"))
        parser.add_option("--ftp-folder", dest="folder",
                          help=("Ftp upload folder "
                                "[default: %default]"))
        parser.add_option("--ftp-delete-old", dest="ftpdelete", action='store_true',
                          help=("Delete files from ftp older 3 days "
                                "[default: %default]"))
        parser.add_option("--ftp-delete-day", dest="ftpdeleteday", type='int',
                          help=("Delete files from ftp older N days "
                                "[default: %default]"))
        parser.set_defaults(username='root', filename=self.__current_date + '.zip', folder='.', ftpdelete=False,
                            ftpdeleteday=3)
        return parser.parse_args()

    def ftp_connect(self, host, username, password):
        """
                :param host: remote host name
                :param username: username for remote host
                :param password: password for remote host
                :type host: str
                :type username: str
                :type password: str
                :return object self.ftp
                """
        try:
            self.ftp = ftplib.FTP(host=host, user=username, passwd=password)
            return self.ftp
        except ftplib.error_perm as error:
            print('Is there something wrong: {}'.format(error))
        except:
            print('Cannot connected to ftp: ', host)
            return False

    def ftp_disconnect(self):
        """
        :return: True
        """
        try:
            self.ftp.close()
            self.ftp = None
            return True
        except:
            return False

    def upload_file_to_ftp(self, filename, folder='.'):
        """
        :param filename: upload file name
        :param folder: special folder - / default
        :type filename: str
        :type folder: str
        :return True
        """
        try:
            self.ftp.cwd(folder)
            self.ftp.dir()
            with open(filename, 'rb') as f:
                self.ftp.storbinary('STOR %s' % filename, f)
            return True
        except ftplib.all_errors as error:
            print('Is there something wrong: {}'.format(error))
            return False

    def remove_old_files_from_ftp(self, folder='.', day=3):
        """
                :param folder: special folder - / default
                :param day: count of day
                :type folder: str
                :type day: int
                :return True
                """
        try:
            self.ftp.cwd(folder)
            facts = self.ftp.mlsd()
            i = 0
            for fact in facts:
                modify = fact[1]['modify'][:8]
                if (int(datetime.datetime.now().strftime('%Y%m%d')) - int(modify)) > int(day):
                    # if we cannot change directory - is file
                    try:
                        self.ftp.cwd(fact[0])
                    except:
                        self.ftp.delete(fact[0])
                        i += 1
            print('Deleted {} files'.format(str(i)))
            return True
        except ftplib.all_errors as error:
            print('Is there something wrong: {}'.format(error))
            return False
        except TypeError:
            print('Day is not number, use 1 or 2,3,n')
            return False


Создал простой класс с несколькими методами:

to_zip(self, file, filename=__current_date + '.zip', append_to_file=False)

Метод принимает файл или папку и создает архив с именем ТЕКУЩАЯДАТА.zip или с вашим именем, если передать append_to_file=True, файлы будут добавлены в существующий архив

run_backup(self, mysql_user, mysql_pw, db)

Делаем резервную копию базы данных, использую линуксовскую утилиту mysqldump, метод принимает ИМЯ ПОЛЬЗОВАТЕЛЯ, ПАРОЛЬ, НАЗВАНИЕ БАЗЫ

parse_options(self)

Парсим переданные опции, об этом в примере ниже…

ftp_connect(self, host, username, password)

Открываем FTP соединение, метод принимает ХОСТ, ИМЯ ПОЛЬЗОВАТЕЛЯ, ПАРОЛЬ от FTP сервера

ftp_disconnect(self)

Не понятный метод с не ясным названием )

upload_file_to_ftp(self, filename, folder='.')

Метод принимает ИМЯ ФАЙЛА и опционально ПАПКУ, как раз в нее и копируется ФАЙЛ

remove_old_files_from_ftp(self, folder='.', day=3)

Удаляет все файлы старше N дней с указанной папки, метод принимает соответственно ПАПКУ и ДНИ

А теперь пример того как этот класс использую я:
def main():
    backup_utils = BackupUtils()
    opts, args = backup_utils.parse_options()
    # required Username, password, database name and path for domain folder
    try:
        if opts.username is None or opts.password is None or opts.database is None or opts.domain is None:
            raise RequiredOpts
    except RequiredOpts:
        print('Use -h or --help option')
        exit()

    # create sql dump
    backup_database = backup_utils.run_backup(opts.username, opts.password, opts.database)
    #  dump archive filename
    dump_archive = 'dump_' + opts.filename if '.zip' in opts.filename else 'dump_' + opts.filename + '.zip'

    if backup_database:
        # add sql dump to zip "dump_filename.zip"
        backup_utils.to_zip(backup_database, dump_archive)
        # remove sql dump
        os.remove(backup_database)

    # find domain name in path - site.com
    try:
        i = opts.domain.index('.')
        if opts.domain[:-1] != '/': opts.domain += '/'
        left = opts.domain.rindex('/', 0, i)
        right = opts.domain.index('/', i)
        domain = opts.domain[left + 1:right]
    except:
        domain = ''

    # backup file name
    backup_archive = 'backup_' + domain + '_' + opts.filename if '.zip' in opts.filename else 'backup_' + domain + '_' + opts.filename + '.zip'

    # check if path exist
    try:
        if not os.path.isdir(opts.domain) and not os.path.isfile(opts.domain):
            raise NotExist
    except NotExist:
        print('{} No such file or directory'.format(opts.domain))
        exit()

    # create domain folder archive
    backup_utils.to_zip(opts.domain, backup_archive)

    if os.path.isfile(dump_archive):
        # add dump archive to domain archive
        backup_utils.to_zip(dump_archive, backup_archive, True)
        # remove dump zip file
        os.remove(dump_archive)

        # upload backup to ftp
        if opts.host and opts.ftpuser and opts.ftppassword and backup_utils.ftp_connect(opts.host, opts.ftpuser,
                                                                                        opts.ftppassword) is not None:
            backup_utils.upload_file_to_ftp(backup_archive, folder=opts.folder)
            backup_utils.ftp_disconnect()
            # remove local backup archive
            os.remove(backup_archive)

        # delete files from ftp older N days
        if opts.ftpdelete and backup_utils.ftp_connect(opts.host, opts.ftpuser,
                                                       opts.ftppassword) is not None:
            backup_utils.remove_old_files_from_ftp(folder=opts.folder, day=opts.ftpdeleteday)
            backup_utils.ftp_disconnect()


if __name__ == "__main__":
    main()
  


И на по следок в cron добавляем команду:
backup.py -p PASSWORD FOR DB -d NAME FO DB -D /PATH/FOR/WEB/SITE.COM/HTML/ --ftp-host FTP HOST NAME --ftp-user FTP USER --ftp-password FTP PASSWORD --ftp-delete-old --ftp-delete-day DAYS --ftp-folder FTP FOLDER

Все! Каждый день создается резервная копия базы и файлов проекта и копируется на ftp, и что бы не переполнять ftp сервер все копии старше 3х дней удаляются.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/335440/


Метки:  

Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1092 1091 [1090] 1089 1088 ..
.. 1 Календарь