process-instrumentation 0.1.1

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.
@@ -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: []