負荷テストで使うJmeterのパラメータを決めやすくする
はじめに
とあるサービスをクラウド上に公開することになった。
負荷テストをやって、ボトルネックの特定、現状対応できる利用者の数、将来の利用者数に合わせたスケールアップのタイミングを調べるため、Jmeterで負荷テストをすることにした。
色んなサイトや本を見て、Jmeterを使った負荷テストは分かってきた・・・気がする
しかし、やっているうちに、負荷の掛け方がイマイチのように思えた
よく変える設定値は「スレッド数」と「ループ回数」のみで、これだけだとボトルネックは分かっても、どこまでスループットが出ているか・レスポンスが急激に遅くなるタイミングが自分にはよく分からなかった
(自分のJmeterの使い方・結果の見方が悪いのかもしれない)
Jmeterのパラメータの決め方について色々調べてみると、わかりやすいサイトがあった
参考サイト
Jmeterに「定数スループットタイマ」というものがある、ということをこのサイトを見るまで知らなかった・・・
パラメータを算出するスクリプトはコチラ↓
スクリプトは以下のパラメータを与えてあげると、「スレッド数」と「ループ回数」と「定数スループットタイマ」を算出
今までは「スレッド数」と「ループ回数」を悩みながら決めていたけど、上記のパラメータの方が決めやすい!
自分は、Jmeterの設定をWindowsで設定し、sshでクラウド上のLinuxに転送し、LinuxでJmeterをコマンド実行するようにしている
なので、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); } }
アプリの見た目はこんな感じ
おわりに
このパラメータの決め方を使うことで、思った通りの負荷を掛けやすくなった。
負荷がわかりやすくなったので、どのスループット辺りから、WebサーバのCPUパワーが足りなくなりそうかも分かりやすくなった。