hoss-agent 1.0.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.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/Bug_report.md +40 -0
- data/.github/ISSUE_TEMPLATE/Feature_request.md +17 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +60 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/Dockerfile +43 -0
- data/Gemfile +105 -0
- data/LICENSE +201 -0
- data/hoss-agent.gemspec +42 -0
- data/lib/hoss/agent.rb +231 -0
- data/lib/hoss/central_config/cache_control.rb +51 -0
- data/lib/hoss/central_config.rb +184 -0
- data/lib/hoss/child_durations.rb +64 -0
- data/lib/hoss/config/bytes.rb +42 -0
- data/lib/hoss/config/duration.rb +40 -0
- data/lib/hoss/config/options.rb +154 -0
- data/lib/hoss/config/regexp_list.rb +30 -0
- data/lib/hoss/config/wildcard_pattern_list.rb +54 -0
- data/lib/hoss/config.rb +304 -0
- data/lib/hoss/context/request/socket.rb +36 -0
- data/lib/hoss/context/request/url.rb +59 -0
- data/lib/hoss/context/request.rb +28 -0
- data/lib/hoss/context/response.rb +47 -0
- data/lib/hoss/context/user.rb +59 -0
- data/lib/hoss/context.rb +64 -0
- data/lib/hoss/context_builder.rb +112 -0
- data/lib/hoss/deprecations.rb +39 -0
- data/lib/hoss/error/exception.rb +70 -0
- data/lib/hoss/error/log.rb +41 -0
- data/lib/hoss/error.rb +49 -0
- data/lib/hoss/error_builder.rb +90 -0
- data/lib/hoss/event.rb +131 -0
- data/lib/hoss/instrumenter.rb +107 -0
- data/lib/hoss/internal_error.rb +23 -0
- data/lib/hoss/logging.rb +70 -0
- data/lib/hoss/metadata/process_info.rb +35 -0
- data/lib/hoss/metadata/service_info.rb +76 -0
- data/lib/hoss/metadata/system_info/container_info.rb +136 -0
- data/lib/hoss/metadata/system_info.rb +47 -0
- data/lib/hoss/metadata.rb +36 -0
- data/lib/hoss/naively_hashable.rb +38 -0
- data/lib/hoss/rails.rb +68 -0
- data/lib/hoss/railtie.rb +42 -0
- data/lib/hoss/report.rb +9 -0
- data/lib/hoss/sinatra.rb +53 -0
- data/lib/hoss/spies/faraday.rb +102 -0
- data/lib/hoss/spies/http.rb +81 -0
- data/lib/hoss/spies/net_http.rb +97 -0
- data/lib/hoss/spies.rb +104 -0
- data/lib/hoss/stacktrace/frame.rb +66 -0
- data/lib/hoss/stacktrace.rb +33 -0
- data/lib/hoss/stacktrace_builder.rb +124 -0
- data/lib/hoss/transport/base.rb +191 -0
- data/lib/hoss/transport/connection/http.rb +139 -0
- data/lib/hoss/transport/connection/proxy_pipe.rb +94 -0
- data/lib/hoss/transport/connection.rb +55 -0
- data/lib/hoss/transport/filters/hash_sanitizer.rb +77 -0
- data/lib/hoss/transport/filters/secrets_filter.rb +48 -0
- data/lib/hoss/transport/filters.rb +60 -0
- data/lib/hoss/transport/headers.rb +74 -0
- data/lib/hoss/transport/serializers/context_serializer.rb +112 -0
- data/lib/hoss/transport/serializers/error_serializer.rb +92 -0
- data/lib/hoss/transport/serializers/event_serializer.rb +73 -0
- data/lib/hoss/transport/serializers/metadata_serializer.rb +92 -0
- data/lib/hoss/transport/serializers/report_serializer.rb +33 -0
- data/lib/hoss/transport/serializers.rb +113 -0
- data/lib/hoss/transport/user_agent.rb +48 -0
- data/lib/hoss/transport/worker.rb +319 -0
- data/lib/hoss/util/inflector.rb +110 -0
- data/lib/hoss/util/lru_cache.rb +65 -0
- data/lib/hoss/util/throttle.rb +52 -0
- data/lib/hoss/util.rb +54 -0
- data/lib/hoss/version.rb +22 -0
- data/lib/hoss-agent.rb +210 -0
- data/lib/hoss.rb +21 -0
- metadata +147 -0
data/lib/hoss/agent.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
require 'hoss/error'
|
21
|
+
|
22
|
+
require 'hoss/context_builder'
|
23
|
+
require 'hoss/error_builder'
|
24
|
+
require 'hoss/stacktrace_builder'
|
25
|
+
|
26
|
+
require 'hoss/central_config'
|
27
|
+
require 'hoss/transport/base'
|
28
|
+
|
29
|
+
require 'hoss/spies'
|
30
|
+
|
31
|
+
module Hoss
|
32
|
+
# @api private
|
33
|
+
class Agent
|
34
|
+
include Logging
|
35
|
+
extend Forwardable
|
36
|
+
|
37
|
+
LOCK = Mutex.new
|
38
|
+
|
39
|
+
# life cycle
|
40
|
+
|
41
|
+
def self.instance # rubocop:disable Style/TrivialAccessors
|
42
|
+
@instance
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.start(config)
|
46
|
+
return @instance if @instance
|
47
|
+
|
48
|
+
config = Config.new(config) unless config.is_a?(Config)
|
49
|
+
|
50
|
+
LOCK.synchronize do
|
51
|
+
return @instance if @instance
|
52
|
+
|
53
|
+
unless config.enabled?
|
54
|
+
config.logger.debug format(
|
55
|
+
"%sAgent disabled with `enabled: false'",
|
56
|
+
Logging::PREFIX
|
57
|
+
)
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
@instance = new(config).start
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.stop
|
66
|
+
LOCK.synchronize do
|
67
|
+
return unless @instance
|
68
|
+
|
69
|
+
@instance.stop
|
70
|
+
@instance = nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.running?
|
75
|
+
!!@instance
|
76
|
+
end
|
77
|
+
|
78
|
+
def initialize(config)
|
79
|
+
@stacktrace_builder = StacktraceBuilder.new(config)
|
80
|
+
@context_builder = ContextBuilder.new(config)
|
81
|
+
@error_builder = ErrorBuilder.new(self)
|
82
|
+
|
83
|
+
@central_config = CentralConfig.new(config)
|
84
|
+
@transport = Transport::Base.new(config)
|
85
|
+
@instrumenter = Instrumenter.new(
|
86
|
+
config,
|
87
|
+
metrics: nil,
|
88
|
+
stacktrace_builder: stacktrace_builder
|
89
|
+
) { |event| enqueue event }
|
90
|
+
@pid = Process.pid
|
91
|
+
end
|
92
|
+
|
93
|
+
attr_reader(
|
94
|
+
:central_config,
|
95
|
+
:config,
|
96
|
+
:context_builder,
|
97
|
+
:error_builder,
|
98
|
+
:instrumenter,
|
99
|
+
:stacktrace_builder,
|
100
|
+
:transport
|
101
|
+
)
|
102
|
+
|
103
|
+
def_delegator :@central_config, :config
|
104
|
+
|
105
|
+
def start
|
106
|
+
unless config.disable_start_message?
|
107
|
+
config.logger.info format(
|
108
|
+
'[%s] Starting agent, reporting to %s',
|
109
|
+
VERSION, config.api_host
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
central_config.start
|
114
|
+
transport.start
|
115
|
+
instrumenter.start
|
116
|
+
# metrics.start
|
117
|
+
|
118
|
+
config.enabled_instrumentations.each do |lib|
|
119
|
+
debug "Requiring spy: #{lib}"
|
120
|
+
require "hoss/spies/#{lib}"
|
121
|
+
end
|
122
|
+
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
def stop
|
127
|
+
debug 'Stopping agent'
|
128
|
+
|
129
|
+
central_config.stop
|
130
|
+
instrumenter.stop
|
131
|
+
transport.stop
|
132
|
+
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
at_exit do
|
137
|
+
stop
|
138
|
+
end
|
139
|
+
|
140
|
+
# transport
|
141
|
+
|
142
|
+
def enqueue(obj)
|
143
|
+
transport.submit obj
|
144
|
+
end
|
145
|
+
|
146
|
+
def current_event
|
147
|
+
instrumenter.current_event
|
148
|
+
end
|
149
|
+
|
150
|
+
def start_event
|
151
|
+
detect_forking!
|
152
|
+
instrumenter.start_event
|
153
|
+
end
|
154
|
+
|
155
|
+
def end_event
|
156
|
+
instrumenter.end_event
|
157
|
+
end
|
158
|
+
|
159
|
+
def set_label(key, value)
|
160
|
+
instrumenter.set_label(key, value)
|
161
|
+
end
|
162
|
+
|
163
|
+
def set_custom_context(context)
|
164
|
+
instrumenter.set_custom_context(context)
|
165
|
+
end
|
166
|
+
|
167
|
+
def set_user(user)
|
168
|
+
instrumenter.set_user(user)
|
169
|
+
end
|
170
|
+
|
171
|
+
def build_context(rack_env:, for_type:)
|
172
|
+
@context_builder.build(rack_env: rack_env, for_type: for_type)
|
173
|
+
end
|
174
|
+
|
175
|
+
# errors
|
176
|
+
|
177
|
+
def report(exception, context: nil, handled: true)
|
178
|
+
return unless config.recording?
|
179
|
+
detect_forking!
|
180
|
+
return if config.filter_exception_types.include?(exception.class.to_s)
|
181
|
+
|
182
|
+
error = @error_builder.build_exception(
|
183
|
+
exception,
|
184
|
+
context: context,
|
185
|
+
handled: handled
|
186
|
+
)
|
187
|
+
enqueue error
|
188
|
+
error.id
|
189
|
+
end
|
190
|
+
|
191
|
+
def report_message(message, context: nil, backtrace: nil, **attrs)
|
192
|
+
return unless config.recording?
|
193
|
+
detect_forking!
|
194
|
+
|
195
|
+
error = @error_builder.build_log(
|
196
|
+
message,
|
197
|
+
context: context,
|
198
|
+
backtrace: backtrace,
|
199
|
+
**attrs
|
200
|
+
)
|
201
|
+
enqueue error
|
202
|
+
error.id
|
203
|
+
end
|
204
|
+
|
205
|
+
# filters
|
206
|
+
|
207
|
+
def add_filter(key, callback)
|
208
|
+
transport.add_filter(key, callback)
|
209
|
+
end
|
210
|
+
|
211
|
+
# misc
|
212
|
+
|
213
|
+
def inspect
|
214
|
+
super.split.first + '>'
|
215
|
+
end
|
216
|
+
|
217
|
+
def detect_forking!
|
218
|
+
return if @pid == Process.pid
|
219
|
+
|
220
|
+
config.logger.debug "Detected forking,
|
221
|
+
restarting threads in process [PID:#{Process.pid}]"
|
222
|
+
|
223
|
+
central_config.handle_forking!
|
224
|
+
transport.handle_forking!
|
225
|
+
instrumenter.handle_forking!
|
226
|
+
metrics.handle_forking!
|
227
|
+
|
228
|
+
@pid = Process.pid
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
module Hoss
|
21
|
+
class CentralConfig
|
22
|
+
# @api private
|
23
|
+
class CacheControl
|
24
|
+
def initialize(value)
|
25
|
+
@header = value
|
26
|
+
parse!(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader(
|
30
|
+
:must_revalidate,
|
31
|
+
:no_cache,
|
32
|
+
:no_store,
|
33
|
+
:no_transform,
|
34
|
+
:public,
|
35
|
+
:private,
|
36
|
+
:proxy_revalidate,
|
37
|
+
:max_age,
|
38
|
+
:s_maxage
|
39
|
+
)
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def parse!(value)
|
44
|
+
value.split(',').each do |token|
|
45
|
+
k, v = token.split('=').map(&:strip)
|
46
|
+
instance_variable_set(:"@#{k.tr('-', '_')}", v ? v.to_i : true)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
require 'hoss/central_config/cache_control'
|
21
|
+
|
22
|
+
module Hoss
|
23
|
+
# @api private
|
24
|
+
class CentralConfig
|
25
|
+
include Logging
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
class ResponseError < InternalError
|
29
|
+
def initialize(response)
|
30
|
+
@response = response
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :response
|
34
|
+
end
|
35
|
+
class ClientError < ResponseError; end
|
36
|
+
class ServerError < ResponseError; end
|
37
|
+
|
38
|
+
def initialize(config)
|
39
|
+
@config = config
|
40
|
+
@modified_options = {}
|
41
|
+
@authorization = "Bearer #{@config.api_key}"
|
42
|
+
@http = Transport::Connection::Http.new(config)
|
43
|
+
@etag = 1
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :config
|
47
|
+
attr_reader :scheduled_task, :promise # for specs
|
48
|
+
|
49
|
+
def start
|
50
|
+
return unless config.central_config?
|
51
|
+
|
52
|
+
debug 'Starting CentralConfig'
|
53
|
+
|
54
|
+
fetch_and_apply_config
|
55
|
+
end
|
56
|
+
|
57
|
+
def stop
|
58
|
+
debug 'Stopping CentralConfig'
|
59
|
+
|
60
|
+
@scheduled_task&.cancel
|
61
|
+
end
|
62
|
+
|
63
|
+
def fetch_and_apply_config
|
64
|
+
@promise =
|
65
|
+
Concurrent::Promise
|
66
|
+
.execute(&method(:fetch_config))
|
67
|
+
.on_success(&method(:handle_success))
|
68
|
+
.rescue(&method(:handle_error))
|
69
|
+
end
|
70
|
+
|
71
|
+
def fetch_config
|
72
|
+
resp = perform_request
|
73
|
+
case resp.status
|
74
|
+
when 200..299
|
75
|
+
resp
|
76
|
+
when 300..399
|
77
|
+
resp
|
78
|
+
when 400..499
|
79
|
+
resp
|
80
|
+
# raise ClientError, resp
|
81
|
+
when 500..599
|
82
|
+
resp
|
83
|
+
# raise ServerError, resp
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def assign(update)
|
88
|
+
# For each updated option, store the original value,
|
89
|
+
# unless already stored
|
90
|
+
update.each_key do |key|
|
91
|
+
@modified_options[key] ||= config.get(key.to_sym)&.value
|
92
|
+
end
|
93
|
+
|
94
|
+
# If the new update doesn't set a previously modified option,
|
95
|
+
# revert it to the original
|
96
|
+
@modified_options.each_key do |key|
|
97
|
+
next if update.key?(key)
|
98
|
+
update[key] = @modified_options.delete(key)
|
99
|
+
end
|
100
|
+
@config.replace_options(update)
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_forking!
|
104
|
+
stop
|
105
|
+
start
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
111
|
+
def handle_success(resp)
|
112
|
+
if (etag = resp.headers['Etag'])
|
113
|
+
@etag = etag
|
114
|
+
end
|
115
|
+
|
116
|
+
if resp.status == 304
|
117
|
+
info 'Received 304 Not Modified'
|
118
|
+
else
|
119
|
+
if resp.body && !resp.body.empty?
|
120
|
+
update = JSON.parse(resp.body.to_s)
|
121
|
+
assign(update['data']) unless update.nil? or update['data'].nil?
|
122
|
+
end
|
123
|
+
|
124
|
+
if update && update.any?
|
125
|
+
info 'Updated config'
|
126
|
+
debug 'Modified: %s', update.inspect
|
127
|
+
debug 'Modified original options: %s', @modified_options.inspect
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
schedule_next_fetch(resp)
|
132
|
+
|
133
|
+
true
|
134
|
+
rescue Exception => e
|
135
|
+
error 'Failed to apply remote config, %s', e.inspect
|
136
|
+
debug e.backtrace.join('\n')
|
137
|
+
end
|
138
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
139
|
+
|
140
|
+
def handle_error(error)
|
141
|
+
puts error.backtrace
|
142
|
+
# For tests, WebMock failures don't have real responses
|
143
|
+
response = error.response if error.respond_to?(:response)
|
144
|
+
|
145
|
+
debug(
|
146
|
+
'Failed fetching config: %s, trying again in %d seconds',
|
147
|
+
response&.body, @config.remote_config_fetch_interval
|
148
|
+
)
|
149
|
+
|
150
|
+
assign({})
|
151
|
+
|
152
|
+
schedule_next_fetch(response)
|
153
|
+
end
|
154
|
+
|
155
|
+
def perform_request
|
156
|
+
body = '{"query":"query AgentConfig {\n agentConfig {\n accountApiConfiguration {\n uuid\n hostBlacklist\n sanitizedHeaders\n sanitizedQueryParams\n sanitizedBodyFields {\n type\n value\n }\n bodyCapture\n }\n apis {\n uuid\n name\n rootDomain\n hosts\n configuration(mergeWithAccountConfiguration: true) {\n uuid\n sanitizedHeaders\n sanitizedQueryParams\n bodyCapture\n sanitizedBodyFields {\n type\n value\n }\n }\n }\n accountRestrictions {\n ingressDisabled\n }\n }\n}","operationName":"AgentConfig"}'
|
157
|
+
@http.post(api_host, body: body, headers: headers)
|
158
|
+
end
|
159
|
+
|
160
|
+
def api_host
|
161
|
+
@api_host ||=
|
162
|
+
config.api_host +
|
163
|
+
'/api/graphql'
|
164
|
+
end
|
165
|
+
|
166
|
+
def headers
|
167
|
+
{ 'Etag': @etag, 'Authorization': @authorization, 'HOSS-SKIP-INSTRUMENTATION': true }
|
168
|
+
end
|
169
|
+
|
170
|
+
def schedule_next_fetch(resp = nil)
|
171
|
+
headers = resp&.headers
|
172
|
+
seconds =
|
173
|
+
if headers && headers['Cache-Control']
|
174
|
+
CacheControl.new(headers['Cache-Control']).max_age
|
175
|
+
else
|
176
|
+
@config.remote_config_fetch_interval
|
177
|
+
end
|
178
|
+
|
179
|
+
@scheduled_task =
|
180
|
+
Concurrent::ScheduledTask
|
181
|
+
.execute(seconds, &method(:fetch_and_apply_config))
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
module Hoss
|
21
|
+
# @api private
|
22
|
+
module ChildDurations
|
23
|
+
# @api private
|
24
|
+
module Methods
|
25
|
+
def child_durations
|
26
|
+
@child_durations ||= Durations.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def child_started
|
30
|
+
child_durations.start
|
31
|
+
end
|
32
|
+
|
33
|
+
def child_stopped
|
34
|
+
child_durations.stop
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
class Durations
|
40
|
+
def initialize
|
41
|
+
@nesting_level = 0
|
42
|
+
@start = nil
|
43
|
+
@duration = 0
|
44
|
+
@mutex = Mutex.new
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :duration
|
48
|
+
|
49
|
+
def start
|
50
|
+
@mutex.synchronize do
|
51
|
+
@nesting_level += 1
|
52
|
+
@start = Util.micros if @nesting_level == 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def stop
|
57
|
+
@mutex.synchronize do
|
58
|
+
@nesting_level -= 1
|
59
|
+
@duration = (Util.micros - @start) if @nesting_level == 0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
module Hoss
|
21
|
+
class Config
|
22
|
+
# @api private
|
23
|
+
class Bytes
|
24
|
+
MULTIPLIERS = {
|
25
|
+
'kb' => 1024,
|
26
|
+
'mb' => 1024 * 1_000,
|
27
|
+
'gb' => 1024 * 100_000
|
28
|
+
}.freeze
|
29
|
+
REGEX = /^(\d+)(b|kb|mb|gb)?$/i.freeze
|
30
|
+
|
31
|
+
def initialize(default_unit: 'kb')
|
32
|
+
@default_unit = default_unit
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(value)
|
36
|
+
_, amount, unit = REGEX.match(String(value)).to_a
|
37
|
+
unit ||= @default_unit
|
38
|
+
MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
module Hoss
|
21
|
+
class Config
|
22
|
+
# @api private
|
23
|
+
class Duration
|
24
|
+
MULTIPLIERS = { 'ms' => 0.001, 'm' => 60 }.freeze
|
25
|
+
REGEX = /^(-)?(\d+)(m|ms|s)?$/i.freeze
|
26
|
+
|
27
|
+
def initialize(default_unit: 's')
|
28
|
+
@default_unit = default_unit
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(str)
|
32
|
+
_, negative, amount, unit = REGEX.match(String(str)).to_a
|
33
|
+
unit ||= @default_unit
|
34
|
+
seconds = MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
|
35
|
+
seconds = 0 - seconds if negative
|
36
|
+
seconds
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|