mnemosyne-ruby 1.14.0 → 1.16.0

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: 8a1ccbc2b8a4f823ec3a848242e53f7b2a4fbd9001de8dda44a70011a90e8bbc
4
- data.tar.gz: 58ee31014f79ca2a7c36f7d19625f2e8c81f1298276733ed37aa5296a420064e
3
+ metadata.gz: 43c02b1229d902d187dee8281c88820a95d13a30ee594af7ca131645ffd85485
4
+ data.tar.gz: c6ebd7b760e56adacacbb89390d5abc463ded7a72e37d346de0d51fb615a6582
5
5
  SHA512:
6
- metadata.gz: b9ec274a32fb1dfe5e9083b252246ff32a670de630d53f2bdb74e162d354d8b1fe314fe9a946fd70e70c309bfb7c7c1936d082460148102e8bde87d8c96158c2
7
- data.tar.gz: 9fffad92d4957da1df5b6a7fe725bf728404716d349db348f85dff77c52730de4a8fcafb750f98d234c0cf90031a804da9a29ab7ebd928d7a84480e0cea6069f
6
+ metadata.gz: 14220075cac092132c13d0c6d82bdfbc374c0c7a50db0d3e0d07e950fae0eda073b6958c30657f3bb089cbfcc0137d18fd56ae2f505e1f16d61917b929d25a6e
7
+ data.tar.gz: a568b0e9ea1b96549ed78082c9ccef3946a5a8dbd8b7b9dc231a609a52d59c928069d7f7a5cf4c99f509589830279ff16b1e05a055114674d5dac8d2ccf85274
@@ -6,7 +6,7 @@ on:
6
6
  jobs:
7
7
  rubocop:
8
8
  name: rubocop
9
- runs-on: ubuntu-20.04
9
+ runs-on: ubuntu-22.04
10
10
 
11
11
  env:
12
12
  BUNDLE_WITHOUT: development test
@@ -6,7 +6,7 @@ on:
6
6
  jobs:
7
7
  test:
8
8
  name: Ruby ${{ matrix.ruby }} / ${{ matrix.suite }}
9
- runs-on: ubuntu-20.04
9
+ runs-on: ubuntu-22.04
10
10
 
11
11
  strategy:
12
12
  fail-fast: false
@@ -27,9 +27,12 @@ jobs:
27
27
  - rails-6.1
28
28
  - rails-7.0
29
29
  - redis-4.0
30
+ - redis-5.0
31
+ - redis-client
30
32
  - restify
31
33
  - sidekiq-5
32
34
  - sidekiq-6
35
+ - sidekiq-7
33
36
  include:
34
37
  - suite: core
35
38
  spec: --tag ~probe
@@ -61,6 +64,12 @@ jobs:
61
64
  - suite: redis-4.0
62
65
  spec: --tag probe:redis
63
66
  gemfile: gemfiles/redis_40.gemfile
67
+ - suite: redis-5.0
68
+ spec: --tag probe:redis
69
+ gemfile: gemfiles/redis_50.gemfile
70
+ - suite: redis-client
71
+ spec: --tag probe:redis_client
72
+ gemfile: gemfiles/core.gemfile
64
73
  - suite: restify
65
74
  spec: --tag probe:restify
66
75
  gemfile: gemfiles/core.gemfile
@@ -70,6 +79,9 @@ jobs:
70
79
  - suite: sidekiq-6
71
80
  spec: --tag probe:sidekiq
72
81
  gemfile: gemfiles/sidekiq_60.gemfile
82
+ - suite: sidekiq-7
83
+ spec: --tag probe:sidekiq
84
+ gemfile: gemfiles/sidekiq_70.gemfile
73
85
  exclude:
74
86
  - suite: rails-5.2
75
87
  ruby: '3.2'
data/.gitignore CHANGED
@@ -4,6 +4,8 @@
4
4
  /coverage/
5
5
  /doc/
6
6
  /Gemfile.lock
7
+ /gemfiles/.bundle/
8
+ /log/
7
9
  /pkg/
8
10
  /spec/examples.txt
9
11
  /spec/reports/
data/Appraisals CHANGED
@@ -56,44 +56,42 @@ appraise 'rails-52' do
56
56
  end
57
57
  end
58
58
 
59
- appraise 'rails-51' do
59
+ appraise 'redis-40' do
60
60
  remove_gem 'rubocop'
61
61
 
62
62
  group :test do
63
- gem 'rails', '~> 5.1.0'
64
- gem 'sqlite3', '~> 1.4'
63
+ gem 'redis', '~> 4.0'
65
64
  end
66
65
  end
67
66
 
68
- appraise 'rails-50' do
67
+ appraise 'redis-50' do
69
68
  remove_gem 'rubocop'
70
69
 
71
70
  group :test do
72
- gem 'rails', '~> 5.0.0'
73
- gem 'sqlite3', '~> 1.3.6'
71
+ gem 'redis', '~> 5.0'
74
72
  end
75
73
  end
76
74
 
77
- appraise 'redis-40' do
75
+ appraise 'sidekiq-50' do
78
76
  remove_gem 'rubocop'
79
77
 
80
78
  group :test do
81
- gem 'redis', '~> 4.0'
79
+ gem 'sidekiq', '~> 5.0'
82
80
  end
83
81
  end
84
82
 
85
- appraise 'sidekiq-50' do
83
+ appraise 'sidekiq-60' do
86
84
  remove_gem 'rubocop'
87
85
 
88
86
  group :test do
89
- gem 'sidekiq', '~> 5.0'
87
+ gem 'sidekiq', '~> 6.0'
90
88
  end
91
89
  end
92
90
 
93
- appraise 'sidekiq-60' do
91
+ appraise 'sidekiq-70' do
94
92
  remove_gem 'rubocop'
95
93
 
96
94
  group :test do
97
- gem 'sidekiq', '~> 6.0'
95
+ gem 'sidekiq', '~> 7.0'
98
96
  end
99
97
  end
data/CHANGELOG.md CHANGED
@@ -4,27 +4,37 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## Unreleased
7
+ ## [Unreleased]
8
8
 
9
- ---
9
+ ## [1.16.0] - 2023-08-24
10
10
 
11
- ### New
11
+ ### Added
12
+
13
+ - Separate new tracing for `redis-client` gem
14
+
15
+ ### Fixed
16
+
17
+ - `NoMethodError` when `RedisClient` is used directly
12
18
 
13
- ### Changes
19
+ ## [1.15.0] - 2023-08-23
14
20
 
15
- ### Fixes
21
+ ### Added
22
+
23
+ - Support for `redis` gem v5+
16
24
 
17
- ### Breaks
25
+ ### Fixed
26
+
27
+ - Method signature for `#publish` in MSGR probe
18
28
 
19
29
  ## [1.14.0] - (2023-07-31)
20
30
 
21
- ### New
31
+ ### Added
22
32
 
23
33
  - Tests with Ruby 3.2
24
34
 
25
35
  ### Changed
26
36
 
27
- - Replace reprecated `!render.view_component` hook with `render.view_component`
37
+ - Replace deprecated `!render.view_component` hook with `render.view_component`
28
38
  - Removed tests for Rails 5.0 and 5.1
29
39
 
30
40
  ## [1.13.0] - 2022-04-14
@@ -192,6 +202,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
192
202
 
193
203
  - Add platform identifier
194
204
 
205
+ [Unreleased]: https://github.com/mnemosyne-mon/mnemosyne-ruby/compare/v1.16.0...HEAD
206
+ [1.16.0]: https://github.com/mnemosyne-mon/mnemosyne-ruby/compare/v1.15.0...v1.16.0
207
+ [1.15.0]: https://github.com/mnemosyne-mon/mnemosyne-ruby/compare/v1.14.0...v1.15.0
195
208
  [1.14.0]: https://github.com/mnemosyne-mon/mnemosyne-ruby/compare/v1.14.0...v1.13.0
196
209
  [1.13.0]: https://github.com/mnemosyne-mon/mnemosyne-ruby/compare/v1.13.0...v1.12.1
197
210
  [1.12.1]: https://github.com/mnemosyne-mon/mnemosyne-ruby/compare/v1.12.1...v1.12.0
data/Gemfile CHANGED
@@ -6,7 +6,7 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  gem 'rake', '~> 13.0'
9
- gem 'rubocop', '~> 1.54.0'
9
+ gem 'rubocop', '~> 1.56.0'
10
10
 
11
11
  group :test do
12
12
  gem 'rspec', '~> 3.6'
@@ -16,6 +16,7 @@ group :test do
16
16
  gem 'msgr'
17
17
  gem 'rails'
18
18
  gem 'redis'
19
+ gem 'redis-client'
19
20
  gem 'restify'
20
21
  gem 'sidekiq'
21
22
  gem 'sqlite3'
@@ -0,0 +1,27 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+
7
+ group :test do
8
+ gem "rspec", "~> 3.6"
9
+ gem "timecop", "~> 0.9.1"
10
+ gem "faraday"
11
+ gem "msgr"
12
+ gem "rails"
13
+ gem "redis", "~> 5.0"
14
+ gem "restify"
15
+ gem "sidekiq"
16
+ gem "sqlite3"
17
+ gem "webmock"
18
+ end
19
+
20
+ group :development do
21
+ gem "appraisal"
22
+ gem "rake-release", "~> 1.3.0"
23
+ gem "pry", require: false
24
+ gem "pry-byebug", require: false
25
+ end
26
+
27
+ gemspec path: "../"
@@ -0,0 +1,27 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rake", "~> 13.0"
6
+
7
+ group :test do
8
+ gem "rspec", "~> 3.6"
9
+ gem "timecop", "~> 0.9.1"
10
+ gem "faraday"
11
+ gem "msgr"
12
+ gem "rails"
13
+ gem "redis"
14
+ gem "restify"
15
+ gem "sidekiq", "~> 7.0"
16
+ gem "sqlite3"
17
+ gem "webmock"
18
+ end
19
+
20
+ group :development do
21
+ gem "appraisal"
22
+ gem "rake-release", "~> 1.3.0"
23
+ gem "pry", require: false
24
+ gem "pry-byebug", require: false
25
+ end
26
+
27
+ gemspec path: "../"
@@ -13,7 +13,7 @@ module Mnemosyne
13
13
  end
14
14
 
15
15
  module Instrumentation
16
- def publish(payload, **options)
16
+ def publish(payload, options = {})
17
17
  if (trace = ::Mnemosyne::Instrumenter.current_trace)
18
18
  meta = {}
19
19
  span = ::Mnemosyne::Span.new(NAME, meta: meta)
@@ -6,121 +6,15 @@ module Mnemosyne
6
6
  module Command
7
7
  class Probe < ::Mnemosyne::Probe
8
8
  def setup
9
+ # Redis v5+ used redis-client, which has it's own probe
10
+ return if Gem::Version.new(::Redis::VERSION) >= Gem::Version.new('5')
11
+
9
12
  ::Redis::Client.prepend ClientPatch
10
13
  end
11
14
 
12
15
  module ClientPatch
13
16
  def process(commands)
14
- trace = ::Mnemosyne::Instrumenter.current_trace
15
- return super unless trace
16
-
17
- span = ::Mnemosyne::Span.new 'db.query.redis',
18
- meta: extract_span_meta(commands)
19
-
20
- span.start!
21
-
22
- super.tap do |retval|
23
- span.meta[:error] = retval.message if retval.is_a?(::Redis::CommandError)
24
-
25
- trace << span.finish!
26
- end
27
- end
28
-
29
- private
30
-
31
- def extract_span_meta(commands)
32
- {
33
- server: id,
34
-
35
- # Each command is an array, consisting of the command name and any
36
- # arguments. We are only interested in the command name.
37
- commands: extract_command_names(commands),
38
-
39
- # If there are multiple commands, that must mean they were pipelined
40
- # (i.e. run in parallel).
41
- pipelined: commands.length > 1
42
- }
43
- end
44
-
45
- def extract_command_names(commands)
46
- commands.map do |c|
47
- # Depending on how the methods on the Redis gem are called,
48
- # there may be an additional level of nesting.
49
- c = c[0] if c[0].is_a?(Array)
50
-
51
- # For some commands, we also extract *some* of the arguments.
52
- name, args = parse_name_and_args(c)
53
-
54
- "#{name} #{args}".strip
55
- end.join("\n")
56
- end
57
-
58
- ##
59
- # A map of known commands to the arguments (identified by position)
60
- # that should be included verbatim in the metadata. Arguments not
61
- # listed here will be replaced by a "?" character.
62
- #
63
- # The value can be a list of safe argument indices, or "*" (all).
64
- #
65
- KNOWN_ARGUMENTS = {
66
- 'BLPOP' => '*',
67
- 'BRPOP' => '*',
68
- 'EVALSHA' => [0, 1],
69
- 'EXISTS' => '*',
70
- 'EXPIRE' => '*',
71
- 'GET' => '*',
72
- 'HGET' => '*',
73
- 'HGETALL' => '*',
74
- 'HMGET' => '*',
75
- 'HMSET' => [0, 1],
76
- 'HSCAN' => '*',
77
- 'INCRBY' => '*',
78
- 'LLEN' => '*',
79
- 'LPUSH' => [0],
80
- 'LRANGE' => '*',
81
- 'LREM' => [0, 1],
82
- 'MGET' => '*',
83
- 'MSET' => [0],
84
- 'RPUSH' => [0],
85
- 'RPOP' => '*',
86
- 'SADD' => [0],
87
- 'SCARD' => '*',
88
- 'SCAN' => '*',
89
- 'SCRIPT LOAD' => [],
90
- 'SET' => [0],
91
- 'SREM' => [0],
92
- 'SSCAN' => '*',
93
- 'UNLINK' => '*',
94
- 'ZADD' => [0],
95
- 'ZCARD' => '*',
96
- 'ZINCRBY' => [0, 1],
97
- 'ZRANGE' => '*',
98
- 'ZRANGEBYSCORE' => '*',
99
- 'ZREM' => [0],
100
- 'ZREMRANGEBYSCORE' => '*',
101
- 'ZREVRANGE' => '*',
102
- 'ZSCAN' => '*'
103
- }.freeze
104
-
105
- def parse_name_and_args(command)
106
- command = command.dup
107
-
108
- # Symbols and lower-case names are allowed
109
- name = command.delete_at(0).to_s.upcase
110
-
111
- allowed = KNOWN_ARGUMENTS[name] || []
112
- args = case allowed
113
- when '*' # All arguments considered safe
114
- command
115
- when Array # A list of allowed argument indices
116
- command.each_with_index.map do |arg, index|
117
- allowed.include?(index) ? arg : '?'
118
- end
119
- else # Unknown command - assume nothing is safe
120
- Array.new(command.length, '?')
121
- end.join(' ')
122
-
123
- [name, args]
17
+ ::Mnemosyne::Support::Redis.instrument(commands, id) { super }
124
18
  end
125
19
  end
126
20
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnemosyne
4
+ module Probes
5
+ module RedisClient
6
+ module Command
7
+ class Probe < ::Mnemosyne::Probe
8
+ def setup
9
+ ::RedisClient.register Instrumentation
10
+ end
11
+
12
+ module Instrumentation
13
+ def call(command, redis_config)
14
+ ::Mnemosyne::Support::Redis.instrument([command], redis_config.server_url) { super }
15
+ end
16
+
17
+ def call_pipelined(commands, redis_config)
18
+ ::Mnemosyne::Support::Redis.instrument(commands, redis_config.server_url) { super }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ register 'RedisClient',
26
+ 'redis_client',
27
+ RedisClient::Command::Probe.new
28
+ end
29
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mnemosyne
4
+ module Support
5
+ module Redis
6
+ ##
7
+ # A map of known commands to the arguments (identified by position)
8
+ # that should be included verbatim in the metadata. Arguments not
9
+ # listed here will be replaced by a "?" character.
10
+ #
11
+ # The value can be a list of safe argument indices, or "*" (all).
12
+ #
13
+ KNOWN_ARGUMENTS = {
14
+ 'BLPOP' => '*',
15
+ 'BRPOP' => '*',
16
+ 'EVALSHA' => [0, 1],
17
+ 'EXISTS' => '*',
18
+ 'EXPIRE' => '*',
19
+ 'GET' => '*',
20
+ 'HGET' => '*',
21
+ 'HGETALL' => '*',
22
+ 'HMGET' => '*',
23
+ 'HMSET' => [0, 1],
24
+ 'HSCAN' => '*',
25
+ 'INCRBY' => '*',
26
+ 'LLEN' => '*',
27
+ 'LPUSH' => [0],
28
+ 'LRANGE' => '*',
29
+ 'LREM' => [0, 1],
30
+ 'MGET' => '*',
31
+ 'MSET' => [0],
32
+ 'RPUSH' => [0],
33
+ 'RPOP' => '*',
34
+ 'SADD' => [0],
35
+ 'SCARD' => '*',
36
+ 'SCAN' => '*',
37
+ 'SCRIPT LOAD' => [],
38
+ 'SET' => [0],
39
+ 'SREM' => [0],
40
+ 'SSCAN' => '*',
41
+ 'UNLINK' => '*',
42
+ 'ZADD' => [0],
43
+ 'ZCARD' => '*',
44
+ 'ZINCRBY' => [0, 1],
45
+ 'ZRANGE' => '*',
46
+ 'ZRANGEBYSCORE' => '*',
47
+ 'ZREM' => [0],
48
+ 'ZREMRANGEBYSCORE' => '*',
49
+ 'ZREVRANGE' => '*',
50
+ 'ZSCAN' => '*'
51
+ }.freeze
52
+
53
+ class << self
54
+ def instrument(commands, server_url)
55
+ trace = ::Mnemosyne::Instrumenter.current_trace
56
+ return yield unless trace
57
+
58
+ span = ::Mnemosyne::Span.new 'db.query.redis',
59
+ meta: extract_span_meta(commands, server_url)
60
+
61
+ span.start!
62
+
63
+ begin
64
+ yield.tap do |retval|
65
+ if defined?(::Redis::CommandError) && retval.is_a?(::Redis::CommandError)
66
+ span.meta[:error] = retval.message
67
+ end
68
+
69
+ trace << span.finish!
70
+ end
71
+ rescue StandardError => e
72
+ span.meta[:error] = e.message
73
+ trace << span.finish!
74
+ raise
75
+ end
76
+ end
77
+
78
+ def extract_span_meta(commands, server_url)
79
+ {
80
+ server: server_url,
81
+
82
+ # Each command is an array, consisting of the command name and any
83
+ # arguments. We are only interested in the command name.
84
+ commands: extract_command_names(commands),
85
+
86
+ # If there are multiple commands, that must mean they were pipelined
87
+ # (i.e. run in parallel).
88
+ pipelined: commands.length > 1
89
+ }
90
+ end
91
+
92
+ def parse_name_and_args(command)
93
+ command = command.dup
94
+
95
+ # Symbols and lower-case names are allowed
96
+ name = command.delete_at(0).to_s.upcase
97
+
98
+ allowed = KNOWN_ARGUMENTS[name] || []
99
+ args = case allowed
100
+ when '*' # All arguments considered safe
101
+ command
102
+ when Array # A list of allowed argument indices
103
+ command.each_with_index.map do |arg, index|
104
+ allowed.include?(index) ? arg : '?'
105
+ end
106
+ else # Unknown command - assume nothing is safe
107
+ Array.new(command.length, '?')
108
+ end.join(' ')
109
+
110
+ [name, args]
111
+ end
112
+
113
+ def extract_command_names(commands)
114
+ commands.map do |c|
115
+ # Depending on how the methods on the Redis gem are called,
116
+ # there may be an additional level of nesting.
117
+ c = c[0] if c[0].is_a?(Array)
118
+
119
+ # For some commands, we also extract *some* of the arguments.
120
+ name, args = parse_name_and_args(c)
121
+
122
+ "#{name} #{args}".strip
123
+ end.join("\n")
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -3,7 +3,7 @@
3
3
  module Mnemosyne
4
4
  module VERSION
5
5
  MAJOR = 1
6
- MINOR = 14
6
+ MINOR = 16
7
7
  PATCH = 0
8
8
  STAGE = nil
9
9
 
data/lib/mnemosyne.rb CHANGED
@@ -43,6 +43,7 @@ module Mnemosyne
43
43
  require 'mnemosyne/probes/msgr/client'
44
44
  require 'mnemosyne/probes/msgr/consumer'
45
45
  require 'mnemosyne/probes/redis/command'
46
+ require 'mnemosyne/probes/redis-client/command'
46
47
  require 'mnemosyne/probes/responder/respond'
47
48
  require 'mnemosyne/probes/restify/base'
48
49
  require 'mnemosyne/probes/sidekiq/client'
@@ -54,5 +55,9 @@ module Mnemosyne
54
55
  require 'mnemosyne/middleware/rack'
55
56
  end
56
57
 
58
+ module Support
59
+ require 'mnemosyne/support/redis'
60
+ end
61
+
57
62
  require 'mnemosyne/railtie' if defined?(Rails)
58
63
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mnemosyne-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.0
4
+ version: 1.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Graichen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-31 00:00:00.000000000 Z
11
+ date: 2023-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -67,8 +67,10 @@ files:
67
67
  - gemfiles/rails_61.gemfile
68
68
  - gemfiles/rails_70.gemfile
69
69
  - gemfiles/redis_40.gemfile
70
+ - gemfiles/redis_50.gemfile
70
71
  - gemfiles/sidekiq_50.gemfile
71
72
  - gemfiles/sidekiq_60.gemfile
73
+ - gemfiles/sidekiq_70.gemfile
72
74
  - lib/mnemosyne-ruby.rb
73
75
  - lib/mnemosyne.rb
74
76
  - lib/mnemosyne/client.rb
@@ -99,6 +101,7 @@ files:
99
101
  - lib/mnemosyne/probes/mnemosyne/tracer.rb
100
102
  - lib/mnemosyne/probes/msgr/client.rb
101
103
  - lib/mnemosyne/probes/msgr/consumer.rb
104
+ - lib/mnemosyne/probes/redis-client/command.rb
102
105
  - lib/mnemosyne/probes/redis/command.rb
103
106
  - lib/mnemosyne/probes/responder/respond.rb
104
107
  - lib/mnemosyne/probes/restify/base.rb
@@ -108,6 +111,7 @@ files:
108
111
  - lib/mnemosyne/railtie.rb
109
112
  - lib/mnemosyne/registry.rb
110
113
  - lib/mnemosyne/span.rb
114
+ - lib/mnemosyne/support/redis.rb
111
115
  - lib/mnemosyne/trace.rb
112
116
  - lib/mnemosyne/version.rb
113
117
  - mnemosyne-ruby.gemspec
@@ -134,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
138
  - !ruby/object:Gem::Version
135
139
  version: '0'
136
140
  requirements: []
137
- rubygems_version: 3.4.10
141
+ rubygems_version: 3.4.18
138
142
  signing_key:
139
143
  specification_version: 4
140
144
  summary: Ruby/Rails client for Mnemosyne APM