Фреймворк для генерации Open Graph изображений

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


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

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

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

Screenshot of an old Twitter preview for GitHub repo links

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

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

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

Screenshot of new Twitter preview card for NASA

Мы создаем аналогичные карточки для проблем, запросов на вытягивание и фиксаций, и в ближайшее время появится больше ресурсов (например, Обсуждения, Релизы и суть).:

Screenshot of open graph Twitter card for a pull request

Откройте изображение графика для запроса на вытягивание

Screenshot of open graph Twitter card for a commit

Откройте изображение графика для фиксации

Screenshot of open graph Twitter card for an issue link

Что происходит за кулисами? Краткое введение в открытый график

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

<meta property="og:image" content="https://www.rd.com/wp-content/uploads/2020/01/GettyImages-454238885-scaled.jpg" />
В дополнение к изображению мы также определяем ряд других мета-тегов, которые используются для отображения информации за пределами GitHub, таких как og:заголовок и og:описание.  Когда искатель (например, бот-обходчик Twitter, который активируется каждый раз, когда вы делитесь ссылкой в Twitter) просматривает вашу страницу, он увидит эти мета-теги и захватит изображение. Затем, когда эта платформа покажет предварительный просмотр вашего веб-сайта, она будет использовать найденную информацию. Одним из примеров является Twitter, но практически все социальные платформы используют Open Graph для развертывания расширенных предварительных просмотров ссылок.  Как работает генератор изображений?  Я тебе покажу! Мы использовали магию технологий с открытым исходным кодом, чтобы объединить некоторые инструменты. Существует множество сервисов, которые создают шаблоны изображений по требованию, но мы хотели развернуть свои собственные в рамках нашей собственной инфраструктуры, чтобы гарантировать, что у нас есть контроль, необходимый для создания любого вида изображений.  Итак: наш пользовательский сервис изображений с открытым графиком немного Node.js приложение, которое использует API GitHub GraphQL для сбора данных, генерирует некоторый HTML-код из шаблона и передает его кукловоду, чтобы “сделать снимок экрана” этого HTML. Это не новая идея—многие компании и проекты (например, vercel/og-image) используют аналогичный процесс для создания изображения.  У нас есть несколько маршрутов, которые соответствуют шаблонам, аналогичным тем, которые вы найдете на GitHub.com:
// https://github.com/rails/rails/pull/41080 router.get("/:owner/:repo/pull/:number", generateImageMiddleware(Pull));  // https://github.com/rails/rails/issues/41078 router.get("/:owner/:repo/issues/:number", generateImageMiddleware(Issue));  // https://github.com/rails/rails/commit/2afc9059c9eb509f47d94250be0a917059afa1ae router.get("/:owner/:repo/commit/:oid", generateImageMiddleware(Commit));  // https://github.com/rails/rails/pull/41080/commits/2afc9059c9eb509f47d94250be0a917059afa1ae router.get("/:owner/:repo/pull/:number/commits/:oid", generateImageMiddleware(Commit));  // https://github.com/rails/rails/* router.get("/:owner/:repo*", generateImageMiddleware(Repository));

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

async function generateImage(template, templateData) {  // Render some HTML from the relevant template  const html = compileTemplate(template, templateData);    // Create a new page  const page = await browser.newPage();    // Set the content to our rendered HTML  await page.setContent(html, { waitUntil: "networkIdle0" });    const screenshotBuffer = await page.screenshot({    fullPage: false,    type: "png",  });    await page.close();    return screenshotBuffer; }

Некоторые проблемы с производительностью

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

Подождите: networkIdle0 агрессивно терпелив, поэтому мы заменили его

Однажды субботним вечером я генерировал и копался в следах хрома, как обычно, чтобы определить, почему эта служба была такой медленной. Я раскопал эти следы с помощью электронного сопровождающего и энтузиаста с запятой @MarshallOfSound. Мы обнаружили огромный двухсекундный блок простоя (в розовом цвете).:

Screenshot showing two seconds of idle time in Chromium trace

Это след всего, что находится между browser.newPage() и page.close(). Гигантская розовая полоса-это “время простоя”, и (методом проб и ошибок) мы определили, что это была опция waitUntil: networkidle0, переданная на страницу.setContent(). Нам нужно было установить эту опцию так, чтобы “продолжить только после завершения загрузки всех изображений, шрифтов и т. Д.”, Чтобы мы не делали скриншоты до того, как страницы будут действительно готовы. Тем не менее, это, казалось, добавило значительное количество времени простоя, несмотря на то, что страница была готова для скриншота через 300 мс. В соответствии с документами networkidle0:

networkidle0 – считайте настройку содержимого завершенной, если в течение не менее 500 мс не более 0 сетевых подключений.

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

Итак, как мы это исправили? Ну, мы хотим подождать, пока не загрузятся все изображения/шрифты, но очевидно, что метод Кукольника был немного жадным. Это трудно увидеть на неподвижном изображении, но на скриншоте ниже показано, что все изображения были декодированы и отрисованы примерно на ~115 мс в трассировке:

Screenshot showing images decoded and rendered

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

   // Set the content to our rendered HTML    await page.setContent(html, { waitUntil: "domcontentloaded" });      // Wait until all images and fonts have loaded    await page.evaluate(async () => {      const selectors = Array.from(document.querySelectorAll("img"));      await Promise.all([        document.fonts.ready,        ...selectors.map((img) => {          // Image has already finished loading, let’s see if it worked          if (img.complete) {            // Image loaded and has presence            if (img.naturalHeight !== 0) return;            // Image failed, so it has no height            throw new Error("Image failed to load");          }          // Image hasn’t loaded yet, added an event listener to know when it does          return new Promise((resolve, reject) => {            img.addEventListener("load", resolve);            img.addEventListener("error", reject);          });        }),      ]);    });

Это не магия—это стандартная практика DOM. Но это было гораздо лучшее решение для нашего варианта использования, чем абстракция, предоставленная Кукловодом. Мы изменили waitUntil на domcontentloaded, чтобы убедиться, что HTML-код был обработан, а затем передали пользовательскую функцию в page.evaluate. Это выполняется в контексте самой страницы, но передает возвращаемое значение во внешний контекст. Это означало, что мы могли прослушивать события загрузки изображений и приостанавливать выполнение до тех пор, пока Обещания не будут выполнены.

Вы можете увидеть разницу в наших графиках производительности (от ~2,25 секунды до ~600 мс).:

Screenshot of difference in performance graphs, difference in our performance graphs, going from ~2.25 seconds to ~600ms

Удвоьте скорость рендеринга с помощью 1 МБ памяти

Больше памяти означает больше скорости, верно? Конечно! На GitHub, когда мы развертываем новую службу в нашей внутренней инфраструктуре Kubernetes, она получает объем памяти по умолчанию: 512 МБ (технически MiB, но кто считает?). Когда мы масштабировали эту службу, чтобы включить ее для 100% репозиториев, мы хотели увеличить лимиты ресурсов, чтобы убедиться, что мы не наблюдаем снижения производительности по мере увеличения трафика. Чего мы не знали, так это того, что 512 МБ было магическим числом – и что, установив наш лимит памяти как минимум на 1 МБ больше, мы значительно улучшим производительность в Chromium.

Когда мы достигли этого предела, мы увидели это изменение:

Graph showing reduction in time to generate image

В производстве это было сокращение почти на 500 мс для создания изображения. Само собой разумеется, что больше памяти будет “быстрее”, но не настолько сильно без какого—либо увеличения трафика-так что же произошло? Что ж, оказывается, что Chromium имеет флаг для устройств с объемом памяти менее 512 МБ и рассматривает эти устройства с низкой спецификацией. Chromium использует этот флаг для последовательного выполнения некоторых процессов вместо параллельного, чтобы повысить надежность за счет производительности на устройствах, которые в любом случае не могут поддерживать повышенную производительность. Если вы заинтересованы в самостоятельном запуске подобной службы, проверьте, сможете ли вы увеличить лимит памяти до 512 МБ – результаты довольно отличные!

Статистика

Создание изображения занимает в среднем 280 мс. Мы могли бы пойти еще ниже, если бы хотели внести некоторые другие изменения, например, создать JPEG вместо PNG.

Сервис "Генератор изображений" генерирует около двух миллионов уникальных изображений в день. Мы также возвращаем кэшированное изображение для 40% от общего числа запросов.

И это все! Я надеюсь, вам нравятся эти изображения в ваших лентах Twitter. Я знаю, что это сделало мой намного более красочным. Если у вас есть какие-либо вопросы или комментарии, не стесняйтесь писать мне в Twitter: @JasonEtco!


Источник: github.blog

Комментарии: