splunk-tracer 0.1.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 +7 -0
- data/.circleci/config.yml +31 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +48 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +52 -0
- data/LICENSE.txt +21 -0
- data/Makefile +24 -0
- data/README.md +74 -0
- data/Rakefile +6 -0
- data/benchmark.rb +138 -0
- data/benchmark/bench.rb +60 -0
- data/benchmark/threading/thread_test.rb +54 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/circle.yml +3 -0
- data/example.rb +33 -0
- data/examples/fork_children/main.rb +54 -0
- data/examples/rack/hello.rb +22 -0
- data/examples/rack/inject_extract.rb +68 -0
- data/lib/splunktracing.rb +50 -0
- data/lib/splunktracing/global_tracer.rb +32 -0
- data/lib/splunktracing/reporter.rb +119 -0
- data/lib/splunktracing/scope.rb +23 -0
- data/lib/splunktracing/scope_manager.rb +54 -0
- data/lib/splunktracing/span.rb +191 -0
- data/lib/splunktracing/span_context.rb +13 -0
- data/lib/splunktracing/tracer.rb +321 -0
- data/lib/splunktracing/transport/base.rb +10 -0
- data/lib/splunktracing/transport/callback.rb +16 -0
- data/lib/splunktracing/transport/http_json.rb +144 -0
- data/lib/splunktracing/transport/nil.rb +9 -0
- data/lib/splunktracing/version.rb +3 -0
- data/scripts/version.rb +5 -0
- data/splunktracing.gemspec +31 -0
- metadata +193 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
require 'splunktracing'
|
|
3
|
+
|
|
4
|
+
SplunkTracing.configure(component_name: 'splunktracing/ruby/example', access_token: '{your_access_token}')
|
|
5
|
+
|
|
6
|
+
puts 'Starting...'
|
|
7
|
+
|
|
8
|
+
mutex = Mutex.new
|
|
9
|
+
done = false
|
|
10
|
+
span_count = 0
|
|
11
|
+
percent_done = 0
|
|
12
|
+
total_time = 0
|
|
13
|
+
|
|
14
|
+
watchThread = Thread.new do
|
|
15
|
+
loop do
|
|
16
|
+
sleep(0.5)
|
|
17
|
+
mutex.lock
|
|
18
|
+
time_per_span = (1e6 * (total_time.to_f / span_count.to_f)).round(2)
|
|
19
|
+
puts "#{span_count} spans #{percent_done}% done #{total_time.round(2)} seconds (#{time_per_span} µs/span)"
|
|
20
|
+
is_done = done
|
|
21
|
+
mutex.unlock
|
|
22
|
+
Thread.exit if is_done
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
thread = Thread.new do
|
|
27
|
+
count = 0
|
|
28
|
+
total_time = 0
|
|
29
|
+
for j in 1..1000
|
|
30
|
+
start = Time.now
|
|
31
|
+
for i in 1..100
|
|
32
|
+
span = SplunkTracing.start_span('my_span')
|
|
33
|
+
span.log(event: 'hello world', count: i)
|
|
34
|
+
span.finish
|
|
35
|
+
count += 1
|
|
36
|
+
end
|
|
37
|
+
delta = Time.now - start
|
|
38
|
+
|
|
39
|
+
mutex.lock
|
|
40
|
+
percent_done = (100.0 * (count / 100_000.0)).ceil
|
|
41
|
+
span_count = count
|
|
42
|
+
total_time += delta
|
|
43
|
+
mutex.unlock
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
thread.join
|
|
48
|
+
mutex.lock
|
|
49
|
+
done = true
|
|
50
|
+
mutex.unlock
|
|
51
|
+
watchThread.join
|
|
52
|
+
|
|
53
|
+
puts 'Done!'
|
|
54
|
+
SplunkTracing.flush
|
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'splunktracing'
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require 'irb'
|
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/circle.yml
ADDED
data/example.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
require 'simplecov'
|
|
3
|
+
SimpleCov.command_name 'example.rb'
|
|
4
|
+
SimpleCov.start
|
|
5
|
+
require 'splunktracing'
|
|
6
|
+
|
|
7
|
+
access_token = '08243c00-a31b-499d-9fae-776b41990997'
|
|
8
|
+
|
|
9
|
+
SplunkTracing.configure(component_name: 'splunktracing/ruby/example', access_token: access_token)
|
|
10
|
+
|
|
11
|
+
puts 'Starting operation...'
|
|
12
|
+
span = SplunkTracing.start_span('my_span')
|
|
13
|
+
thread1 = Thread.new do
|
|
14
|
+
(1..10).each do |i|
|
|
15
|
+
sleep(0.15)
|
|
16
|
+
puts "Logging event #{i}..."
|
|
17
|
+
span.log(event: 'hello world', count: i)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
thread2 = Thread.new do
|
|
21
|
+
current = 1
|
|
22
|
+
(1..16).each do |i|
|
|
23
|
+
child = SplunkTracing.start_span('my_child', child_of: span.span_context)
|
|
24
|
+
sleep(0.1)
|
|
25
|
+
current *= 2
|
|
26
|
+
child.log(event: "2^#{i}", result: current)
|
|
27
|
+
child.finish
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
[thread1, thread2].each(&:join)
|
|
31
|
+
span.finish
|
|
32
|
+
SplunkTracing.flush
|
|
33
|
+
puts 'Done!'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# A simple, manual test ensuring that tracer instances still report after a
|
|
2
|
+
# Process.fork.
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'splunktracing'
|
|
6
|
+
|
|
7
|
+
SplunkTracing.configure(
|
|
8
|
+
component_name: 'splunktracing/ruby/examples/fork_children',
|
|
9
|
+
access_token: '{your_access_token}'
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
puts 'Starting...'
|
|
13
|
+
(1..20).each do |k|
|
|
14
|
+
puts "Explicit reset iteration #{k}..."
|
|
15
|
+
|
|
16
|
+
pid = Process.fork do
|
|
17
|
+
10.times do
|
|
18
|
+
span = SplunkTracing.start_span("my_forked_span-#{Process.pid}")
|
|
19
|
+
sleep(0.0025 * rand(k))
|
|
20
|
+
span.finish
|
|
21
|
+
end
|
|
22
|
+
SplunkTracing.flush
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
3.times do
|
|
26
|
+
span = SplunkTracing.start_span("my_process_span-#{Process.pid}")
|
|
27
|
+
sleep(0.0025 * rand(k))
|
|
28
|
+
span.set_tag(:empty, "")
|
|
29
|
+
span.set_tag(:full, "full")
|
|
30
|
+
span.finish
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Make sure redundant enable calls don't cause problems
|
|
34
|
+
# NOTE: disabling discards the buffer by default, so all spans
|
|
35
|
+
# get cleared here except the final toggle span
|
|
36
|
+
10.times do
|
|
37
|
+
SplunkTracing.disable
|
|
38
|
+
SplunkTracing.enable
|
|
39
|
+
SplunkTracing.disable
|
|
40
|
+
SplunkTracing.disable
|
|
41
|
+
SplunkTracing.enable
|
|
42
|
+
SplunkTracing.enable
|
|
43
|
+
span = SplunkTracing.start_span("my_toggle_span-#{Process.pid}")
|
|
44
|
+
sleep(0.0025 * rand(k))
|
|
45
|
+
span.finish
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
puts "Parent, pid #{Process.pid}, waiting on child pid #{pid}"
|
|
49
|
+
Process.wait(pid)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
puts 'Done!'
|
|
53
|
+
|
|
54
|
+
SplunkTracing.flush
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
require 'splunktracing'
|
|
3
|
+
|
|
4
|
+
require 'rack'
|
|
5
|
+
require 'rack/server'
|
|
6
|
+
|
|
7
|
+
SplunkTracing.configure(
|
|
8
|
+
component_name: 'splunktracing/ruby/examples/rack',
|
|
9
|
+
access_token: '{your_access_token}'
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
class HelloWorldApp
|
|
13
|
+
def self.call(env)
|
|
14
|
+
span = SplunkTracing.start_span('request',tags: {name: "G"})
|
|
15
|
+
span.log event: 'env', env: env
|
|
16
|
+
resp = [200, {}, ["Hello World. You said: #{env['QUERY_STRING']}"]]
|
|
17
|
+
span.finish
|
|
18
|
+
resp
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Rack::Server.start app: HelloWorldApp
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
require 'splunktracing'
|
|
3
|
+
require 'opentracing'
|
|
4
|
+
|
|
5
|
+
require 'rack'
|
|
6
|
+
require 'rack/server'
|
|
7
|
+
|
|
8
|
+
$token = '{your_access_token}'
|
|
9
|
+
$request_id = 'abc123'
|
|
10
|
+
|
|
11
|
+
class Router
|
|
12
|
+
def initialize
|
|
13
|
+
@tracer = SplunkTracing::Tracer.new(component_name: 'router', access_token: $token)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(env)
|
|
17
|
+
span = @tracer.start_span("router_call").set_baggage_item("request-id", $request_id)
|
|
18
|
+
span.log(event: "router_request", env: env)
|
|
19
|
+
puts "parent #{span.span_context.trace_id}"
|
|
20
|
+
|
|
21
|
+
client = Net::HTTP.new("localhost", "9002")
|
|
22
|
+
req = Net::HTTP::Post.new("/")
|
|
23
|
+
@tracer.inject(span.span_context, OpenTracing::FORMAT_RACK, req)
|
|
24
|
+
res = client.request(req)
|
|
25
|
+
|
|
26
|
+
span.log(event: "application_response", response: res.to_s)
|
|
27
|
+
span.finish
|
|
28
|
+
@tracer.flush
|
|
29
|
+
puts "----> #{$token} span_guid=#{span.span_context.id} at_micros=#{span.start_micros} <----"
|
|
30
|
+
[200, {}, [res.body]]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class App
|
|
35
|
+
def initialize
|
|
36
|
+
@tracer = SplunkTracing::Tracer.new(component_name: 'app', access_token: $token)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def call(env)
|
|
40
|
+
wire_ctx = @tracer.extract(OpenTracing::FORMAT_RACK, env)
|
|
41
|
+
span = @tracer.start_span("app_call", child_of: wire_ctx)
|
|
42
|
+
puts "child #{span.to_h[:trace_guid]}"
|
|
43
|
+
span.log(event: "application", env: env)
|
|
44
|
+
sleep 0.05
|
|
45
|
+
span.finish
|
|
46
|
+
@tracer.flush
|
|
47
|
+
[200, {}, ["application"]]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
router_thread = Thread.new do
|
|
52
|
+
Thread.abort_on_exception = true
|
|
53
|
+
Rack::Server.start(app: Router.new, Port: 9001)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
app_thread = Thread.new do
|
|
57
|
+
Thread.abort_on_exception = true
|
|
58
|
+
Rack::Server.start(app: App.new, Port: 9002)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
loop do
|
|
62
|
+
begin
|
|
63
|
+
p Net::HTTP.get(URI("http://127.0.0.1:9001/"))
|
|
64
|
+
break
|
|
65
|
+
rescue Errno::ECONNREFUSED
|
|
66
|
+
sleep 0.05
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
require 'logger'
|
|
3
|
+
|
|
4
|
+
# Splunk Tracer
|
|
5
|
+
module SplunkTracing
|
|
6
|
+
extend SingleForwardable
|
|
7
|
+
|
|
8
|
+
# Base class for all SplunkTracing errors
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
|
|
11
|
+
# Returns the singleton instance of the Tracer.
|
|
12
|
+
def self.instance
|
|
13
|
+
SplunkTracing::GlobalTracer.instance
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def_delegator :instance, :configure
|
|
17
|
+
def_delegator :instance, :start_span
|
|
18
|
+
def_delegator :instance, :start_active_span
|
|
19
|
+
def_delegator :instance, :disable
|
|
20
|
+
def_delegator :instance, :enable
|
|
21
|
+
def_delegator :instance, :flush
|
|
22
|
+
|
|
23
|
+
# Convert a time to microseconds
|
|
24
|
+
def self.micros(time)
|
|
25
|
+
(time.to_f * 1E6).floor
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns a random guid. Note: this intentionally does not use SecureRandom,
|
|
29
|
+
# which is slower and cryptographically secure randomness is not required here.
|
|
30
|
+
def self.guid
|
|
31
|
+
unless @_lastpid == Process.pid
|
|
32
|
+
@_lastpid = Process.pid
|
|
33
|
+
@_rng = Random.new
|
|
34
|
+
end
|
|
35
|
+
@_rng.bytes(8).unpack('H*')[0]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.logger
|
|
39
|
+
@logger ||= defined?(::Rails) ? Rails.logger : Logger.new(STDOUT)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.logger=(logger)
|
|
43
|
+
@logger = logger
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
require 'splunktracing/tracer'
|
|
48
|
+
require 'splunktracing/global_tracer'
|
|
49
|
+
require 'splunktracing/scope'
|
|
50
|
+
require 'splunktracing/scope_manager'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
|
|
3
|
+
module SplunkTracing
|
|
4
|
+
# GlobalTracer is a singleton version of the SplunkTracing::Tracer.
|
|
5
|
+
#
|
|
6
|
+
# You should access it via `SplunkTracing.instance`.
|
|
7
|
+
class GlobalTracer < Tracer
|
|
8
|
+
private
|
|
9
|
+
def initialize
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
public
|
|
13
|
+
include Singleton
|
|
14
|
+
|
|
15
|
+
# Configure the GlobalTracer
|
|
16
|
+
# See {SplunkTracing::Tracer#initialize}
|
|
17
|
+
def configure(**options)
|
|
18
|
+
if configured
|
|
19
|
+
SplunkTracing.logger.warn "[SplunkTracing] Already configured"
|
|
20
|
+
SplunkTracing.logger.info "Stack trace:\n\t#{caller.join("\n\t")}"
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
self.configured = true
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_accessor :configured
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
|
|
3
|
+
require 'splunktracing/version'
|
|
4
|
+
|
|
5
|
+
module SplunkTracing
|
|
6
|
+
# Reporter builds up reports of spans and flushes them to a transport
|
|
7
|
+
class Reporter
|
|
8
|
+
DEFAULT_PERIOD_SECONDS = 3.0
|
|
9
|
+
attr_accessor :max_span_records
|
|
10
|
+
attr_accessor :period
|
|
11
|
+
|
|
12
|
+
def initialize(max_span_records:, transport:, guid:, component_name:, tags: {})
|
|
13
|
+
@max_span_records = max_span_records
|
|
14
|
+
@span_records = Concurrent::Array.new
|
|
15
|
+
@dropped_spans = Concurrent::AtomicFixnum.new
|
|
16
|
+
@transport = transport
|
|
17
|
+
@period = DEFAULT_PERIOD_SECONDS
|
|
18
|
+
|
|
19
|
+
start_time = SplunkTracing.micros(Time.now)
|
|
20
|
+
@report_start_time = start_time
|
|
21
|
+
|
|
22
|
+
@runtime = {
|
|
23
|
+
guid: guid,
|
|
24
|
+
device: Socket.gethostname,
|
|
25
|
+
start_micros: start_time,
|
|
26
|
+
component_name: component_name,
|
|
27
|
+
tracer_platform: "ruby",
|
|
28
|
+
tracer_version: SplunkTracing::VERSION,
|
|
29
|
+
tracer_platform_version: RUBY_VERSION,
|
|
30
|
+
attrs: tags
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
reset_on_fork
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add_span(span)
|
|
37
|
+
reset_on_fork
|
|
38
|
+
|
|
39
|
+
@span_records.push(span.to_h)
|
|
40
|
+
if @span_records.size > max_span_records
|
|
41
|
+
@span_records.shift
|
|
42
|
+
@dropped_spans.increment
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def clear
|
|
47
|
+
reset_on_fork
|
|
48
|
+
|
|
49
|
+
span_records = @span_records.slice!(0, @span_records.length)
|
|
50
|
+
@dropped_spans.increment(span_records.size)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def flush
|
|
54
|
+
reset_on_fork
|
|
55
|
+
|
|
56
|
+
return if @span_records.empty?
|
|
57
|
+
|
|
58
|
+
now = SplunkTracing.micros(Time.now)
|
|
59
|
+
|
|
60
|
+
span_records = @span_records.slice!(0, @span_records.length)
|
|
61
|
+
dropped_spans = 0
|
|
62
|
+
@dropped_spans.update do |old|
|
|
63
|
+
dropped_spans = old
|
|
64
|
+
0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
report_request = {
|
|
68
|
+
runtime: @runtime,
|
|
69
|
+
oldest_micros: @report_start_time,
|
|
70
|
+
youngest_micros: now,
|
|
71
|
+
span_records: span_records,
|
|
72
|
+
internal_metrics: {
|
|
73
|
+
counts: [{
|
|
74
|
+
name: 'spans.dropped',
|
|
75
|
+
int64_value: dropped_spans
|
|
76
|
+
}]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@report_start_time = now
|
|
81
|
+
|
|
82
|
+
begin
|
|
83
|
+
@transport.report(report_request)
|
|
84
|
+
rescue StandardError => e
|
|
85
|
+
SplunkTracing.logger.error "SplunkTracing error reporting to collector: #{e.message}"
|
|
86
|
+
# an error occurs, add the previous dropped_spans and count of spans
|
|
87
|
+
# that would have been recorded
|
|
88
|
+
@dropped_spans.increment(dropped_spans + span_records.length)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# When the process forks, reset the child. All data that was copied will be handled
|
|
95
|
+
# by the parent. Also, restart the thread since forking killed it
|
|
96
|
+
def reset_on_fork
|
|
97
|
+
if @pid != $$
|
|
98
|
+
@pid = $$
|
|
99
|
+
@span_records.clear
|
|
100
|
+
@dropped_spans.value = 0
|
|
101
|
+
report_spans
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def report_spans
|
|
106
|
+
return if @period <= 0
|
|
107
|
+
Thread.new do
|
|
108
|
+
begin
|
|
109
|
+
loop do
|
|
110
|
+
sleep(@period)
|
|
111
|
+
flush
|
|
112
|
+
end
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
SplunkTracing.logger.error "SplunkTracing failed to report spans: #{e.message}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|