vpsb_client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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