rorvswild 1.5.12 → 1.5.15

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48093e6f93439e8216bd5b0c631b6b56386281d9792f728312d227634834ee11
4
- data.tar.gz: 9d7cc84527fc054d47e17ed8f8c736c119660f59d7f077a24b420588875e0685
3
+ metadata.gz: 29d23fa806fe2d9547169a35795b4b313bf7510264aba4d0f8f3471ae7acf047
4
+ data.tar.gz: 681d88bef1fb76ede952387ef1d8ffbdf4bfa4cbf08f990f7964624401ff71a9
5
5
  SHA512:
6
- metadata.gz: 55d90ef1666a3e5696cfbf998a13446d8dc2cddeccf05d56d5dcc6aa3855e5bd5443eb0dccd0b2d54e6af00b1e5babce359fabd2899a4a624a0fcb9f5ab1a5c4
7
- data.tar.gz: 909982650664ed49fed2103c7e031ae761ccea22eb5fd39bb82b85e2e3996fd7ec28b891b2151d41ebeadae6f2076ff3c14d895e0a552c006e444e6178d77754
6
+ metadata.gz: e54dba0143bdb8e94677519698857c7ddc62f91ecb0a876ecc9e0e2fb402e4d11055a59f03cca1c2ca5a2e73b82cf5380574d9e3e4c73d1a66e21c7ea54cfe6c
7
+ data.tar.gz: 92d4cb2e15015929ccc609b6b81837fb1a78ea12ebe4538555e0a023266fc852001d1aa17d7e620b50dd6ac3f856cbf08bba1d3cb9e4f24f10333b1faa26064d
data/README.md CHANGED
@@ -89,24 +89,22 @@ If you are using `Rack::Deflater` middleware you won't see the small button in t
89
89
  *RoRvsWild.com* makes it easy to monitor requests, background jobs and errors in your production and staging environment.
90
90
  It also comes with some extra options listed below.
91
91
 
92
- #### Measure any code
92
+ #### Measure any section of code
93
93
 
94
- You can measure any code like this (useful to monitor cronjobs):
94
+ RorVsWild measures a lot of events such as SQL queries. But it might not be enough for you. There is a solution to measure any section of code to help you find the most hidden bottlenecks.
95
95
 
96
96
  ```ruby
97
- RorVsWild.measure_code("User.all.do_something_great")
98
- ```
97
+ # Measure a code given as a string
98
+ RorVsWild.measure("bubble_sort(array)")
99
99
 
100
- Or like that:
100
+ # Measure a code given as a block
101
+ RorVsWild.measure { bubble_sort(array) }
101
102
 
102
- ```ruby
103
- RorVsWild.measure_block("A great job name") { User.all.do_something_great }
103
+ # Measure a code given as a block with an optional description
104
+ RorVsWild.measure("Optional description") { bubble_sort(array) }
104
105
  ```
105
106
 
106
- Then it will appear in the jobs page.
107
-
108
- Note that Calling `measure_code` or `measure_block` inside or a request or a job will add a section.
109
- That is convenient to profile finely parts of your code.
107
+ For each custom measure, a section is added with the file name and line number where it has been called.
110
108
 
111
109
  #### Send errors manually
112
110
 
@@ -136,6 +134,18 @@ RorVsWild.record_error(exception, {something: "important"})
136
134
  RorVsWild.catch_error(something: "important") { 1 / 0 }
137
135
  ```
138
136
 
137
+ It is also possible to pre-fill this context data at the begining of each request or job :
138
+
139
+ ```ruby
140
+ class ApplicationController < ActionController::Base
141
+ before_action :prefill_error_context
142
+
143
+ def prefill_error_context
144
+ RorVsWild.merge_error_context(something: "important")
145
+ end
146
+ end
147
+ ```
148
+
139
149
  #### Ignore requests, jobs, exceptions and plugins
140
150
 
141
151
  From the configuration file, you can tell RorVsWild to skip monitoring some requests, jobs, exceptions and plugins.
@@ -208,6 +218,30 @@ In the case you want a custom logger such as Syslog, you can only do it by initi
208
218
  RorVsWild.start(api_key: "API_KEY", logger: Logger::Syslog.new)
209
219
  ```
210
220
 
221
+ #### Server metrics monitoring
222
+
223
+ We are adding server metrics as a beta feature.
224
+ It monitors load average, CPU, memory, swap and disk space.
225
+ For now, only Linux is supported.
226
+ It has to be explicitly enabled with a feature flag :
227
+
228
+ ```yaml
229
+ # config/rorvswild.yml
230
+ production:
231
+ api_key: API_KEY
232
+ features:
233
+ - server_metrics
234
+ ```
235
+
236
+ Here is the equivalent if you prefer initialising RorVsWild manually :
237
+
238
+ ```ruby
239
+ # config/initializers/rorvswild.rb
240
+ RorVsWild.start(api_key: "API_KEY", features: ["server_metrics"])
241
+ ```
242
+
243
+ The data are available in a server tab beside requests and jobs.
244
+
211
245
  ## Contributing
212
246
 
213
247
  1. Fork it ( https://github.com/[my-github-username]/rorvswild/fork )
@@ -16,7 +16,7 @@ module RorVsWild
16
16
 
17
17
  def self.default_ignored_exceptions
18
18
  if defined?(Rails)
19
- %w[ActionController::RoutingError] + Rails.application.config.action_dispatch.rescue_responses.map { |(key,value)| key }
19
+ ActionDispatch::ExceptionWrapper.rescue_responses.keys
20
20
  else
21
21
  []
22
22
  end
@@ -26,6 +26,7 @@ module RorVsWild
26
26
 
27
27
  def initialize(config)
28
28
  @config = self.class.default_config.merge(config)
29
+ load_features
29
30
  @client = Client.new(@config)
30
31
  @queue = config[:queue] || Queue.new(client)
31
32
  @locator = RorVsWild::Locator.new
@@ -35,6 +36,12 @@ module RorVsWild
35
36
  cleanup_data
36
37
  end
37
38
 
39
+ def load_features
40
+ features = config[:features] || []
41
+ features.include?("server_metrics")
42
+ require "rorvswild/metrics" if features.include?("server_metrics")
43
+ end
44
+
38
45
  def setup_plugins
39
46
  for name in RorVsWild::Plugin.constants
40
47
  next if config[:ignore_plugins] && config[:ignore_plugins].include?(name.to_s)
@@ -49,7 +56,7 @@ module RorVsWild
49
56
  measure_block(code) { eval(code) }
50
57
  end
51
58
 
52
- def measure_block(name, kind = "code".freeze, &block)
59
+ def measure_block(name = nil, kind = "code".freeze, &block)
53
60
  current_data ? measure_section(name, kind: kind, &block) : measure_job(name, &block)
54
61
  end
55
62
 
@@ -113,6 +120,18 @@ module RorVsWild
113
120
  current_data[:error]
114
121
  end
115
122
 
123
+ def merge_error_context(hash)
124
+ self.error_context = error_context ? error_context.merge(hash) : hash
125
+ end
126
+
127
+ def error_context
128
+ current_data[:error_context] if current_data
129
+ end
130
+
131
+ def error_context=(hash)
132
+ current_data[:error_context] = hash if current_data
133
+ end
134
+
116
135
  def current_data
117
136
  Thread.current[:rorvswild_data]
118
137
  end
@@ -134,6 +153,12 @@ module RorVsWild
134
153
  config[:ignore_jobs].include?(name)
135
154
  end
136
155
 
156
+ def os_description
157
+ @os_description ||= `uname -sr`
158
+ rescue Exception => ex
159
+ @os_description = RbConfig::CONFIG["host_os"]
160
+ end
161
+
137
162
  #######################
138
163
  ### Private methods ###
139
164
  #######################
@@ -162,15 +187,16 @@ module RorVsWild
162
187
  client.post_async("/errors".freeze, error: hash)
163
188
  end
164
189
 
165
- def exception_to_hash(exception, extra_details = nil)
190
+ def exception_to_hash(exception, context = nil)
166
191
  file, line = locator.find_most_relevant_file_and_line_from_exception(exception)
192
+ context = context ? error_context.merge(context) : error_context if error_context
167
193
  {
168
194
  line: line.to_i,
169
195
  file: locator.relative_path(file),
170
196
  message: exception.message,
171
197
  backtrace: exception.backtrace || ["No backtrace"],
172
198
  exception: exception.class.to_s,
173
- extra_details: extra_details,
199
+ extra_details: context,
174
200
  environment: {
175
201
  os: os_description,
176
202
  user: Etc.getlogin,
@@ -186,11 +212,5 @@ module RorVsWild
186
212
  def ignored_exception?(exception)
187
213
  (config[:ignored_exceptions] || config[:ignore_exceptions]).include?(exception.class.to_s)
188
214
  end
189
-
190
- def os_description
191
- @os_description ||= `uname -a`
192
- rescue Exception => ex
193
- @os_description = RUBY_PLATFORM
194
- end
195
215
  end
196
216
  end
@@ -0,0 +1,31 @@
1
+ module RorVsWild
2
+ class Metrics
3
+ class Cpu
4
+ attr_reader :user, :system, :idle, :waiting, :stolen
5
+ attr_reader :load_average, :count
6
+
7
+ def update
8
+ if vmstat = execute(:vmstat)
9
+ vmstat = vmstat.split("\n").last.split
10
+ @user = vmstat[12].to_i
11
+ @system = vmstat[13].to_i
12
+ @idle = vmstat[14].to_i
13
+ @waiting = vmstat[15].to_i
14
+ @stolen = vmstat[16].to_i
15
+ end
16
+ if uptime = execute(:uptime)
17
+ @load_average = uptime.split[-3].to_f
18
+ end
19
+ if nproc = execute(:nproc)
20
+ @count = nproc.to_i
21
+ end
22
+ end
23
+
24
+ def execute(command)
25
+ `#{command}`
26
+ rescue => ex
27
+ nil
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,57 @@
1
+ module RorVsWild
2
+ class Metrics
3
+ class Memory
4
+ attr_reader :ram_total, :ram_free, :ram_available, :ram_buffers, :ram_cached
5
+ attr_reader :swap_total, :swap_free
6
+ attr_reader :storage_total, :storage_used
7
+
8
+ def ram_used
9
+ ram_total - ram_available
10
+ end
11
+
12
+ def swap_used
13
+ swap_total - swap_free
14
+ end
15
+
16
+ PROC_MEMINFO = "/proc/meminfo".freeze
17
+ MEM_TOTAL = "MemTotal" # Total usable RAM (i.e., physical RAM minus a few reserved bits and the kernel binary code).
18
+ MEM_FREE = "MemFree" # The sum of LowFree+HighFree.
19
+ MEM_AVAILABLE = "MemAvailable" # An estimate of how much memory is available for starting new applications, without swapping.
20
+ BUFFERS = "Buffers" # Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).
21
+ CACHED = "Cached" # In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
22
+ SWAP_TOTAL = "SwapTotal" # Total amount of swap space available.
23
+ SWAP_FREE = "SwapFree" # Amount of swap space that is currently unused.
24
+
25
+ def update
26
+ info = read_meminfo
27
+ @ram_total = convert_to_bytes(info[MEM_TOTAL])
28
+ @ram_free = convert_to_bytes(info[MEM_FREE])
29
+ @ram_available = convert_to_bytes(info[MEM_AVAILABLE])
30
+ @ram_buffers = convert_to_bytes(info[BUFFERS])
31
+ @ram_cached = convert_to_bytes(info[CACHED])
32
+ @swap_total = convert_to_bytes(info[SWAP_TOTAL])
33
+ @swap_free = convert_to_bytes(info[SWAP_FREE])
34
+ end
35
+
36
+ private
37
+
38
+ def units
39
+ @units ||= {"kb" => 1000, "mb" => 1000 * 1000, "gb" => 1000 * 1000 * 1000}.freeze
40
+ end
41
+
42
+ def read_meminfo
43
+ return unless File.readable?(PROC_MEMINFO)
44
+ File.read(PROC_MEMINFO).split("\n").reduce({}) do |hash, line|
45
+ name, value = line.split(":")
46
+ hash[name] = value.strip
47
+ hash
48
+ end
49
+ end
50
+
51
+ def convert_to_bytes(string)
52
+ value, unit = string.split
53
+ value.to_i * units[unit.downcase]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ module RorVsWild
2
+ class Metrics
3
+ class Storage
4
+ attr_reader :used, :free
5
+
6
+ def update
7
+ array = `df -k | grep " /$"`.split
8
+ @used = array[2].to_i * 1000
9
+ @free = array[3].to_i * 1000
10
+ end
11
+
12
+ def total
13
+ used + free
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,54 @@
1
+ module RorVsWild
2
+ class Metrics
3
+ UPDATE_INTERVAL_MS = 60_000 # One metric every minute
4
+
5
+ attr_reader :cpu, :memory, :storage, :updated_at
6
+
7
+ def initialize
8
+ @cpu = RorVsWild::Metrics::Cpu.new
9
+ @memory = RorVsWild::Metrics::Memory.new
10
+ @storage = RorVsWild::Metrics::Storage.new
11
+ end
12
+
13
+ def update
14
+ if staled?
15
+ cpu.update
16
+ memory.update
17
+ storage.update
18
+ @updated_at = RorVsWild.clock_milliseconds
19
+ end
20
+ end
21
+
22
+ def staled?
23
+ !updated_at || RorVsWild.clock_milliseconds - updated_at > UPDATE_INTERVAL_MS
24
+ end
25
+
26
+ def to_h
27
+ {
28
+ hostname: Socket.gethostname,
29
+ os: RorVsWild.agent.os_description,
30
+ cpu_user: cpu.user,
31
+ cpu_system: cpu.system,
32
+ cpu_idle: cpu.idle,
33
+ cpu_waiting: cpu.waiting,
34
+ cpu_stolen: cpu.stolen,
35
+ cpu_count: cpu.count,
36
+ load_average: cpu.load_average,
37
+ ram_total: memory.ram_total,
38
+ ram_free: memory.ram_free,
39
+ ram_used: memory.ram_used,
40
+ ram_cached: memory.ram_cached,
41
+ swap_total: memory.swap_total,
42
+ swap_used: memory.swap_used,
43
+ swap_free: memory.swap_free,
44
+ storage_total: storage.total,
45
+ storage_used: storage.used,
46
+ storage_free: storage.free,
47
+ }
48
+ end
49
+ end
50
+ end
51
+
52
+ require "rorvswild/metrics/cpu"
53
+ require "rorvswild/metrics/memory"
54
+ require "rorvswild/metrics/storage"
@@ -2,7 +2,6 @@ module RorVsWild
2
2
  module Plugin
3
3
  class NetHttp
4
4
  HTTP = "http".freeze
5
- HTTPS = "https".freeze
6
5
 
7
6
  def self.setup
8
7
  return if !defined?(Net::HTTP)
@@ -21,9 +20,7 @@ module RorVsWild
21
20
 
22
21
  def request_with_rorvswild(req, body = nil, &block)
23
22
  return request_without_rorvswild(req, body, &block) if request_called_twice?
24
- scheme = use_ssl? ? HTTPS : HTTP
25
- url = "#{req.method} #{scheme}://#{address}#{req.path}"
26
- RorVsWild.agent.measure_section(url, kind: HTTP) do
23
+ RorVsWild.agent.measure_section("#{req.method} #{address}", kind: HTTP) do
27
24
  request_without_rorvswild(req, body, &block)
28
25
  end
29
26
  end
@@ -18,7 +18,7 @@ module RorVsWild
18
18
  end
19
19
 
20
20
  def self.commands_to_string(commands)
21
- commands.map { |c| c[0] == :auth ? "auth *****".freeze : c.join(" ".freeze) }.join("\n".freeze)
21
+ commands.map { |c| c[0] }.join("\n".freeze)
22
22
  end
23
23
 
24
24
  APPENDABLE_COMMANDS = [:auth, :select]
@@ -1,6 +1,6 @@
1
1
  module RorVsWild
2
2
  class Queue
3
- SLEEP_TIME = 10
3
+ SLEEP_TIME = 30
4
4
  FLUSH_TRESHOLD = 10
5
5
 
6
6
  attr_reader :mutex, :thread, :client
@@ -11,6 +11,7 @@ module RorVsWild
11
11
  @requests = []
12
12
  @client = client
13
13
  @mutex = Mutex.new
14
+ @metrics = RorVsWild::Metrics.new if defined?(Metrics)
14
15
  Kernel.at_exit { flush }
15
16
  end
16
17
 
@@ -50,6 +51,10 @@ module RorVsWild
50
51
  result
51
52
  end
52
53
 
54
+ def pull_server_metrics
55
+ @metrics && @metrics.update && @metrics.to_h
56
+ end
57
+
53
58
  def flush_indefinetely
54
59
  sleep(SLEEP_TIME) and flush while true
55
60
  rescue Exception => ex
@@ -60,6 +65,7 @@ module RorVsWild
60
65
  def flush
61
66
  data = pull_jobs and client.post("/jobs", jobs: data)
62
67
  data = pull_requests and client.post("/requests", requests: data)
68
+ data = pull_server_metrics and client.post("/metrics", metrics: data)
63
69
  end
64
70
 
65
71
  def start_thread
@@ -1,3 +1,3 @@
1
1
  module RorVsWild
2
- VERSION = "1.5.12".freeze
2
+ VERSION = "1.5.15".freeze
3
3
  end
data/lib/rorvswild.rb CHANGED
@@ -23,6 +23,10 @@ module RorVsWild
23
23
  @logger ||= initialize_logger
24
24
  end
25
25
 
26
+ def self.measure(code_or_name = nil, &block)
27
+ block ? measure_block(code_or_name, &block) : measure_code(code_or_name)
28
+ end
29
+
26
30
  def self.measure_code(code)
27
31
  agent ? agent.measure_code(code) : eval(code)
28
32
  end
@@ -39,6 +43,10 @@ module RorVsWild
39
43
  agent.record_error(exception, extra_details) if agent
40
44
  end
41
45
 
46
+ def self.merge_error_context(hash)
47
+ agent.merge_error_context(hash) if agent
48
+ end
49
+
42
50
  def self.initialize_logger(destination = nil)
43
51
  if destination.respond_to?(:info) && destination.respond_to?(:warn) && destination.respond_to?(:error)
44
52
  destination
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rorvswild
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.12
4
+ version: 1.5.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Bernard
8
8
  - Antoine Marguerie
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-05-22 00:00:00.000000000 Z
12
+ date: 2022-08-17 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Performances and errors insights for rails developers.
15
15
  email:
@@ -40,6 +40,10 @@ files:
40
40
  - lib/rorvswild/local/stylesheet/local.css
41
41
  - lib/rorvswild/local/stylesheet/vendor/prism.css
42
42
  - lib/rorvswild/locator.rb
43
+ - lib/rorvswild/metrics.rb
44
+ - lib/rorvswild/metrics/cpu.rb
45
+ - lib/rorvswild/metrics/memory.rb
46
+ - lib/rorvswild/metrics/storage.rb
43
47
  - lib/rorvswild/plugin/action_controller.rb
44
48
  - lib/rorvswild/plugin/action_mailer.rb
45
49
  - lib/rorvswild/plugin/action_view.rb
@@ -62,8 +66,10 @@ files:
62
66
  homepage: https://www.rorvswild.com
63
67
  licenses:
64
68
  - MIT
65
- metadata: {}
66
- post_install_message:
69
+ metadata:
70
+ source_code_uri: https://github.com/BaseSecrete/rorvswild
71
+ changelog_uri: https://github.com/BaseSecrete/rorvswild/blob/master/CHANGELOG.md
72
+ post_install_message:
67
73
  rdoc_options: []
68
74
  require_paths:
69
75
  - lib
@@ -78,8 +84,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
84
  - !ruby/object:Gem::Version
79
85
  version: '0'
80
86
  requirements: []
81
- rubygems_version: 3.0.3
82
- signing_key:
87
+ rubygems_version: 3.2.22
88
+ signing_key:
83
89
  specification_version: 4
84
90
  summary: Ruby on Rails applications monitoring
85
91
  test_files: []