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,436 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpartanAPM
4
+ # This is the main interface for reading the APM metrics. It will expose
5
+ # a list of metrics and errors from a given time period. The report can also
6
+ # be filtered by action and/or host.
7
+ #
8
+ # You can use this class to query the APM metrics if you want to construct
9
+ # your own interface or create custom monitors.
10
+ #
11
+ # The metrics in the report are averaged and grouped over a number of minutes. This
12
+ # number is set in the `interver_minutes` attribute and it increases as the time range
13
+ # of the report increases. The time range for each interval is referred to as the
14
+ # interval time.
15
+ #
16
+ # Loading the report metrics, errors, and actions will each send a request to Redis
17
+ # for each minute in the time range. At larger interval times, hourly or daily aggregated
18
+ # metrics are used and hosts, actions, and errors are not available.
19
+ #
20
+ # The end time for the report must be at least one minute in the past since metrics are
21
+ # queued up before they are persisted to Redis so there will always be at least a one
22
+ # minute lag.
23
+ class Report
24
+ attr_reader :env, :app, :start_time, :end_time, :minutes, :interval_minutes, :host, :action
25
+
26
+ class << self
27
+ def interval_minutes(minutes)
28
+ if minutes <= 60
29
+ 1
30
+ elsif minutes <= 2 * 60
31
+ 2
32
+ elsif minutes <= 4 * 60
33
+ 5
34
+ elsif minutes <= 12 * 60
35
+ 10
36
+ elsif minutes <= 14 * 24 * 60
37
+ 60
38
+ else
39
+ 60 * 24
40
+ end
41
+ end
42
+ end
43
+
44
+ # @param app [String, Symbol] The app name to get metrics for.
45
+ # @param start_time [Time] The start time for the report (inclusive).
46
+ # @param end_time [Time] The end time for the report (inclusive).
47
+ # @param host [String] Optional host name to filter the metrics.
48
+ # @param action [String] Optional action name to filter the metrics.
49
+ # @param actions_limit [Integer] Limit on how many actions to pull from Redis.
50
+ def initialize(app, start_time, end_time, host: nil, action: nil, actions_limit: 100, env: SpartanAPM.env)
51
+ @app = app.to_s.dup.freeze
52
+ @env = env.to_s.dup.freeze
53
+ @start_time = normalize_time(start_time)
54
+ @end_time = normalize_time(end_time)
55
+ @end_time = @start_time if @end_time < @start_time
56
+
57
+ @minutes = ((@end_time - @start_time) / 60).to_i + 1
58
+ @interval_minutes = self.class.interval_minutes(@minutes)
59
+ if !aggregated? && @start_time < Time.now - SpartanAPM.ttl
60
+ @interval_minutes = 60 unless SpartanAPM.env == "test"
61
+ end
62
+ @end_time += (60 * (@minutes % @interval_minutes))
63
+
64
+ @host = host&.dup.freeze
65
+ @action = action&.dup.freeze
66
+ @actions_limit = [actions_limit, SpartanAPM.max_actions].min
67
+ @metrics = nil
68
+ @actions = nil
69
+ @errors = nil
70
+ end
71
+
72
+ # Returns true if using aggregated metrics averaged over an hour or day
73
+ # @return [Boolean]
74
+ def aggregated?
75
+ interval_minutes >= 60
76
+ end
77
+
78
+ # Returns true if using hourly aggregated metrics
79
+ # @return [Boolean]
80
+ def aggregated_to_hour?
81
+ interval_minutes == 60
82
+ end
83
+
84
+ # Returns true if using daily aggregated metrics
85
+ # @return [Boolean]
86
+ def aggregated_to_day?
87
+ interval_minutes > 60
88
+ end
89
+
90
+ # Iterate over each time segment in the report. Yields the start time
91
+ # for each segment.
92
+ def each_time
93
+ time = start_time
94
+ while time <= end_time
95
+ yield time
96
+ time += 60 * interval_minutes
97
+ end
98
+ end
99
+
100
+ # Collects a value for each time segment in the report. Yields the start
101
+ # time for each segment and returns and array of the return values from the block.
102
+ def collect
103
+ list = []
104
+ each_time do |time|
105
+ list << yield(time)
106
+ end
107
+ list
108
+ end
109
+
110
+ # Get a Metric from a given time in the report.
111
+ # @param time [Time] The time to get the metric for.
112
+ # @return [SpartanAPM::Metric]
113
+ def metric(time)
114
+ load_metrics
115
+ @metrics[normalize_time(time)] || Metric.new(time)
116
+ end
117
+
118
+ # Get the average request time taken in milliseconds for the named component at a time interval.
119
+ # @param time [Time] The interval time.
120
+ # @param name [String, Symbol] The component to get the value for.
121
+ # @return [Integer]
122
+ def component_request_time(time, name)
123
+ value = 0.0
124
+ count = 0
125
+ each_interval(time) do |t|
126
+ value += metric(t).component_request_time(name).to_f
127
+ count += 1
128
+ end
129
+ (value / count).round
130
+ end
131
+
132
+ # Get the average number of calls per request for the named component.
133
+ # @param time [Time] The interval time.
134
+ # @param name [String, Symbol] The component to get the value for.
135
+ # @return [Integer]
136
+ def component_request_count(time, name)
137
+ value = 0.0
138
+ count = 0
139
+ each_interval(time) do |t|
140
+ value += metric(t).component_request_count(name).to_f
141
+ count += 1
142
+ end
143
+ value / count
144
+ end
145
+
146
+ # Get the average total time taken in milliseconds for a request at a time interval.
147
+ # @param time [Time] The interval time.
148
+ # @param measurement [String, Symbol] The measurement to get. This must be one of :avg, :p50, :p90, or :p99.
149
+ # @return [Integer]
150
+ def request_time(time, measurement)
151
+ value = 0.0
152
+ count = 0
153
+ each_interval(time) do |t|
154
+ value += metric(t).send(measurement).to_f
155
+ count += 1
156
+ end
157
+ (value / count).round
158
+ end
159
+
160
+ # Get the average time in milliseconds taken for a component for the report time range.
161
+ # @param name [String, Symbol] The component name to get.
162
+ # @return [Integer]
163
+ def avg_component_time(name)
164
+ total = 0
165
+ count = 0
166
+ each_time do |time|
167
+ each_interval(time) do |t|
168
+ value = metric(t).component_request_time(name)
169
+ if value
170
+ count += 1
171
+ total += value.to_f
172
+ end
173
+ end
174
+ end
175
+ return 0 if count == 0
176
+ (total / count).round
177
+ end
178
+
179
+ # Get the average time in milliseconds taken for a component for the report time range.
180
+ # @param name [String, Symbol] The component name to get.
181
+ # @return [Integer]
182
+ def avg_component_count(name)
183
+ total = 0.0
184
+ count = 0
185
+ each_time do |time|
186
+ each_interval(time) do |t|
187
+ m = metric(t)
188
+ count += 1
189
+ total += m.component_request_count(name).to_f
190
+ end
191
+ end
192
+ return 0 if count == 0
193
+ total / count
194
+ end
195
+
196
+ # Get the average time in milliseconds taken for a measurement for the report time range.
197
+ # @param measurement [String, Symbol] The measurement to get. This must be one of :avg, :p50, :p90, or :p99.
198
+ # @return [Integer]
199
+ def avg_request_time(measurement)
200
+ total = 0
201
+ count = 0
202
+ each_time do |time|
203
+ each_interval(time) do |t|
204
+ value = metric(t)&.send(measurement)
205
+ if value
206
+ count += 1
207
+ total += value.to_f
208
+ end
209
+ end
210
+ end
211
+ return 0 if count == 0
212
+ (total / count).round
213
+ end
214
+
215
+ # Get the total number of requests for a time interval.
216
+ # @param time [Time] The interval time.
217
+ # @return [Integer]
218
+ def request_count(time)
219
+ count = 0
220
+ each_interval(time) do |t|
221
+ count += metric(t)&.count.to_i
222
+ end
223
+ count
224
+ end
225
+
226
+ # Get the average number of requests per minute for a time interval.
227
+ # @param time [Time] The interval time.
228
+ # @return [Integer]
229
+ def requests_per_minute(time)
230
+ (request_count(time).to_f / interval_minutes).round
231
+ end
232
+
233
+ # Get the average requests per minute for the report time range.
234
+ # @return [Integer]
235
+ def avg_requests_per_minute
236
+ total = 0.0
237
+ count = 0
238
+ each_time do |time|
239
+ count += 1
240
+ each_interval(time) do |t|
241
+ total += metric(t)&.count.to_f
242
+ end
243
+ end
244
+ if count > 0
245
+ ((total / count) / interval_minutes).round
246
+ else
247
+ 0
248
+ end
249
+ end
250
+
251
+ # Get the number of errors reported for a time interval.
252
+ # @param time [Time] The interval time.
253
+ # @return [Integer]
254
+ def error_count(time)
255
+ total = 0
256
+ each_interval(time) do |t|
257
+ total += metric(t)&.error_count.to_i
258
+ end
259
+ total
260
+ end
261
+
262
+ # Get the average number of errors per minute for the report time range.
263
+ # @return [Integer]
264
+ def avg_errors_per_minute
265
+ total = 0.0
266
+ count = 0
267
+ each_time do |time|
268
+ each_interval(time) do |t|
269
+ error_count = metric(t)&.error_count
270
+ if error_count
271
+ count += 1
272
+ total += error_count
273
+ end
274
+ end
275
+ end
276
+ if count > 0
277
+ (total.to_f / count).round(2)
278
+ else
279
+ 0.0
280
+ end
281
+ end
282
+
283
+ # Get the average of error rate (errors per request) for a time interval.
284
+ # @param time [Time] The interval time.
285
+ # @return [Float]
286
+ def error_rate(time)
287
+ count = request_count(time)
288
+ if count > 0
289
+ error_count(time).to_f / count.to_f
290
+ else
291
+ 0.0
292
+ end
293
+ end
294
+
295
+ # Get the average error rate (errors per request) for the report time range.
296
+ # @return [Float]
297
+ def avg_error_rate
298
+ total_rate = 0.0
299
+ count = 0
300
+ each_time do |time|
301
+ count += 1
302
+ total_rate += error_rate(time)
303
+ end
304
+ if count > 0
305
+ total_rate / count
306
+ else
307
+ 0.0
308
+ end
309
+ end
310
+
311
+ # Get the list of errors reported during the report time range.
312
+ # @return [Array<SpartanAPM::ErrorInfo]
313
+ def errors
314
+ load_errors
315
+ all_errors = {}
316
+ @errors.values.each do |time_errors|
317
+ time_errors.each do |error_info|
318
+ key = [error_info.class_name, error_info.backtrace]
319
+ err = all_errors[key]
320
+ unless err
321
+ err = ErrorInfo.new(nil, error_info.class_name, error_info.message, error_info.backtrace, 0)
322
+ all_errors[key] = err
323
+ end
324
+ err.count += error_info.count
325
+ end
326
+ end
327
+ all_errors.values.sort_by { |error_info| -error_info.count }
328
+ end
329
+
330
+ # Get the list of action names reported during the report time range. The returned
331
+ # value is limited by the `actions_limit` argument passed in the constructor. The
332
+ # list will be sorted by the amount of time spent in each action with the most
333
+ # heavily used actions coming first.
334
+ # @return [Array<String>] List of action names.
335
+ def actions
336
+ load_actions
337
+ @actions.keys
338
+ end
339
+
340
+ # The the percent of time spent in the specified action.
341
+ # @return [Float]
342
+ def action_percent_time(action)
343
+ load_actions
344
+ @actions[action]
345
+ end
346
+
347
+ # Get the list of host names that reported metrics during the report time range.
348
+ # @return [Array<String>]
349
+ def hosts
350
+ load_metrics
351
+ @hosts.sort
352
+ end
353
+
354
+ # Get the list of component names that reported metrics during the report time range.
355
+ # @return [Array<String>]
356
+ def component_names
357
+ load_metrics
358
+ @names.sort
359
+ end
360
+
361
+ private
362
+
363
+ # Lazily load the metric data.
364
+ def load_metrics
365
+ return if @metrics
366
+ metrics_map = {}
367
+ names = Set.new
368
+ metrics = nil
369
+ hosts = []
370
+ persistence = Persistence.new(app, env: env)
371
+ if interval_minutes >= 60 * 24
372
+ metrics = persistence.daily_metrics([start_time, end_time])
373
+ elsif interval_minutes >= 60
374
+ metrics = persistence.hourly_metrics([start_time, end_time])
375
+ else
376
+ metrics, hosts = persistence.report_info([start_time, end_time], host: host, action: action)
377
+ end
378
+ metrics.each do |metric|
379
+ metric.component_names.each { |n| names << n }
380
+ metrics_map[metric.time] = metric
381
+ end
382
+ @names = names.to_a.freeze
383
+ @hosts = hosts.sort.freeze
384
+ @metrics = metrics_map
385
+ end
386
+
387
+ # Lazily load the action data.
388
+ def load_actions
389
+ return if @actions
390
+ actions = {}
391
+ unless aggregated?
392
+ Persistence.new(app, env: env).actions([start_time, end_time], interval: interval_minutes, limit: @actions_limit).each do |action, load_val|
393
+ actions[action] = load_val
394
+ end
395
+ end
396
+ @actions = actions
397
+ end
398
+
399
+ # Lazily load the error data.
400
+ def load_errors
401
+ return if @errors
402
+ errors = {}
403
+ unless aggregated?
404
+ Persistence.new(app, env: env).errors([start_time, end_time]).each do |error|
405
+ time_errors = errors[error.time]
406
+ unless time_errors
407
+ time_errors = []
408
+ errors[error.time] = time_errors
409
+ end
410
+ time_errors << error
411
+ end
412
+ end
413
+ @errors = errors
414
+ end
415
+
416
+ def normalize_time(time)
417
+ SpartanAPM.bucket_time(SpartanAPM.bucket(time)).freeze
418
+ end
419
+
420
+ def each_interval(time)
421
+ time = normalize_time(time)
422
+ if aggregated?
423
+ time = if aggregated_to_hour?
424
+ SpartanAPM::Persistence.truncate_to_hour(time)
425
+ else
426
+ SpartanAPM::Persistence.truncate_to_date(time)
427
+ end
428
+ yield(time)
429
+ else
430
+ interval_minutes.times do |interval|
431
+ yield(time + (interval * 60))
432
+ end
433
+ end
434
+ end
435
+ end
436
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpartanAPM
4
+ # Simple string cache. This is used to prevent memory bloat
5
+ # when collecting measures by caching and reusing strings
6
+ # in the enqueued metrics rather than having the same string
7
+ # repeated for each request.
8
+ class StringCache
9
+ def initialize
10
+ @cache = Concurrent::Hash.new
11
+ end
12
+
13
+ # Fetch a string from the cache. If it isn't already there,
14
+ # then it will be frozen and stored in the cache so the same
15
+ # object can be returned by subsequent calls for a matching string.
16
+ def fetch(value)
17
+ return nil if value.nil?
18
+ value = value.to_s
19
+ cached = @cache[value]
20
+ unless cached
21
+ cached = -value
22
+ @cache[cached] = cached
23
+ end
24
+ cached
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpartanAPM
4
+ module Web
5
+ # Wrapper for API requests.
6
+ class ApiRequest
7
+ attr_reader :request
8
+
9
+ def initialize(request)
10
+ @request = request
11
+ end
12
+
13
+ # Response for /metrics
14
+ def metrics
15
+ report = create_report
16
+ component_data = {}
17
+ report.component_names.each do |name|
18
+ component_data[name] = {
19
+ time: report.collect { |time| report.component_request_time(time, name) },
20
+ count: report.collect { |time| report.component_request_count(time, name).round(1) }
21
+ }
22
+ end
23
+ {
24
+ env: report.env,
25
+ app: report.app,
26
+ host: report.host,
27
+ action: report.action,
28
+ hosts: report.hosts,
29
+ actions: report.actions,
30
+ minutes: report.minutes,
31
+ interval_minutes: report.interval_minutes,
32
+ times: report.collect { |time| time.iso8601 },
33
+ avg: {
34
+ avg: report.avg_request_time(:avg),
35
+ data: component_data
36
+ },
37
+ p50: {
38
+ avg: report.avg_request_time(:p50),
39
+ data: report.collect { |time| report.request_time(time, :p50) }
40
+ },
41
+ p90: {
42
+ avg: report.avg_request_time(:p90),
43
+ data: report.collect { |time| report.request_time(time, :p90) }
44
+ },
45
+ p99: {
46
+ avg: report.avg_request_time(:p99),
47
+ data: report.collect { |time| report.request_time(time, :p99) }
48
+ },
49
+ throughput: {
50
+ avg: report.avg_requests_per_minute,
51
+ data: report.collect { |time| report.requests_per_minute(time) }
52
+ },
53
+ errors: {
54
+ avg: report.avg_errors_per_minute,
55
+ data: report.collect { |time| report.error_count(time) }
56
+ },
57
+ error_rate: {
58
+ avg: report.avg_error_rate,
59
+ data: report.collect { |time| report.error_rate(time) }
60
+ }
61
+ }
62
+ end
63
+
64
+ # Response for /live_metrics
65
+ def live_metrics
66
+ begin
67
+ current_bucket = SpartanAPM.bucket(Time.now - 65)
68
+ last_bucket = SpartanAPM.bucket(Time.parse(param(:live_time)))
69
+ return({}) if current_bucket <= last_bucket
70
+ rescue
71
+ return({})
72
+ end
73
+ metrics
74
+ end
75
+
76
+ # Response for /errors
77
+ def errors
78
+ report = create_report
79
+ {
80
+ errors: report.errors.collect { |error| {class_name: error.class_name, message: error.message, count: error.count, backtrace: error.backtrace} }
81
+ }
82
+ end
83
+
84
+ # Response for /actions
85
+ def actions
86
+ report = create_report
87
+ {
88
+ actions: report.actions.collect { |action| {name: action, load: report.action_percent_time(action)} }
89
+ }
90
+ end
91
+
92
+ private
93
+
94
+ def param(name, default: nil)
95
+ value = request.params[name.to_s]
96
+ if value.nil? || value == ""
97
+ value = default&.to_s
98
+ end
99
+ value
100
+ end
101
+
102
+ def create_report
103
+ env = (param(:env) || SpartanAPM.env)
104
+ env = SpartanAPM.env unless SpartanAPM.environments.include?(env)
105
+ app = param(:app)
106
+ action = param(:action)
107
+ host = param(:host)
108
+ minutes = param(:minutes).to_i
109
+ minutes = 30 if minutes <= 0
110
+ minutes = 60 * 24 * 365 if minutes > 60 * 24 * 365
111
+ start_time = nil
112
+ time = param(:time)
113
+ if time
114
+ begin
115
+ start_time = Time.parse(time)
116
+ rescue
117
+ # Use default
118
+ end
119
+ end
120
+ if start_time.nil?
121
+ interval_minutes = Report.interval_minutes(minutes)
122
+ start_time = if interval_minutes < 60
123
+ Time.now - (minutes * 60)
124
+ else
125
+ Time.now - ((interval_minutes * 60) + (minutes * 60))
126
+ end
127
+ end
128
+ end_time = start_time + ((minutes - 1) * 60)
129
+ Report.new(app, start_time, end_time, host: host, action: action, env: env)
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpartanAPM
4
+ module Web
5
+ # Helper classes for the web UI.
6
+ class Helpers
7
+ VIEWS_DIR = File.expand_path(File.join("..", "..", "..", "app", "views"), __dir__).freeze
8
+
9
+ @mutex = Mutex.new
10
+ @templates = {}
11
+
12
+ class << self
13
+ # @api private
14
+ # ERB template cache.
15
+ def template(path)
16
+ template = @templates[path]
17
+ unless template
18
+ template = File.read(path)
19
+ if path.end_with?(".erb")
20
+ template = ERB.new(File.read(path))
21
+ end
22
+ @mutex.synchronize { @templates[path] = template } unless ENV["DEVELOPMENT"] == "true"
23
+ end
24
+ template
25
+ end
26
+ end
27
+
28
+ attr_reader :request
29
+
30
+ def initialize(request)
31
+ @request = request
32
+ end
33
+
34
+ # Render an ERB template.
35
+ # @param path [String] Relative path to the template in the gem app/views directory.
36
+ # @variables [Hash] Local variables to set for the ERB binding.
37
+ def render(path, variables = {})
38
+ file_path = File.expand_path(File.join(VIEWS_DIR, *path.split("/")))
39
+ raise ArgumentError.new("Invalid template ") unless file_path.start_with?(VIEWS_DIR)
40
+ template = self.class.template(file_path)
41
+ if template.is_a?(ERB)
42
+ template.result(get_binding(variables))
43
+ else
44
+ template
45
+ end
46
+ end
47
+
48
+ # HTML escape text.
49
+ def h(text)
50
+ ERB::Util.h(text.to_s)
51
+ end
52
+
53
+ # List of available apps.
54
+ def apps
55
+ SpartanAPM.apps
56
+ end
57
+
58
+ # List of available apps.
59
+ def environments
60
+ SpartanAPM.environments
61
+ end
62
+
63
+ # Optional URL for authenticating access to the web UI.
64
+ def authentication_url
65
+ SpartanAPM.authentication_url
66
+ end
67
+
68
+ # Optional application name to show in the web UI.
69
+ def application_name
70
+ SpartanAPM.application_name || "SpartanAPM"
71
+ end
72
+
73
+ # Optional link URL back to the application for the web UI.
74
+ def application_url
75
+ SpartanAPM.application_url
76
+ end
77
+
78
+ private
79
+
80
+ def get_binding(local_variables = {})
81
+ local_variables.each do |name, value|
82
+ binding.local_variable_set(name, value)
83
+ end
84
+ binding
85
+ end
86
+ end
87
+ end
88
+ end