process-instrumentation 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ module Instrumentation
2
+ # Reads RSS memory from the PID specified
3
+ class Memory
4
+ def initialize(pid)
5
+ @pid = pid
6
+ end
7
+
8
+ def read
9
+ case system
10
+ when :mac_os
11
+ rss_via_ps
12
+ else
13
+ raise "Unknown system #{system.inspect}"
14
+ end
15
+ end
16
+
17
+ def system
18
+ :mac_os
19
+ end
20
+
21
+ def rss_via_ps
22
+ `ps -o rss= -p #{@pid}`.strip.to_i
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'tubesock'
2
+
3
+ module Instrumentation
4
+ # Main Rack application that serves the `websocket` data or the index page
5
+ class RackApp
6
+ def initialize(report)
7
+ @report = report
8
+ @view = Instrumentation::View.new
9
+ end
10
+
11
+ def call(env)
12
+ if env['HTTP_UPGRADE'] == 'websocket'
13
+ socket = Tubesock.hijack(env)
14
+ socket.listen
15
+ @report.socket = socket
16
+ [-1, {}, []]
17
+ else
18
+ ['200', { 'Content-Type' => 'text/html' }, [@view.render]]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,61 @@
1
+ module Instrumentation
2
+ # Reads data from system and process information and writes it to
3
+ # websocket
4
+ class Report
5
+ attr_reader :memory, :loadavg
6
+ attr_accessor :socket
7
+
8
+ def initialize(pid)
9
+ @pid = pid
10
+ @sleep = 1
11
+ @memory = BoundedArray.new(300)
12
+ @loadavg = BoundedArray.new(300)
13
+ @socket = nil
14
+ end
15
+
16
+ def start
17
+ @thread = Thread.new do
18
+ loop do
19
+ read_data
20
+ send_data if socket
21
+ sleep @sleep
22
+ end
23
+ end
24
+ end
25
+
26
+ def read_data
27
+ now = Time.now.strftime('%FT%T')
28
+ @memory = @memory << [now, read_memory]
29
+ @loadavg = @loadavg << [now, read_loadavg]
30
+ rescue => error
31
+ puts "Error when reading data: #{error.inspect}"
32
+ end
33
+
34
+ def send_data
35
+ socket.send_data(event(:memory, @memory.items))
36
+ socket.send_data(event(:loadavg, @loadavg.items))
37
+ rescue => error
38
+ puts "Error when sending data: #{error.inspect}"
39
+ end
40
+
41
+ def event(name, data)
42
+ { data_type: name, data: data }.to_json
43
+ end
44
+
45
+ def read_memory
46
+ Memory.new(@pid).read
47
+ end
48
+
49
+ def read_loadavg
50
+ LoadAverage.new.read[:one]
51
+ end
52
+
53
+ def join
54
+ @thread.join
55
+ end
56
+
57
+ def shutdown
58
+ @thread.kill
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,3 @@
1
+ module Instrumentation
2
+ VERSION = '0.1.1'.freeze
3
+ end
@@ -0,0 +1,17 @@
1
+ module Instrumentation
2
+ # Handles rendering of ERB templates and asset loading
3
+ class View
4
+ include Erb::View
5
+ template :index
6
+
7
+ def asset(path)
8
+ File.read(asset_root.join('lib/assets', path))
9
+ end
10
+
11
+ private
12
+
13
+ def asset_root
14
+ Instrumentation.root
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ module Instrumentation
2
+ # Webserver using `puma` that handles requests for stats
3
+ class Webserver
4
+ def initialize
5
+ @worker = nil
6
+ end
7
+
8
+ def run(app, opts = {})
9
+ config = build_config(opts.merge(app: app))
10
+
11
+ @launcher = Puma::Launcher.new(config, events: Puma::Events.stdio)
12
+ @worker = Thread.new { @launcher.run }
13
+ end
14
+
15
+ def join
16
+ @worker.join
17
+ end
18
+
19
+ def stop
20
+ @launcher.stop
21
+ @worker.kill
22
+ end
23
+
24
+ private
25
+
26
+ def build_config(opts)
27
+ Puma::Configuration.new do |c|
28
+ host = opts.fetch(:host, Puma::Configuration::DefaultTCPHost)
29
+ port = opts.fetch(:port, Puma::Configuration::DefaultTCPPort)
30
+
31
+ c.port port, host
32
+ c.app opts.fetch(:app)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ require 'instrumentation/version'
2
+ require 'pathname'
3
+ require 'json'
4
+ require 'rack'
5
+ require 'puma'
6
+ require 'erb/view'
7
+
8
+ Erb.root = Pathname.new(__FILE__).join('..', '..').join('lib/templates')
9
+
10
+ # Instrumentation
11
+ #
12
+ # Start a server by calling:
13
+ # Instrumentation.start_server(Process.pid)
14
+ #
15
+ # By default the server is started on http://localhost:8080
16
+ module Instrumentation
17
+ def start_server(pid:, port: 8080)
18
+ report = Report.new(pid)
19
+ app = RackApp.new(report)
20
+ server = Webserver.new
21
+
22
+ report.start
23
+ server.run(app, port: port)
24
+
25
+ [server, report].map(&:join)
26
+ rescue Interrupt => _
27
+ print "\n=> Shutting down instrumentation.\n"
28
+ report.shutdown
29
+ server.stop
30
+ end
31
+
32
+ def root
33
+ Pathname.new(__FILE__).join('..', '..')
34
+ end
35
+
36
+ module_function :start_server, :root
37
+ end
38
+
39
+ require 'instrumentation/bounded_array'
40
+ require 'instrumentation/memory'
41
+ require 'instrumentation/load_average'
42
+ require 'instrumentation/rack_app'
43
+ require 'instrumentation/report'
44
+ require 'instrumentation/view'
45
+ require 'instrumentation/webserver'
@@ -0,0 +1,31 @@
1
+ <!DOCTYPE html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <style type="text/css"><%= asset('styles.css') %></style>
5
+ </head>
6
+ <body>
7
+ <div class="content">
8
+ <div class="container">
9
+ <div class="stats-column">
10
+ <h5>Memory Usage (RSS)</h5>
11
+ <h1 id="last-memory">-</h1>
12
+ </div>
13
+ <div class="chart-container">
14
+ <canvas id="memory" width="1000" height="300"></canvas>
15
+ </div>
16
+ </div>
17
+
18
+ <div class="container">
19
+ <div class="stats-column">
20
+ <h5>One Minute Load Avg</h5>
21
+ <h1 id="last-loadavg">-</h1>
22
+ </div>
23
+ <div class="chart-container">
24
+ <canvas id="loadavg" width="1000" height="300"></canvas>
25
+ </div>
26
+ </div>
27
+ </div>
28
+
29
+ <script charset="utf-8"><%= asset('chart.min.js') %></script>
30
+ <script charset="utf-8"><%= asset('application.js') %></script>
31
+ </body>
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: process-instrumentation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Felipe Philipp
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-02-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '11.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '11.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-rg
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 2.0.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 2.0.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: tubesock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.7
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.7
97
+ - !ruby/object:Gem::Dependency
98
+ name: puma
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 3.6.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 3.6.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: erb-view
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.1'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.1'
125
+ description: Monitor process memory and CPU over time
126
+ email:
127
+ - felipeelias@gmail.com
128
+ executables:
129
+ - instrument
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - ".gitignore"
134
+ - ".travis.yml"
135
+ - CODE_OF_CONDUCT.md
136
+ - Gemfile
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - bin/console
141
+ - bin/setup
142
+ - example.gif
143
+ - exe/instrument
144
+ - instrumentation.gemspec
145
+ - lib/assets/application.js
146
+ - lib/assets/chart.min.js
147
+ - lib/assets/styles.css
148
+ - lib/instrumentation.rb
149
+ - lib/instrumentation/bounded_array.rb
150
+ - lib/instrumentation/load_average.rb
151
+ - lib/instrumentation/memory.rb
152
+ - lib/instrumentation/rack_app.rb
153
+ - lib/instrumentation/report.rb
154
+ - lib/instrumentation/version.rb
155
+ - lib/instrumentation/view.rb
156
+ - lib/instrumentation/webserver.rb
157
+ - lib/templates/index.erb
158
+ homepage: https://github.com/felipeelias/instrumentation
159
+ licenses:
160
+ - MIT
161
+ metadata: {}
162
+ post_install_message:
163
+ rdoc_options: []
164
+ require_paths:
165
+ - lib
166
+ required_ruby_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ requirements: []
177
+ rubyforge_project:
178
+ rubygems_version: 2.5.1
179
+ signing_key:
180
+ specification_version: 4
181
+ summary: Monitor process stats over time
182
+ test_files: []