JMeterはとても強力なツールなんですが、UIがいまいち(ですよね?)なのとテストケースがXMLなので、あまり積極的に使っていませんでした。
しかし、どうしてもJMeterを使わざるを得ないケースが出てきて*1、GUIツールとXMLを避ける方法をいろいろと探していたところ、ruby-jmeterがというライブラリが見つかりました。
ruby-jmeter
https://github.com/flood-io/ruby-jmeter
Tired of using the JMeter GUI or looking at hairy XML files?
はい、疲れました…というひとのためにRubyのDSLでjmxを出力 or JMeterを実行してくれます。
require 'ruby-jmeter' test do thread_group count: 3, duration: 60 do visit name: 'example_com', url: 'http://example.com' do header([ {name: 'User-Agent', value: 'test.rb'} ]) end post name: 'www_example_com', url: 'http://www.example.com', raw_body: 'any_data' do {name: 'User-Agent', value: 'test.rb'} end end end.run(properties: nil) # run -> jmx でjmxファイルを出力
おかげさまでJMeter嫌いがほぼ直った気がします。
Rubyでグラフ化
出力したjtlファイルはGUIツールでグラフ化できますが、それもだるいのでjtlファイルのパーサをRubyで書きました。
https://bitbucket.org/winebarrel/jtl
Gruff等のグラフ描画ライブラリと連携すれば、GUIツールを使わなくてもグラフが書けます。
jtl = Jtl.new('jmeter.jtl', interval: 10_000) g = Gruff::Line.new g.title = 'elapsed (avg)' g.labels = jtl.scale_marks.map {|i| i.strftime('%M:%S') }.to_gruff_labels g.data :all, jtl.elapseds {|i| i.mean } g.data :example_com, jtl.elapseds.example_com {|i| i.mean } g.data :www_example_com, jtl.elapseds.www_example_com {|i| i.mean }
jtl = Jtl.new('jmeter.jtl').flatten frequencies = jtl.elapseds.frequencies(10) g = Gruff::Bar.new g.title = 'histogram' g.labels = frequencies.keys.to_gruff_labels {|k, v| (v % 100).zero? } g.data :elapsed, frequencies.values g.write('histogram.png')
統計レポートも以下のようなかんじで出力できます。(厳密な90% Lineの計算方法は調べてないですが…)
require 'jtl' jtl = Jtl.new('jmeter.jtl') rows = [] # たぶん不正確です。正規化しないとまずいような… def _90_line(data) data = data.sort n = (data.length * 0.1).to_i data.slice!(-n, -n) data.max end def error_rate(data) 100.0 * data.count {|i| !i } / data.length end def data_to_row(label, data_set) [ label.slice(0, 7), data_set.flatten.length, data_set.flatten.mean.to_i, data_set.flatten.median.to_i, _90_line(data_set.flatten), data_set.flatten.min, data_set.flatten.max, error_rate(data_set.flatten), data_set.map {|i| i.length }.mean.to_i ] end # header rows << [ 'Label', 'Samples', 'Average', 'Median', '90%Line', 'Min', 'Max', 'Error%', 'req/sec', ] jtl.labels.each do |label| rows << data_to_row(label, jtl.elapseds[label]) end rows << data_to_row('Total', jtl.elapseds) puts rows.map {|i| i.join("\t") }.join("\n")
Label Samples Average Median 90%Line Min Max Error% req/sec example 689 130 126 707 120 707 0.0 11 www_exa 688 126 121 668 117 668 0.0 11 Total 1377 128 125 707 117 707 0.0 22
jmeterでRSpec
RSpecも書けそうなので書いてみました。
require 'ruby-jmeter' require 'jtl' require 'tempfile' def jmeter(params = {}, &block) jtl = nil Tempfile.open(File.basename(__FILE__) + ".#{$$}") do |f| test(&block).run(jtl: f.path, properties: nil) f.flush jtl = Jtl.new(f.path) end return jtl end describe "load" do it "should return a status code 200" do jtl = jmeter { constant_throughput_timer throughput: 60.0 thread_group(count: 1, duration: 10) do visit(url: 'http://example.com/') end } expect(jtl.response_codes.flatten.all? {|i| i == 200 }).to be_true expect(jtl.elapseds.flatten.all? {|i| i <= 1_000 }).to be_true # <= 1,000ms end end
確認すべきパラメータはもっとあるのですが(サーバ側の負荷とか)、うまくすればCIに組み込めるかも…という夢が見れます。
その他
- コードで「.run(properties: nil)」としているのは、未指定だとruby-jmeterのpropertyファイルが読み込まれて、GUIツールでパースできなくなるためです
- flood.ioと連携できるようですが、使ったことないです
- simple_statsは便利ですね
*1:リクエストごとにヘッダを変える必要がありました