tracebin 0.0.7
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 +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +42 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dump.rdb +0 -0
- data/lib/vizsla/agent.rb +87 -0
- data/lib/vizsla/background_job_instrumentation/active_job.rb +14 -0
- data/lib/vizsla/background_job_instrumentation/resque.rb +37 -0
- data/lib/vizsla/background_job_instrumentation/sidekiq.rb +19 -0
- data/lib/vizsla/background_job_instrumentation.rb +34 -0
- data/lib/vizsla/background_timer.rb +9 -0
- data/lib/vizsla/config.rb +21 -0
- data/lib/vizsla/events.rb +67 -0
- data/lib/vizsla/health_monitor.rb +24 -0
- data/lib/vizsla/helpers.rb +7 -0
- data/lib/vizsla/logger.rb +55 -0
- data/lib/vizsla/middleware.rb +51 -0
- data/lib/vizsla/patches/action_view_layout.rb +34 -0
- data/lib/vizsla/patches/mysql2.rb +22 -0
- data/lib/vizsla/patches/postgres.rb +42 -0
- data/lib/vizsla/patches/sidekiq_health.rb +27 -0
- data/lib/vizsla/patches/sinatra.rb +21 -0
- data/lib/vizsla/patches.rb +44 -0
- data/lib/vizsla/puppet_master.rb +17 -0
- data/lib/vizsla/recorder.rb +43 -0
- data/lib/vizsla/reporter.rb +74 -0
- data/lib/vizsla/storage.rb +30 -0
- data/lib/vizsla/subscribers.rb +172 -0
- data/lib/vizsla/system_health_sample.rb +187 -0
- data/lib/vizsla/timer.rb +60 -0
- data/lib/vizsla/version.rb +3 -0
- data/lib/vizsla/worker_process_monitor.rb +25 -0
- data/lib/vizsla.rb +17 -0
- data/vizsla.gemspec +29 -0
- metadata +154 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
::Mysql2::Client.class_eval do
|
2
|
+
alias_method :query_without_vizsla, :query
|
3
|
+
|
4
|
+
def query(*args, &block)
|
5
|
+
start_time = Time.now
|
6
|
+
result = query_without_vizsla(*args, &block)
|
7
|
+
end_time = Time.now
|
8
|
+
|
9
|
+
event_data = [
|
10
|
+
'sql.mysql2_query',
|
11
|
+
start_time,
|
12
|
+
end_time,
|
13
|
+
{
|
14
|
+
sql: args[0]
|
15
|
+
}
|
16
|
+
]
|
17
|
+
|
18
|
+
::Vizsla::Patches.handle_event :mysql2, event_data
|
19
|
+
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
::PG::Connection.class_eval do
|
2
|
+
alias_method :exec_without_vizsla, :exec
|
3
|
+
alias_method :exec_params_without_vizsla, :exec_params
|
4
|
+
|
5
|
+
def exec_params(*args, &block)
|
6
|
+
start_time = Time.now
|
7
|
+
result = exec_params_without_vizsla(*args, &block)
|
8
|
+
end_time = Time.now
|
9
|
+
|
10
|
+
event_data = [
|
11
|
+
'sql.postgres_exec',
|
12
|
+
start_time,
|
13
|
+
end_time,
|
14
|
+
{
|
15
|
+
sql: args[0]
|
16
|
+
}
|
17
|
+
]
|
18
|
+
|
19
|
+
::Vizsla::Patches.handle_event :postgres, event_data
|
20
|
+
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
def exec(*args, &block)
|
25
|
+
start_time = Time.now
|
26
|
+
result = exec_without_vizsla(*args, &block)
|
27
|
+
end_time = Time.now
|
28
|
+
|
29
|
+
event_data = [
|
30
|
+
'sql.postgres_exec',
|
31
|
+
start_time,
|
32
|
+
end_time,
|
33
|
+
{
|
34
|
+
sql: args[0]
|
35
|
+
}
|
36
|
+
]
|
37
|
+
|
38
|
+
::Vizsla::Patches.handle_event :postgres, event_data
|
39
|
+
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'vizsla/patches'
|
2
|
+
require 'vizsla/system_health_sample'
|
3
|
+
require 'concurrent'
|
4
|
+
|
5
|
+
require 'sidekiq/launcher'
|
6
|
+
|
7
|
+
::Sidekiq::Launcher.class_eval do
|
8
|
+
alias_method :run_without_vizsla, :run
|
9
|
+
alias_method :stop_without_vizsla, :stop
|
10
|
+
|
11
|
+
def run
|
12
|
+
@vizsla_task = Concurrent::TimerTask.new(execution_interval: 10) do
|
13
|
+
health = Vizsla::SystemHealthSample.new process: :worker
|
14
|
+
::Vizsla::Patches.handle_event :sidekiq_health, health
|
15
|
+
end
|
16
|
+
|
17
|
+
@vizsla_task.execute
|
18
|
+
|
19
|
+
run_without_vizsla
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
@vizsla_task.shutdown
|
24
|
+
|
25
|
+
stop_without_vizsla
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
::Sinatra::Base.class_eval do
|
2
|
+
alias_method :dispatch_without_vizsla!, :dispatch!
|
3
|
+
|
4
|
+
def dispatch!(*args, &block)
|
5
|
+
start_time = Time.now
|
6
|
+
result = dispatch_without_vizsla!(*args, *block)
|
7
|
+
end_time = Time.now
|
8
|
+
route = env['sinatra.route']
|
9
|
+
|
10
|
+
event_data = [
|
11
|
+
'sinatra.route_exec',
|
12
|
+
start_time,
|
13
|
+
end_time,
|
14
|
+
route
|
15
|
+
]
|
16
|
+
|
17
|
+
::Vizsla::Patches.handle_event :sinatra, event_data
|
18
|
+
|
19
|
+
result
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'vizsla/helpers'
|
2
|
+
|
3
|
+
module Vizsla
|
4
|
+
##
|
5
|
+
# This singleton class handles patching for any given library we wish to
|
6
|
+
# instrument. To create a new patch for a library, just create a file in the
|
7
|
+
# +lib/patches+ directory with any name. These files typically contain code
|
8
|
+
# that will monkeypatch a given library. When you wish to execute the code
|
9
|
+
# in that file, just call its corresponding +patch_+ method. For example, if
|
10
|
+
# we have a file +lib/patches/foo.rb+, then we would just call:
|
11
|
+
#
|
12
|
+
# ::Vizsla::Patches.patch_foo
|
13
|
+
#
|
14
|
+
class Patches
|
15
|
+
include ::Vizsla::Helpers
|
16
|
+
|
17
|
+
PATCH_METHOD_REGEX = /^patch_(.*)$/
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def handle_event(handler_name, event_data)
|
21
|
+
handler = instance_variable_get "@#{handler_name}_event_handler"
|
22
|
+
handler.call event_data unless handler.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(method_sym, *args, &block)
|
26
|
+
if method_sym.to_s =~ PATCH_METHOD_REGEX
|
27
|
+
patch_name = $1
|
28
|
+
instance_variable_set "@#{patch_name}_event_handler", block
|
29
|
+
require "vizsla/patches/#{patch_name}"
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_to?(method_sym, include_private = false)
|
36
|
+
if method_sym.to_s =~ PATCH_METHOD_REGEX
|
37
|
+
true
|
38
|
+
else
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'vizsla/logger'
|
2
|
+
require 'vizsla/reporter'
|
3
|
+
|
4
|
+
module Vizsla
|
5
|
+
class PuppetMaster
|
6
|
+
def initialize(puppet, options = {})
|
7
|
+
@puppet = puppet
|
8
|
+
@logger = RequestLogger.new(options[:logger])
|
9
|
+
@storage = ::Vizsla::Agent.storage
|
10
|
+
end
|
11
|
+
|
12
|
+
def process
|
13
|
+
# @logger.display_payload @puppet.payload
|
14
|
+
@storage << @puppet.payload
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Vizsla
|
2
|
+
class Recorder
|
3
|
+
THREAD_LOCAL_KEY = :_vizsla_current
|
4
|
+
LOCK = Mutex.new
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def current
|
8
|
+
LOCK.synchronize do
|
9
|
+
Thread.current[THREAD_LOCAL_KEY]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def current=(val)
|
14
|
+
Thread.current[THREAD_LOCAL_KEY] = val
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_recording
|
18
|
+
self.current = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def recording?
|
22
|
+
!self.current.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_event(event)
|
26
|
+
return unless self.recording?
|
27
|
+
self.current[:events] ||= []
|
28
|
+
self.current[:events] << event.data_hash
|
29
|
+
end
|
30
|
+
alias_method :<<, :add_event
|
31
|
+
|
32
|
+
def events
|
33
|
+
self.current[:events]
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop_recording
|
37
|
+
LOCK.synchronize do
|
38
|
+
Thread.current[THREAD_LOCAL_KEY] = nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'concurrent'
|
4
|
+
|
5
|
+
module Vizsla
|
6
|
+
class Reporter
|
7
|
+
attr_reader :logger, :config, :storage
|
8
|
+
|
9
|
+
def initialize(storage = Vizsla::Agent.storage, config = Vizsla::Agent.config, logger = Vizsla::Agent.logger)
|
10
|
+
@logger = logger
|
11
|
+
@config = config
|
12
|
+
@storage = storage
|
13
|
+
|
14
|
+
host = Vizsla::Agent.config.host
|
15
|
+
path = Vizsla::Agent.config.report_path
|
16
|
+
@uri = URI("#{host}/#{path}")
|
17
|
+
|
18
|
+
@bin_id = Vizsla::Agent.config.bin_id
|
19
|
+
end
|
20
|
+
|
21
|
+
def start!
|
22
|
+
@task = Concurrent::TimerTask.new do
|
23
|
+
unless storage.unloaded?
|
24
|
+
payload = storage.unload
|
25
|
+
res = send_data payload
|
26
|
+
|
27
|
+
handle_response res, payload
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
logger.info 'TRACEBIN: Reporter starting.'
|
32
|
+
@task.execute
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop!
|
36
|
+
logger.info 'TRACEBIN: Reporter stopping. The agent will no longer report metrics to the server.'
|
37
|
+
@task.shutdown if @task && @task.running?
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def send_data(payload)
|
43
|
+
logger.info 'TRACEBIN: Sending analytics data to the server.'
|
44
|
+
|
45
|
+
Net::HTTP.start(@uri.host, @uri.port) do |http|
|
46
|
+
body = {
|
47
|
+
bin_id: @bin_id,
|
48
|
+
report: payload
|
49
|
+
}.to_json
|
50
|
+
|
51
|
+
req = Net::HTTP::Post.new @uri
|
52
|
+
req.content_type = 'application/json'
|
53
|
+
req.body = body
|
54
|
+
|
55
|
+
http.request req
|
56
|
+
end
|
57
|
+
rescue Exception => e
|
58
|
+
logger.warn "TRACEBIN: Exception occurred sending data to the server: #{e.message}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_response(res, payload)
|
62
|
+
case res
|
63
|
+
when Net::HTTPSuccess
|
64
|
+
logger.info 'TRACEBIN: Successfully sent payload to the server.'
|
65
|
+
when Net::HTTPBadRequest
|
66
|
+
logger.warn 'TRACEBIN: App bin ID not found. Please create a new app bin and add it to the config.'
|
67
|
+
self.stop!
|
68
|
+
else
|
69
|
+
logger.warn 'TRACEBIN: Failed to send data to the server. Will try again in 1 minute.'
|
70
|
+
@storage.add_payload payload
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
3
|
+
module Vizsla
|
4
|
+
class Storage
|
5
|
+
attr_reader :values
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@values = Concurrent::Array.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(payload)
|
12
|
+
@values << payload
|
13
|
+
end
|
14
|
+
alias_method :<<, :add
|
15
|
+
|
16
|
+
def add_payload(payload)
|
17
|
+
@values += payload if payload.is_a?(Array)
|
18
|
+
end
|
19
|
+
|
20
|
+
def unload
|
21
|
+
duplicate_values = @values.dup
|
22
|
+
@values.clear
|
23
|
+
duplicate_values
|
24
|
+
end
|
25
|
+
|
26
|
+
def unloaded?
|
27
|
+
@values.empty?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'vizsla/recorder'
|
2
|
+
require 'vizsla/patches'
|
3
|
+
require 'vizsla/events'
|
4
|
+
require 'vizsla/background_job_instrumentation'
|
5
|
+
|
6
|
+
module Vizsla
|
7
|
+
##
|
8
|
+
# Subscribes to certain events, handels them, and passes event data to the
|
9
|
+
# +Recorder+ class. The general workflow goes like this:
|
10
|
+
#
|
11
|
+
# 1. Patch the method you want to profile. It should generate an array that
|
12
|
+
# looks like the following:
|
13
|
+
#
|
14
|
+
# [
|
15
|
+
# "event_type.event_domain",
|
16
|
+
# start_time,
|
17
|
+
# stop_time,
|
18
|
+
# etc...,
|
19
|
+
# { event: :data }
|
20
|
+
# ]
|
21
|
+
#
|
22
|
+
# Note that the event hash must be the last element in the array (this is to
|
23
|
+
# maintain consistency with ActiveSupport::Notifications).
|
24
|
+
#
|
25
|
+
# 2. Store that event array into an appropriate +Event+ subclass.
|
26
|
+
# 3. Add each +Event+ object to +@events_data+ using the +#<<+ method.
|
27
|
+
#
|
28
|
+
class Subscribers
|
29
|
+
def initialize
|
30
|
+
@events_data = Recorder
|
31
|
+
collect_events_data
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def collect_events_data
|
37
|
+
if rails_app?
|
38
|
+
rails_hooks
|
39
|
+
else
|
40
|
+
other_hooks
|
41
|
+
end
|
42
|
+
|
43
|
+
background_job_hooks
|
44
|
+
end
|
45
|
+
|
46
|
+
def rails_hooks
|
47
|
+
sql_hook
|
48
|
+
process_action_hook
|
49
|
+
render_layout_hook
|
50
|
+
render_template_hook
|
51
|
+
render_partial_hook
|
52
|
+
end
|
53
|
+
|
54
|
+
def other_hooks
|
55
|
+
sinatra_hook if sinatra_app?
|
56
|
+
db_hooks
|
57
|
+
background_job_hooks
|
58
|
+
end
|
59
|
+
|
60
|
+
def background_job_hooks
|
61
|
+
if defined? ::ActiveJob
|
62
|
+
active_job_hook
|
63
|
+
else
|
64
|
+
sidekiq_hook
|
65
|
+
resque_hook
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def db_hooks
|
70
|
+
postgres_hook
|
71
|
+
mysql2_hook
|
72
|
+
end
|
73
|
+
|
74
|
+
# ===---------------------------===
|
75
|
+
# Rails Hooks
|
76
|
+
# ===---------------------------===
|
77
|
+
|
78
|
+
def sql_hook
|
79
|
+
subscribe_asn 'sql.active_record', SQLEvent
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_action_hook
|
83
|
+
subscribe_asn 'process_action.action_controller', ControllerEvent
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_layout_hook
|
87
|
+
unless [ActionPack::VERSION::MAJOR, ActionPack::VERSION::MINOR] == [3, 0]
|
88
|
+
::Vizsla::Patches.patch_action_view_layout do |event_data|
|
89
|
+
event = ViewEvent.new event_data
|
90
|
+
@events_data << event
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def render_template_hook
|
96
|
+
subscribe_asn 'render_template.action_view', ViewEvent
|
97
|
+
end
|
98
|
+
|
99
|
+
def render_partial_hook
|
100
|
+
subscribe_asn 'render_partial.action_view', ViewEvent
|
101
|
+
end
|
102
|
+
|
103
|
+
def active_job_hook
|
104
|
+
::Vizsla::BackgroundJobInstrumentation.install :active_job
|
105
|
+
end
|
106
|
+
|
107
|
+
# ===---------------------------===
|
108
|
+
# DB Hooks
|
109
|
+
# ===---------------------------===
|
110
|
+
|
111
|
+
def postgres_hook
|
112
|
+
return unless defined? ::PG
|
113
|
+
::Vizsla::Patches.patch_postgres do |event_data|
|
114
|
+
event = SQLEvent.new event_data
|
115
|
+
@events_data << event
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def mysql2_hook
|
121
|
+
return unless defined? ::Mysql2
|
122
|
+
::Vizsla::Patches.patch_mysql2 do |event_data|
|
123
|
+
event = SQLEvent.new event_data
|
124
|
+
@events_data << event
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# ===---------------------------===
|
129
|
+
# Background Job Hooks
|
130
|
+
# ===---------------------------===
|
131
|
+
|
132
|
+
def sidekiq_hook
|
133
|
+
return unless defined? ::Sidekiq
|
134
|
+
::Vizsla::BackgroundJobInstrumentation.install :sidekiq
|
135
|
+
end
|
136
|
+
|
137
|
+
def resque_hook
|
138
|
+
return unless defined? ::Resque
|
139
|
+
::Vizsla::BackgroundJobInstrumentation.install :resque
|
140
|
+
end
|
141
|
+
|
142
|
+
# ===---------------------------===
|
143
|
+
# Sinatra Hooks
|
144
|
+
# ===---------------------------===
|
145
|
+
|
146
|
+
def sinatra_hook
|
147
|
+
::Vizsla::Patches.patch_sinatra do |event_data|
|
148
|
+
event = SinatraEvent.new event_data
|
149
|
+
@events_data << event
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# ===---------------------------===
|
154
|
+
# Aux
|
155
|
+
# ===---------------------------===
|
156
|
+
|
157
|
+
def subscribe_asn(event_name, event_klass)
|
158
|
+
ActiveSupport::Notifications.subscribe event_name do |*args|
|
159
|
+
event = event_klass.new args
|
160
|
+
@events_data << event if event.valid?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def rails_app?
|
165
|
+
defined? ::Rails
|
166
|
+
end
|
167
|
+
|
168
|
+
def sinatra_app?
|
169
|
+
defined? ::Sinatra
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module Vizsla
|
2
|
+
class SystemHealthSample
|
3
|
+
DATA_TYPE = 'system_health_sample'.freeze
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@process = options[:process] || :web
|
7
|
+
@sampled_at = Time.new
|
8
|
+
@metrics = sample_metrics
|
9
|
+
end
|
10
|
+
|
11
|
+
def payload
|
12
|
+
{
|
13
|
+
type: DATA_TYPE,
|
14
|
+
|
15
|
+
data: {
|
16
|
+
sampled_at: @sampled_at,
|
17
|
+
|
18
|
+
metrics: @metrics
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def sample_metrics
|
26
|
+
{
|
27
|
+
process: @process.to_s,
|
28
|
+
cpu: processor_info,
|
29
|
+
memory: mem_info,
|
30
|
+
disks: disk_info,
|
31
|
+
machine_id: machine_info
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def ruby_os_identifier
|
36
|
+
RbConfig::CONFIG['target_os']
|
37
|
+
end
|
38
|
+
|
39
|
+
def darwin?
|
40
|
+
!!(ruby_os_identifier =~ /darwin/i)
|
41
|
+
end
|
42
|
+
|
43
|
+
def linux?
|
44
|
+
!!(ruby_os_identifier =~ /linux/i)
|
45
|
+
end
|
46
|
+
|
47
|
+
def machine_info
|
48
|
+
ip_string = `dig +short myip.opendns.com @resolver1.opendns.com`.strip
|
49
|
+
hostname = `hostname`.strip
|
50
|
+
|
51
|
+
kernel = nil
|
52
|
+
|
53
|
+
if darwin?
|
54
|
+
kernel = 'darwin'
|
55
|
+
elsif linux?
|
56
|
+
kernel = 'linux'
|
57
|
+
end
|
58
|
+
|
59
|
+
{
|
60
|
+
hostname: hostname,
|
61
|
+
ip: ip_string,
|
62
|
+
kernel: kernel
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def disk_info(disks = ["disk0"])
|
67
|
+
if darwin?
|
68
|
+
read_iostat(disks)
|
69
|
+
elsif linux?
|
70
|
+
parse_proc
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_proc
|
75
|
+
{
|
76
|
+
load_average: `cat /proc/loadavg | awk '{print $1,$2,$3}'`.strip
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_iostat(disks)
|
81
|
+
disks_info = {}
|
82
|
+
captured_data = []
|
83
|
+
|
84
|
+
disks.each do |disk|
|
85
|
+
disk_data = `iostat -Ud #{disk}`.strip.split(/\n/)
|
86
|
+
captured_data << disk_data
|
87
|
+
disks_info["load_average"] = disk_data.last.strip.split(/\s+/)[3..-1].map(&:to_f) unless disks_info["load_average"]
|
88
|
+
end
|
89
|
+
|
90
|
+
captured_data.each_with_index do |disk, index|
|
91
|
+
disk_name = disks[index]
|
92
|
+
disk_stats = disk.last.strip.split(/\s+/)[0, 3]
|
93
|
+
disks_info[disk_name] = {
|
94
|
+
kb_per_trans: disk_stats[0].to_f,
|
95
|
+
trans_num: disk_stats[1].to_f,
|
96
|
+
mb_per_sec: disk_stats[2].to_f
|
97
|
+
}
|
98
|
+
end
|
99
|
+
disks_info
|
100
|
+
end
|
101
|
+
|
102
|
+
def mem_info
|
103
|
+
if darwin?
|
104
|
+
total, wired, free, used = get_mach_memory_stats
|
105
|
+
return {
|
106
|
+
total_memory: total,
|
107
|
+
wired_memory: wired,
|
108
|
+
free_memory: free,
|
109
|
+
used_memory: used
|
110
|
+
}
|
111
|
+
elsif linux?
|
112
|
+
total, cache, free, used, available = get_linux_memory_stats
|
113
|
+
return {
|
114
|
+
total_memory: total,
|
115
|
+
wired_memory: cache,
|
116
|
+
free_memory: free,
|
117
|
+
used_memory: used,
|
118
|
+
available_memory: available
|
119
|
+
}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_mach_memory_stats
|
124
|
+
used, wired, free = `top -l 1 -s 0 | grep PhysMem`.scan(/\d+/)
|
125
|
+
total = `sysctl -n hw.memsize`.to_i / 1024 / 1024
|
126
|
+
[total.to_i, wired.to_i, free.to_i, used.to_i]
|
127
|
+
end
|
128
|
+
|
129
|
+
def get_linux_memory_stats
|
130
|
+
total, used, free, _, cache, available = `free | grep Mem`.scan(/\d+/).map { |mem_stat| mem_stat.to_i / 1024 }
|
131
|
+
[total.to_s, cache.to_s, free.to_s, used.to_s, available.to_s]
|
132
|
+
end
|
133
|
+
|
134
|
+
def processor_info
|
135
|
+
info = {}
|
136
|
+
|
137
|
+
if darwin?
|
138
|
+
info[:model_name] = get_sysctl_value('machdep.cpu.brand_string')
|
139
|
+
info[:processor_count] = get_sysctl_value('hw.packages').to_i
|
140
|
+
info[:core_count] = get_sysctl_value('hw.physicalcpu_max').to_i,
|
141
|
+
info[:logical_cpu_count] = get_sysctl_value('hw.logicalcpu_max').to_i
|
142
|
+
elsif linux?
|
143
|
+
proc_string = read_proc('/proc/cpuinfo')
|
144
|
+
info = parse_proc_cpuinfo_string(proc_string)
|
145
|
+
end
|
146
|
+
info
|
147
|
+
end
|
148
|
+
|
149
|
+
def get_sysctl_value(key)
|
150
|
+
`sysctl -n #{key} 2>/dev/null`
|
151
|
+
end
|
152
|
+
|
153
|
+
def read_proc(path)
|
154
|
+
return nil unless File.exist? path
|
155
|
+
`cat #{path} 2>/dev/null`
|
156
|
+
end
|
157
|
+
|
158
|
+
def parse_proc_cpuinfo_string(proc_string)
|
159
|
+
threads = proc_string.split(/\n\n/).map { |core| core.split(/\n/) }
|
160
|
+
|
161
|
+
units = {}
|
162
|
+
cores = {}
|
163
|
+
model_name = nil
|
164
|
+
|
165
|
+
threads.each do |thread|
|
166
|
+
thread.each do |line|
|
167
|
+
if matched_line = line.match(/physical\ id\s*\:\s*(\d)/i)
|
168
|
+
id = matched_line[1]
|
169
|
+
units[id] = true
|
170
|
+
elsif matched_line = line.match(/core\ id\s*\:\s*(\d)/i)
|
171
|
+
id = matched_line[1]
|
172
|
+
cores[id] = true
|
173
|
+
elsif matched_line = line.match(/model\ name\s*\:\s*(.*)/i)
|
174
|
+
model_name = matched_line[1] unless model_name
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
{
|
180
|
+
model_name: model_name,
|
181
|
+
processor_count: units.count,
|
182
|
+
core_count: cores.count,
|
183
|
+
logical_cpu_count: threads.count
|
184
|
+
}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|