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