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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/bin/close_trial.rb +20 -0
- data/bin/create_trial.rb +13 -0
- data/bin/get_current_trial.rb +8 -0
- data/bin/get_item_ids.rb +9 -0
- data/bin/get_trial_last_metric.rb +8 -0
- data/bin/signed_in.rb +7 -0
- data/bin/sync_metrics.rb +19 -0
- data/bin/test_timing_interval.rb +18 -0
- data/bin/upload_metric.rb +37 -0
- data/doc/design.rb +109 -0
- data/lib/vpsb_client/api/close_trial_request.rb +23 -0
- data/lib/vpsb_client/api/create_trial_request.rb +38 -0
- data/lib/vpsb_client/api/get_current_trial_request.rb +30 -0
- data/lib/vpsb_client/api/get_item_id_request.rb +33 -0
- data/lib/vpsb_client/api/get_trial_last_metric_request.rb +25 -0
- data/lib/vpsb_client/api/post_metric_request.rb +28 -0
- data/lib/vpsb_client/api/request.rb +57 -0
- data/lib/vpsb_client/api/response.rb +30 -0
- data/lib/vpsb_client/builders/system_info_parser.rb +127 -0
- data/lib/vpsb_client/builders/trial.rb +54 -0
- data/lib/vpsb_client/client/upload_metrics.rb +39 -0
- data/lib/vpsb_client/config.rb +18 -0
- data/lib/vpsb_client/curl_wrapper.rb +41 -0
- data/lib/vpsb_client/datafiles/formatted_sar_log_parser.rb +38 -0
- data/lib/vpsb_client/datafiles/logfile_decompressor.rb +48 -0
- data/lib/vpsb_client/datafiles/pxx_aggregator.rb +94 -0
- data/lib/vpsb_client/datafiles/sar_manager.rb +71 -0
- data/lib/vpsb_client/datafiles/timing_log_parser.rb +32 -0
- data/lib/vpsb_client/http_client.rb +60 -0
- data/lib/vpsb_client/manager.rb +149 -0
- data/lib/vpsb_client/metrics/interval_builder.rb +85 -0
- data/lib/vpsb_client/metrics/interval_config.rb +45 -0
- data/lib/vpsb_client/metrics/manager.rb +34 -0
- data/lib/vpsb_client/metrics/uploader.rb +16 -0
- data/lib/vpsb_client/version.rb +3 -0
- data/lib/vpsb_client.rb +33 -0
- data/spec/lib/client/upload_metrics_spec.rb +23 -0
- data/spec/lib/config_spec.rb +26 -0
- data/spec/lib/logfile_decompressor_spec.rb +74 -0
- data/spec/lib/metrics/interval_builder_spec.rb +88 -0
- data/spec/lib/metrics/interval_config_spec.rb +99 -0
- data/spec/lib/metrics/manager_spec.rb +60 -0
- data/spec/lib/metrics/uploader_spec.rb +45 -0
- data/spec/lib/pxx_spec.rb +120 -0
- data/spec/lib/request_spec.rb +186 -0
- data/spec/lib/sar_manager_spec.rb +57 -0
- data/spec/lib/system_info_parser_spec.rb +51 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/sarfiles/formatted/formatted_sa20 +147 -0
- data/spec/support/sarfiles/formatted/formatted_sa21 +85 -0
- data/spec/support/sarfiles/orig/sa18 +0 -0
- data/spec/support/sarfiles/orig/sa19 +0 -0
- data/spec/support/sarfiles/orig/sa20 +0 -0
- data/spec/support/sarfiles/orig/sa21 +0 -0
- data/spec/support/timingfiles/other.log.2.gz +0 -0
- data/spec/support/timingfiles/timings.log +11 -0
- data/spec/support/timingfiles/timings.log.1 +11 -0
- data/spec/support/timingfiles/timings.log.2.gz +0 -0
- data/spec/support/timingfiles/timings.log.3.gz +0 -0
- data/spec/support/vpsb.yml +12 -0
- data/vpsb_client.gemspec +29 -0
- 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
|