Архивы автора: Geom

Необычное исправление ошибки сброса временной зоны в Arch Linux и совместимых дистрибутивах

Почему сбрасывается временная зона в Arch Linux?

Если ваш компьютер или сервер на Arch Linux (или другом совместимом дистрибутиве) неожиданно решил, что живёт в другом часовом поясе, а timedatectl упрямо отказывается менять время, возможно, проблема в /etc/localtime. В этой статье разберёмся, почему это происходит, и как привести систему в чувство.

Симптомы проблемы

Вы смотрите на timedatectl status и понимаете, что что-то явно пошло не так: время сбилось, NTP отключён. Ладно, пробуем исправить:

sudo timedatectl set-timezone Europe/Moscow

А в ответ:

Failed to set time zone: Is a directory

Что? Заглядываем в /etc/localtime и видим нечто странное: там одновременно и символическая ссылка, и папка. Видимо, система запуталась и решила просто игнорировать реальность. В результате timedatectl не может установить временную зону, а NTPD работает, но остаётся невидимым для системы.

Разбор причин сбоя

Основные причины проблемы:

  1. В /etc/localtime присутствуют и символическая ссылка, и каталог, что сбивает timedatectl.
  2. timedatectl не может обновить часовой пояс из-за странной структуры /etc/localtime.
  3. ntpd исправно трудится, но система этого как будто не замечает.
  4. systemd-timesyncd и ntpd ведут тихую войну за контроль над временем.

Теперь разберёмся, как это исправить.

Решение: настройка правильной синхронизации времени

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

sudo rm -rf /etc/localtime
sudo ln -s /usr/share/zoneinfo/Europe/Moscow /etc/localtime

(Замените Europe/Moscow на нужный часовой пояс.)

2. Выключаем systemd-timesyncd, чтобы он не мешал

sudo systemctl stop systemd-timesyncd
sudo systemctl disable systemd-timesyncd

3. Запускаем NTPD и проверяем его работу

sudo systemctl enable --now ntpd
sudo systemctl restart ntpd
ntpq -p
systemctl status ntpd

Если ntpq -p показывает сервера NTP, значит, всё работает.

4. Включаем автоматическую синхронизацию времени

sudo timedatectl set-ntp true

Проверяем статус:

timedatectl status

Если System clock synchronized: yes, значит, всё в порядке.

Итог

Теперь система работает стабильно, а временная зона не ускользает в неизвестность. Если у вас случится подобная проблема, проверьте /etc/localtime: возможно, у вас тоже одновременно и символическая ссылка, и папка. Удалите лишнее, настройте ntpd, и всё заработает как часы.

Ключевые слова для SEO:

  • Arch Linux сброс временной зоны
  • timedatectl Failed to set time zone: Is a directory
  • Настройка NTP в Arch Linux
  • Как исправить сброс времени в Linux
  • systemd-timesyncd vs ntpd
  • Почему не работает timedatectl в Arch Linux
  • Временная зона сбрасывается в Linux
  • Проблема с NTP в Arch Linux и Manjaro
🔥 Полезные команды:
timedatectl set-timezone Europe/Moscow
ntpd -qg (Синхронизация времени)

Модуль отладки SQL для опенкарта

Сегодня разберу какие изменения я внёс в модуль отладки от open4dev. Изменения простые и будут вполне понятны начинающим разработчикам. Чем меня не устраивал модуль отладки, я описывал в одном из постов.

Ключевые слова MySQL

Подсветка синтаксиса в модуле уже была. Вот она.

define('DEBUG_SQL_KEYWORDS', 'select|delete|update|count|as|from|and|order by|or|where|inner join|left join|right join|join|group by|having|on|asc|desc|limit');

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

define('DEBUG_SQL_KEYWORDS', 'select|delete|update|count|as|from|and|order by|or|where|distinct|replace|into|date_sub|now|interval|hour|sum|lcase|set|inner join|left join|right join|join|group by|having|on|asc|desc|limit');

Я добавил несколько ключевых слов и встроенных функций. Результат на картинке ниже.

Добавление строки вызова

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

class QueryDebug {
	private $queries = array();
	
    public function add($sql, $duration = 'not_defined') {
    	$this->queries[] = array('text' => $sql, 'duration' => $duration);
    }

И превращаем его.

class QueryDebug {
	private $queries = array();
	
    public function add($sql, $duration = 'not_defined', $backtrace = array()) {
    	$this->queries[] = array('text' => $sql, 'duration' => $duration, 'backtrace' => $backtrace);
    }

Этим мы научили объект QueryDebug сохранять массив $backtrace в массиве отчётах о запросах $queies[]. Теперь нужно поправить вызов метода add(). Его можно найти дальше в модуле.

	<!-- Wrap the query operation to count the time took and add it to our class  (< 2.2.0.0)-->
    <operation>
      <ignoreif>$this->adaptor->query</ignoreif>
      <search><![CDATA[return $this->db->query($sql);]]></search>
      <add position="replace"><![CDATA[
      $time_start = microtime(true);
      $result = $this->db->query($sql);
      $time_end = microtime(true);
      $this->queryDebug->add($sql, ($time_end - $time_start)); 
      return $result;
]]></add>
</operation>

Этот участок модуля для ocmod отвечает за замену строки возврата результата запроса к БД в опенкарте младше 2.2.0.0, на наш, который перехватывает запрос и сохраняет его в лог, попутно считая время выполнения. Добавим в лог информацию о стеке вызовов вызвав debug_backtrace() внутри этого метода.

	<!-- Wrap the query operation to count the time took and add it to our class  (< 2.2.0.0)-->
    <operation>
      <ignoreif>$this->adaptor->query</ignoreif>
      <search><![CDATA[return $this->db->query($sql);]]></search>
      <add position="replace"><![CDATA[
      $time_start = microtime(true);
      $result = $this->db->query($sql);
      $time_end = microtime(true);
      $this->queryDebug->add($sql, ($time_end - $time_start),debug_backtrace()); 
      return $result;
]]></add>
</operation>

В опенкарте старше 2.2.0.0 вызов метода query() немного изменился и модуль это учитывает. Отдельная ветка изменений для свежего opencart 2.3.
Было:

<!-- Wrap the query operation to count the time took and add it to our class  (>= 2.2.0.0)-->
    <operation>
      <ignoreif>$this->db->query</ignoreif>
      <search><![CDATA[return $this->adaptor->query($sql, $params);]]></search>
      <add position="replace"><![CDATA[
      	$time_start = microtime(true);
		$result = $this->adaptor->query($sql, $params);
		$time_end = microtime(true);
		$this->queryDebug->add($sql, ($time_end - $time_start)); 
		return $result;
	  ]]></add>
    </operation>

Стало после изменений в нашем коде.

<!-- Wrap the query operation to count the time took and add it to our class  (>= 2.2.0.0)-->
    <operation>
      <ignoreif>$this->db->query</ignoreif>
      <search><![CDATA[return $this->adaptor->query($sql, $params);]]></search>
      <add position="replace"><![CDATA[
      	$time_start = microtime(true);
		$result = $this->adaptor->query($sql, $params);
		$time_end = microtime(true);
		$this->queryDebug->add($sql, ($time_end - $time_start),debug_backtrace()); 
		return $result;
	  ]]></add>
    </operation>

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

Сортировка и вывод запросов SQL

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

Стили самое простое — с них и начнём. Находим строку с определением стиля для вывода запросов.

$out .= ".query { white-space: normal; background-color: #efefef; margin-left: 90px; padding: 4px; border: 1px solid #bbbbbb; } " ."\n";

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

$out .= ".query,.line  { white-space: normal; background-color: #efefef; margin-left: 90px; padding: 4px; border: 1px solid #bbbbbb; } " ."\n";

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

$out .= '
Queries: ' . count($this->queries) . ', Total time: ' . number_format($total_time * 1000, 3) . ' ms [By time] [By id]
';

И добавляем новую кнопку [By line]

$out .= '
Queries: ' . count($this->queries) . ', Total time: ' . number_format($total_time * 1000, 3) . ' ms [By time] [By id] [By line]
';

Ну вот. Кнопка на месте. Теперь нужно вывести саму строку с указанием файла и номера строки. Не забываем про css класс .line.
Находим

$out .= '
' . $count . ' (' . number_format($q['duration'] * 1000, 3) . ') ' . '
' . $sql . '
';

Меняем на

$out .= '
' . $count . ' (' . number_format($q['duration'] * 1000, 3) . ') ' . '
' . $sql . '
'.$q['backtrace'][0]['file'].':'.$q['backtrace'][0]['line'].'
';

Из кода видно как собирается строка с указанием файла и линии вызова. Теперь нужно добавить обработчик на нашу кнопку и отсортировать результаты. Всё очень просто.
Для начала находим хвости JS кода.

        $out .= '     $(".queries.qrow").remove(); ' . "\n";
        $out .= '     $(".queries").append(sorted); ' . "\n";
        $out .= "     return false; \n";
        $out .= "}); \n";

А теперь перед ним вешаем наш обработчик.

$out .= '$("#query-sort-line").click(function(e) {' ."\n"; 
        $out .= "     e.stopPropagation(); \n";
        $out .= "     var sorted = $('.qrow').sort( function(a, b) { \n";
        $out .= "         var d = 0;\n";	
        $out .= "         var t1 = $(a).find('.line').prop('innerHTML');\n";
        $out .= "         var file1 = t1.slice(0,t1.lastIndexOf(':'))\n";
        $out .= "         var file1Line = t1.slice(t1.lastIndexOf(':'))\n";
        $out .= "         var t1id = $(a).find('.count').prop('id').replace('c', '') || 0;\n";
        $out .= "         var t2 = $(b).find('.line').prop('innerHTML');\n";
        $out .= "         var file2 = t2.slice(0,t2.lastIndexOf(':'))\n";
        $out .= "         var file2Line = t2.slice(t2.lastIndexOf(':'));\n";  
        $out .= "         var t2id = $(b).find('.count').prop('id').replace('c', '') || 0;\n";
        $out .= "         d = file1.localeCompare(file2);\n";	
        $out .= "         if (d==0){\n";
        $out .= "             d = file1Line.localeCompare(file2Line);\n";
        $out .= "             if (d==0){\n";
        $out .= "                 d = t1id - t2id;\n";
        $out .= "             };\n";
        $out .= "	  	  };\n";
        $out .= "         return d;\n";
        $out .= "     });\n";

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

Вывод максимального объёма выделенной памяти

В добавок ко всему выше написанному можно в две строчки добавить вывод. Добавить к стилю .total, стиль .memory

$out .= ".total,.memory { font-weight: bold; margin: 0px 10px 10px 95px; border-bottom: 1px solid #bbbbbb;} " ."\n";

А перед выводом кнопок сделать вывод memory_get_peak_usage().

$out .= '
Total memory used: '.round(memory_get_peak_usage()/1024/1024,2).' MB
';

Репозиторий проекта на github — Opencart-debug-database-queries

Модуль отладки SQL в опенкарт 2.3

Вчера я написал про допил напильником модуля отладки Debug database queries for Opencart. Изменения очень простые, но сначала о проблеме.

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

Как узнать строку в которой произошёл вызов к $DB ?

PHP предоставляет такую возможность благодаря функции debug_backtrace. Она позволяет собрать стек вызовов для метода, с указанием файла и строки. Мне потребовалось сделать ещё сортировку по месту вызова, что бы быстрее определить места особенно нуждающиеся в кэшировании. Для этого добавил новое поле в вывод отладчика [ by line ] и дописал обработчик события. Группировку по строке сделал с помощью JS-функции localeCompare(). Конечно, получило не в алфавитном порядке, и иногда вызовы из одного файла могут быть перемешаны с вызовыми из другого файла. Но все вызовы из одной строки одного файла всегда будут рядом. Это позволяет быстро оценить, сколько раз осуществлялся конкретный вызов к БД.

На сладкое добавил вывод memory_get_peak_usage для определения максимального выделения памяти под выполнение скрипта.

Патч для модуля отладки

Для debug_backtrace я сформировал патч и отослал мейнтейнеру модуля. Посмотрим, что он ответит, на всякий случай сделал копию модуля на github.

UPD. open4dev обещал внести правки в релиз =)

Новый опенкарт 2. Часть 1.

Опенкарт. Переезд на новый движок 2.3

Сегодня начал переезд на новый опенкарт.  Взял за основу сборку opencart.pro 2.3.0.2.2
Раньше был opencart 1.5.4 с сильно модифицированным движком. Но время идёт и чем больше изменений, тем труднее поддержка. Да и о будущем нужно подумать. У движка неплохое сообщества, а у моего мода только я один. За всем не уследишь. Так что принял решение о переезде.

Почему я не взял платную версию?

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

Первые впечатления

Сыро. Не, реально сыро. Над системой ещё работать и работать. Для начала поставил следующие модули.

Импорт и экспорт

Export/Import Tool (V3.9) for OpenCart 2.x

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

Open Graph tags

Facebook Open Graph Tags for Opencart v2.2 — v1.1

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

Дебаг MySQL

Debug database queries

Завёлся с пол пинка. Пинок понадобился в том плане, что я включил принудительный вывод внизу сайта. По-умолчанию он скрыт и нужно смотреть через панель разработчика, что не удобно на этапе настройки и анализа. Итог печален. Опенкарт плохо кэширует. 106 запроса с главной. Нажимаю F5 и снова 52 запроса… И это при том, что у меня в магазине на тот момент был всего один товар. Такая же ситуация была на 1.4, когда я на него первый раз взглянул. Ну и ладно. Можно было податься к Йоде и купить у него супер пупер турбо модуль и я настоятельно советую всем здравомыслящим людям именно так и сделать, но мне он попросту не нужен. На магазинах дикой загрузки сейчас нет, а поставить пару строк в коде, что бы сильно снизить, не как у Йоды, конечно, нагрузку я могу и так. Придёт время, там и увидим. Кстати, хочу слегонца его оптимизировать. Выложу потом diff.

AJAX поиск

AJAX Live Search

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

Гвоздь программы — корзина в один шаг

One Page Check Out

Самый жуткий модуль, имеет место замещение кода, последующие несовместимости и пр. Нуждается в рефакторинге. И самое главное — модуль на 2.3 не завёлся. Там немного переделали вызовы и всё рушилось. Сначала я было решил переделать как надо ручками, но потом погуглил и нашёл, что всё уже сделано. Ссылка внизу топика
В любом случае модуль из коробки не готов к употреблению. Ставьте себе кларну или что-то типа. Не парьтесь с этим модулем, он вам ни к чему. Мне он опять же потребовался из-за чуть замысловатой логики заказа на одном из магазинов. Большинству этого не надо.

Продолжение следует…

Подключение к Билайну. Часть 2.

Настройка роутера Zyxel Keenetic Giga 2

Итак, кабель затянут в квартиру и был подключён к ноутбуку. Минимум был выполнен, но основная цель была подключить к локальной сети через роутер. В качестве роутера у меня трудится Zyxel Keenetic Giga 2. Вполне рабочая машинка, на которую был повешен через USB-хаб модем Yota.
Простое подключение к роутеру кабеля в соответствии с инструкцией на сайте не принесло желаемого эффекта. На самом роутере интернета не было и выкачивать обновления он не мог. Впрочем, ноут исправно показал Яндекс, но телефон и основная машина жаловались на отсутвие интернета. В интернете все как один ссылаются на ту же инструкцию или на подключение через L2TP. У меня же новая технология IPoE, и vpn сервера естественно никакого нет.

Решил эксперементально подставить MAC ноута в подключение на роутере и … на роутере появился интернет! Zyxel бодренько рапортовал о доступных обновлениях, а на странице настроек легко пинговал Яшу. Но на машинах и телефонах интернет не появился. Напротив, он и на ноутбуке исчез.

Разгадка проблемы с роутером при подключении к Билайну

Проблема решилась снятием указания VLAN 2 для соединения Билайна. После этого чудо всё же произошло и нормальный интернет появился везде. Единственно, в дополнение к динамическим DNS серверам прописал статично DNS Гугла, на всякий пожарный.
Теперь Yota стала резервным каналом, а основной идёт через Билайн.

Что касается скорости — спидтест показывает честные 95-98 Mb/s на приём и примерно так же на отдачу. Тест делался в вечернее время около 22:00. Пинг до серверов WOT 25-35, что тоже не может не радовать.

Итоги подключения к роутеру

+ Скорость соответствует заявленной.

+/- Подключение к моей локальной сети было не из самых простых. Впрочем, Билайн не зря продаёт свои роутеры — не хочешь настраивать — плати 100 рублей ежемесячно.

Подключение к Билайну. Часть 1.

Вот и я теперь пчёловод.

Настали тяжёлые времена для YOTA или краткая повесть о подключении к Билайну в 2 частях.

6 февраля 2017 оформили заявку на подключение.

7 числа пришёл мастер Роман в 12:30 (заявка была с 12 до 14, предварительно уведомили по смс) и быстренько прокинув кабель по чердачному помещению (туда ещё не лазал, но как буду обязательно сделаю фотоотчёт) подвёл его к двери. В подъезде кабель был уложен в кабель-канал, но только в тех местах где канал присутствовал. В дверь прошёл через пену чуть отбив штукатурку.

В квартире мастер спросил сколько нужно кабеля до рабочего места. Тут я порадовался, хорошо что есть проект квартиры в sketchup, не пришлось бегать с рулеткой. Проект — это очень важно! Об этом ещё Алексей Земсков говорил. Вобщем, замерили по проекту, получилось 15 метров. Конечно не ровно 15 метров, это с запасом и округлением.

После этого монтажник отрезал кабель кусачками и попросил компьютер для подключения. Обжал он кабель клещами, ну а чем ещё? Не отвёрткой же вбивать зубчики =) Правда обжал без защитного колпачка, нужно будет переделывать. Свернул кабель бухточкой и обжал стяжкой.
Я притащил ноут, побитый детьми и падавший с 1,5 метров на асфальт и он довольно долго грузился, так как винда (семёрка, которая упорно хочет обновится до десятки, но не может) успела нахвататься обновлений безопасности.

В это время монтажник Роман что-то усиленно тыкал в смартфоне, попросив паспорт, видимо регистрировал во внутреннем приложении билайновцев. А дальше после некоторой заминки мы зашли на сайт login.beeline.ru и активировались, просто введя логин и пароль. После чего я сделал 4 подписи на договорах и актах, и монтажник быстро исчез в проёме двери.

Теперь осталось переподключить кабель на роутер и оттестироваться.

Предварительные итоги организации нового канала

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

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