spartan_apm 0.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +55 -0
  5. data/VERSION +1 -0
  6. data/app/assets/flatpickr-4.6.9/LICENSE.md +21 -0
  7. data/app/assets/flatpickr-4.6.9/flatpickr.min.css +13 -0
  8. data/app/assets/flatpickr-4.6.9/flatpickr.min.js +2 -0
  9. data/app/assets/nice-select2-2.0.0/LICENSE +21 -0
  10. data/app/assets/nice-select2-2.0.0/nice-select2.min.css +1 -0
  11. data/app/assets/nice-select2-2.0.0/nice-select2.min.js +1 -0
  12. data/app/assets/spartan.svg +5 -0
  13. data/app/views/_help.html.erb +147 -0
  14. data/app/views/index.html.erb +231 -0
  15. data/app/views/scripts.js +911 -0
  16. data/app/views/styles.css +332 -0
  17. data/config.ru +36 -0
  18. data/lib/spartan_apm/engine.rb +45 -0
  19. data/lib/spartan_apm/error_info.rb +17 -0
  20. data/lib/spartan_apm/instrumentation/active_record.rb +13 -0
  21. data/lib/spartan_apm/instrumentation/base.rb +36 -0
  22. data/lib/spartan_apm/instrumentation/bunny.rb +24 -0
  23. data/lib/spartan_apm/instrumentation/cassandra.rb +13 -0
  24. data/lib/spartan_apm/instrumentation/curb.rb +13 -0
  25. data/lib/spartan_apm/instrumentation/dalli.rb +13 -0
  26. data/lib/spartan_apm/instrumentation/elasticsearch.rb +18 -0
  27. data/lib/spartan_apm/instrumentation/excon.rb +13 -0
  28. data/lib/spartan_apm/instrumentation/http.rb +13 -0
  29. data/lib/spartan_apm/instrumentation/httpclient.rb +13 -0
  30. data/lib/spartan_apm/instrumentation/net_http.rb +13 -0
  31. data/lib/spartan_apm/instrumentation/redis.rb +13 -0
  32. data/lib/spartan_apm/instrumentation/typhoeus.rb +13 -0
  33. data/lib/spartan_apm/instrumentation.rb +71 -0
  34. data/lib/spartan_apm/measure.rb +172 -0
  35. data/lib/spartan_apm/metric.rb +26 -0
  36. data/lib/spartan_apm/middleware/rack/end_middleware.rb +29 -0
  37. data/lib/spartan_apm/middleware/rack/start_middleware.rb +57 -0
  38. data/lib/spartan_apm/middleware/sidekiq/end_middleware.rb +25 -0
  39. data/lib/spartan_apm/middleware/sidekiq/start_middleware.rb +34 -0
  40. data/lib/spartan_apm/middleware.rb +16 -0
  41. data/lib/spartan_apm/persistence.rb +648 -0
  42. data/lib/spartan_apm/report.rb +436 -0
  43. data/lib/spartan_apm/string_cache.rb +27 -0
  44. data/lib/spartan_apm/web/api_request.rb +133 -0
  45. data/lib/spartan_apm/web/helpers.rb +88 -0
  46. data/lib/spartan_apm/web/router.rb +90 -0
  47. data/lib/spartan_apm/web.rb +10 -0
  48. data/lib/spartan_apm.rb +399 -0
  49. data/spartan_apm.gemspec +39 -0
  50. metadata +161 -0
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module SpartanAPM
6
+ module Web
7
+ # Rack application for serving up the web UI. The application can either
8
+ # be mounted as a Rack application or as Rack middleware.
9
+ class Router
10
+ ASSET_ROOT_DIR = File.expand_path(File.join(__dir__, "..", "..", "..", "app", "assets"))
11
+
12
+ def initialize(app = nil, root_path = "")
13
+ @app = app
14
+ @root_path = root_path.chomp("/")
15
+ @root_prefix = "#{@root_path}/"
16
+ end
17
+
18
+ def call(env)
19
+ path = env["PATH_INFO"]
20
+ response = nil
21
+ if path.start_with?(@root_path)
22
+ app_path = path[@root_path.length, path.length]
23
+ request = Rack::Request.new(env)
24
+ case app_path
25
+ when ""
26
+ response = [302, {"Location" => root_url(request)}, []]
27
+ when "/"
28
+ body = Helpers.new(request).render("index.html.erb")
29
+ response = [200, {"Content-Type" => "text/html; charset=utf-8"}, [body]]
30
+ when "/metrics"
31
+ response = json_response(ApiRequest.new(request).metrics)
32
+ when "/live_metrics"
33
+ response = json_response(ApiRequest.new(request).live_metrics)
34
+ when "/errors"
35
+ response = json_response(ApiRequest.new(request).errors)
36
+ when "/actions"
37
+ response = json_response(ApiRequest.new(request).actions)
38
+ else
39
+ if app_path.start_with?("/assets/")
40
+ response = asset_response(app_path.sub("/assets/", ""))
41
+ end
42
+ end
43
+ end
44
+
45
+ if response
46
+ response
47
+ elsif @app.nil?
48
+ not_found_response
49
+ else
50
+ @app.call(env)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def not_found_response
57
+ [404, {"Content-Type" => "text/plain"}, ["Not found"]]
58
+ end
59
+
60
+ def json_response(result)
61
+ [200, {"Content-Type" => "application/json; charset=utf-8"}, [JSON.dump(result)]]
62
+ end
63
+
64
+ def asset_response(asset_path)
65
+ file_path = File.expand_path(File.join(ASSET_ROOT_DIR, asset_path.split("/")))
66
+ return nil unless file_path.start_with?(ASSET_ROOT_DIR) && File.exist?(file_path)
67
+ data = File.read(file_path)
68
+ headers = {"Content-Type" => mime_type(file_path)}
69
+ headers["Cache-Control"] = "max-age=604800" if asset_path.match?(/\d\./)
70
+ [200, headers, [data]]
71
+ end
72
+
73
+ def mime_type(file_path)
74
+ extension = file_path.split(".").last
75
+ case extension
76
+ when "css"
77
+ "text/css"
78
+ when "js"
79
+ "application/javascript"
80
+ else
81
+ "application/octet-stream"
82
+ end
83
+ end
84
+
85
+ def root_url(request)
86
+ request.url.sub(/$|(\?.*)/, '/\1')
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpartanAPM
4
+ module Web
5
+ end
6
+ end
7
+
8
+ require_relative "web/api_request"
9
+ require_relative "web/helpers"
10
+ require_relative "web/router"
@@ -0,0 +1,399 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "digest"
5
+ require "zlib"
6
+ require "time"
7
+ require "json"
8
+
9
+ require "redis"
10
+ require "msgpack"
11
+ require "concurrent-ruby"
12
+ require "rack"
13
+
14
+ module SpartanAPM
15
+ DEFAULT_TTL = 60 * 60 * 24 * 7
16
+
17
+ # These are the default locations of javascript and CSS assets.
18
+ @plotly_js_url = ENV.fetch("SPARTAN_APM_PLOTLY_JS_URL", "https://cdn.plot.ly/plotly-basic-2.9.0.min.js")
19
+
20
+ @ignore_patterns = {}
21
+
22
+ @max_actions = 100
23
+
24
+ class << self
25
+ # The configure method is just syntactic sugar to call a block that yields this module.
26
+ # ```
27
+ # SpartanAPM.configure do |config|
28
+ # config.env = "production"
29
+ # config.sample_rate = 0.1
30
+ # end
31
+ # ```
32
+ def configure
33
+ yield self
34
+ end
35
+
36
+ # Set the environment name for the application. This can be used if you want to share
37
+ # the same Redis server between different environments. For instance if you have multiple
38
+ # staging environments, you could have them all share a single Redis rather than have to
39
+ # stand up a dedicated one for each environment. The value can also be set with the
40
+ # SPARTAN_APM_ENV environment variable.
41
+ def env=(value)
42
+ value = value.to_s.dup.freeze
43
+ raise ArgumentError.new("env cannot contain a tilda") if value.include?("~")
44
+ @_env_set = true
45
+ @env = value
46
+ end
47
+
48
+ # @return [String] The environment name for the application.
49
+ def env
50
+ @env ||= ENV.fetch("SPARTAN_APM_ENV", "default")
51
+ end
52
+
53
+ # @api private
54
+ def env_set?
55
+ !!(defined?(@_env_set) && @_env_set)
56
+ end
57
+
58
+ # Set the list of available environments for the Web UI. This is only useful if there
59
+ # are multiple environments being saved to the same Redis database.
60
+ # @param value [Array<String>] List of environments.
61
+ def environments=(value)
62
+ @environments = Array(value).collect(&:to_s)
63
+ end
64
+
65
+ # Get the list of environments being tracked. This is used in the web UI to allow switching
66
+ # between multiple environments that are being stored to the same Redis database.
67
+ # @return [Array<String>] List of environments.
68
+ def environments
69
+ @environments ||= ENV.fetch("SPARTAN_APM_ENVIRONMENTS", env).split(/\s*,\s*/)
70
+ end
71
+
72
+ # Set the sample rate for monitoring. If your application gets a lot traffic, you
73
+ # can use this setting to only record a percentage of it rather than every single
74
+ # request. The value can also be set with the SPARTAN_APM_SAMPLE_RATE environment
75
+ # variable
76
+ def sample_rate=(value)
77
+ @sample_rate = value&.to_f
78
+ end
79
+
80
+ # @return [Float] The sample rate for the application.
81
+ def sample_rate
82
+ @sample_rate ||= ENV.fetch("SPARTAN_APM_SAMPLE_RATE", "1.0").to_f
83
+ end
84
+
85
+ # Set the time to live in seconds for how long data will be stored in Redis.
86
+ # The value can also be set with the SPARTAN_APM_TTL environment variable.
87
+ def ttl=(value)
88
+ value = value.to_i
89
+ raise ArgumentError.new("ttl must be > 0") if value < 0
90
+ @ttl = value
91
+ end
92
+
93
+ # @return [Integer] The time to live in seconds for how long data will be persisted in Redis.
94
+ def ttl
95
+ unless defined?(@ttl)
96
+ value = ENV["SPARTAN_APM_TTL"].to_i
97
+ value = DEFAULT_TTL if value <= 0
98
+ @ttl = value
99
+ end
100
+ @ttl
101
+ end
102
+
103
+ # Maximum number of actions that will be tracked. If more actions than this are being
104
+ # used by the application, then only the top most used actions by request time will
105
+ # be tracked.
106
+ attr_accessor :max_actions
107
+
108
+ # Set a Logger object where events can be logged. You can set this if you want
109
+ # to record how long it takes to persist the instrumentation data to Redis.
110
+ attr_writer :logger
111
+
112
+ # @return [Logger]
113
+ def logger
114
+ @logger = nil unless defined?(@logger)
115
+ @logger
116
+ end
117
+
118
+ # Set an optional application name to show in the web UI.
119
+ attr_writer :application_name
120
+
121
+ def application_name
122
+ @application_name ||= ENV.fetch("SPARTAN_APM_APPLICATION_NAME", "SpartanAPM")
123
+ end
124
+
125
+ # Set an option URL for a link in the web UI header.
126
+ attr_accessor :application_url
127
+
128
+ # URL for authenticating access to the application. This would normally be some kind of
129
+ # login page. Browsers will be redirected here if they are denied access to the web UI.
130
+ attr_accessor :authentication_url
131
+
132
+ # Set the list of app names to show in the web UI.
133
+ def apps=(value)
134
+ @apps = Array(value).collect(&:to_s)
135
+ end
136
+
137
+ def apps
138
+ @apps ||= ENV.fetch("SPARTAN_APM_APPS", "").split(/\s*,\s*/)
139
+ end
140
+
141
+ # Set a list of errors to ignore. If an error matches a designated class,
142
+ # then it will not be recorded as an error. You can use this to strip out
143
+ # expected errors so they don't show up in the error monitoring.
144
+ # @param error_classes [Class, Module, String] class, class name, or module to ignore
145
+ def ignore_errors(error_classes)
146
+ error_classes = Array(error_classes)
147
+ @ignore_error_modules = error_classes.select { |klass| klass.is_a?(Module) }
148
+ @ignore_error_class_names = Set.new(error_classes.reject { |klass| klass.is_a?(Module) })
149
+ end
150
+
151
+ # @return [Boolean] Returns true if the error matches an error class specified with ignore_errors.
152
+ def ignore_error?(error)
153
+ return false unless defined?(@ignore_error_class_names) && @ignore_error_class_names
154
+ return true if @ignore_error_modules.any? { |klass| error.is_a?(klass) }
155
+ return false if @ignore_error_class_names.empty?
156
+ ignore_error = false
157
+ klass = error.class
158
+ while klass && klass.superclass != klass
159
+ if @ignore_error_class_names.include?(klass.name)
160
+ ignore_error = true
161
+ break
162
+ end
163
+ klass = klass.superclass
164
+ end
165
+ ignore_error
166
+ end
167
+
168
+ # A backtrace cleaner can be set with a Proc (or anything that responds to `call`) to
169
+ # remove extraneous lines from error backtraces. Setting up a backtrace cleaner
170
+ # can save space in redis and make the traces easier to follow by removing uninteresting
171
+ # lines. The Proc will take a backtrace as an array and should return an array.
172
+ attr_writer :backtrace_cleaner
173
+
174
+ # Clean an error backtrace by passing it to the backtrace cleaner if one was set.
175
+ def clean_backtrace(backtrace)
176
+ if defined?(@backtrace_cleaner) && @backtrace_cleaner && backtrace
177
+ cleaned = Array(@backtrace_cleaner.call(backtrace[1, backtrace.size]))
178
+ # Alway make sure the top line of the trace is included.
179
+ cleaned.unshift(backtrace[0])
180
+ cleaned
181
+ else
182
+ backtrace
183
+ end
184
+ end
185
+
186
+ # The measure block should be wrapped around whatever you want to consider a request
187
+ # in your application (i.e. web request, asynchronous job execution, etc.). This method
188
+ # will collect all instrumentation stats that occur within the block and report them
189
+ # as a single unit.
190
+ #
191
+ # In order to measure both an app name and an action name need to be provided. The app
192
+ # name should be something that describes the application function (i.e. "web" for web
193
+ # requests) while the action should describe the particular action being taken by the
194
+ # request (i.e. controller and action for a web request, worker name for an asynchronous
195
+ # job, etc.).
196
+ #
197
+ # The action name is optional here, but it is required in order for metrics to
198
+ # be recorded. This is because a measure block should encompass as much of the request
199
+ # as possible in order to be accurate. However, this might be before the action name
200
+ # is known. The action can be provided retroactively from within the block by setting
201
+ # `SpartanAPM.current_action`.
202
+ #
203
+ # @param app [String, Symbol] The name of the app that is collecting metrics.
204
+ # @param action [String, Symbol] The name of the action that is being performed.
205
+ def measure(app, action = nil)
206
+ if Thread.current[:spartan_apm_measure].nil? && (sample_rate >= 1.0 || rand < sample_rate)
207
+ measure = Measure.new(app, action)
208
+ Thread.current[:spartan_apm_measure] = measure
209
+ # rubocop:disable Lint/RescueException
210
+ begin
211
+ yield
212
+ rescue Exception => e
213
+ measure.capture_error(e) unless ignore_error?(e)
214
+ raise
215
+ ensure
216
+ Thread.current[:spartan_apm_measure] = nil
217
+ measure.record!
218
+ end
219
+ # rubocop:enable Lint/RescueException
220
+ else
221
+ yield
222
+ end
223
+ end
224
+
225
+ # Set the action name for the current measure. This allows you to set up a measure
226
+ # before you know the action name and then provide it later once that information
227
+ # is available.
228
+ # @param action [String, Symbol]
229
+ def current_action=(action)
230
+ measure = Thread.current[:spartan_apm_measure]
231
+ measure.action = action if measure
232
+ end
233
+
234
+ # Set the app name for the current measure. This allows you to set up a measure
235
+ # and override the app name if you want to segment some of your requests into a
236
+ # different app.
237
+ # @param app [String, Symbol]
238
+ def current_app=(app)
239
+ measure = Thread.current[:spartan_apm_measure]
240
+ measure.app = app if measure
241
+ end
242
+
243
+ # Capture a metric for a block. The metric value recorded will be the amount of time
244
+ # in seconds elapsed during block execution and will be stored with the current
245
+ # measure.
246
+ #
247
+ # The exclusive flag can be set to true to indicate that no other components should
248
+ # be captured in the block. This can be used if you want to measure one component
249
+ # as a unit but that component calls other instrumented components. For example, if you
250
+ # are instrumenting HTTP requests, but also want to instrument a service call that makes
251
+ # an HTTP request, you would capture the service call as exclusive.
252
+ #
253
+ # @param name [String, Symbol] The name of the component being measured.
254
+ # @param exclusive [Boolean]
255
+ def capture(name, exclusive: false)
256
+ measure = Thread.current[:spartan_apm_measure]
257
+ if measure
258
+ measure.capture(name, exclusive: exclusive) { yield }
259
+ else
260
+ yield
261
+ end
262
+ end
263
+
264
+ # Capture a time for a component in the current measure. This is the same as calling
265
+ # `capture` but without a block and passing the value explicitly.
266
+ def capture_time(name, elapsed_time)
267
+ measure = Thread.current[:spartan_apm_measure]
268
+ measure&.capture_time(name, elapsed_time)
269
+ end
270
+
271
+ # Capture an error in the current measure.
272
+ def capture_error(error)
273
+ measure = Thread.current[:spartan_apm_measure]
274
+ if measure && !ignore_error?(error)
275
+ measure.capture_error(error)
276
+ end
277
+ end
278
+
279
+ # Set the host name for the current process. Defaults to the system host name.
280
+ attr_writer :host
281
+
282
+ # @return The host name.
283
+ def host
284
+ @host ||= Socket.gethostname
285
+ end
286
+
287
+ # Set the Redis instance to use for storing metrics. This can be either a Redis
288
+ # instance or a Proc that returns a Redis instance. If it is a Proc, the Proc
289
+ # will be be called at runtime.
290
+ #
291
+ # If the Redis instance is not explicitly set, then the default connection URL
292
+ # will be looked up from environment variables. If the value of `SPARTAN_APM_REDIS_URL_PROVIDER`
293
+ # is set, then the URL will be gotten from the named environment variable. Otherwise
294
+ # it will use the value in `REDIS_URL`.
295
+ attr_writer :redis
296
+
297
+ # @return [Redis] Redis instance where metrics are stored.
298
+ def redis
299
+ @redis ||= default_redis
300
+ @redis.is_a?(Proc) ? @redis.call : @redis
301
+ end
302
+
303
+ # Set a list of patterns to ignore for an app. Patterns can be passed in as either
304
+ # a string to match or a Regexp. Strings can be specified with wildcards using "*"
305
+ # (i.e. "/debug/*" would match "/debug/info", etc.).
306
+ # @param app [String, Symbol] The app name the patterns are for.
307
+ # @param patterns [String, Regexp] Patterns to match that should be ignored.
308
+ def ignore_requests(app, *patterns)
309
+ app = app.to_s
310
+ patterns = patterns.compact
311
+ if patterns.empty?
312
+ @ignore_patterns.delete(app)
313
+ else
314
+ regexp_patterns = patterns.flatten.collect do |pattern|
315
+ if pattern.is_a?(Regexp)
316
+ pattern
317
+ else
318
+ exp = Regexp.escape(pattern).gsub("\\*", "(?:.*)")
319
+ Regexp.new("\\A#{exp}\\z")
320
+ end
321
+ end
322
+ @ignore_patterns[app] = Regexp.union(regexp_patterns)
323
+ end
324
+ end
325
+
326
+ # Determine if a value is set to be ignored. This method should be called
327
+ # by whatever code is calling `measure`. There is no set definition for what the
328
+ # value being passed should be and it left up to the implementation to set
329
+ # a convention.
330
+ #
331
+ # For instance in the bundled Rack implementation, the value passed to this
332
+ # method is the web request path. This allows that implementation to disable
333
+ # instrumentation for a set of request paths.
334
+ def ignore_request?(app, value)
335
+ return false if value.nil?
336
+ !!@ignore_patterns[app.to_s]&.match(value.to_s)
337
+ end
338
+
339
+ # The web UI will use a locked version of the plotly.js library from the official
340
+ # distribution CDN. If your company security policy requires all tools to pull
341
+ # from an internal source, you can change the URL with this setting.
342
+ attr_accessor :plotly_js_url
343
+
344
+ # @api private
345
+ # Get the bucket for the specified time.
346
+ # @return [Integer]
347
+ def bucket(time)
348
+ (time.to_f / 60.0).floor
349
+ end
350
+
351
+ # @api private
352
+ # Get the time for the specified bucket.
353
+ # @return [Time]
354
+ def bucket_time(bucket)
355
+ Time.at(bucket * 60.0)
356
+ end
357
+
358
+ # @api private
359
+ # Used for testing for disabling asynchronous metric persistence.
360
+ def persist_asynchronously?
361
+ unless defined?(@persist_asynchronously)
362
+ @persist_asynchronously = true
363
+ end
364
+ @persist_asynchronously
365
+ end
366
+
367
+ # @api private
368
+ # Used for testing for disabling asynchronous metric persistence.
369
+ def persist_asynchronously=(value)
370
+ @persist_asynchronously = !!value
371
+ end
372
+
373
+ private
374
+
375
+ def default_redis
376
+ var_name = ENV.fetch("SPARTAN_APM_REDIS_URL_PROVIDER", "REDIS_URL")
377
+ url = ENV[var_name]
378
+ Redis.new(url: url)
379
+ end
380
+ end
381
+ end
382
+
383
+ require_relative "spartan_apm/string_cache"
384
+ require_relative "spartan_apm/error_info"
385
+ require_relative "spartan_apm/measure"
386
+ require_relative "spartan_apm/persistence"
387
+ require_relative "spartan_apm/report"
388
+ require_relative "spartan_apm/metric"
389
+ require_relative "spartan_apm/instrumentation"
390
+ require_relative "spartan_apm/middleware"
391
+ require_relative "spartan_apm/web"
392
+
393
+ if defined?(Rails::Engine)
394
+ require_relative "spartan_apm/engine"
395
+ end
396
+
397
+ at_exit do
398
+ SpartanAPM::Measure.flush
399
+ end
@@ -0,0 +1,39 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "spartan_apm"
3
+ spec.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
4
+ spec.authors = ["Brian Durand"]
5
+ spec.email = ["bbdurand@gmail.com"]
6
+
7
+ spec.summary = "Simple redis backed application performance monitoring tool."
8
+ spec.homepage = "https://github.com/bdurand/spartan_apm"
9
+ spec.license = "MIT"
10
+
11
+ # Specify which files should be added to the gem when it is released.
12
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
13
+ ignore_files = %w[
14
+ .
15
+ Appraisals
16
+ Gemfile
17
+ Gemfile.lock
18
+ Rakefile
19
+ bin/
20
+ docker/
21
+ gemfiles/
22
+ sample_data
23
+ spec/
24
+ ]
25
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
27
+ end
28
+
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "redis"
32
+ spec.add_dependency "msgpack"
33
+ spec.add_dependency "concurrent-ruby"
34
+ spec.add_dependency "rack"
35
+
36
+ spec.add_development_dependency "bundler"
37
+
38
+ spec.required_ruby_version = ">= 2.5"
39
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spartan_apm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0.rc1
5
+ platform: ruby
6
+ authors:
7
+ - Brian Durand
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-03-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: msgpack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: concurrent-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - bbdurand@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - CHANGELOG.md
91
+ - MIT-LICENSE
92
+ - README.md
93
+ - VERSION
94
+ - app/assets/flatpickr-4.6.9/LICENSE.md
95
+ - app/assets/flatpickr-4.6.9/flatpickr.min.css
96
+ - app/assets/flatpickr-4.6.9/flatpickr.min.js
97
+ - app/assets/nice-select2-2.0.0/LICENSE
98
+ - app/assets/nice-select2-2.0.0/nice-select2.min.css
99
+ - app/assets/nice-select2-2.0.0/nice-select2.min.js
100
+ - app/assets/spartan.svg
101
+ - app/views/_help.html.erb
102
+ - app/views/index.html.erb
103
+ - app/views/scripts.js
104
+ - app/views/styles.css
105
+ - config.ru
106
+ - lib/spartan_apm.rb
107
+ - lib/spartan_apm/engine.rb
108
+ - lib/spartan_apm/error_info.rb
109
+ - lib/spartan_apm/instrumentation.rb
110
+ - lib/spartan_apm/instrumentation/active_record.rb
111
+ - lib/spartan_apm/instrumentation/base.rb
112
+ - lib/spartan_apm/instrumentation/bunny.rb
113
+ - lib/spartan_apm/instrumentation/cassandra.rb
114
+ - lib/spartan_apm/instrumentation/curb.rb
115
+ - lib/spartan_apm/instrumentation/dalli.rb
116
+ - lib/spartan_apm/instrumentation/elasticsearch.rb
117
+ - lib/spartan_apm/instrumentation/excon.rb
118
+ - lib/spartan_apm/instrumentation/http.rb
119
+ - lib/spartan_apm/instrumentation/httpclient.rb
120
+ - lib/spartan_apm/instrumentation/net_http.rb
121
+ - lib/spartan_apm/instrumentation/redis.rb
122
+ - lib/spartan_apm/instrumentation/typhoeus.rb
123
+ - lib/spartan_apm/measure.rb
124
+ - lib/spartan_apm/metric.rb
125
+ - lib/spartan_apm/middleware.rb
126
+ - lib/spartan_apm/middleware/rack/end_middleware.rb
127
+ - lib/spartan_apm/middleware/rack/start_middleware.rb
128
+ - lib/spartan_apm/middleware/sidekiq/end_middleware.rb
129
+ - lib/spartan_apm/middleware/sidekiq/start_middleware.rb
130
+ - lib/spartan_apm/persistence.rb
131
+ - lib/spartan_apm/report.rb
132
+ - lib/spartan_apm/string_cache.rb
133
+ - lib/spartan_apm/web.rb
134
+ - lib/spartan_apm/web/api_request.rb
135
+ - lib/spartan_apm/web/helpers.rb
136
+ - lib/spartan_apm/web/router.rb
137
+ - spartan_apm.gemspec
138
+ homepage: https://github.com/bdurand/spartan_apm
139
+ licenses:
140
+ - MIT
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '2.5'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">"
154
+ - !ruby/object:Gem::Version
155
+ version: 1.3.1
156
+ requirements: []
157
+ rubygems_version: 3.1.6
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Simple redis backed application performance monitoring tool.
161
+ test_files: []