splunk-tracer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|