- PVSM.RU - https://www.pvsm.ru -

Оптимизация расходов Yota: попытка #3

Привет!

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

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

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

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

Всех йотоводов и автоматизаторов прошу под кат.

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

Постановка задачи

Вариант [1]linx56 [3] мне не подошел ну совсем никак, потому как он работает phantomjs, для работы которого нужно довольно нехилое железо (которым Raspberry PI не является). Да и даже если я поставлю это на свой основной компьютер и оставлю его работать круглосуточно, забив в cron расписание по которому нужно отключать интернет, то я получу счет за электричество больше, чем сэкономлю все равно не получу нормального результата, потому как интернет используется крайне ненормировано (при том не только мной, но и членами семьи, для которых главное чтобы «Просто работало»).

Второй вариант [2] от bambrman [4] уже лучше хотя бы тем, что использует curl вместо phantomjs. Но по функциональности остается на том же уровне.

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

Реализация

В основе всего лежит Raspberry Pi, на котором крутится демон, написанный на Python. Он каждые n секунд (задается в конфиге, котрый будет приведен в следующем разделе) подключается по SSH к главному домашнему роутеру (TP-LINK WDR4300 перепрошитому на OpenWrt) и при помощи команды ifconfig стягивает статистику нужного интерфейса.

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

modes: {
0: 320,
290: 512,
495: 768,
700: 1.0,
900: 2.1,
1900: 3.1,
2900: 4.1,
3900: 6.5,
6500: 8.0,
8000: 10.0,
10000: 15.0,
14800: 20.0
}

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

По результатам замера текущей скорости у нас получилось, скажем, 1530 килобит. Округляем в меньшую сторону до ближайшего режима. Получается, что эта скорость соответствует режиму 900, то есть тарифу 2.1 мегабит.

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

Новый == текущий

В этом случае цикл просто продолжится

Новый > текущий

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

Но и тут есть одна особенность. Дело в том, что в конфиге есть такой параметр как:

speed_increase_step: 1

Он указывает на размер шага апгрейда между тарифами. То есть обычно скорость с предыдущего тарифа может переключиться только на следующий (потому как например при тарифе в 10 мегабит невозможно достигнуть скорости в 14800 для переключения сразу на 20. То есть между 10 и 20 все равно должен быть 15). Иногда это не очень удобно. Попробую объяснить на примере:

Вы не пользовались интернетом некоторое время, а потом пришли и сразу включили поток с YouTube в 720-1080p. Соответственно, скорости в 320 килобит, которая была установлена в ваше отсутствие для этого явно не хватает. И тут 2 варианта: либо демон будет постепено повышать тариф (320 — 512 — 768 — 1.0 и т.д.) или же он увеличит его с запасом скажем, до 1 мегабита, а затем уменьшит до нужной скорости (см. механизм даунгрейда).

Но в этом случае есть и определенные минусы. Например, если вы/ваше устройство грузить будете что-то маленькое и легкое (вроде RSS ленты например). На это у него уйдет всего пара секунд. Да и 320 килобит будет достаточно. Но тариф то все равно подскочит до мегабита и будет висеть на этом уровне до тех пор пока его не выполнится даунгрейд.

Особенности:

  • Параметр speed_increase_step в 0 выставлять нельзя. Минимальное и стандартное значение — 1
  • При значении 2 и новом режиме 900 (на основе текущей скорости) тариф будет переключен не на 2.1, а на 3.1
  • Если новый режим нельзя увеличить на указанное количество режимов, то он будет увеличен до максимально возможного / не будет увеличен (если максимальный)
Новый < текущий

В этом случае запускается механизм даунгрейда. Он сохраняет текущую статистику трафика (не скорости) роутера (вместе с таймстемпом создания), а затем создает задачу на исполнение даунгрейда через определенное время. Это время задается в минутах в параметре:

hold_high_speed_time: 3

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

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

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

Для чего вообще нужен весь этот механизм и почему по умолчанию именно 3 минуты? Объясню, опять же, на примере:

Вы смотрите ролик на YouTube. Он небольшой. Всего пара минут. И пока он скачивался скорость подскочила до 8 мегабит. И вот вы досмотрели ролик и переходите к следующему. Если бы не было этой задержки в механизме, то демон бы уже понизил скорость и при загрузке второго ролика ему пришлось бы снова поднимать скорость.То есть вам придется дожидаться пока скорость вновь будет повышена. В то же время это еще и нагружает и так не шибко стабильный ЛК Yota. Да и согласитесь, за 1 минуту (ну пусть за 2) вы много не сэкономите.

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

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

Уведомления о смене тарифа через PushBullet

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

Включается это дело все там же, в конфиге:

pb_enabled: true
pb_api: v13WA7HYfwj99gUz8rwvK2m7eLN1uheVxZujAgP8gn2su
pb_devices: [
ujAgP8gn2sasFDsgsWhxs,
ujAgP8gn2mkasnuH2Gtga
]

Как видите, здесь все довольно просто. pb_enabled принимает значения true/false. Назначение очевидно.
pb_api — ваш ключ со страницы www.pushbullet.com/account [5]
pb_devices — список идентификаторов устройств, на которые рассылаются уведомления. Взять можно тут: api.pushbullet.com/v2/devices [6]. При входе вас попросят авторизоваться: просто введите ключ api в поле логина. Полное описание метода тут: docs.pushbullet.com/v2/devices/ [7]

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

Установка

  • Первым делом идем и устанавливаем на свой роутер OpenWrt. Ну или что-нибудь подобное с SSH, авторизацией по ключам и утилитой ifconfig.
  • После этого нужно найти что-то вроде Raspberry Pi (именно на нем у меня все и крутится). То, на чем можно запустить третий питон и где есть ssh-клиент с авторизацией по ключам. Если ничего такого нет, то вполне сможет сгодиться и полноценный компьютер с Linux/виртуалка с Linux.
  • Настраиваем авторизацию по ключу для подключения к роутеру. Убеждаемся что команда вида (с вашими данными, конечно же) «ssh 192.168.1.1 -p 22 -l root ifconfig» работает без дополнительных вопросов/требования пароля
  • Ставим Python 3 и pip. Изначально все писалось и тестировалось на 3.2.3. На совместимость с более ранними версиями тестов не было
  • Скачиваем исходный код отсюда [8]
  • Распаковываем скачанный архив в любую папку на target-девайсе. Я, например, сделал это в /root/yota.
  • Делаем cd в папку, в которую распаковали архив
  • Выполняем pip3 install -r requirements.txt. Обратите внимание, что pip может иметь другое имя (pip-3.2 например)
  • Редактируем config.yaml. Описания большинства его параметров есть вверху. Остальные, как мне кажется, в представлении не нуждаются. Ну разве что кроме interface. По умолчанию там указан eth0.2. Обычно в openwrt именно так называется интерфейс через который идет обмен траффиком с внешней сетью. Также стоит отметить опцию use_ssh. Если выставить ее в false, то совершенно логично, мониториться будет локальный интерфейс. Ну это на случай если у кого-нибудь в качестве роутера используется полноценный компьютер
  • После этого запускайте main.py. Можно как напрямую (предварительно дав ему права на исполнения), так и передав интерпретатору
  • Если все работает, то устанавливайте supervisor (обычно есть в стандартных репозиториях)
  • Переместите файл yota.conf в папку /etc/supervisor/conf.d/ попутно подправив в нем пути к main.py и, если хотите, к файлу, в который будет писаться лог
  • Перезагрузите supervisor командой supervisorctl reload. Демон должен запуститься автоматически. Посмотрите напоследок логи. В них должно быть что-то вроде:

    2014-07-06 14:16:01,805 [INFO] yota | Initializing
    2014-07-06 14:16:11,497 [INFO] yota.web | CURRENT TARIFF: 512
    2014-07-06 14:16:11,712 [INFO] yota | Initialised
    2014-07-06 14:16:11,718 [INFO] yota | Starting
    2014-07-06 14:16:16,016 [DEBUG] yota.current_speed_provider | Current speed: RX: 5 TX: 9. Summary: 14, Time: 3
    2014-07-06 14:16:17,262 [INFO] yota.speed_control | Downgrade started: 2014-07-06 14:19:16.022307
    2014-07-06 14:16:18,315 [DEBUG] yota.current_speed_provider | Current speed: RX: 7 TX: 19. Summary: 26, Time: 2
    2014-07-06 14:16:20,575 [DEBUG] yota.current_speed_provider | Current speed: RX: 12 TX: 20. Summary: 32, Time: 2

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

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

Автор: rdvlip

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/raspberry-pi/64294

Ссылки в тексте:

[1] раз: http://habrahabr.ru/post/166721/

[2] два: http://habrahabr.ru/post/176817/

[3] linx56: http://habrahabr.ru/users/linx56/

[4] bambrman: http://habrahabr.ru/users/bambrman/

[5] www.pushbullet.com/account: https://www.pushbullet.com/account

[6] api.pushbullet.com/v2/devices: https://api.pushbullet.com/v2/devices

[7] docs.pushbullet.com/v2/devices/: http://docs.pushbullet.com/v2/devices/

[8] отсюда: https://bitbucket.org/Rdvlip/yota-speed-controller/get/master.zip

[9] Источник: http://habrahabr.ru/post/228959/