Использование Яндекса Pandora: стресс-тестирование GRPC и сервисов Flatbuffer

Использование Яндекса Pandora: стресс-тестирование GRPC и сервисов Flatbuffer

15 декабря 2023 г.

Привет!

Это продолжение моей первой статьи о том, как используйте Яндекс Танк для стресс-тестирования своих сервисов. В документации упоминается, что основным движком для загрузки является Phantom, но его можно заменить на Pandora, если вам нужно создавать уникальные тестовые сценарии или работать с другими типами запросов (например, grpc или любыми другими бинарниками). В этой статье мы рассмотрим, как интегрировать Яндекс Пандору в двух случаях:

  1. GRPC
  2. плоский буфер (через http)
  3. Как я уже упоминал ранее, наша главная цель — использовать простой инструмент с понятной документацией, который не требует найма группы людей для настройки теста. Pandora, как и Tank, имеет очень простую и понятную документацию, делает именно то, что нам нужно, и имеет низкий порог входа. В моем случае ==основной стек — это Golang, и я выбираю решения вокруг него.==

    https://hackernoon.com /turbocharge-load-testing-yandextank-ghz-combo-for-lightning-fast-code-checks?embedable=true

    Давайте начнем с документации и посмотрим, что такое Pandora. n «Pandora — это высокопроизводительный генератор нагрузки на языке Go. Он имеет встроенную поддержку HTTP(S) и HTTP/2, и вы можете писать свои собственные сценарии загрузки на Go, компилируя их непосредственно перед тестом».

    Поскольку нас интересуют сценарии, давайте взглянем на документацию:

    https://yandex.github.io/pandora/eng/custom.html?source=post_page----- 12c250f2bff2--------------------------------&embedable=true

    Как мы видим из документации, есть отличный пример того, как написать нашу «пушку» для создания нашего загрузочного движка. Отлично, давайте возьмем этот пример и скопируем его в наш репозиторий. Далее давайте посмотрим на наши сервисные контракты:

    syntax = "proto3";
    
    option go_package = "/docs";
    
    service DocumentService {
     rpc GetAllByLimitAndOffset(GetAllRequest) returns (GetAllResponse) {}
     rpc Save(SaveRequest) returns (SaveResponse) {}
     rpc Validate(ValidateRequest) returns (ValidateResponse) {}
    }
    

    Наша задача включает в себя рассмотрение двух методов (==да, в данном случае мы пропустим метод сохранения==):

    • Сохранить
    • Подтвердить

    Создаём папку Яндекс-танка, создаём внутри неё папку GRPC, перемещаем туда наш PROTO-файл и генерируем контракты командой:

    protoc - go_out=./docs - go_opt=paths=source_relative 
     - go-grpc_out=./docs - go-grpc_opt=paths=source_relative docs.proto
    

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

    package main
    
    import (
     "context"
     "log"
     "strconv"
     "time"
    
     _ "github.com/golang/protobuf/ptypes/timestamp"
     "github.com/spf13/afero"
     "google.golang.org/grpc"
     pb "grpc-gun/docs"
    
     "github.com/yandex/pandora/cli"
     "github.com/yandex/pandora/components/phttp/import"
     "github.com/yandex/pandora/core"
     "github.com/yandex/pandora/core/aggregator/netsample"
     "github.com/yandex/pandora/core/import"
     "github.com/yandex/pandora/core/register"
    )
    
    type Ammo struct {
     Tag    string
     Param1 string
     Param2 string
     Param3 string
    }
    

    Теперь нам нужно добавить GunConfig и Gun, куда мы сможем добавить наш клиент grpc (просто скопируйте и вставьте из официальной документации)

    type GunConfig struct {
     Target string `validate:"required"` // Configuration will fail, without target defined
    }
    
    type Gun struct {
     // Configured on construction.
     client grpc.ClientConn
     conf   GunConfig
     // Configured on Bind, before shooting
     aggr core.Aggregator // Maybe your custom Aggregator.
     core.GunDeps
    }
    

    И добавьте новые функции для Gun, почти готово!

    func NewGun(conf GunConfig) *Gun {
       return &Gun{conf: conf}
    }
    

    Мы видим, что нам нужно добавить еще немного для бега:

    – Метод привязки. Мы просто связываем нашего клиента и аргументы:

    – переопределить метод Shoot

    func (g *Gun) Bind(aggr core.Aggregator, deps core.GunDeps) error {
     // create gRPC stub at gun initialization
     conn, err := grpc.Dial(
      g.conf.Target,
      grpc.WithInsecure(),
      grpc.WithTimeout(time.Second),
      grpc.WithUserAgent("load test, pandora custom shooter"))
     if err != nil {
      log.Fatalf("FATAL: %s", err)
     }
     g.client = *conn
     g.aggr = aggr
     g.GunDeps = deps
     return nil
    }
    
    func (g *Gun) Shoot(ammo core.Ammo) {
     customAmmo := ammo.(*Ammo)
     g.shoot(customAmmo)
    }
    

    Пришло время добавить метод переключения для запросов SaveCase и ValidateCase, а также добавить методы, как пример для ValidateCase:

    func (g *Gun) caseValidate(client pb.DocumentServiceClient, ammo *Ammo) int {
     code := 0
     docName := ammo.Param1
    
     out, err := client.Validate(
      context.TODO(), &pb.ValidateRequest{Document: createDoc(docName)},
     )
    
     if err != nil {
      log.Printf("FATAL: %s", err)
      code = 500
     }
    
     if out != nil {
      code = 200
     }
     return code
    }
    

    Полный код здесь:

    https://github.com/IliaEre/serialisation-contest/tree/ main/common/loadtest/yandex-tank/grpc?source=post_page-----12c250f2bff2----------------------------- ---&embedable=true

    Хорошо, обратите пристальное внимание!

    func main() {
         // Standard imports.
         fs := afero.NewOsFs()
         coreimport.Import(fs)
         // May not be imported, if you don't need http guns, etc.
         phttp.Import(fs)
        // Custom imports. Integrate your custom types into configuration system.
         coreimport.RegisterCustomJSONProvider("grpc_provider", func() core.Ammo { return &Ammo{} })
        register.Gun("grpc_gun", NewGun, func() GunConfig {
         return GunConfig{
         Target: "localhost:84",
         }
         })
        cli.Run()
    }
    

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

    • Создаем провайдера, указывая имя "grpc_provider"
    • Создаем пистолет и указываем имя "grpc_gun"
    • В цели мы указываем**«localhost:84»** местоположение, где доступен наш сервис.

    ооо, подождите, пожалуйста. почти готово!Хорошо, продолжим. Мы успешно скопировали и скорректировали код пистолета. Теперь, как нам его запустить и что нам нужно? Для начала давайте снова создадим боеприпасы. На этот раз мы просто опишем их так:

    {"tag": "/ValidateCase", "Param1": "validate_doc_grpc_pandora"}
    

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

    Отлично, патроны готовы. Теперь настроим сам тест:

    pools:
      - id: HTTP pool
        gun:
          type: grpc_gun   # custom gun name (specified at `register.Gun("my_custom_gun_name", ...`)
          target: "localhost:84"
        ammo:
          type: grpc_provider
          source:
            type: file
            path: validate-json.ammo
        result:
          type: phout
          destination: ./phout.log
        rps: { duration: 60s, type: const, ops: 1000 }
        startup:
          type: once
          times: 10
    

    n Здесь мы указываем наши боеприпасы как «validate-json.ammo». n Их тип — «grpc_provider». n Наше оружие — «grpc_gun».

    Нагрузка 1000 об/с в течение 60 секунд:

    rps: { duration: 60s, type: const, ops: 1000 }
    

    А вывод идёт в файл «./pout.log» — Яндекс Танк это понимает.

    Финишная прямая — теперь выполним три простых действия:

    1. Собери наше оружие.
    2. Собрать новый конфигуратор танка.
    3. Запустите тесты и проверьте результаты.
    4. Создать его очень просто — как простое приложение Go:

      GOOS=linux GOARCH=amd64 go build
      

      Важно указать `GOOS=linux GOARCH=amd64`; в противном случае вы можете столкнуться с ошибками при запуске танка. Вот и все; бинарный файл готов. Обновим еще один конфигуратор:

      phantom:
        enabled: false
      pandora:
        enabled: true
        package: yandextank.plugins.Pandora
        pandora_cmd: ./grpc-gun # Pandora executable path
        config_file: ./validate-load.yml # Pandora config path
      overload:
        enabled: true
        package: yandextank.plugins.DataUploader
        job_name: "grpc validate report"
        token_file: "env/token.txt"
      telegraf:
        enabled: false
      autostop:
        autostop:
          - time(1s,10s) # if request average > 1s
          - http(5xx,100%,1s) # if 500 errors > 1s
          - http(4xx,25%,10s) # if 400 > 25%
          - net(xx,25,10) # if amount of non-zero net-codes in every second of last 10s period is more than 25
      

      Указываем, где находится наш пистолет и его настройки. Все остальное оставьте как раньше и запустите все этим скриптом:

      echo "run tank"
      docker run 
       -v $(pwd):/var/loadtest 
       - net="host" 
       -it direvius/yandex-tank -c validate-tank-load.yml
      

      После этого мы увидим наши результаты.

      overview n результаты с %


      Специальный пистолет для плоского буфера!

      Для плоского буфера история повторяется. Все, что вам нужно сделать, это изменить код клиента с grpc на http. Вот пример:

      type Gun struct {
         // Configured on construction.
         client http.Client
         conf GunConfig
         // Configured on Bind, before shooting.
         aggr core.Aggregator // Maybe your custom Aggregator.
         core.GunDeps
         files [][]byte
      }
      
      func NewGun(conf GunConfig) *Gun {
         return &Gun{conf: conf}
      }
      
      func (g *Gun) Bind(aggr core.Aggregator, deps core.GunDeps) error {
         c := http.Client{Timeout: time.Duration(1) * time.Second}
         g.client = c
         g.GunDeps = deps
         g.aggr = aggr
      }
      

      А затем создайте аналогичные методы и опишите их поведение:

      func (g *Gun) caseValidate(client *http.Client) int {
        host := g.conf.Target
        response, err := client.Post(host+"/report/validate", "application/octet-stream", bytes.NewBuffer(g.files[2]))
        if err != nil {
         log.Printf("FATAL: %s", err)
         return 500
        }
        return response.StatusCode
      }
      

      Вот и все!


      В заключение

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

      На этом все, надеюсь, было полезно! Спасибо.

      You are the best!

      Полный код здесь: n

      https://github.com/IliaEre/serialisation -contest/tree/main/common/loadtest/yandex-tank?embedable=true


      Также опубликовано здесь .


      Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE