...

суббота, 30 мая 2020 г.

Возможно, вам не нужен Svelte чтобы уменьшить ваш JavaScript

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

Недавно вышла статья "Хороший ли выбор Svelte для реализации виджета?" с опытом реализации проекта с критичным размером бандла. Это отличный повод проверить обещания пиарщиков Svelte на реальном проекте.

Давайте его проанализируем!


Исходные данные

Рассматриваемый проект – это встраиваемый виджет, распространяется как монолитный Javascript-бандл общим размером в 71 Кб (c учётом gzip). Много это или мало? В статье "Цена JavaScript в 2019 году" была рекомендация избегать бандлов больше 50-100 Кб. То есть текущий проект находится в жёлтой зоне, размер ещё приемлемый, но уже пора задуматься, о том как в него добавить новой функциональности, и при этом уместиться в верхний лимит в 100 Кб.

Теперь скачаем этот скрипт, отформатируем его и посмотрим, что там интересного.


Настройки бандлера

Первое, что бросается в глаза при просмотре кода – повторение вот такого паттерна:

function (t, e, n) {
    t.exports = {
        // какой-то код
    }
}

Это очень похоже на CommonJS модуль. Однако в современных проектах рекомендуется использовать ES-модули, потому что этот формат обеспечивает возможность для оптимизаций – Scope hoisting и Tree-shaking. В случае CommonJS-модулей, бандлеры оптимизировать их не могут и выдают больше кода на выходе.

Иногда ещё может получиться так, что вы используете ES-модули в своем коде, а бандл все равно выходит не оптимизирован. В этом случае полезно запустить webpack с флагом --display-optimization-bailout и посмотреть, что пошло не так с оптимизациями.

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

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


Оптимизация ресурсов

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

Начнем с SVG. Для его оптимизации есть инструмент SVGO, также доступна онлайн-версия.

Всего в бандле нашлось 7 svg-файлов общим весом в 10 Кб, и, по-видимому, разработчики не занимались их оптимизацией. Пропустив их через SVGO удалось уменьшить общий размер на 3 килобайта.

Теперь про JPG. Заметим, что картинки загружаются в блок размером 59х27 пикселей, а сами оригиналы 183x72. Избыточный размер, даже с учетом поддержки retina-экранов. Кроме того, есть шансы улучшить размер подбором более оптимальных форматов сжатия. Очень удобно это делать в веб-приложении https://squoosh.app/, которое позволяет пережать изображения прямо в браузере с использованием различных кодеков. Применив его к картинкам из виджета, сокращаем их размер на треть, получая еще 7 килобайт экономии.

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


Ненужный код

Теперь переходим к разбору собственно Javascript. Там встречается нетранспилированный ES6-код, а значит поддержка IE от разработчиков не требовалась. И это действительно хорошая стратегия уменьшить размер кода – по моим данным, использование ES6 сокращает размер кода на 7%. Если вам не нужно поддерживать Internet Explorer, это может оказаться очень хорошим подспорьем.

Далее обратим внимание на используемые библиотеки.


  • axios – библиотека для http-запросов. Поскольку вопрос поддержки IE не стоит, авторы могли использовать нативное Fetch API, а не брать эту зависимость, которая весит 4 Кб.
  • process – полифилл объекта process из Node.js. Обычно включение этой зависимости является следствием неправильной конфигурации webpack, который видит process.env.NODE_ENV, и добавляет полноразмерный полифилл для process вместо одного только объекта process.env. Избавление от этого модуля сэкономит нам еще 1 килобайт.
  • sentry – трекинг пользователя. Не будем здесь обсуждать хорошо ли следить за пользователями, но посмотрим как избежать этих 17 килобайт в бандле.
    • Sentry предлагает вариант загружать скрипт с их CDN. Здесь этот вариант помог бы, потому что скрипт скорее всего уже загружен в кэш пользователя на других сайтах, и время загрузки страницы сократится.
    • Здесь ещё стоит объяснить, почему обычно не рекомендуется загружать библиотеки с чужих CDN, а здесь это приемлемо. Дело в том, что библиотека предназначена для отправления данных на сервера Sentry. Поэтому у вас в любом случае есть зависимость от их сервиса, неважно загружаете ли вы скрипт со своего сервера или их CDN.

Code coverage

Еще полезно прогнать код через Code Coverage из Chrome Devtools. Для этого виджета инструмент показывает, что 24% Javascript не используется. Это не обязательно означает, что весь неиспользуемый код можно удалить, возможно, он нужен для обработки ошибок и других пограничных случаев, но это хороший сигнал обратить на него внимание и подумать, а зачем этот код вообще нужен.


Итог

Итого, оптимизации позволили бы сократить размер этого виджета на 46 килобайт (более чем в 2 раза!)


  • 14 Кб – настройка бандлера
  • 10 Кб – оптимизация изображений
  • 22 Кб – Javascript-зависимости

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

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

Let's block ads! (Why?)

systemd десять лет спустя. Историческая и техническая ретроспектива

Десять лет назад был анонсирован systemd, который устроил революцию в управлении системой дистрибутивов Linux, тем самым разделив пользователей Linux на несколько лагерей. Качество и природа дебатов не сильно улучшилась со времён пламенных войн 2012-2014 годов, и systemd всё ещё остаётся не до конца понятым и изученным инструментом и с технической, и с общественной стороны, несмотря на пристальное внимание к нему сообщества.

Это пост не совсем о том, как пользоваться systemd. Тут, скорее, будет говориться об истории его возникновения, о его компонентах в целом, и о том, как понять систему, которая начиналось как просто PID 1 и стала тем, что я бы назвал middleware современного дистрибутива Linux.

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

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

Но прежде чем начать речь о systemd, хочу рассказать об init.

init


В седьмом издании UNIX (1979) задача init — это выполнить при старте /etc/rc и создавать процессы инициализации терминала (getty) и логина (login).
Затем постепенно добавлялись новые задачи. Например, перехват ничейных процессов. Или очистка каталога /tmp. Или монтирование файловых систем.

Так, сегодня основная задача init — привести систему из состояния «ядро запущено» к «систему готова к работе». Запустить userspace, другими словами.

Время шло, появился интернет, а вместе с ним — приложения, базы данных, кэши, веб-серверы, и много чего ещё. Материализовалось понятие сервиса, как набор тесно связанных демонов или целых систем, выполняющих одну цель.
И /etc/rc со скриптами, конечно, могут запустить всё, что нужно, но что, если, скажем, надо просто перезапустить какой-то демон? Вышедший 1983-м UNIX System V, который ввёл понятие runlevel, или уровень выполнения, частично решал эту проблему и позволял таким образом управлять целыми группами демонов.
Linux перенял у UNIX интерфейс инициализации (sysvinit), и в целом, всех всё устраивало, но тем не менее, были пользователи, которых sysvinit стеснял. Так, Matthias S. Brinkmann однажды написал в своём блоге заметку «Почему sysvinit — отстой». Оригинал удалён, поскольку, по мнению Матьяса «sysvinit уже мёртв», но вольно переведу вкратце найденную в интернете перепечатку:

  • Если у вас нет чёрного пояса в микадо, вы быстро потеряетесь в инсталляции sysvinit. Большинство скриптов имеет, по крайней мере, три репрезентации на диске: скрипт, симлинк запуска и симлинк останова.
  • Надо самостоятельно настраивать порядок запуска сервисов. Для всех. Без исключений. Но серьёзно, какая разница, что запускается раньше — инициализация клавиатуры или системные часы? Если вы хотите это контролировать, то ладно, но SysVinit принуждает вас к контролю всего.
  • Нет управления зависимостями. Да, можно дать скрипту номер 100, а его завиимости — 99, но иногда этого недостаточно. Что, если сервис B требует сервис A в запущенном состоянии, а тот упал после запуска? SysVinit всё равно запустит сервис B! Таким образом, вы рискуете получить систему, где что-то провалилось, скажем, файловая система не примонтировалась и от этого часть системы отвалилась, но программа SysVinit runlevel весело отрапортует, что всё запущено, и у вас есть X сервер с сетью.
  • Его тяжело редактировать: иначе зачем люди пишут красивые GUI для автоматизации добавления-удаления скриптов из уровней выполнения? Потому что вручную управлять дюжиной симлинков, где каждая опечатка может привести к сломанной системе, это безумие.
  • Он плохо масштабируется. Взгляните на Linux From Scratch: он использует три числа для обозначения последовательности запуска. Говорит о многом про систему с только десятком загрузочных скриптов, не так ли? Проблема в том, что когда вам надо добавить новый скрипт, его надо уместить в последовательность загрузки и, возможно, переиндексировать все скрипты инициализации. Вообще, это напоминает мне о временах BASIC с номерами строк, когда надо было уместить больше девяти строк между строкой N и строкой N+10. К счастью, в BASIC есть команда renum. А в SysVinit, конечно, вы сами можете написать скрипт, который сделает переупорядочивание за вас. Админы SysVinit любят упражнения.
  • Он не очень уживается в автоматических инсталляциях и системах, где пакеты поставляются со своими загрузочными скриптами. Допустим, пользователь создал свой загрузочный скрипт с номером N. А потом решил поставить пакет FTP-сервера, который идёт со своим скриптом с тем же номером. Привет, конфликт.
  • В SysVinit невозможно тестировать изменения. Чтобы протестировать последовательность загрузки SysVinit и выполнение уровней, надо выполнять потенциально опасные команды. Иногда требуется несколько циклов перезагрузки, прежде чем всё заработает.
  • Небезопасный шатдаун. SysVinit полагается исключительно на загрузочные скрипты, чтобы отмонтировать файловые системы и остановить процессы. Это крайне небезопасно и может вылиться в потерю данных в худших случаях.

Позже, разработчик Busybox Денис Власенко опубликовал в своём блоге запись SysV init must die, и, несмотря на то, что сразу делается оговорка, что это шутка, да и вообще пост сделан в формате диалога, а не конфликта, претензии к системе были.

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

В атмосфере беспорядочного хаоса было предприняты несколько попыток решить проблемы sysvinit:


Но на сегодня самой известной альтернативой (кроме systemd) является, пожалуй, Upstart.

Upstart


Часть проблем sysvinit вызвался решить Upstart (также известный как ReplacementInit), альтернативная система инициализации, выпущенная в 2006-м году Canonical, разработчиками Ubuntu. Его главная концепция — event-driven подход. Запуск системы — это событие, новое устройство — событие, запуск демона — событие. Процессы могли подписываться на события и запускаться только при явной необходимости.
Upstart совместим с sysvinit-скриптами, благодаря этому он был быстро интегрирован во многие дистрибутивы Linux. Тем не менее, после того, как большинство дистрибутивов Linux по тем или иным причинам перешли на systemd, разработка сошла на нет: последний релиз был в 2014-м году, и сегодня Upstart используется только в Chrome OS.

Новый init


«we are experimenting with a new init system and it is fun»

Весной 2010 года в блоге Леннарта Поттеринга вышел материал Rethinking PID 1, в которой он описал своё видение эффективной системы инициализации.
По его мнению, эффективный init должен обладать следующими характеристиками:

  • Запускать меньше демонов. Точнее говоря, запускать только то, что требуется: подключили сеть — включаем сетевые демоны, зашёл пользователь — включаем его персональные демоны, и так далее.
  • Запускать ещё меньше демонов! Что, если не запускать sshd сразу, а начинать «слушать» TCP-сокет вместо него и при первом подключении запускать sshd, передавая ему дескриптор сокета.
  • Запускать больше демонов параллельно. Например, какая разница, кого запускать первым — sshd, cupsd или httpd, если можно запустить все вместе?
  • Быть динамичным. init должен реагировать на изменения как в демонах, так и в «железе», и запускать (и иногда останавливать) соответствущие службы.

В то же время Поттеринг подчёркивает, что эти идеи не новые, и уже известна система с такими возможностями — launchd от Apple, система инициализации в macOS. launchd имеет схожие с Upstart event-driven идеи, но более того, он вобрал в себя задачи других системных сервисов, таких, как cron и inetd.

Другая идея, которую можно почерпнуть из процесса загрузки macOS: shell-скрипты — это зло. Их легко написать, но трудно поддерживать, и они медленно выполняются: каждый вызов grep/awk/sed/cut в одном скрипте — это запуск процесса, подгрузка его библиотек, операции типа локализации вывода, и так далее. И так с каждым init-скриптом. В контексте запуска системы выполнение множества консольных команд тормозит весь процесс.
«Так давайте избавимся от shell-скриптов в загрузке!» — воскликнул Леннарт. «Большая часть init-скриптинга сводится к тривиальному запуску-остановке демонов, и эту логику можно переписать на C и поместить или в отдельные исполняемые файлы, или в сами демоны, или просто в init».

Другие возможности, которыми должен обладать хороший, по мнению Поттеринга, PID 1:

  • Распараллеливание задач файловых систем. Проверка на ошибки, монтирование и квотирование. Что, если монтировать файловые системы одновременно, или только при доступе к ним?
  • Отслеживание процессов. Одна из задач системы управления сервисами — это мониторинг процессов. Перезапускать, если они упали. Собрать информацию, если они крашнулись. Кроме того, система должна уметь останавливать сервисы, что может быть непросто, если процесс сделал двойной вызов fork().
  • Управление ресурсами сервисов. В идеале, init должен настроить окружение updatedb (обновление БД команды locate) так, чтобы процесс выполнялся только при полном бездействии.
  • Логирование также является важной частью выполнения сервисов. Несмотря на то, что логи — сложная штука, простые сценарии типа вывода в stdout/stderr или обработка логов ядра (dmesg) должны решаться компонентами init.

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

Почему бы просто не добавить всё это в Upstart, зачем изобретать по-новому? Или почему бы не портировать launchd? Ответ прост — на взгляд Леннарта, у Upstart, несмотря на event-driven подход, есть недостатки в архитектуре и дизайне. А launchd вряд ли хорошо прижился бы в Linux.

systemd

Анонсированный в Rethinking PID 1 прототип systemd, ранее известный как "BabyKit" (от process babysitter — нянька процессов), быстро получил доминирующий статус благодаря своей лёгкости.
Сегодня на странице проекта говорится уже не про PID 1, а про комплект базовых блоков для системы Linux.
Что под этим скрывается? systemd теперь называется конструктор из компонентов, каждый из которых отвечает за свою задачу, связанную с сервисами или системой в целом.
systemd — это система управления системой. Он берёт и объединяет в системный слой подсистемы, которые, с одной стороны, не забота ядра, а с другой — пользователю они редко интересны.

Восход к власти


Разработчики systemd не теряли времени на проповедование. Несмотря на пятна от помидоров в списке рассылки fedora-devel, systemd в июле 2010 года стал стандартом инициализации в Fedora Rawhide.
В обсуждениях разработки Fedora, связанных с systemd, Леннарт занимал боевую позицию, особенно в вопросах обратной совместимости: так, отвечая на беспокойства Мэтью Миллера касательно совместимости с inittab, он спрашивал: «Может, мы должны проверять ещё и AUTOEXEC.BAT?… Да, давайте снова вернёмся к теме inittab. Это было так весело!». Когда его спросили про необходимости исправлять пакеты из-за нововведений systemd, он ответил: «Круто, полагаю, теперь я стану разработчиком X (X-сервера). Вероятно, если KMS сломан, мой долг — исправить это. Ура!»
Всё обсуждение демонстрировало в чём-то инфантильный недостаток внимания Леннарта к ситуации, в которой многие пакеты и подсистемы перед релизом Fedora 14 нужно было подготовить к работе с systemd. Естественно, эта мыльная опера появилась на LWN.

Как бы то ни было, амбиций у Леннарта было полно. В июле 2010 он был уверен, что дистрибутивы возьмут к себе systemd:
7) Перейдут ли другие дистрибутивы на systemd?
Ну, не так быстро, как Fedora, но похоже на то.
А в сентябре 2010 он чётко заявил кросс-дистрибутивную интеграцию как одну из целей systemd:
Мы намерены мягко подталкивать дистрибутивы в одном направлении, чтобы они прекратили поддерживать другие решения ...

В августе 2012 года Arch Linux переключился на systemd, хоть и с небольшой драмой.
Немного позже, в октябре того же года, благодаря упорству Леннарта, GNOME перешёл на systemd-logind. В октябре 2019 вышел GNOME 3.34 полностью интегрированный с systemd.

На момент 2013 и 2014 годов Debian и Ubuntu были последними, среди ведущих дистрибутивов, не принявших systemd.

Josselin Mouette в октябре 2013, будучи мейнтейнером пакета GNOME в Debian, заявил в списке рассылки Debian следующее:
systemd становится де-факто стандартом в дистрибутивах Linux (по крайней мере в Fedora, SuSE и Arch) и получает превосходную поддержку во множестве пакетов. До сих пор только Gentoo использует OpenRC, и только Ubuntu использует Upstart. Поэтому использование OpenRC бы означало необходимость в поддержке множества собственных патчей, а использование Upstart бы значило, что мы становимся апстримом Ubuntu.
… Завершая, я скажу как мейнтейнер пакета GNOME. GNOME-у в Jessie понадобится systemd как система инициализации для работы всего функционала так же, как ему требуется NetworkManager для работы конфигурирования сети.

Более того, в декабре 2013, в изложении ситуации с init в Debian за авторством Расса Олбери, в разделе «3.1. Реальность экосистемы», сказал, что разговор идёт уже не о «systemd-против-альтернатив», а о «как-много-будет-systemd»:
Одним из опущенных в процессе обсуждения моментов, который было бы важно подчеркнуть, заключается в том, что все в целом согласны, что Debian примет части systemd. Systemd — это всеобъемлющий проект, включающий в себя множество компонентов, и некоторые из них более значимы, чем другие. Большинство этих частей явно превосходят всё, что мы имеем сейчас на платформе Linux, и они будут использоваться далее в дистрибутиве.

Другими словами, это дебат не о systemd против upstart в обычном смысле. Вопрос, скорее, в том, принимаем ли мы systemd со всеми его составляющими, включая систему инициализации, или же мы берём его подсистемы, но в качестве системы инициализации выбираем upstart. В любом случае, будет работать udev, logind, timedated и другие службы.

Я думаю, это меняет характер дискуссии в его ключевых аспектах. Мы, на самом деле, не говорим о выборе между двумя соревнующимися системами. Мы, скорее, говорим о том, заменять или нет главный компонент из системы компонентом, который нам больше нравится.
Это и произошло. Debian Jessie вышла с systemd в качестве менеджера системы, и вслед за Debian systemd был включён в Ubuntu 15.04.

По правда говоря, это пост не о войнах systemd. Если хотите почитать ещё, но лень копаться в списках рассылки, то держите чей-то материал, который отчасти вдохновил меня на этот пост.

Компоненты systemd


System manager


systemd как менеджер системы вводит в систему множество концепций, и основная — это юнит. Юнит является абстрактной сущностью, которая содержит имя, описание и те или иные завимости.
Под каждым юнитом скрывается конфигурационный файл, во многом похожий на Windows .ini файлы.
На момент systemd 234 юниты делятся на 13 типов, вкратце:
  • service. Содержит набор команд и настроек, необходимых для работы демона. Или не демона — в сервисе можно запускать и короткоживущие команды или скрипты. Типичный пример сервиса — sshd.service, демон ssh. Интересный факт: сервисы можно запустить без определения текстового файла через systemd-run.
  • target. Аналог runlevel в sysvinit. Некая точка в жизненном цикле системы, к которой могут «цепляться» другие юниты.
  • timer. С некоторым интервалом запускает связанный с ним юнит. Аналог задач в cron, однако с поддержкой гораздо более богатого синтаксиса правил дат, который можно посмотреть в man systemd.time.
  • mount. Аналог записи в /etc/fstab, даже более того — systemd самостоятельно парсит fstab и динамически создаёт на его основе юнит-файлы. Так, например, через systemctl cat — -.mount можно увидеть, какой юнит-файл создаётся для корневого раздела.
  • automount. То же самое, что и mount, только монтируется «лениво», при первом доступе.
  • socket. Позволяет запустить связанный с ним сервис отложенно: systemd при запуске сети начинает слушать сокет, а при первом пакете или подключении запускает сервис, передавая ему объект сокета. Требует интеграции сервиса с libsystemd.
  • path. Запускает связанный с ним юнит при доступе или изменения пути, будь то файл или каталог.
  • swap. Особый вариант mount, содержит информацию о файле или разделе, выделенный под swap.
  • slice. Концепция подсистемы управления ресурсами systemd. Подробнее можно почитать в посте Red Hat.
  • scope. Внутренняя часть systemd. Этот юнит создаётся при запуске сервиса и используется для объединения процессов в группы и управления ресурсами сервиса.
  • snapshot. У этого юнита, как и у scope, нет конфигурационного файла, он создаётся динамически при выполнении команды systemctl snapshot. Созданный «снимок» будет содержать статус всех юнитов на данный момент, впоследствии на него можно «откатиться» командой systemctl isolate после запуска/остановки сервисов и других юнитов.
  • device. Юнит устройства, такого, как блочное или сетевого устройство. Тесно связан с sysfs/systemd-udevd.
  • nspawn. Юнит systemd-контейнера, о которых чуть позже.

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

journald


Пожалуй, одна из самых популярных частей systemd. В journalctl можно посмотреть всё, что произошло с системой: логи sshd, логи ядра и его драйверов, сообщения о крахах, и многое другое.
История journald: syslog

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

Целью демона syslog является, как понятно из названия, логирование процессов системы. Демон получает от приложений и сервисов сообщения в относительно свободной форме и сохраняет их на диске. Как правило, единственные метаданные сообщения — это facility (примеры: ядро, почта, сеть, и т.д.) и приоритет, а также timestamp, тег процесса и его PID. Большинство этих полей опционально, и точный синтаксис варьируется от реализации к реализации.

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

Система syslog уже 30 лет (на момент появления journald), благодаря своей простоте, является незаменимым инструментом администраторов. Однако число ограничений солидно, и со временем они стали заметными проблемами:

1. В целом, данные сообщений не проходят аутентификацию: любой локальный процесс может заявить, что он Apache с PID 4711, и syslog поверит ему.
2. Данные записываются в вольном формате. Анализаторы логов должны парсить человеческий язык, чтобы a) опознать тип сообщения, и b) извлечь из него параметры. Это выливается в т.н. «regex horrors» и многие другие неприятные последствия, в том числе для разработчиков программ, которые пишут эти самые логи.
3. Временные метки не обязаны содержать информацию о временной зоне, даже при том, что новые спецификации дают возможность их передавать.
4. syslog, хоть и является стандартом, лишь одна из многих систем логирования. Отдельные логи держат utmp/wtmp, lastlog, audit, ядро, логи прошивок, и много других подсистем и приложений.
5. Чтение логов простое, но крайне неэффективное. Большинство операций с логами имеет сложность O(n). Индексация невозможна.
6. Сетевой протокол syslog простой, но также крайне ограниченный. Он поддерживает только push-модель, и не применяет модель хранения-передачи, что вытекает в проблемы типа Thundering Herd или потери данных, которые затрудняют использование syslog.
7. Контроль доступа отсутствует как таковой. Если отставить в сторону всякий скриптинг, администратор может дать пользователю или полный доступ, или ничего.
8. Автоматическая ротация логов есть, но далека от идеала в большинстве реализаций. Вместо того, чтобы непрерывно мониторить использование диска и реагировать на заполнение, ротация происходит лишь через некоторые интервалы времени. Что открывает дорогу различным DoS-атакам.
9. Ограничение пропускной способности (rate limit) есть в некоторых реализациях, однако в целом использование диска не учитывается.
10. Сжатие структуры логов на диске, в целом, присутствует, но, как правило, это просто эффект ротации и ничего хорошего в плане производительности поиска не даёт.
11. Классический syslog бесполезен в сценариях логирования запуска или остановки системы, впрочем, последние улучшения (в т.ч. и в systemd) улучшили положение дел.
12. Нельзя логировать бинарные данные, что может быть необходимо в некоторых сценариях (дампы прошивок, блобы ATA SMART или SCSI)

С целью решить эти проблемы и был представлен journald:

1. Сообщения проходят аутентификацию. Кроме того, метаданные вроде PID или команды процесса journald добавляет самостоятельно, у процесса нет возможности представиться кем-то другим.
2. Данные структурированы: запись журнала состоит из key-value полей, что упрощает их последующую обработку.
3. Временные метки состоят из монотонного времени и realtime времени, причём realtime метки хранятся в наносекундах с UNIX epoch в UTC, чтобы избежать проблем с таймзонами.
4. Функционал journald достаточно универсален, чтобы решить большинство сценариев логирования демонов и приложений.
5. Журналы хранятся в бинарном виде. Это упрощает хранение, ротацию и индексацию.
6. Сетевой протокол journald сделан с оглядкой на огрехи существующих систем.
7. Файлы журналов разделены по пользователям и у каждого свои права доступа. Так, обычный пользователь имеет доступ к логам своих процессов, и не может открыть журнал системы.
8. Журналы ротируются автоматически при превышении определённых пределов использования места.
9. По умолчанию логи процессов, которые пишут, скажем, гигабайты отладочных сообщений, подвергаются rate limit-у. При этом делается это аккуратно и исходя из ресурсов файловой системы, оставляя при возможности сообщения с более высоким уровнем.
10. Данные сжимаются по полям, это обеспечивает хорошие показатели компрессии.
11. Одна из идей journald — это объединить все различные технологии логирования, такие, как wtmp, логгеры загрузки, логи ядра, и даже логи подсистемы аудита.
12. Несмотря на то, что основной фокус сделан на ASCII-сообщения в логах, бинарные данные в полях тоже поддерживаются.

О journald

Базовая интеграция с journald элементарна: процессу достаточно писать в stdout/stderr, а дальше подсистема сама разберётся, какой это сервис, сессия пользователя, исполняемая команда и прочие атрибуты.
Для более тесной интеграции понадобится использовать библиотеки: так, например, для Python есть модуль pystemd.journal библиотеки pystemd.

Подробнее про то, как работать с journald, можно узнать из поста Selectel и в man journalctl.
Документ за авторством Леннарта с описанием journald и его тонкостей можно почитать тут.

machined


systemd-machined — часть systemd-nspawn, платформы контейнеров systemd. По концепции контейнеры systemd далеки от, скажем, Docker: когда как Docker — прежде всего, платформа stateless контейнеров, systemd-nspawn является просто способом запустить systemd в stateful контейнере. nspawn гораздо проще Docker: тут нет REST API, нет labels, нет управления сетями, volume и прочим. С одной стороны, это минус контейнеров systemd, с другой — они подойдут тем, кто привык настраивать сеть и окружение самостоятельно.
Главное преимущество systemd-nspawn — это тесная интеграция с экосистемой systemd. Для каждого контейнера создаётся слайс и сервис, ресурсы можно настроить через systemctl set-property, и внутри контейнера systemd работает без проблем.
Подробнее о systemd-nspawn можно почитать на Arch wiki или в посте Selectel.

Другие известные компоненты


  • coredump. systemd-coredump перехватывает крахи процессов и записывает их дампы памяти в свою базу для дальнейшего анализа.
  • logind. systemd-logind управляет жизненным циклом пользователей и их сессиями: создаёт slice под каждого пользователя, запускает getty для логина из консоли, обрабатывает кнопки включения-выключения системы, и прочее.
  • udevd. Отвечает за аппаратную часть системы в userspace: названия сетевых интерфейсов и блочных устройств.
  • hostnamed. Крохотный демон, который отвечает за предоставление имени системы и связанных с системой данных (тип корпуса, ОС, архитектура и т.д.) другим приложениям.
  • timedated и timesyncd управляют настройками, связанными со временем: датой-временем системы, временной зоной, переводом времени и прочим.
  • localed занимается такими параметрами, как язык системы и раскладка клавиатуры.
  • networkd и resolved. Неплохая альтернатива NetworkManager и dnsmasq в сценариях, когда их функционал избыточен.
  • systemd-boot. Простой загрузчик ОС, принципиально совместимый только с UEFI.
  • systemd-homed. Принципиально новый подход к управлению домашними каталогами, настолько новый, что кроме статьи в Arch wiki ничего по нему найти не смог.

Мифы о systemd

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

systemd сложный.
Нонсенс. Платформа systemd, на самом деле, гораздо проще традиционного Linux, потому что она объединяет системные объекты и их зависимости в юнитах с простым синтаксисом конфигурационных файлов. Также, у systemd есть вполне исчерпывающая документация о почти каждой детали systemd, в том числе и об API для разработчиков.
Конечно, у systemd есть кривая обучения. Она есть у любого софта. Но systemd всё равно остаётся проще больших shell-скриптов, да и проще синтаксиса shell в целом.
В целом, systemd, возможно, настолько прост, насколько системный менеджер может быть простым.

systemd — продукт синдрома «not invented here».
Это неправда. До того, как приступить к systemd, Поттеринг продвигал Upstart, впрочем, потом он пришёл к заключению, что дизайн Upstart неидеален: вместо того, чтобы самой решать проблему зависимостей, система оставляла это администратору. Но это лишь одна из причин.

systemd — это не UNIX.
В этом есть некоторая правда: в исходных кодах systemd нет ни одной строки из UNIX. Но разработчики черпают вдохновение из UNIX, взгляните на те же десятки файлов, каждый из которых делает свою задачу. Кроме того, способ ведения проекта (т.е. развитие в одном git-репозитории) во многом похож на модель BSD (который настоящий UNIX, в отличии от Linux).

Вообще, UNIX для каждого значит своё. Для Леннарта Поттеринга и других разработчиков это источник вдохновения, а для кого-то это религия, и, как и у других мировых религий, есть разные трактовки и понимания UNIX: под ним можно понимать стиль кода, набор команд и API, а кто-то считает, что UNIX — это набор логик и поведений.
Конечно, сделать всех этих людей счастливыми просто невозможно.

systemd раздут, это зоопарк разных фич и изменения ради изменений.

systemd наверняка покрывает больше задач, чем должен был. Это давно не просто система инициализации, это система для построения операционной системы.
В то же время, разработчики стараются поддерживать оптимальные размеры systemd, выделять общий код в библиотеки, и сохранять модульность: можно во время сборки выбрать компоненты, которые необходимы, а какие не нужны. То же самое с зависимостями: у systemd меньше десяти обязательных зависимостей, включая систему сборки.
Таким образом, пользователи могут выбирать тот уровень «раздутости» и набор фич, который им необходим.
Кстати, собирается systemd на удивление быстро: на моём i7-8550U сборка ядра v245 заняла 43 секунды.

systemd нестабилен и забагован.
Вовсе нет. Мейнтейнеры, по мнению Леннарта, внимательно относятся к issues на GitHub, мониторят багтрекеры дистрибутивов Linux и, надо сказать, число багов довольно мало для такого ключевого компонента ОС.
Разработчики вполне справляются с блокирующими разработку и использование багами, также помогает относительно быстрый цикл разработки с инкрементальными нововведениями с целью качества и стабильности.

Использование systemd dbus-а вместо сокетов делает его интерфейс непрозрачным.
Это утверждение противоречит себе: dbus — это просто стандарт сериализации сообщений поверх обычных сокетов. Формат сообщений документирован и понятен, и к dbus есть множество библиотек для разных языков.
В отличии от различных классических UNIX-демонов, у каждого из которых свои протоколы и форматы.

Я перевёл лишь самую, на мой взгляд, интересную часть мифов. Остальные можете почитать здесь.

В заключение


Сегодня systemd — это больше, чем PID 1: это прослойка между kernel space и приложениями, которая обладает всем, что может понадобиться для работы дистрибутива Linux:
  • Широкий набор инструментов для решения большинства, если не любого, типа задач, связанных с управлением системой.
  • dbus API: почти каждый компонент systemd предоставляет интерфейсы в dbus, системе межпроцессного взаимодействия. Подробнее о dbus можно почитать тут и тут.
  • Модульность: пользователь дистрибутива или его разработчик может убрать, скажем, networkd и resolved, поставить на их место NetworkManager и dnsmasq, и всё будет работать.

Но для меня лично systemd это даже больше, чем middleware: это новая эпоха в истории экосистемы Linux, в одном ряду с KVM, Wine, Kubernetes и другими технологиями.

Let's block ads! (Why?)

[Перевод] Новшества ES2020, которые мне очень нравятся

В последние годы JavaScript развивается очень быстро. Особенно это характерно для периода, следующего за выходом стандарта ES6 в 2015 году. С тех пор в языке появилось множество замечательных возможностей. Немало нового было предложено и для включения в стандарт ES2020.

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

Опциональные цепочки


Опциональные цепочки (Optional Chaining) — это, лично для меня, одна из самых восхитительных возможностей стандарта ES2020. Я написал множество программ, в которых эта возможность оказалась бы крайне полезной. 

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

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

▍Код до появления опциональных цепочек

const user = {
   firstName:"Joseph",
   lastName:"Kuruvilla",
   age:38,
   address:{
      number:"239",
      street:"Ludwig Lane",
      city:"Chennai",
      zip:"600028",
   prop1:{
    prop2:{
     prop3:{
      prop4:{
       value:'sample'
      }
     }
    }
   }
   }
}
if(user && user.address){
 console.log(user.address.zip);
 //600028
}
if(user && user.address && user.address.prop1 && user.address.prop1.prop2 && user.address.prop1.prop2.prop3 && user.address.prop1.prop2.prop3.prop4){
 console.log(user.address.prop1.prop2.prop3.prop4.value);
 //sample
}
//Попытка доступа к несуществующему свойству
console.log(user.address.prop102.po);
//Error

Как видите, для того чтобы избежать возникновения ошибок, вроде Cannot read property 'po' of undefined, нужно, на каждом уровне вложенности, проверять свойства на предмет их существования. При увеличении глубины вложенности сущностей растёт и количество проверяемых свойств. Это означает, что программисту приходится самому писать код, который защищает его от свойств, при обращении к которым можно столкнуться со значениями null или undefined.

▍Код после появления опциональных цепочек


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

Вот как это выглядит:

const user = {
   firstName:"Joseph",
   lastName:"Kuruvilla",
   age:38,
   address:{
      number:"239",
      street:"Ludwig Lane",
      city:"Chennai",
      zip:"600028",
   prop1:{
    prop2:{
     prop3:{
      prop4:{
       value:'sample'
      }
     }
    }
   }
   }
}
console.log(user?.address?.zip);
//600028
console.log(user?.address?.prop1?.prop2?.prop3?.prop4?.value);
//sample
//Попытка доступа к несуществующему свойству
console.log(user?.address?.prop102?.po);
//undefined

Ну не прекрасно ли это? Благодаря этому новшеству ES2020 стало возможным избавление от огромного количества строк кода.

Проверка значений только на null и undefined


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

Известно, что в JavaScript существуют «ложные» и «истинные» значения. Теперь можно сказать, что к ним добавились и «нулевые» значения. В состав таких значений входят null и undefined. С точки зрения JavaScript «ложными» являются пустые строки, число 0, значения undefined, null, false, NaN. То есть, например, некое выражение для проверки значения на «ложность» сработает и на пустой строке, и на значении undefined, и много ещё на чём. А выражение для проверки значения на предмет того, является ли оно «нулевым», вернёт true только для null и undefined. Может, лично вам эта возможность не кажется такой уж замечательной, но, на самом деле, она очень и очень важна.

Рассмотрим примеры.

▍Код до появления возможности проверки значений только на null и undefined


Недавно я работал над проектом, в котором мне нужно было реализовать функционал переключения между светлой и тёмной темами. Мне нужно было при этом проверять состояние элемента управления, узнавать, соответствует ли оно значению true или false. Если же пользователь не устанавливал никакого значения, оно, по умолчанию, должно было равняться true. Вот как я решал эту задачу до появления возможности проверки значений только на null и undefined:
const darkModePreference1 = true
const darkModePreference2 = false
const darkModePreference3 = undefined
const darkModePreference4 = null
const getUserDarkModePreference = (darkModePreference) => {
  if (darkModePreference || darkModePreference === false) {
    return darkModePreference
  }
  return true
}
getUserDarkModePreference(darkModePreference1) 
// true
getUserDarkModePreference(darkModePreference2) 
// false
getUserDarkModePreference(darkModePreference3) 
// true
getUserDarkModePreference(darkModePreference4) 
// true

▍Код после появления возможности проверки значений только на null и undefined


После того, как эта возможность появилась в языке, для проверки на null и undefined достаточно оператора ??. При этом можно обойтись без условного оператора if:
const darkModePreference1 = true
const darkModePreference2 = false
const darkModePreference3 = undefined
const darkModePreference4 = null
const getUserDarkModePreference = (darkModePreference) => {
  return darkModePreference ?? true;
}
getUserDarkModePreference(darkModePreference1) 
// true
getUserDarkModePreference(darkModePreference2) 
// false
getUserDarkModePreference(darkModePreference3) 
// true
getUserDarkModePreference(darkModePreference4) 
// true

Здесь происходит следующее: если переменная darkModePreference содержит «нулевое» значение, тогда возвращается значение true. Благодаря этому упрощается написание кода, он оказывается компактным и лёгким для понимания.

Динамические импорты


Динамические импорты (Dynamic Imports) способствуют более эффективной работе приложений. Эта технология позволяет импортировать JS-файлы динамически, в виде модулей, без привлечения дополнительных инструментов. При этом, если модуль не нужен, то он не импортируется. До ES2020 модули импортировались вне зависимости от того, используются ли они приложением или нет.

Например, предположим, что нам нужно реализовать функционал загрузки некоего файла в формате PDF.

Рассмотрим, как обычно, старый и новый варианты решения этой задачи.

▍Код до появления поддержки динамических импортов


Исходя из реального положения дел, можно ожидать того, что возможность загрузки материалов в формате PDF будет использоваться не всеми посетителями страницы. Но соответствующий модуль, всё равно, нужно импортировать в код. Это означает, что модуль, нужен он или нет, будет загружен при загрузке страницы.
import { exportAsPdf } from './export-as-pdf.js'
const exportPdfButton = document.querySelector('.exportPdfBtn');
exportPdfButton.addEventListener('click', exportAsPdf);

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

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

▍Код после появления поддержки динамических импортов

const exportPdfButton = document.querySelector('.exportPdfBtn');
exportPdfButton.addEventListener('click', () => {
  import('./export-as-pdf.js')
    .then(module => {
      module.exportAsPdf()
    })
    .catch(err => {
      // если модуль загрузить не удаётся - обработать ошибку
    })
})

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

Конструкция Promise.allSettled


Если вам нужно выполнить некое действие только в том случае, когда все промисы успешно разрешились, вы можете воспользоваться методом Promise.all(). Правда, у этого метода есть один недостаток. Метод выдаст ошибку в том случае, если хотя бы один переданный ему промис окажется отклонённым. Это значит, что необходимое действие не будет выполнено до тех пор, пока все промисы не будут успешно разрешены.

Возможно, вам это не нужно. Возможно, вас устроит следующий сценарий: «Мне не важен результат. Мне нужно, чтобы код был выполнен после завершения работы всех промисов». В таком случае вам пригодится метод Promise.allSettled(). Соответствующий промис успешно разрешается только после того, как завершится работа других промисов. При этом неважно — успешно или неуспешно они отработали.

▍Код, в котором используется конструкция Promise.all

const PromiseArray = [
    Promise.resolve(100),
    Promise.reject(null),
    Promise.resolve("Data release"),
    Promise.reject(new Error('Something went wrong'))];
Promise.all(PromiseArray)
  .then(data => console.log('all resolved! here are the resolve values:', data))
  .catch(err => console.log('got rejected! reason:', err))
//got rejected! reason: null

Как видно, Promise.all выдаёт ошибку после отклонения одного из переданных ему промисов.

▍Код, в котором используется конструкция Promise.allSettled

const PromiseArray = [
    Promise.resolve(100),
    Promise.reject(null),
    Promise.resolve("Data release"),
    Promise.reject(new Error('Something went wrong'))];
Promise.allSettled(PromiseArray).then(res =>{
console.log(res);
}).catch(err => console.log(err));
//[
//{status: "fulfilled", value: 100},
//{status: "rejected", reason: null},
//{status: "fulfilled", value: "Data release"},
//{status: "rejected", reason: Error: Something went wrong ...}
//]

А здесь, хотя некоторые из промисов и отклонены, Promise.allSettled возвращает результаты, выданные всеми переданными ему промисами.

Другие примечательные возможности


▍Тип данных BigInt


Новый тип данных BigInt позволяет работать с числами, длина которых превышает длину чисел, с которыми можно было работать в JavaScript до его появления (pow(2,53)-1). Правда, этот тип данных не является обратно совместимым с тем, что было в языке раньше. Стандарт IEEE 754, на котором основана работа с числами в JavaScript, не поддерживает таких чисел, работа с которыми возможна благодаря BigInt

▍Метод String.prototype.matchAll


Метод String.prototype.matchAll() имеет отношение к регулярным выражениям. Он возвращает итератор, позволяющий работать со всеми совпадениями, найденными в строке с использованием регулярного выражения, включая группы.

▍Глобальное свойство globalThis


Глобальное свойство globalThis хранит ссылку на глобальный объект, соответствующий окружению, в котором выполняется код. В браузере глобальный объект представлен объектом window. В Node.js — это объект global. В веб-воркерах это — объект self.

А какие новшества ES2020 нравятся больше всего вам?

Let's block ads! (Why?)

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

image

Самый известный алгоритм для нахождения всех простых чисел, не больших заданного, – решето Эратосфена. Он замечательно работает для чисел до миллиардов, может быть, до десятков миллиардов, если аккуратно написан. Однако каждый, кто любит развлекаться с простыми числами, знает, что их всегда хочется иметь под рукой как можно больше. Как-то раз мне для решения одной задачи на хакерранке понадобилась in-memory база данных простых чисел до ста миллиардов. При максимальной оптимизации по памяти, если в решете Эратосфена представлять нечетные числа битовым массивом, его размер будет около 6 гигабайт, что в память моего ноутбука не влезало. Существует модификация алгоритма, гораздо менее требовательная по памяти (делящая исходный диапазон чисел на несколько кусков и обрабатывающая по одному куску за раз) – сегментированное решето Эратосфена, но она сложнее в реализации, и результат целиком в память все равно не влезет. Ниже предлагаю вашему вниманию алгоритм почти такой же простой, как и решето Эратосфена, но дающий двукратную оптимизацию по памяти (то есть, база данных простых чисел до ста миллиардов будет занимать около 3 гигабайт, что уже должно влезать в память стандартного ноутбука).
Для начала вспомним, как работает решето Эратосфена: в массиве чисел от 1 до N вычеркиваются числа, делящиеся на два, потом числа, делящиеся на 3, и так далее. Оставшиеся после вычеркивания числа и будут простыми:

2 3 . 5 . 7 . 9 . 11 . 13 . 15 . 17 . 19 . 21 . 23 . 25 . 27  . 29  ...
2 3 . 5 . 7 . . . 11 . 13 .  . . 17 . 19 .  . . 23 . 25 .  .  . 29  ...
2 3 . 5 . 7 . . . 11 . 13 .  . . 17 . 19 .  . . 23 .  . .  .  . 29  ...

Код на c#
class EratospheneSieve
{
    private bool[] Data;

    public EratospheneSieve(int length)
    {
        Data = new bool[length];

        for (int i = 2; i < Data.Length; i++) Data[i] = true;

        for (int i = 2; i * i < Length; i++)
        {
            if (!Data[i]) continue;
            for (int d = i * i; d < Length; d += i) Data[d] = false;
        }
    }

    public int Length => Data.Length;

    public bool IsPrime(int n) => Data[n];
}


На моем ноутбуке до миллирарда считает 15 секунд и потребляет гигабайт памяти.

Алгоритм, который мы будем использовать, называется Wheel Factorization. Чтобы понять его суть, напишем натуральные числа табличкой по 30 штук в ряд:

 0  1  2  3  4  5 ... 26 27 28 29
30 31 32 33 34 35 ... 56 57 58 59
60 61 62 63 64 65 ... 86 87 88 89
...

И вычеркнем все числа, делящиеся на 2, 3, и 5:
.  1 . . . . .  7 . . . 11 . 13 . . . 17 . 19 . . . 23 . . . . . 29
. 31 . . . . . 37 . . . 41 . 43 . . . 47 . 49 . . . 53 . . . . . 59
. 61 . . . . . 67 . . . 71 . 73 . . . 77 . 79 . . . 83 . . . . . 89
...

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

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

image

Техническая реализация


Во-первых, наша цель – дойти до ста миллиардов, поэтому четырехбайтными числами уже не обойтись. Поэтому наш алгоритм будет выглядеть как-то так:
class Wheel235
{
    private byte[] Data;

    public Wheel235(long length)
    {
        ...
    }
    public bool IsPrime(long n)
    {
        ...
    }
    ...
}

Для простоты реализации, будем округлять length вверх до ближайшего числа, делящегося на 30. Тогда
class Wheel235
{
    private byte[] Data;

    public Wheel235(long length)
    {
        // ensure length divides by 30
        length = (length + 29) / 30 * 30; 
        Data = new byte[length/30];
        ...
    }

    public long Length => Data.Length * 30L;

    ...
}

Далее, нам нужны два массива: один переводит индекс 0..29 в номер бита, второй, наоборот, номер бита в позицию в тридцатке. Используя эти массивы и немного битовой арифметики, мы строим однозначное соответствие между натуральными числами и битами в массива Data. Для простоты мы используем только два метода: IsPrime(n), позволяющий узнать, является ли число простым, и ClearPrime(n), устанавливающий соответствующий бит в 0, маркируя число n как составное:
class Wheel235
{
    private static int[] BIT_TO_INDEX = new int[] { 1, 7, 11, 13, 17, 19, 23, 29 };

    private static int[] INDEX_TO_BIT = new int[] {
        -1, 0,
        -1, -1, -1, -1, -1, 1,
        -1, -1, -1, 2,
        -1, 3,
        -1, -1, -1, 4,
        -1, 5,
        -1, -1, -1, 6,
        -1, -1, -1, -1, -1, 7,
    };

    private byte[] Data;

    ...

    public bool IsPrime(long n)
    {
        if (n <= 5) return n == 2 || n == 3 || n == 5;
        int bit = INDEX_TO_BIT[n % 30];
        if (bit < 0) return false;
        return (Data[n / 30] & (1 << bit)) != 0;
    }

    private void ClearPrime(long n)
    {
        int bit = INDEX_TO_BIT[n % 30];
        if (bit < 0) return;
        Data[n / 30] &= (byte)~(1 << bit);
    }
}

Заметим, что, если число делится на 2, 3 или 5, то номер бита будет -1, что означает, что число заведомо составное. Ну и, конечно, мы обрабатываем специальный случай n

Теперь, собственно, сам алгоритм, который практически дословно повторяет решето Эратосфена:

    public Wheel235(long length)
    {
        length = (length + 29) / 30 * 30;
        Data = new byte[length / 30];
        for (long i = 0; i < Data.Length; i++) Data[i] = byte.MaxValue;

        for (long i = 7; i * i < Length; i++)
        {
            if (!IsPrime(i)) continue;
            for (long d = i * i; d < Length; d += i) ClearPrime(d);
        }
    }

Соответственно, целиком получившийся класс:
выглядит так
class Wheel235
{
    private static int[] BIT_TO_INDEX = new int[] { 1, 7, 11, 13, 17, 19, 23, 29 };

    private static int[] INDEX_TO_BIT = new int[] {
        -1, 0,
        -1, -1, -1, -1, -1, 1,
        -1, -1, -1, 2,
        -1, 3,
        -1, -1, -1, 4,
        -1, 5,
        -1, -1, -1, 6,
        -1, -1, -1, -1, -1, 7,
    };

    private byte[] Data;

    public Wheel235(long length)
    {
        // ensure length divides by 30
        length = (length + 29) / 30 * 30;
        Data = new byte[length / 30];
        for (long i = 0; i < Data.Length; i++) Data[i] = byte.MaxValue;

        for (long i = 7; i * i < Length; i++)
        {
            if (!IsPrime(i)) continue;
            for (long d = i * i; d < Length; d += i) ClearPrime(d);
        }
    }

    public long Length => Data.Length * 30L;

    public bool IsPrime(long n)
    {
        if (n >= Length) throw new ArgumentException("Number too big");
        if (n <= 5) return n == 2 || n == 3 || n == 5;
        int bit = INDEX_TO_BIT[n % 30];
        if (bit < 0) return false;
        return (Data[n / 30] & (1 << bit)) != 0;
    }

    private void ClearPrime(long n)
    {
        int bit = INDEX_TO_BIT[n % 30];
        if (bit < 0) return;
        Data[n / 30] &= (byte)~(1 << bit);
    }
}


Есть только одна маленькая проблемка: этот код не работает для чисел, больших примерно 65 миллиардов! При попытке её запустить с такими числами программа падает с ошибкой:
Unhandled Exception: System.OverflowException: Array dimensions exceeded supported range.

Проблема в том, что в c# массивы не могут иметь больше 2^31 элементов (видимо, потому что внутренняя имплементация использует четырехбайтный индекс массива). Один из вариантов – вместо массива byte[] создавать, например, массив long[], но это немного усложнит битовую арифметику. Для простоты мы пойдем другим путем, создав специальный класс, симулирующий нужный массив, держащий внутри два коротких массива. Заодно мы ему дадим возможность сохранять себя на диск, чтобы можно было один раз вычислить простые числа, и потом пользоваться базой повторно:
    class LongArray
    {
        const long MAX_CHUNK_LENGTH = 2_000_000_000L;
        private byte[] firstBuffer;
        private byte[] secondBuffer;

        public LongArray(long length)
        {
            firstBuffer = new byte[Math.Min(MAX_CHUNK_LENGTH, length)];
            if (length > MAX_CHUNK_LENGTH) secondBuffer = new byte[length - MAX_CHUNK_LENGTH];
        }

        public LongArray(string file)
        {
            ...
        }

        public long Length => firstBuffer.LongLength + (secondBuffer == null ? 0 : secondBuffer.LongLength);

        public byte this[long index]
        {
            get => index < MAX_CHUNK_LENGTH ? firstBuffer[index] : secondBuffer[index - MAX_CHUNK_LENGTH];
            set
            {
                if (index < MAX_CHUNK_LENGTH) firstBuffer[index] = value; 
                else secondBuffer[index - MAX_CHUNK_LENGTH] = value;
            }
        }

        public void Save(string file)
        {
            ...
        }

    }


Наконец, объединим все шаги в
окончательный вариант алгоритма
class Wheel235
{
    class LongArray
    {
        const long MAX_CHUNK_LENGTH = 2_000_000_000L;
        private byte[] firstBuffer;
        private byte[] secondBuffer;

        public LongArray(long length)
        {
            firstBuffer = new byte[Math.Min(MAX_CHUNK_LENGTH, length)];
            if (length > MAX_CHUNK_LENGTH) secondBuffer = new byte[length - MAX_CHUNK_LENGTH];
        }

        public LongArray(string file)
        {
            using(FileStream stream = File.OpenRead(file))
            {
                long length = stream.Length;
                firstBuffer = new byte[Math.Min(MAX_CHUNK_LENGTH, length)];
                if (length > MAX_CHUNK_LENGTH) secondBuffer = new byte[length - MAX_CHUNK_LENGTH];
                stream.Read(firstBuffer, 0, firstBuffer.Length);
                if(secondBuffer != null) stream.Read(secondBuffer, 0, secondBuffer.Length);
            }
        }

        public long Length => firstBuffer.LongLength + (secondBuffer == null ? 0 : secondBuffer.LongLength);

        public byte this[long index]
        {
            get => index < MAX_CHUNK_LENGTH ? firstBuffer[index] : secondBuffer[index - MAX_CHUNK_LENGTH];
            set
            {
                if (index < MAX_CHUNK_LENGTH) firstBuffer[index] = value; 
                else secondBuffer[index - MAX_CHUNK_LENGTH] = value;
            }
        }

        public void Save(string file)
        {
            using(FileStream stream = File.OpenWrite(file))
            {
                stream.Write(firstBuffer, 0, firstBuffer.Length);
                if (secondBuffer != null) stream.Write(secondBuffer, 0, secondBuffer.Length);
            }
        }

    }

    private static int[] BIT_TO_INDEX = new int[] { 1, 7, 11, 13, 17, 19, 23, 29 };

    private static int[] INDEX_TO_BIT = new int[] {
        -1, 0,
        -1, -1, -1, -1, -1, 1,
        -1, -1, -1, 2,
        -1, 3,
        -1, -1, -1, 4,
        -1, 5,
        -1, -1, -1, 6,
        -1, -1, -1, -1, -1, 7,
    };

    private LongArray Data;

    public Wheel235(long length)
    {
        // ensure length divides by 30
        length = (length + 29) / 30 * 30;
        Data = new LongArray(length / 30);
        for (long i = 0; i < Data.Length; i++) Data[i] = byte.MaxValue;

        for (long i = 7; i * i < Length; i++)
        {
            if (!IsPrime(i)) continue;
            for (long d = i * i; d < Length; d += i) ClearPrime(d);
        }
    }

    public Wheel235(string file)
    {
        Data = new LongArray(file);
    }

    public void Save(string file) => Data.Save(file);

    public long Length => Data.Length * 30L;

    public bool IsPrime(long n)
    {
        if (n >= Length) throw new ArgumentException("Number too big");
        if (n <= 5) return n == 2 || n == 3 || n == 5;
        int bit = INDEX_TO_BIT[n % 30];
        if (bit < 0) return false;
        return (Data[n / 30] & (1 << bit)) != 0;
    }

    private void ClearPrime(long n)
    {
        int bit = INDEX_TO_BIT[n % 30];
        if (bit < 0) return;
        Data[n / 30] &= (byte)~(1 << bit);
    }

}


В результате, мы получили класс, который может вычислить простые числа, не превосходящие ста миллиардов, и сохранить результат на диск (у меня этот код на ноутбуке работал 50 минут; размер созданного файла получился примерно 3 гигабайта, как и ожидалось):
        long N = 100_000_000_000L;
        Wheel235 wheel = new Wheel235(N);
        wheel.Save("./primes.dat");

А потом зачитать с диска и использовать:
        DateTime start = DateTime.UtcNow;
        Wheel235 loaded = new Wheel235("./primes.dat");
        DateTime end = DateTime.UtcNow;
        Console.WriteLine($"Database of prime numbers up to {loaded.Length:N0} loaded from file to memory  in {end - start}");
        long number = 98_000_000_093L;
        Console.WriteLine($"{number:N0} is {(loaded.IsPrime(number) ? "prime" : "not prime")}!");

Let's block ads! (Why?)

[Перевод] Военный и разведывательный персонал можно отслеживать с помощью приложения Untappd Beer


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

Готовность пропустить бокал-другой военнослужащим или использование им социальных сетей — само по себе это вряд ли кому интересно. Однако пользователи Untappd регистрируют сотни, зачастую тысячи географических меток с указанием времени, когда они там были. Эти места аккуратно отсортированы по более чем 900 категориям, столь же разнообразными сколь и конкретизирующими, например, «Ботанический сад», «Стриптиз-клуб», «Гей-бар», «Ресторан западноукраинской кухни» или «Зал ожидания в аэропорту». В результате чего приложение позволяет любому отслеживать перемещение других пользователей между интересными местами — а также их любимыми барами, отелями, ресторанами, районами, а иногда даже частными домами.

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

EDISON Software - web-development
По части разведки (геологической, не военной), мы в EDISON тоже не лыком шиты.


Мы разработали «AmberLight» — это программа для сейсмогеологического моделирования нефтяных и газовых месторождений на основе интерпретации данных сейсморазведки.

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



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

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

Уровень активности варьируется в зависимости от местоположения. В некоторых местах в настоящее время много активных пользователей, в других же — последнее посещение зарегистрировано несколько лет назад. Например, местоположение сотрудника Blackwater в Северной Каролине. Даже неактивные местоположения могут многие рассказать о посетителях и их социальных связях.

Ниже приведены примеры страниц местоположения и истории мест, посещённых пользователем. Слева находится страница размещения авиабазы «Ramstein», на которой отмечено около 600 уникальных посетителей, более 2600 раз проголосовавших за пиво. Справа — небольшая часть истории отметок одного пользователя, во время его перемещений по Афганистану, включая посещение им бара посольства Соединенных Штатов в Кабуле под названием «Duck and Cover» и других мест. У этого пользователя также есть тысячи других отметок, распределённых по сотням различных точек земного шара.


Слева: страница с местом базирования авиабазы «Ramstein» в приложении Untappd.
Справа: часть истории отметок пользователя.

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

Untappd


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

Кроме того, может не так уж и много мест, представляющих интерес, поскольку в поиске Untappd только перечислены такие места, как отели, бары и рестораны, а места, отмеченные, например, как «правительственное здание» или «военная база» в списке отсутствуют. Ещё более усложняет то, что одно географическое место может иметь несколько отдельных страниц в Untappd, а также одна страница, может соответствовать нескольким точкам. Это затрудняет оценку того, сколько пользователей отметились в целом в конкретном месте. Например, две страницы обозначены как «Пентагон», а также есть отдельные страницы для внутреннего двора Пентагона (Pentagon’s courtyard), E-кольца (E-ring) и других локаций, относящихся к пятиугольному зданию. Эти местоположения нужно выискивать с помощью функции поиска Untappd.

image

Слева: отображение карты при поиске по запросу «The Pentagon».
Справа: результаты поиска по запросу «The Pentagon»

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

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

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

Давайте возьмём военный учебный лагерь Camp Peary, широко известный как «Ферма». В этом месте размещаются учебные центры для сотрудников ЦРУ и РУМО (Разведывательное управление Министерства обороны США), а также взлётно-посадочная полоса, которая, по некоторым данным, использовалась для полётов, совершаемых с целью принудительной передачи.

Начав с простого поиска в Untappd и Google, легко находим посадочную полосу в Camp Peary. Однако самого лагеря Camp Peary нет в поиске даже на гугл-картах. Вероятная причина этого заключается в том, что взлётно-посадочная полоса проходит по категории «аэропорт», а не как «военная база». Глядя на результаты, можно предположить, что в случае с Camp Peary просто невозможно отметиться в таком местоположении в Untappd.


Слева: результаты поиска по запросу «Camp Peary»

Если на картах Foursquare и Untappd перейти туда, где фактически находится Camp Peary, можно убедиться, что ни в одном из приложений не зарегистрировано местоположение самого лагеря.


В Untappd (слева) и Foursquare (справа) карты в месте расположения Camp Peary не показывают саму базу.

Если в приложениях Untappd (слева) и Foursquare (справа) поискать по запросу «Camp Peary», то поиск не даст результатов.

Однако, если в Foursquare поищем «Camp Peary» непосредственно в списках мест, получаем следующие результаты:


Обратите внимание, что Foursquare теперь даёт результат для Camp Peary (хотя местоположение не содержит полезной информации). Мы также можем видеть, что местоположение Camp Peary теперь появилось и на карте Foursquare, которая была пустой во время наших предыдущих поисков.

Что ещё интереснее, Foursquare показывает Camp Peary, а Untappd — нет. Как помним, Untappd для всей информации о местах использует Foursquare. Untappd, по-видимому, активно скрывает из результатов поиска некоторые категории местоположений, включая такую как «Военные базы».

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


Расположение лагеря «Camp Peary» обнаруживается, если отметиться в приложении.

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

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


Страница места Camp Peary в приложении.

Как видим, в Camp Peary отметились несколько уникальных профайлов. Однако, пока что неясно: может, эти пользователи случайно наткнулись на место Camp Peary в поиске, когда они находились просто поблизости, и зарегистрировались шутки ради? Или же они использовали наш метод и преднамеренно подделали в приложении своё местоположение, чтобы иметь возможность указать фактическое место своего пребывания? Итак, как можно проверить, были ли отметившиеся пользователи на самом деле там?

Геолокация


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

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


Геолокация бутылки пива на фоне группы зданий в Camp Peary

Вид сверху зданий в Camp Peary, рядом с рекой Йорк, штат Вирджиния

История местоположений


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

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


Слева: история мест пользователя, классифицированных как «Правительственные учреждения».
Справа: история мест пользователя, отсортированная по частоте отметок.

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

Например, один пользователь, который зарегистрировался в Camp Peary, также отмечался и во многих военных точках по всему Ближнему Востоку — зарегистрировано 700+ отметок в 500+ уникальных местах. Подобная проверка для другого пользователя, который отметился возле тюрьмы Гуантанамо показывает, что он также несколько раз посещал Пентагон и другие военные объекты. Опять же, благодаря их истории регистрации и перекрёстным ссылкам с другими открытыми источниками, можно идентифицировать самого пользователя.

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

Масштабирование


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

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

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

Например, изучив пользователей, которые являются «лояльными посетителями» военных объектов в Нидерландах (голубые точки на карте) и определив другие военные объекты (красные точки), которые они посетили, можно анализировать возможные голландские военные связи по всему миру, включая места военных учений и миссий:


Карта военных баз, посещённых «лояльными посетителями» военных объектов в Нидерландах.

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

Социальная сеть для любителей выпить


Помимо обширной истории местоположений, Untappd работает также как любая другая социальная сеть. Пользователи могут публиковать сообщения, сопровождающие их авторизации, отмечать знакомых, получать лайки и добавлять друг друга в друзья. При регистрации необходимо указывать ник и полное имя. Хотя Untappd показывает только первую букву фамилии другим пользователям, при сопоставлении с ником часто можно раскрыть полное имя. Например, профиль Джона Доу может показывать «Джон Д. (Jdoe2002)» (имя + первая буква фамилии + ник). Кроме того, пользователи могут также подключить Twitter и Facebook к своим профилям в Untappd, что также способствует их деанонимизации.

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


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

Фотографии F-16, размещенные на Untappd, с указанием их местонахождения.

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

Зачем публиковать эти выводы?


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

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

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

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

Переводы в блоге компании Эдисон:

Let's block ads! (Why?)

5 экспериментов с WiFi на ESP32

Привет Хабр.

Платы ESP32 весьма популярны в виду низкой цены, неплохой вычислительной мощности (процессор 200МГц), развитого SDK с поддержкой как MicroPython так и Arduino IDE, наличием GPIO c поддержкой периферии (SPI, I2C и пр) и беспроводной связи (WiFi, Bluetooth). Сегодня мы посмотрим, что можно сделать на такой плате ценой всего лишь около 12$.

Мы рассмотрим разные варианты использования WiFi, от простого коннекта к сети до WiFi-сниффера. Для тестов понадобится любая плата с ESP32 (лучше с OLED-экраном, как на картинке) и Arduino IDE.

Для тех кому интересно как это работает, продолжение под катом.

Я не буду писать, как подключить библиотеки ESP32 к Arduino IDE, желающие могут посмотреть здесь. Отмечу лишь, что у данной платы есть особенность — для загрузки кода из Arduino IDE нужно во время заливки нажать и подержать кнопку Boot. В остальном, использование платы ничем не отличается от обычных Arduino.

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

1. Подключение к WiFi и получение точного времени


Раз уж на плате есть WiFi, самое простое что мы можем сделать, это подключиться к существующей WiFi-сети. Это общеизвестно, и работало еще на ESP8266. Однако просто так подключиться и ничего не делать неинтересно, покажем как загрузить точное время по NTP. С помощью нижеприведенного кода нашу плату с ESP несложно превратить в настольные (или для гиков 100lvl наручные:) часы.

Код довольно прост, интересно что поддержка NTP уже встроена в стандартные библиотеки, и ничего доустанавливать не нужно. Для работы OLED-экрана нужно установить библиотеку SSD1306.

Переменные ssid и password нужно будет заменить на параметры реальной точки доступа, в остальном, все работает «из коробки».

#include <WiFi.h>
#include <SSD1306Wire.h>
#include <time.h>
 
const char* ssid     = "MYWIFI";     
const char* password = "12345678";

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;

// OLED Display 128x64
SSD1306Wire  display(0x3c, 5, 4);
 
void setup() {
  Serial.begin(115200);         
  delay(10);
  Serial.println('\n');
  
  WiFi.begin(ssid, password);             // Connect to the network
  while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
    delay(500);
    Serial.print('.');
  }
  Serial.println('\n');
  Serial.println("Connection established");  
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP()); 

  // Get the NTP time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

  // OLED display init
  display.init();
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 0, "Access Point connected");
  display.drawString(0, 24, "AP IP address: ");
  display.drawString(0, 36, WiFi.localIP().toString());
  display.display();
  delay(1000);
}

void draw_time(char *msg) {
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  display.setFont(ArialMT_Plain_24);
  display.drawString(display.getWidth()/2, 0, msg);
  display.display();

  Serial.println(msg);
}
 
void loop() { 
  struct tm timeinfo;
  if (getLocalTime(&timeinfo)) {
      char time_str[16];
      strftime(time_str, 16, "%H:%M:%S", &timeinfo);

      draw_time(time_str);
  }  
  delay(500);
}

2. WiFi точка доступа


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

Фото того, как это работает, показано на КДПВ.

#include <WiFi.h>
#include <DNSServer.h>
#include <SSD1306Wire.h>

// Access Point credentials
const char *ssid = "TEST-123";
const char *password = NULL; // "12345678";
int connections = 0;

// Onboard WiFi server
WiFiServer server(80);
String responseHTML = "<!DOCTYPE html><html>"
                      "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
                      "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}"
                      "</style></head>"
                      "<body><h1>ESP32 Web Server</h1>"
                      "<p>Hello World</p>"
                      "</body></html>";

// OLED Display 128x64
SSD1306Wire  display(0x3c, 5, 4);

void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info){
  connections += 1;
  showConnectionsCount();
}

void showConnectionsCount() {
  char data[32];
  sprintf(data, "Connections: %d", connections);
  draw_message(data);
}

void setup() {
  Serial.begin(115200);                   
  Serial.println();
  Serial.println("Configuring access point...");

  // Start access point 
  WiFi.mode(WIFI_AP);                   
  WiFi.softAP(ssid, password);
  WiFi.onEvent(WiFiStationConnected, SYSTEM_EVENT_AP_STACONNECTED);

  IPAddress ip_address = WiFi.softAPIP();     //IP Address of our accesspoint

  // Start web server
  server.begin();
  
  Serial.print("AP IP address: ");
  Serial.println(ip_address);

  // Oled display 
  display.init();
  // Draw info
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 0, "Access Point started");
  display.drawString(0, 12, ssid);
  display.drawString(0, 24, "AP IP address: ");
  display.drawString(0, 36, ip_address.toString());
  display.display();

  // Total number of connections
  showConnectionsCount();
}

void draw_message(char *msg) {
  display.setColor(BLACK);
  display.fillRect(0, 50, display.getWidth(), 12);
  display.setColor(WHITE);
  display.drawString(0, 50, msg);
  display.display();

  Serial.println(msg);
}

void loop() {
  WiFiClient client = server.available();   // Listen for incoming clients
  if (client) {                             // If a new client connects,
    draw_message("Client connected");
    
    String currentLine = "";   // make a String to hold incoming data from the client
    while (client.connected()) {  // loop while the client's connected
      if (client.available()) {        // if there's bytes to read from the client,
        char c = client.read();      // read a byte, then
        Serial.write(c);                // print it out the serial monitor
        if (c == '\n') {                 // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // Send header
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            // Display the HTML web page
            client.println(responseHTML);            
            // The HTTP response ends with another blank line
            client.println();
            break;
          } else { // if we got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if we got anything else but a CR character,
          currentLine += c;   // add it to the end of the currentLine
        }
      }
    }
    // Close the connection
    client.stop();
    showConnectionsCount();
  }
}

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

Сервер будет работать и без OLED-экрана, в этом случае отладочную информацию можно смотреть с помощью Serial Monitor в Arduino IDE.

3. WiFi точка доступа с DNS


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

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

#include <WiFi.h>
#include <DNSServer.h>
#include <WebServer.h>

WebServer webServer(80);

const char *ssid = "TEST-123";
const char *password = NULL; // "12345678";

IPAddress apIP(192, 168, 1, 4);
DNSServer dnsServer;
const char *server_name = "www.myesp32.com";  // Can be "*" to all DNS requests

String responseHTML = "<!DOCTYPE html><html>"
                      "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
                      "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}"
                      "</style></head>"
                      "<body><h1>ESP32 Web Server</h1>"
                      "<p>Hello World</p>"
                      "</body></html>";

void setup() {
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, password);
  delay(100);
  
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));

  const byte DNS_PORT = 53;
  dnsServer.start(DNS_PORT, server_name, apIP);

  webServer.onNotFound([]() {
    webServer.send(200, "text/html", responseHTML);
  });
  webServer.begin();
}

void loop() {
  dnsServer.processNextRequest();
  webServer.handleClient();
}

4. WiFI Sniffer


Еще один интересный пример использования WiFi приведен на странице https://github.com/ESP-EOS/ESP32-WiFi-Sniffer. WiFi на ESP32 можно перевести в так называемый promiscuous mode, что позволяет незаметно мониторить пакеты WiFi, не подключаясь к самой сети. В частности, можно видеть MAC-адреса находящихся поблизости устройств:

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

Исходный код можно скачать со страницы https://github.com/ESP-EOS/ESP32-WiFi-Sniffer.

5. WiFi Packet Monitor


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

Исходный код был взят на https://github.com/spacehuhn/PacketMonitor32, из него была убрана поддержка записи на SD (на плате её все равно нет) и был исправлен баг с графической библиотекой. Переключать номер канала для мониторинга можно либо нажатием кнопки (на плате её тоже нет:) либо посылкой соответствующего числа через Serial Monitor в Arduino IDE.
Исходный код
#include <esp_wifi.h>
#include <esp_wifi_types.h>
#include <esp_system.h>
#include <esp_event.h>
#include <esp_event_loop.h>
#include <nvs_flash.h>
#include <stdio.h>
#include <string>
#include <cstddef>
#include <Wire.h>
#include <Preferences.h>
using namespace std;

#define MAX_CH 14       // 1 - 14
#define SNAP_LEN 2324   // max len of each recieved packet

#define BUTTON_PIN 5    // button to change the channel

#define USE_DISPLAY     // comment out if you don't want to use OLED
//#define FLIP_DISPLAY    // comment out if you don't like to flip it
#define MAX_X 128
#define MAX_Y 64

#if CONFIG_FREERTOS_UNICORE
#define RUNNING_CORE 0
#else
#define RUNNING_CORE 1
#endif

#ifdef USE_DISPLAY
#include <SSD1306Wire.h>
#endif

esp_err_t event_handler(void* ctx, system_event_t* event) {
  return ESP_OK;
}

// OLED Display 128x64
#ifdef USE_DISPLAY
SSD1306Wire  display(0x3c, 5, 4);
#endif

Preferences preferences;

bool useSD = false;
bool buttonPressed = false;
bool buttonEnabled = true;
uint32_t lastDrawTime;
uint32_t lastButtonTime;
uint32_t tmpPacketCounter;
uint32_t pkts[MAX_X];       // here the packets per second will be saved
uint32_t deauths = 0;       // deauth frames per second
unsigned int ch = 1;       // current 802.11 channel
int rssiSum;

/* ===== functions ===== */
double getMultiplicator() {
  uint32_t maxVal = 1;
  for (int i = 0; i < MAX_X; i++) {
    if (pkts[i] > maxVal) maxVal = pkts[i];
  }
  if (maxVal > MAX_Y) return (double)MAX_Y / (double)maxVal;
  else return 1;
}

void setChannel(int newChannel) {
  ch = newChannel;
  if (ch > MAX_CH || ch < 1) ch = 1;

  preferences.begin("packetmonitor32", false);
  preferences.putUInt("channel", ch);
  preferences.end();

  esp_wifi_set_promiscuous(false);
  esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous_rx_cb(&wifi_promiscuous);
  esp_wifi_set_promiscuous(true);
}

void wifi_promiscuous(void* buf, wifi_promiscuous_pkt_type_t type) {
  wifi_promiscuous_pkt_t* pkt = (wifi_promiscuous_pkt_t*)buf;
  wifi_pkt_rx_ctrl_t ctrl = (wifi_pkt_rx_ctrl_t)pkt->rx_ctrl;

  if (type == WIFI_PKT_MGMT && (pkt->payload[0] == 0xA0 || pkt->payload[0] == 0xC0 )) deauths++;

  if (type == WIFI_PKT_MISC) return;             // wrong packet type
  if (ctrl.sig_len > SNAP_LEN) return;           // packet too long

  uint32_t packetLength = ctrl.sig_len;
  if (type == WIFI_PKT_MGMT) packetLength -= 4;  // fix for known bug in the IDF https://github.com/espressif/esp-idf/issues/886

  //Serial.print(".");
  tmpPacketCounter++;
  rssiSum += ctrl.rssi;
}

void draw() {
#ifdef USE_DISPLAY
  double multiplicator = getMultiplicator();
  int len;
  int rssi;

  if (pkts[MAX_X - 1] > 0) rssi = rssiSum / (int)pkts[MAX_X - 1];
  else rssi = rssiSum;

  display.clear();

  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.drawString( 10, 0, (String)ch);
  display.drawString( 14, 0, ("|"));
  display.drawString( 30, 0, (String)rssi);
  display.drawString( 34, 0, ("|"));
  display.drawString( 82, 0, (String)tmpPacketCounter);
  display.drawString( 87, 0, ("["));
  display.drawString(106, 0, (String)deauths);
  display.drawString(110, 0, ("]"));
  display.drawString(114, 0, ("|"));
  display.drawString(128, 0, (useSD ? "SD" : ""));
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.drawString( 36,  0, ("Pkts:"));

  display.drawLine(0, 63 - MAX_Y, MAX_X, 63 - MAX_Y);
  for (int i = 0; i < MAX_X; i++) {
    len = pkts[i] * multiplicator;
    display.drawLine(i, 63, i, 63 - (len > MAX_Y ? MAX_Y : len));
    if (i < MAX_X - 1) pkts[i] = pkts[i + 1];
  }
  display.display();
#endif
}

void setup() {
  // Serial
  Serial.begin(115200);

  // Settings
  preferences.begin("packetmonitor32", false);
  ch = preferences.getUInt("channel", 1);
  preferences.end();

  // System & WiFi
  nvs_flash_init();
  tcpip_adapter_init();
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
  ESP_ERROR_CHECK(esp_wifi_init(&cfg));
  //ESP_ERROR_CHECK(esp_wifi_set_country(WIFI_COUNTRY_EU));
  ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL));
  ESP_ERROR_CHECK(esp_wifi_start());

  esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);

  // I/O
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  // display
#ifdef USE_DISPLAY
  display.init();
#ifdef FLIP_DISPLAY
  display.flipScreenVertically();
#endif

  /* show start screen */
  display.clear();
  display.setFont(ArialMT_Plain_16);
  display.drawString(6, 6, "PacketMonitor32");
  display.setFont(ArialMT_Plain_10);
  display.drawString(24, 34, "Made with <3 by");
  display.drawString(29, 44, "@Spacehuhn");
  display.display();

  delay(1000);
#endif

  // second core
  xTaskCreatePinnedToCore(
    coreTask,               /* Function to implement the task */
    "coreTask",             /* Name of the task */
    2500,                   /* Stack size in words */
    NULL,                   /* Task input parameter */
    0,                      /* Priority of the task */
    NULL,                   /* Task handle. */
    RUNNING_CORE);          /* Core where the task should run */

  // start Wifi sniffer
  esp_wifi_set_promiscuous_rx_cb(&wifi_promiscuous);
  esp_wifi_set_promiscuous(true);
}

void loop() {
  vTaskDelay(portMAX_DELAY);
}

void coreTask( void * p ) {
  uint32_t currentTime;

  while(true) {
    currentTime = millis();

    // check button
    if (digitalRead(BUTTON_PIN) == LOW) {
      if (buttonEnabled) {
        if (!buttonPressed) {
          buttonPressed = true;
          lastButtonTime = currentTime;
        } else if (currentTime - lastButtonTime >= 2000) {
          draw();
          buttonPressed = false;
          buttonEnabled = false;
        }
      }
    } else {
      if (buttonPressed) {
        setChannel(ch + 1);
        draw();
      }
      buttonPressed = false;
      buttonEnabled = true;
    }

    // draw Display
    if ( currentTime - lastDrawTime > 1000 ) {
      lastDrawTime = currentTime;
      // Serial.printf("\nFree RAM %u %u\n", heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT), heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT));// for debug purposes

      pkts[MAX_X - 1] = tmpPacketCounter;

      draw();

      Serial.println((String)pkts[MAX_X - 1]);

      tmpPacketCounter = 0;
      deauths = 0;
      rssiSum = 0;
    }

    // Serial input
    if (Serial.available()) {
      ch = Serial.readString().toInt();
      if (ch < 1 || ch > 14) ch = 1;
      setChannel(ch);
    }
  }
}

Одна плата ESP32 может мониторить только 1 канал, но при дешевизне этих плат вполне можно сделать вот так.

Заключение


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

В целом, модули ESP32 интересны тогда, когда возможностей Arduino уже не хватает, а использовать Raspberry Pi с Linux еще избыточно. Кстати, вычислительные возможности ESP32 позволяют использовать даже модуль камеры, так что плату можно использовать в качестве беспроводного видеозвонка или прототипа для домашней системы видеонаблюдения.

ESP32 с камерой


Всем удачных экспериментов.

Let's block ads! (Why?)