vpsb_client 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +2 -0
  7. data/bin/close_trial.rb +20 -0
  8. data/bin/create_trial.rb +13 -0
  9. data/bin/get_current_trial.rb +8 -0
  10. data/bin/get_item_ids.rb +9 -0
  11. data/bin/get_trial_last_metric.rb +8 -0
  12. data/bin/signed_in.rb +7 -0
  13. data/bin/sync_metrics.rb +19 -0
  14. data/bin/test_timing_interval.rb +18 -0
  15. data/bin/upload_metric.rb +37 -0
  16. data/doc/design.rb +109 -0
  17. data/lib/vpsb_client/api/close_trial_request.rb +23 -0
  18. data/lib/vpsb_client/api/create_trial_request.rb +38 -0
  19. data/lib/vpsb_client/api/get_current_trial_request.rb +30 -0
  20. data/lib/vpsb_client/api/get_item_id_request.rb +33 -0
  21. data/lib/vpsb_client/api/get_trial_last_metric_request.rb +25 -0
  22. data/lib/vpsb_client/api/post_metric_request.rb +28 -0
  23. data/lib/vpsb_client/api/request.rb +57 -0
  24. data/lib/vpsb_client/api/response.rb +30 -0
  25. data/lib/vpsb_client/builders/system_info_parser.rb +127 -0
  26. data/lib/vpsb_client/builders/trial.rb +54 -0
  27. data/lib/vpsb_client/client/upload_metrics.rb +39 -0
  28. data/lib/vpsb_client/config.rb +18 -0
  29. data/lib/vpsb_client/curl_wrapper.rb +41 -0
  30. data/lib/vpsb_client/datafiles/formatted_sar_log_parser.rb +38 -0
  31. data/lib/vpsb_client/datafiles/logfile_decompressor.rb +48 -0
  32. data/lib/vpsb_client/datafiles/pxx_aggregator.rb +94 -0
  33. data/lib/vpsb_client/datafiles/sar_manager.rb +71 -0
  34. data/lib/vpsb_client/datafiles/timing_log_parser.rb +32 -0
  35. data/lib/vpsb_client/http_client.rb +60 -0
  36. data/lib/vpsb_client/manager.rb +149 -0
  37. data/lib/vpsb_client/metrics/interval_builder.rb +85 -0
  38. data/lib/vpsb_client/metrics/interval_config.rb +45 -0
  39. data/lib/vpsb_client/metrics/manager.rb +34 -0
  40. data/lib/vpsb_client/metrics/uploader.rb +16 -0
  41. data/lib/vpsb_client/version.rb +3 -0
  42. data/lib/vpsb_client.rb +33 -0
  43. data/spec/lib/client/upload_metrics_spec.rb +23 -0
  44. data/spec/lib/config_spec.rb +26 -0
  45. data/spec/lib/logfile_decompressor_spec.rb +74 -0
  46. data/spec/lib/metrics/interval_builder_spec.rb +88 -0
  47. data/spec/lib/metrics/interval_config_spec.rb +99 -0
  48. data/spec/lib/metrics/manager_spec.rb +60 -0
  49. data/spec/lib/metrics/uploader_spec.rb +45 -0
  50. data/spec/lib/pxx_spec.rb +120 -0
  51. data/spec/lib/request_spec.rb +186 -0
  52. data/spec/lib/sar_manager_spec.rb +57 -0
  53. data/spec/lib/system_info_parser_spec.rb +51 -0
  54. data/spec/spec_helper.rb +9 -0
  55. data/spec/support/sarfiles/formatted/formatted_sa20 +147 -0
  56. data/spec/support/sarfiles/formatted/formatted_sa21 +85 -0
  57. data/spec/support/sarfiles/orig/sa18 +0 -0
  58. data/spec/support/sarfiles/orig/sa19 +0 -0
  59. data/spec/support/sarfiles/orig/sa20 +0 -0
  60. data/spec/support/sarfiles/orig/sa21 +0 -0
  61. data/spec/support/timingfiles/other.log.2.gz +0 -0
  62. data/spec/support/timingfiles/timings.log +11 -0
  63. data/spec/support/timingfiles/timings.log.1 +11 -0
  64. data/spec/support/timingfiles/timings.log.2.gz +0 -0
  65. data/spec/support/timingfiles/timings.log.3.gz +0 -0
  66. data/spec/support/vpsb.yml +12 -0
  67. data/vpsb_client.gemspec +29 -0
  68. metadata +241 -0
@@ -0,0 +1,127 @@
1
+ module VpsbClient
2
+ module Builders
3
+ class SystemInfoParser
4
+ class NoMatchError < StandardError; end
5
+
6
+ def initialize(cmd)
7
+ @cmd = cmd
8
+ end
9
+
10
+ def lines
11
+ IO.popen(@cmd) { |f| f.readlines }
12
+ end
13
+
14
+ def find_matches(regex)
15
+ regex = Regexp.new(regex)
16
+ matches = nil
17
+ lines.each do |line|
18
+ matches = regex.match(line)
19
+ break if matches
20
+ end
21
+ matches
22
+ end
23
+
24
+ def find_matches!(regex)
25
+ matches = find_matches(regex)
26
+ raise NoMatchError, "Cannot find /#{regex}/" unless matches
27
+ matches
28
+ end
29
+ end
30
+
31
+ class MemoryParser < SystemInfoParser
32
+ attr_reader :used, :free, :total
33
+
34
+ REGEX_CACHE = 'cache:\s+(?<used>\d+)\s+(?<free>\d+)$'
35
+ REGEX_AVAIL = '^Mem:\s+\d+\s+(?<used>\d+)\s+\d+\s+\d+\s+\d+\s+(?<avail>\d+)'
36
+ REGEX_TOTAL = '^Mem:\s+(?<total>\d+)'
37
+
38
+ def initialize
39
+ super('free')
40
+ end
41
+
42
+ def parse
43
+ matches = find_matches(REGEX_CACHE)
44
+ if matches
45
+ @used = matches[:used].to_i
46
+ @free = matches[:free].to_i
47
+ elsif matches = find_matches(REGEX_AVAIL)
48
+ @used = matches[:used].to_i
49
+ @free = matches[:avail].to_i
50
+ else
51
+ raise NoMatchError, "No regex matches 'free' output"
52
+ end
53
+ matches = find_matches!(REGEX_TOTAL)
54
+ @total = matches[:total].to_i
55
+ end
56
+ end
57
+
58
+ class UnameParser < SystemInfoParser
59
+ attr_reader :kernel, :os_type
60
+ # Linux lino 3.12.6-x86_64-linode36 #2 SMP Mon Jan 13 18:54:10 EST 2014 x86_64 x86_64 x86_64 GNU/Linux
61
+ REGEX = Regexp.new(/\w+ \S+ (?<kernel>\d+\.\d+\.\d+)-(?<type>[^\-\s]+)[\-\s]/)
62
+
63
+ def initialize
64
+ super('uname -a')
65
+ end
66
+
67
+ def parse
68
+ matches = find_matches!(REGEX)
69
+ @kernel = matches[:kernel]
70
+ @os_type = matches[:type]
71
+ end
72
+ end
73
+
74
+ class CpuinfoParser < SystemInfoParser
75
+ attr_reader :model, :num, :mhz
76
+ # model name : Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz
77
+ REGEX = Regexp.new('^model name\s*:\s*(?<model>.*$)')
78
+ REGEX_PROCESSOR = Regexp.new('^Processor\s*:\s*(?<model>.*$)')
79
+
80
+ def initialize
81
+ super('cat /proc/cpuinfo')
82
+ end
83
+
84
+ def parse
85
+ matches = find_matches(REGEX)
86
+ matches = find_matches!(REGEX_PROCESSOR) unless matches
87
+ @model = matches[:model]
88
+ parse_num_processors
89
+ parse_cpu_speed
90
+ end
91
+
92
+ private
93
+ def parse_num_processors
94
+ @num = 0
95
+ lines.each do |line|
96
+ if line =~ /^processor\s*:\s*(?<num>\d+)/
97
+ @num += 1
98
+ end
99
+ end
100
+ end
101
+
102
+ def parse_cpu_speed
103
+ matches = find_matches(/^cpu MHz\s*:\s*(?<mhz>\d+)/)
104
+ if matches
105
+ @mhz = matches[:mhz].to_i
106
+ else
107
+ @mhz = nil
108
+ end
109
+ end
110
+ end
111
+
112
+ class IssueParser < SystemInfoParser
113
+ attr_reader :os
114
+
115
+ REGEX = '^(?<os>[^\\\\]+)(?: \\\\n \\\\l)?$'
116
+
117
+ def initialize
118
+ super('cat /etc/issue')
119
+ end
120
+
121
+ def parse
122
+ matches = find_matches!(REGEX)
123
+ @os = matches[:os]
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,54 @@
1
+ module VpsbClient
2
+ module Builders
3
+ class Trial
4
+ def initialize(config, hoster_id=nil, application_id=nil, plan_id=nil)
5
+ @config = config
6
+ @hoster_id = hoster_id
7
+ @application_id = application_id
8
+ @plan_id = plan_id
9
+ end
10
+
11
+ def lookup_params
12
+ trial_params = {}
13
+ trial_params[:client_hostname] = @config['client_hostname']
14
+ trial_params
15
+ end
16
+
17
+ def create_params
18
+ trial_params = {}
19
+ trial_params[:started_at] = Time.now
20
+ trial_params[:hoster_id] = @hoster_id if @hoster_id
21
+ trial_params[:application_id] = @application_id if @application_id
22
+ trial_params[:plan_id] = @plan_id if @plan_id
23
+
24
+ trial_params[:comment] = @config['comment']
25
+
26
+ cpuinfo_parser = Builders::CpuinfoParser.new
27
+ cpuinfo_parser.parse
28
+ trial_params[:cpu_type] = cpuinfo_parser.model
29
+ trial_params[:num_cores] = cpuinfo_parser.num
30
+ trial_params[:cpu_mhz] = cpuinfo_parser.mhz
31
+
32
+ issue_parser = Builders::IssueParser.new
33
+ issue_parser.parse
34
+ trial_params[:os] = issue_parser.os
35
+
36
+ memory_parser = Builders::MemoryParser.new
37
+ memory_parser.parse
38
+ trial_params[:free_memory_mb] = memory_parser.free
39
+
40
+ uname_parser = Builders::UnameParser.new
41
+ uname_parser.parse
42
+ trial_params[:kernel] = uname_parser.kernel
43
+
44
+ trial_params[:client_hostname] = @config['client_hostname']
45
+ trial_params[:ruby_version] = RUBY_VERSION
46
+ trial_params[:rails_version] = defined?(Rails) ? Rails.version : nil
47
+
48
+ trial_params[:datacenter] = @config['datacenter']
49
+
50
+ trial_params
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,39 @@
1
+ module VpsbClient
2
+ module Client
3
+ module UploadMetrics
4
+ def upload_metrics(trial)
5
+ unless enabled?
6
+ logger.debug "not running because vpsb_client is disabled"
7
+ return
8
+ end
9
+
10
+ prepare_logfiles
11
+
12
+ metric_ids = []
13
+ [ 10*60, 3600, 86400 ].each do |interval_length|
14
+ metric_ids += upload_for_interval_length(trial, interval_length)
15
+ end
16
+ metric_ids
17
+ end
18
+
19
+ private
20
+
21
+ def upload_for_interval_length(trial, interval_length)
22
+ last_started_at = trial_last_metric_started_at(trial['id'], interval_length)
23
+ if last_started_at
24
+ start_time = last_started_at + interval_length
25
+ force = true
26
+ else
27
+ trial_started_at = DateTime.parse(trial['started_at']).to_time
28
+ start_time = trial_started_at
29
+ force = false
30
+ end
31
+ logger.debug "[vpsb] upload_metrics: length=#{interval_length} start_time=#{start_time} force=#{force}"
32
+ interval_config = Metrics::IntervalConfig.new(start_time, interval_length, force: force)
33
+ metrics_manager = metrics_manager(trial['id'], interval_config)
34
+ metrics_manager.run
35
+ metrics_manager.created_metric_ids
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ require 'yaml'
2
+
3
+ module VpsbClient
4
+ class Config
5
+ def initialize(path)
6
+ raise ArgumentError, "Can't find #{path}" unless File.exist?(path)
7
+ @yml = YAML.load_file(path)
8
+ end
9
+
10
+ def fetch(name, default=nil)
11
+ default ? @yml.fetch(name.to_s, default) : @yml.fetch(name.to_s)
12
+ end
13
+
14
+ def [](name)
15
+ fetch(name.to_s)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,41 @@
1
+ require 'curb'
2
+
3
+ module VpsbClient
4
+ class CurlWrapper
5
+ def initialize(auth_token)
6
+ @auth_token = auth_token
7
+ end
8
+
9
+ def get(url, &block)
10
+ Curl.get(url) do |curl|
11
+ curl.ssl_verify_host = false
12
+ curl.ssl_verify_peer = false
13
+ curl.headers['Authorization'] = "Token #{@auth_token}"
14
+
15
+ yield curl if block_given?
16
+ end
17
+ end
18
+
19
+ def post(url, post_params, content_type, &block)
20
+ Curl.post(url, post_params) do |curl|
21
+ curl.ssl_verify_host = false
22
+ curl.ssl_verify_peer = false
23
+ curl.headers['content-type'] = content_type
24
+ curl.headers['Authorization'] = "Token #{@auth_token}"
25
+
26
+ yield curl if block_given?
27
+ end
28
+ end
29
+
30
+ def put(url, put_params, content_type, &block)
31
+ Curl.put(url, put_params) do |curl|
32
+ curl.ssl_verify_host = false
33
+ curl.ssl_verify_peer = false
34
+ curl.headers['content-type'] = content_type
35
+ curl.headers['Authorization'] = "Token #{@auth_token}"
36
+
37
+ yield curl if block_given?
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ require 'logfile_interval'
2
+ require File.join(File.expand_path('..', __FILE__), 'pxx_aggregator')
3
+
4
+ module VpsbClient
5
+ module Datafiles
6
+ class FormattedSarLogParser < LogfileInterval::ParsedLine::Base
7
+
8
+ # # hostname;interval;timestamp;CPU;%user;%nice;%system;%iowait;%steal;%idle
9
+ # lino;300;1394780701;-1;4.19;0.00;0.40;0.87;5.49;89.05
10
+ set_regex /^\S+;\S+;(?<ts>\d+);\S+;(?<user>[\d\.]+);(?<nice>[\d\.]+);(?<system>[\d\.]+);(?<iowait>[\d\.]+);(?<steal>[\d\.]+);(?<idle>[\d\.]+)$/
11
+
12
+ PXX_RANGES = {
13
+ 1 => 0.02,
14
+ 10 => 0.1,
15
+ 20 => 0.5,
16
+ 100 => 1
17
+ }
18
+
19
+ add_column :name => 'timestamp', :pos => 1, :aggregator => :timestamp
20
+ add_column :name => 'user', :pos => 2, :aggregator => :average, :conversion => :float
21
+ add_column :name => 'nice', :pos => 3, :aggregator => :average, :conversion => :float
22
+ add_column :name => 'system', :pos => 4, :aggregator => :average, :conversion => :float
23
+ add_column :name => 'iowait', :pos => 5, :aggregator => :average, :conversion => :float
24
+ add_column :name => 'pxx_iowait', :pos => 5, :aggregator => :pxx, :conversion => :float,
25
+ :custom_options => { :ranges => PXX_RANGES, :pxx_keys => [ 75, 95, 99 ] }
26
+ add_column :name => 'cpu_steal', :pos => 6, :aggregator => :average, :conversion => :float
27
+ add_column :name => 'pxx_cpusteal', :pos => 6, :aggregator => :pxx, :conversion => :float,
28
+ :custom_options => { :ranges => PXX_RANGES, :pxx_keys => [ 75, 95, 99 ] }
29
+ add_column :name => 'cpu_idle', :pos => 7, :aggregator => :average, :conversion => :float
30
+ add_column :name => 'pxx_cpuidle', :pos => 7, :aggregator => :one_hundred_minus_pxx, :conversion => :float,
31
+ :custom_options => { :ranges => PXX_RANGES, :pxx_keys => [ 75, 95, 99 ] }
32
+
33
+ def time
34
+ Time.at(self.timestamp.to_i)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,48 @@
1
+ module VpsbClient
2
+ module Datafiles
3
+ class LogfileDecompressor
4
+ class NotFoundError < StandardError; end
5
+
6
+ UNLIMITED_ROTATION_ID = 10000
7
+
8
+ def initialize(orig_path, target_path, options = {})
9
+ raise NotFoundError, "#{orig_path} is not a directory" unless File.directory?(orig_path)
10
+ @orig_path = orig_path
11
+ @target_path = target_path
12
+ @filename_prefix = options.fetch(:filename_prefix, '*')
13
+ @max_rotation_id = options.fetch(:max_rotation_id, UNLIMITED_ROTATION_ID)
14
+ end
15
+
16
+ def run
17
+ unzip
18
+ end
19
+
20
+ private
21
+
22
+ def unzip
23
+ timing_logs = Dir.glob("#{@orig_path}/#{@filename_prefix}.log.*.gz")
24
+ VpsbClient.logger.debug "Will gunzip #{timing_logs.inspect}"
25
+ timing_logs.each do |zipfile|
26
+ unzip_file(zipfile)
27
+ end
28
+ end
29
+
30
+ def unzip_file(zipfile)
31
+ if md = /[^\/]*\/(?<name>[^\/]+)\.(?<num>\d+)\.gz$/.match(zipfile)
32
+ return if md[:num].to_i > @max_rotation_id
33
+ unzipped_file = "#{@target_path}/#{md[:name]}.#{md[:num]}"
34
+ else
35
+ raise "Cannot convert to unzipped filename #{zipfile}"
36
+ end
37
+ VpsbClient.logger.debug "Will unzip #{zipfile} to #{unzipped_file}"
38
+ File.open(unzipped_file, 'w') do |fout|
39
+ File.open(zipfile) do |fin|
40
+ gz = Zlib::GzipReader.new(fin)
41
+ fout.write(gz.read)
42
+ gz.close
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,94 @@
1
+ module LogfileInterval
2
+ module Aggregator
3
+ class Pxx < Base
4
+ def initialize(options)
5
+ super
6
+ @val = PxxBuckets.new(options)
7
+ end
8
+
9
+ def add(value, group_by = nil)
10
+ raise NotImplementedError if group_by
11
+ @val.increment(value)
12
+ end
13
+
14
+ def value(group)
15
+ @val.value(group.to_f / 100.0)
16
+ end
17
+
18
+ def single_value?
19
+ false
20
+ end
21
+ end
22
+
23
+ class OneHundredMinusPxx < Pxx
24
+ def add(value, group_by = nil)
25
+ raise NotImplementedError if group_by
26
+ @val.increment(100.0 - value)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ class PxxBuckets
33
+ attr_reader :buckets, :ary, :total
34
+
35
+ def initialize(options)
36
+ i = 0
37
+ @ary = [ 0 ]
38
+ ranges = options.fetch(:ranges)
39
+ ranges.keys.sort.each do |ceiling|
40
+ while(i < ceiling)
41
+ i += ranges[ceiling]
42
+ i = i.round(2) if i.is_a?(Float) # float decimal errors
43
+ @ary << i
44
+ end
45
+ end
46
+
47
+ @buckets = {}
48
+ @ary.each do |min|
49
+ @buckets[min] = 0
50
+ end
51
+
52
+ @pxx_keys = options.fetch(:pxx_keys)
53
+ @total = 0
54
+ end
55
+
56
+ def keys
57
+ @pxx_keys
58
+ end
59
+
60
+ def each_key(&block)
61
+ keys.each(&block)
62
+ end
63
+
64
+ def [](key)
65
+ value(key.to_f/100.0)
66
+ end
67
+
68
+ def increment(n)
69
+ k = find_bucket_key(n)
70
+ @buckets[@ary[k]] += 1
71
+ @total += 1
72
+ end
73
+
74
+ def empty?
75
+ return total==0
76
+ end
77
+
78
+ def value(percent)
79
+ target = @total.to_f * (1.0 - percent)
80
+ sum = 0
81
+ @buckets.keys.sort.reverse.each do |floor|
82
+ sum += @buckets[floor]
83
+ # puts "target=#{target} floor=#{floor} sum=#{sum}"
84
+ return floor if sum >= target
85
+ end
86
+ end
87
+
88
+ def find_bucket_key(n)
89
+ idx = (0...@ary.size).bsearch do |i|
90
+ @ary[i] > n
91
+ end
92
+ idx ? idx - 1 : @ary.size - 1
93
+ end
94
+ end
@@ -0,0 +1,71 @@
1
+ module VpsbClient
2
+ module Datafiles
3
+ class SarManager
4
+ class NotFoundError < StandardError; end
5
+ class PermissionDeniedError < StandardError; end
6
+ class SadfError < StandardError; end
7
+
8
+ attr_reader :sadf_runner
9
+
10
+ def initialize(orig_path, target_path, sadf = Sadf)
11
+ raise NotFoundError, "#{orig_path} is not a directory" unless File.directory?(orig_path)
12
+ @orig_path = orig_path
13
+ @target_path = target_path
14
+ @sadf_runner = sadf
15
+ end
16
+
17
+ def run
18
+ create_target_path
19
+ create_daily_formatted
20
+ create_current_day_temp_formatted
21
+ end
22
+
23
+ private
24
+
25
+ def create_target_path
26
+ if File.directory?(@target_path)
27
+ raise PermissionDeniedError, "#{@target_path} is not writable" unless File.writable?(@target_path)
28
+ return
29
+ end
30
+ raise PermissionDeniedError unless File.writable?(File.dirname(@target_path))
31
+ Dir.mkdir(@target_path, 0755)
32
+ end
33
+
34
+ def create_daily_formatted
35
+ raw_sar_filenames = Dir.glob("#{@orig_path}/sa*")
36
+ raw_sar_filenames.each do |filename|
37
+ filename.match /sa(?<num>\d+)$/ do |matchdata|
38
+ fileday = matchdata[:num]
39
+ next if fileday.to_i == Time.now.day
40
+
41
+ formatted_filename = "#{@target_path}/formatted_sa#{fileday}"
42
+ next if File.exist?(formatted_filename)
43
+ sadf(filename, formatted_filename)
44
+ end
45
+ end
46
+ end
47
+
48
+ def create_current_day_temp_formatted
49
+ sa_filename = "#{@orig_path}/sa#{'%02d' % Time.now.day}"
50
+ formatted_filename = "#{@target_path}/formatted_sa#{'%02d' % Time.now.day}"
51
+ File.delete(formatted_filename) if File.exist?(formatted_filename)
52
+ sadf(sa_filename, formatted_filename)
53
+ end
54
+
55
+ def sadf(src, dest)
56
+ sadf_runner.run(src, dest)
57
+ end
58
+ end
59
+
60
+ class Sadf
61
+ SADF = '/usr/bin/sadf'
62
+
63
+ def self.run(src, dest)
64
+ raise NotFoundError unless File.executable?(SADF)
65
+ cmd = "#{SADF} -d #{src} -U > #{dest}"
66
+ ret = system cmd
67
+ raise VpsbClient::Datafiles::SarManager::SadfError, "\"#{cmd}\" failed" unless ret
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,32 @@
1
+ require 'logfile_interval'
2
+ require File.join(File.expand_path('..', __FILE__), 'pxx_aggregator')
3
+
4
+ module VpsbClient
5
+ module Datafiles
6
+ class TimingLogParser < LogfileInterval::ParsedLine::Base
7
+
8
+ # 1389477034 200 PackagesController#full_url total=98 view=38.4 db=5.3 ip=66.249.76.30
9
+ set_regex /^(\d+)\s+(\d+)\s([\S]+)\s+total=([\d\.]+)\sview=([\d\.]+)\sdb=([\d\.]+)\sip=([\d\.]+)$/
10
+
11
+ PXX_RANGES = {
12
+ 100 => 2,
13
+ 200 => 10,
14
+ 500 => 20,
15
+ 1000 => 50,
16
+ 3000 => 100
17
+ }
18
+
19
+ add_column :name => :timestamp, :pos => 1, :aggregator => :timestamp
20
+ add_column :name => :resptime_total_ms, :pos => 4, :aggregator => :average, :conversion => :float
21
+ add_column :name => :resptime_view_ms, :pos => 5, :aggregator => :average, :conversion => :float
22
+ add_column :name => :resptime_db_ms, :pos => 6, :aggregator => :average, :conversion => :float
23
+ add_column :name => :num_requests, :pos => 7, :aggregator => :num_lines
24
+ add_column :name => :pxx_total_ms, :pos => 4, :aggregator => :pxx, :conversion => :integer,
25
+ :custom_options => { :ranges => PXX_RANGES, :pxx_keys => [ 50, 75, 95, 99 ] }
26
+
27
+ def time
28
+ Time.at(self.timestamp.to_i)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,60 @@
1
+ module VpsbClient
2
+ class HttpClient
3
+ def initialize(curl_wrapper, protocol, hostname)
4
+ @protocol = protocol
5
+ @hostname = hostname
6
+ @curl_wrapper = curl_wrapper
7
+ end
8
+
9
+ def get(request)
10
+ @curl_wrapper.get(url(request))
11
+ end
12
+
13
+ def post(request)
14
+ post_params = post_params(request, request.content_type)
15
+ @curl_wrapper.post(url(request), post_params, request.content_type)
16
+ end
17
+
18
+ def put(request)
19
+ put_params = put_params(request, request.content_type)
20
+ @curl_wrapper.put(url(request), put_params, request.content_type)
21
+ end
22
+
23
+ private
24
+
25
+ def url(request)
26
+ query_string = url_encode(request.query_params)
27
+ "#{@protocol}://#{@hostname}#{request.url_path}#{suffix(request)}#{query_sep(query_string)}#{query_string}"
28
+ end
29
+
30
+ def query_sep(query_string)
31
+ query_string.empty? ? '' : '?'
32
+ end
33
+
34
+ def suffix(request)
35
+ '.json' if request.accept == 'application/json'
36
+ end
37
+
38
+ def post_params(request, content_type)
39
+ post_params = request.post_params
40
+ if request.content_type == 'application/json'
41
+ JSON.generate(post_params) # curl doesn't do the json encoding by itself
42
+ else
43
+ post_params # but curl does the www form encoding
44
+ end
45
+ end
46
+ alias_method :put_params, :post_params
47
+
48
+ def url_encode(params)
49
+ return '' if params.empty?
50
+ s = ''
51
+ i = 0
52
+ params.each do |k,v|
53
+ s << '&' unless i==0
54
+ s << "#{ERB::Util.url_encode(k)}=#{ERB::Util.url_encode(v)}"
55
+ i += 1
56
+ end
57
+ s
58
+ end
59
+ end
60
+ end