Запустите бенчмарк параллельно, т.е. симулируйте одновременные запросы - PullRequest
4 голосов
/ 22 апреля 2019

При тестировании процедуры базы данных, вызываемой из API, когда она выполняется последовательно, кажется, что она выполняется последовательно в течение ~ 3 с.Однако мы заметили, что когда поступает несколько запросов одновременно , это может занять гораздо больше времени, что приводит к превышению времени ожидания.Я пытаюсь воспроизвести случай «несколько запросов одновременно» в виде go test.

Я попробовал флаг -parallel 10 go test, но время было одинаковым при ~28 с.

Что-то не так с моей функцией бенчмарка ?

func Benchmark_RealCreate(b *testing.B) {
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        name := randomdata.SillyName()
        r := gofight.New()
        u := []unit{unit{MefeUnitID: name, MefeCreatorUserID: "user", BzfeCreatorUserID: 55, ClassificationID: 2, UnitName: name, UnitDescriptionDetails: "Up on the hills and testing"}}
        uJSON, _ := json.Marshal(u)
        r.POST("/create").
            SetBody(string(uJSON)).
            Run(h.BasicEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
                assert.Contains(b, r.Body.String(), name)
                assert.Equal(b, http.StatusOK, r.Code)
            })
    }
}

Иначе, как мне достичь того, что я делаю?

Ответы [ 5 ]

4 голосов
/ 25 апреля 2019

Флаг -parallel не предназначен для запуска одного и того же теста или параллельного теста в нескольких случаях.

Цитирование из Команда go: Флаги тестирования:

-parallel n
    Allow parallel execution of test functions that call t.Parallel.
    The value of this flag is the maximum number of tests to run
    simultaneously; by default, it is set to the value of GOMAXPROCS.
    Note that -parallel only applies within a single test binary.
    The 'go test' command may run tests for different packages
    in parallel as well, according to the setting of the -p flag
    (see 'go help build').

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

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

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

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

Первое, что приходит в голову, - это использовать цикл for для запуска n процедур, которые все пытаются вызвать тестируемый сервис. Одна проблема с этим заключается в том, что это обеспечивает только n одновременных подпрограмм в начале, потому что, как только вызовы начнут завершаться, будет все меньше и меньше параллелизма для остальных.

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

Таким образом, в общем, запустите рабочий пул с n работниками, сделайте так, чтобы программа отправляла ему задания на произвольное время (например, в течение 30 секунд или 1 минуты) и измеряла (подсчитывала) выполненные задания. Результат теста будет простым делением.

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

2 голосов
/ 22 апреля 2019

Ваш пример кода смешивает несколько вещей.Почему вы используете assert там?Это не тест, это эталон.Если методы assert медленные, ваш бенчмарк будет.

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

func executeRoutines(routines int) {
    wg := &sync.WaitGroup{}
    wg.Add(routines)
    starter := make(chan struct{})
    for i := 0; i < routines; i++ {
        go func() {
            <-starter
            // your request here
            wg.Done()
        }()
    }
    close(starter)
    wg.Wait()
}

https://play.golang.org/p/ZFjUodniDHr

Здесь мы запускаем некоторые процедуры, которые ждут, пока starter не закроется.Таким образом, вы можете установить свой запрос сразу после этой строки.Что функция ждет, пока все запросы не будут выполнены, мы используем WaitGroup.

НО ВАЖНО: Go просто поддерживает параллелизм.Таким образом, если ваша система не имеет 10 ядер, 10 программ не будут работать параллельно.Поэтому убедитесь, что у вас достаточно ядер.

С этим стартом можно немного поиграть.Вы можете начать вызывать эту функцию в своем тесте.Вы также можете поиграть с числами горутин.

1 голос
/ 28 апреля 2019

Я новичок, но почему бы вам не попытаться создать функцию и запустить ее с помощью стандартного параллельного теста?

func Benchmark_YourFunc(b *testing.B) {
    b.RunParralel(func(pb *testing.PB) {
        for pb.Next() {
            YourFunc(staff ...T)
        }
    })
}
1 голос
/ 25 апреля 2019

Как указано в документации, флаг parallel разрешает параллельное выполнение нескольких различных тестов. Как правило, вы не хотите запускать параллельные тесты, потому что это будет запускать разные тесты одновременно, отбрасывая результаты для всех из них. Если вы хотите протестировать параллельный трафик, вам нужно включить параллельное генерирование трафика в ваш тест. Вам нужно решить, как это должно работать с b.N, который является вашим рабочим фактором; Я, вероятно, использовал бы его как общее количество запросов и написал бы тест или несколько тестов, тестирующих различные уровни одновременной нагрузки, например ::10000

func Benchmark_RealCreate(b *testing.B) {
    concurrencyLevels := []int{5, 10, 20, 50}
    for _, clients := range concurrencyLevels {
        b.Run(fmt.Sprintf("%d_clients", clients), func(b *testing.B) {
            sem := make(chan struct{}, clients)
            wg := sync.WaitGroup{}
            for n := 0; n < b.N; n++ {
                wg.Add(1)
                go func() {
                    name := randomdata.SillyName()
                    r := gofight.New()
                    u := []unit{unit{MefeUnitID: name, MefeCreatorUserID: "user", BzfeCreatorUserID: 55, ClassificationID: 2, UnitName: name, UnitDescriptionDetails: "Up on the hills and testing"}}
                    uJSON, _ := json.Marshal(u)
                    sem <- struct{}{}
                    r.POST("/create").
                        SetBody(string(uJSON)).
                        Run(h.BasicEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {})
                    <-sem
                    wg.Done()
                }()
            }
            wg.Wait()
        })
    }
}

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

0 голосов
/ 01 мая 2019

Одна вещь - это бенчмаркинг (измерение времени выполнения кода), другая - нагрузочное / стресс-тестирование.

Флаг -parallel, как указано выше, предназначен для параллельного выполнения ряда тестов, позволяянабор тестов должен выполняться быстрее, а не выполнять несколько тестов N раз параллельно.

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

  • Вы определяете тест и помечаете его для параллельного выполнения => TestAverage с вызовом t.Parallel
  • Затем вы определяете другой тест и используете RunParallel для выполнения желаемого количества экземпляров теста (TestAverage).

Класс дляtest:

package math

import (
    "fmt"
    "time"
)

func Average(xs []float64) float64 {
  total := float64(0)
  for _, x := range xs {
    total += x
  }

  fmt.Printf("Current Unix Time: %v\n", time.Now().Unix())
  time.Sleep(10 * time.Second)
  fmt.Printf("Current Unix Time: %v\n", time.Now().Unix())

  return total / float64(len(xs))
}

Функции тестирования:

package math

import "testing"

func TestAverage(t *testing.T) {
  t.Parallel()
  var v float64
  v = Average([]float64{1,2})
  if v != 1.5 {
    t.Error("Expected 1.5, got ", v)
  }
}

func TestTeardownParallel(t *testing.T) {
    // This Run will not return until the parallel tests finish.
    t.Run("group", func(t *testing.T) {
        t.Run("Test1", TestAverage)
        t.Run("Test2", TestAverage)
        t.Run("Test3", TestAverage)
    })
    // <tear-down code>
}

Затем просто выполните go-тест, и вы увидите:

X:\>go test
Current Unix Time: 1556717363
Current Unix Time: 1556717363
Current Unix Time: 1556717363

И через 10 секунд после этого

...
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717373
Current Unix Time: 1556717383
PASS
ok      _/X_/y        20.259s

Две дополнительные строки, в конце концов, заключаются в том, что также выполняется TestAverage.

Интересный момент: если вы удалите t.Parallel () из TestAverage, все это будет выполненопоследовательно:

X:> go test
Current Unix Time: 1556717564
Current Unix Time: 1556717574
Current Unix Time: 1556717574
Current Unix Time: 1556717584
Current Unix Time: 1556717584
Current Unix Time: 1556717594
Current Unix Time: 1556717594
Current Unix Time: 1556717604
PASS
ok      _/X_/y        40.270s

Это, конечно, можно сделать более сложным и расширяемым ...

...