もなかアイスの試食品

「とりあえずやってみたい」そんな気持ちが先走りすぎて挫折が多い私のメモ書きみたいなものです.

負荷テストで使うJmeterのパラメータを決めやすくする

はじめに

とあるサービスをクラウド上に公開することになった。

負荷テストをやって、ボトルネックの特定、現状対応できる利用者の数、将来の利用者数に合わせたスケールアップのタイミングを調べるため、Jmeterで負荷テストをすることにした。

色んなサイトや本を見て、Jmeterを使った負荷テストは分かってきた・・・気がする

しかし、やっているうちに、負荷の掛け方がイマイチのように思えた

よく変える設定値は「スレッド数」と「ループ回数」のみで、これだけだとボトルネックは分かっても、どこまでスループットが出ているか・レスポンスが急激に遅くなるタイミングが自分にはよく分からなかった

(自分のJmeterの使い方・結果の見方が悪いのかもしれない)

Jmeterのパラメータの決め方について色々調べてみると、わかりやすいサイトがあった

参考サイト

qiita.com

Jmeterに「定数スループットタイマ」というものがある、ということをこのサイトを見るまで知らなかった・・・

パラメータを算出するスクリプトはコチラ↓

github.com

スクリプトは以下のパラメータを与えてあげると、「スレッド数」と「ループ回数」と「定数スループットタイマ」を算出

  • リクエスト送信に使用するサーバ台数
  • 想定スループット
  • 1スレッドグループに含まれるリクエスト数
  • 試験実施時間
  • 想定リクエスト時間

今までは「スレッド数」と「ループ回数」を悩みながら決めていたけど、上記のパラメータの方が決めやすい!

自分は、Jmeterの設定をWindowsで設定し、sshクラウド上のLinuxに転送し、LinuxJmeterをコマンド実行するようにしている

なので、C#版を作った

見た目

<Window x:Class="JmeterParam.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:JmeterParam"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="552.119">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>

            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Label Content="リクエスト送信に使用するサーバ台数:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="0"/>
        <TextBox HorizontalAlignment="Left" Text="{Binding ServerCount}" Height="23" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="0" TextAlignment="Right" PreviewTextInput="TextBox_PreviewTextInput"/>

        <Label Content="想定スループット:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/>
        <TextBox HorizontalAlignment="Left" Text="{Binding AssumedThroughput}" Height="23" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="1" TextAlignment="Right" PreviewTextInput="TextBox_PreviewTextInput"/>

        <Label Content="1スレッドグループに含まれるリクエスト数:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="2"/>
        <TextBox HorizontalAlignment="Left" Text="{Binding RequestCountByThreadGroup}" Height="23" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="2" TextAlignment="Right" PreviewTextInput="TextBox_PreviewTextInput"/>

        <Label Content="試験実施時間:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="3"/>
        <TextBox HorizontalAlignment="Left" Text="{Binding StressPeriod}" Height="23" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="3" TextAlignment="Right" PreviewTextInput="TextBox_PreviewTextInput"/>

        <Label Content="想定リクエスト処理時間:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="4"/>
        <TextBox HorizontalAlignment="Left" Text="{Binding AssumedResponceTime}" Height="23" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="4" TextAlignment="Right" PreviewTextInput="TextBox_PreviewTextInput"/>

        <Button x:Name="CalcButton" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="5" Content="計算" Margin="10,5" Click="CalcButton_Click"/>

        <Label Content="スレッド数:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="7"/>
        <TextBox HorizontalAlignment="Left" Height="23" Text="{Binding ThreadCount}" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="7" IsReadOnly="True" TextAlignment="Right"/>

        <Label Content="ループ回数:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="8"/>
        <TextBox HorizontalAlignment="Left" Height="23" Text="{Binding LoopCount}" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="8" IsReadOnly="True" TextAlignment="Right"/>

        <Label Content="定数スループットタイマー:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="9"/>
        <TextBox HorizontalAlignment="Left" Height="23" Text="{Binding ConstantThroughputTimer}" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="9" IsReadOnly="True" TextAlignment="Right"/>

        <Label Content="総アクセス数:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="0" Grid.Row="10"/>
        <TextBox HorizontalAlignment="Left" Height="23" Text="{Binding AllAccessCount}" TextWrapping="Wrap" VerticalAlignment="Center" Width="70" Grid.Column="1" Grid.Row="10" IsReadOnly="True" TextAlignment="Right"/>

    </Grid>
</Window>
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
    private class ViewModel: INotifyPropertyChanged
    {
        private int serverCount;
        private int assumedThroughput;
        private int requestCountByThreadGroup;
        private int stressPeriod;
        private double assumedResponceTime;
        private int threadCount;
        private int loopCount;
        private int constantThroughputTimer;
        private int allAccessCount;


        public int ServerCount
        {
            get
            {
                return this.serverCount;
            }
            set
            {
                this.serverCount = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ServerCount"));
            }
        }

        public int AssumedThroughput
        {
            get
            {
                return this.assumedThroughput;
            }
            set
            {
                this.assumedThroughput = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AssumedThroughput"));
            }
        }

        public int RequestCountByThreadGroup
        {
            get
            {
                return this.requestCountByThreadGroup;
            }
            set
            {
                this.requestCountByThreadGroup = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("RequestCountByThreadGroup"));
            }
        }

        public int StressPeriod
        {
            get
            {
                return this.stressPeriod;
            }
            set
            {
                this.stressPeriod = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("StressPeriod"));
            }
        }

        public double AssumedResponceTime
        {
            get
            {
                return this.assumedResponceTime;
            }
            set
            {
                this.assumedResponceTime = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AssumedResponceTime"));
            }
        }

        public int ThreadCount
        {
            get
            {
                return this.threadCount;
            }
            set
            {
                this.threadCount = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ThreadCount"));
            }
        }

        public int LoopCount
        {
            get
            {
                return this.loopCount;
            }
            set
            {
                this.loopCount = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LoopCount"));
            }
        }

        public int ConstantThroughputTimer
        {
            get
            {
                return this.constantThroughputTimer;
            }
            set
            {
                this.constantThroughputTimer = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ConstantThroughputTimer"));
            }
        }

        public int AllAccessCount
        {
            get
            {
                return this.allAccessCount;
            }
            set
            {
                this.allAccessCount = value;
                this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AllAccessCount"));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void UpdateAllAccessCount()
        {
            this.allAccessCount = this.ThreadCount * this.RequestCountByThreadGroup * this.LoopCount * this.ServerCount;
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AllAccessCount"));
        }
    }

    public MainWindow()
    {
        this.InitializeComponent();
        this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
        this.WindowStyle = WindowStyle.ToolWindow;

        this.DataContext = this.model;
        this.model.AssumedResponceTime = 1.5;
    }

    private ViewModel model = new ViewModel();

    private void CalcButton_Click(object sender, RoutedEventArgs e)
    {
        if (this.model.ServerCount <= 0)
        {
            MessageBox.Show(this, "サーバ台数は 0 以上");
            return;
        }
        if (this.model.AssumedResponceTime <= 0)
        {
            MessageBox.Show(this, "想定リクエスト処理時間は 0 以上");
            return;
        }
        if (this.model.RequestCountByThreadGroup <= 0)
        {
            MessageBox.Show(this, "リクエスト数は 0 以上");
            return;
        }

        this.model.ThreadCount = (int)Math.Ceiling(this.model.AssumedThroughput / this.model.ServerCount * this.model.AssumedResponceTime);
        this.model.LoopCount = (int)Math.Ceiling(this.model.StressPeriod / this.model.AssumedResponceTime / this.model.RequestCountByThreadGroup);
        this.model.ConstantThroughputTimer = (int)Math.Ceiling(60.0 / this.model.AssumedResponceTime * this.model.ThreadCount);
        this.model.UpdateAllAccessCount();
    }

    private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        Regex regex = new Regex("[0-9\\.]+");
        e.Handled = !regex.IsMatch(e.Text);
    }
}

アプリの見た目はこんな感じ

f:id:monakaice88:20190531065350p:plain

おわりに

このパラメータの決め方を使うことで、思った通りの負荷を掛けやすくなった。

負荷がわかりやすくなったので、どのスループット辺りから、WebサーバのCPUパワーが足りなくなりそうかも分かりやすくなった。

Jmeterの定数スループットタイマは使うべき