Привет, Хабр!

Меня зовут Илья Улизко, я занимаюсь нагрузочным тестированием ДБО ЮЛ в блоке Цифровой Трансформации «РСХБ-Интех». В этой статье я поделюсь с вами опытом автоматизации сбора графиков в Grafana при отсутствии установленного grafana-image-render плагина на сервере. Для того, чтобы научить Apache Jmeter делать скриншоты панелей в Grafana, нам понадобится Selenium и Browsermob-proxy.

Использовано в иллюстративных целях.
Использовано в иллюстративных целях.
  1. Начнём со скачивания всех необходимых зависимостей. Сами jar файлы можно скачать с официальных Maven репозиториев. Однако этого будет мало, поэтому нужно рекурсивно скачать зависимости для самих этих jar'ников. В этом нам поможет сервис Jar-Download. Вставляем в окно Maven XML 3 зависимости из selenium, proxy, webdrivermanager.

    Нажимаем кнопку Submit, затем Download project.
    Нажимаем кнопку Submit, затем Download project.



















    В итоге получаем архив с более чем 40 jar файлами, которые кладём в apache-jmeter-5.4.3/lib/junit/.

  2. Теперь нам необходимо скачать webdriver'ы под конкретные браузеры: Firefox и Chrome. С версией драйвера для Chrome всё просто: какая версия браузера, такая версия и драйвера. Например, если у вас Google Chrome 111, то и драйвер нужен версии 111. А вот с Firefox задача немного усложняется, но на сайте Mozilla есть таблица с соответствиями, которая поможет разобраться и выбрать нужный драйвер.
    Для гибкости рекомендую скачать оба драйвера и установить оба браузера.
    После того как всё скачано, складываем драйверы в отдельную папку WebDriver/bin.

  3. Далее нам нужно создать API ключ в Grafana, его мы будем отсылать в заголовке запросов в качестве авторизации. Для этого в Grafana слева нажимаем Configuration => API keys => New API key. Записываем где-нибудь у себя в блокноте, чтобы не забыть API ключ. Его мы будем далее передавать в переменную API_KEY.

  4. Теперь надо прописать в переменных окружения путь до webdriver'ов и API-ключа, чтобы при необходимости всё поменять в одном месте, не затрагивая скрипты Jmeter. Я использую Linux, поэтому привожу пример для этой ОС:
    echo 'export PATH=$PATH:/path/to/WebDriver/bin' >> ~/.profile
    echo 'export API_GRAFANA=eyJblablablablablablablablabla9' >> ~/.profile
    source ~/.profile

  5. Вытаскиваем все id'шники панелей из дашборда. У нас все дашборды для разных микросервисов шаблонизированы, поэтому количество и название панелей везде одинаковое. Заходим на любой дашборд и переходим в Dashboard settings => Json model. С помощью JSONPath $.panels[*].[id,title] и онлайн сервиса получаем список всех id'шников панелей. Этот массив мы далее будем передавать в переменную PANELIDS.

  6. С помощью панели разработчика определяем какой селектор или атрибут отвечает за отображение картинки внутри панели дашборда. Методом проб и ошибок был подобран атрибут class="panel-container", в отличие от css selector, - он не меняется в разных панелях. Его мы будем передавать в переменную SELECTOR.

  7. Приступаем к написанию скрипта в Jmeter. В начале тест плана создаём setUp Thread Group, внутри которой создаём JSR223 Sampler. В нём мы заводим переменную, которая будет хранить время начала теста и инициализировать переменную FROM:
    props.put("TESTSTART",'${__time()}')

  8. В конце тест плана создаём tearDown Thread Group, внутри которой формируем JSR223 Sampler. В нём пишем весь основной код.
    Затем заводим параметризованные переменные:
    final String URL = "http://10.10.10.10:3000/d/";
    final String DASHBOARD = "VQB0x5pVz/settings";
    final int[] PANELIDS = new int[]{26,28,38,37,29,4,2,23,39,40,36,25,27}; final String FROM = '${__P(TESTSTART)}';
    final String TO = '${__time()}';
    final String SELECTOR = "panel-container";
    final String API_KEY = System.getenv("API_GRAFANA");
    final double SCALE_MONITOR = 1;
    final SimpleDateFormat formatter = new SimpleDateFormat("'_'dd.MM.yyyy");
    Обратите внимание на переменную SCALE_MONITOR, она нужна, чтобы нивелировать масштабирование мониторов с высоким разрешением. Например, если у вас монитор с разрешением 4K и в настройках параметров экрана выставлен масштаб в 150%, то значение этой переменной будет 1.5, для масштаб в 125% нужно выставить - 1.25, ну и т.д.

  9. Создадим и настроим объект BrowserMobProxy, он нужен для того, чтобы к запросам из Selenium добавлялся заголовок с авторизацией по API ключу:
    BrowserMobProxy proxy = new BrowserMobProxyServer();
    proxy.setTrustAllServers(true);
    proxy.start();
    proxy.addHeader("Authorization", "Bearer ".concat(API_KEY));

  10. Теперь создадим веб драйвер с конкретной реализацией Firefox и передадим в него наш прокси с добавленным заголовком, а также добавим несколько настроек:
    WebDriver driver;
    DesiredCapabilities caps = new DesiredCapabilities();
    Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy);
    System.setProperty("sun.java2d.uiScale", "1");
    System.setProperty(FirefoxDriver.SystemProperty.BROWSER_LOGFILE, "null");
    FirefoxOptions firefoxOptions = new FirefoxOptions();
    firefoxOptions.addArguments("-private");
    caps.setCapability("moz:firefoxOptions", firefoxOptions);
    caps.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);
    caps.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
    caps.setCapability(CapabilityType.PROXY, seleniumProxy);
    driver = new FirefoxDriver(caps);

  11. Через веб драйвер открываем браузер, разворачиваем на весь экран, делаем авторизованный запрос на URL адрес Grafana. Затем снимаем скриншоты всех панелей, id которых мы собрали в п. 5.
    Далее сохраняем каждый скриншот под собственным названием в указанную директорию:
    driver.manage().window().maximize();
    for (int i = 0; i < PANELIDS.length; i++) {
    driver.navigate().to(URL
    .concat(DASHBOARD)
    .concat("?orgId=1&viewPanel=")
    .concat(String.valueOf(PANELIDS[i]))
    .concat("&from=")
    .concat(FROM)
    .concat("&to=")
    .concat(TO));
    Thread.sleep(2000);
    WebElement elem = new WebDriverWait(driver, 5)
    .until(ExpectedConditions
    .visibilityOfElementLocated(By.className(SELECTOR)));
    File screenshot = ((org.openqa.selenium.TakesScreenshot) driver)
    .getScreenshotAs(org.openqa.selenium.OutputType.FILE);
    BufferedImage image = ImageIO.read(screenshot);
    int x = (int) (elem.getLocation().getX() * SCALE_MONITOR);
    int y = (int) (elem.getLocation().getY() * SCALE_MONITOR);
    int width = (int) (elem.getSize().getWidth() * SCALE_MONITOR);
    int height = (int) (elem.getSize().getHeight() * SCALE_MONITOR);
    BufferedImage elemScreenshot = image.getSubimage(x, y, width, height);
    ImageIO.write(elemScreenshot, "png", new File("/path/GrafanaScreenshots/"
    .concat(elem.getAttribute("aria-label")
    .substring(0,elem.getAttribute("aria-label").length()-6))
    .concat(formatter.format(System.currentTimeMillis()))
    .concat(".png")));
    }

  12. Закрываем браузер и останавливаем прокси:
    driver.quit();
    proxy.stop();

  13. В итоге JSR223 Sampler будет выглядеть следующим образом:

java code
import net.lightbody.bmp.BrowserMobProxy;
import net.lightbody.bmp.BrowserMobProxyServer;
import net.lightbody.bmp.core.har.HarEntry;
import net.lightbody.bmp.client.ClientUtil;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
      
    final String URL = "http://10.10.10.10:3000/d/";
    final String DASHBOARD = "VQB0x5pVz/settings";
    final int[] PANELIDS = new int[]{4,23};
    final String FROM = '${__P(TESTSTART)}';
    final String TO = '${__time()}';
    final String SELECTOR = "panel-container";
    final String API_KEY = System.getenv("API_GRAFANA"); 
    final double SCALE_MONITOR = 1;
    final SimpleDateFormat formatter = new SimpleDateFormat("'_'dd.MM.yyyy");

    BrowserMobProxy proxy = new BrowserMobProxyServer();
    proxy.setTrustAllServers(true);
    proxy.start();
    proxy.addHeader("Authorization", "Bearer ".concat(API_KEY));
    
    try {       
        WebDriver driver = getDriver(proxy);
        driver.manage().window().maximize();
        for (int i = 0; i < PANELIDS.length; i++) {
        driver.navigate().to(URL
            .concat(DASHBOARD)
            .concat("?orgId=1&viewPanel=")
            .concat(String.valueOf(PANELIDS[i]))
            .concat("&from=")
            .concat(FROM)
            .concat("&to=")
            .concat(TO));
        Thread.sleep(2000);
        
        WebElement elem = new WebDriverWait(driver, 5)
            .until(ExpectedConditions.visibilityOfElementLocated(By.className(SELECTOR)));
        File screenshot = ((org.openqa.selenium.TakesScreenshot) driver).getScreenshotAs(org.openqa.selenium.OutputType.FILE);
        BufferedImage image = ImageIO.read(screenshot);
        int x = (int) (elem.getLocation().getX() * SCALE_MONITOR);
        int y = (int) (elem.getLocation().getY() * SCALE_MONITOR);
        int width = (int) (elem.getSize().getWidth() * SCALE_MONITOR);
        int height = (int) (elem.getSize().getHeight() * SCALE_MONITOR);
        BufferedImage elemScreenshot = image.getSubimage(x, y, width, height);
        ImageIO.write(elemScreenshot, "png", new File("/path/to/GrafanaScreenshots/"
                    .concat(elem.getAttribute("aria-label").substring(0,elem.getAttribute("aria-label").length()-6))
                    .concat(formatter.format(System.currentTimeMillis()))
                    .concat(".png")));
    }
        driver.quit();
    } catch (IOException | InterruptedException ignored) {
      } finally {
        proxy.stop();
    }

    final WebDriver getDriver(BrowserMobProxy proxy) {
        WebDriver driver;
        DesiredCapabilities caps = new DesiredCapabilities();
        Proxy seleniumProxy = ClientUtil.createSeleniumProxy(proxy);
        System.setProperty("sun.java2d.uiScale", "1");
        System.setProperty(FirefoxDriver.SystemProperty.BROWSER_LOGFILE, "null");
        FirefoxOptions firefoxOptions = new FirefoxOptions();
        firefoxOptions.addArguments("-private");
        caps.setCapability("moz:firefoxOptions", firefoxOptions);
        caps.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true);
        caps.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
        caps.setCapability(CapabilityType.PROXY, seleniumProxy);
        driver = new FirefoxDriver(caps);
        return driver;
    }

Заключение

Таким образом у нас получилось автоматизировать процесс сбора графиков из дашборда Grafana без установленного grafana-image-render плагина.

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

Комментарии (2)


  1. KMA7
    14.04.2023 13:44
    +5

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


    1. login40k
      14.04.2023 13:44

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

      1. https://github.com/kirillyu/jmeterReports

      2. https://github.com/kirillyu/jmeterReports2