lightstep 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 842c2598853d8cd0b169c367beb6fb16b3cc54f3
4
+ data.tar.gz: 19a192a4a00afb358410c6ff8953858dbb7837e5
5
+ SHA512:
6
+ metadata.gz: e2c209b4d660be14a771f96676c0ab76f5c2b39a3e731b756a6cc4a2f7259a25030933aa525d08c1b76bdf47aed90133ffdbb1b75f716be4b9092f17205af693
7
+ data.tar.gz: d5087a55948bfdd293e4d275e290428f47857ba75909df2e3f2b6a54f1817cb1a0f04608831dcc73a8e179a635a5ea68d8ddd6cb7b2d5dbe9bc64799d6ea04c8
@@ -0,0 +1,13 @@
1
+ .bundle/
2
+ .yardoc
3
+ Gemfile.lock
4
+ _yardoc/
5
+ coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *~
11
+ Gemfile.lock
12
+ lightstep-tracer*.gem
13
+ lightstep*.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 LightStep
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ .PHONY: build test benchmark publish
2
+
3
+ build:
4
+ gem build lightstep-tracer.gemspec
5
+
6
+ test:
7
+ bundle exec rake spec
8
+ ruby example.rb
9
+ ruby examples/fork_children/main.rb
10
+
11
+ benchmark:
12
+ ruby benchmark/bench.rb
13
+ ruby benchmark/threading/thread_test.rb
14
+
15
+ bump-version:
16
+ ruby -e 'require "bump"; Bump::Bump.run("patch")'
17
+ make build # rebuild after version increment
18
+ git tag `ruby scripts/version.rb`
19
+
20
+ publish: build test benchmark
21
+ git push
22
+ git push --tags
23
+ gem push lightstep-`ruby scripts/version.rb`.gem
@@ -0,0 +1,56 @@
1
+ # lightstep-tracer-ruby
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/lightstep-tracer.svg)](https://badge.fury.io/rb/lightstep-tracer) [![Circle CI](https://circleci.com/gh/lightstep/lightstep-tracer-ruby.svg?style=shield)](https://circleci.com/gh/lightstep/lightstep-tracer-ruby) [![MIT license](http://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) [![Code Climate](https://codeclimate.com/github/lightstep/lightstep-tracer-ruby/badges/gpa.svg)](https://codeclimate.com/github/lightstep/lightstep-tracer-ruby)
4
+
5
+ The LightStep distributed tracing library for Ruby.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'lightstep'
12
+
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install lightstep
21
+
22
+
23
+ ## Getting started
24
+
25
+ require 'lightstep'
26
+
27
+ # Initialize the singleton tracer
28
+ LightStep.configure(component_name: 'lightstep/ruby/example', access_token: 'your_access_token')
29
+
30
+ # Create a basic span and attach a log to the span
31
+ span = LightStep.start_span('my_span')
32
+ span.log(event: 'hello world', count: 42)
33
+
34
+ # Create a child span (and add some artificial delays to illustrate the timing)
35
+ sleep(0.1)
36
+ child = LightStep.start_span('my_child', child_of: span)
37
+ sleep(0.2)
38
+ child.finish
39
+ sleep(0.1)
40
+ span.finish
41
+
42
+ ## Thread Safety
43
+
44
+ The LightStep Tracer is threadsafe. For increased performance, you can add the
45
+ `concurrent-ruby-ext` gem to your Gemfile. This will enable C extensions for
46
+ concurrent operations.
47
+
48
+ **The LightStep Tracer is not inherently Fork-safe**. When forking, you should
49
+ `disable` the Tracer before forking, then `enable` it in the child Process
50
+ and parent Process after forking. See the `fork_children` example for more.
51
+
52
+ ## Development
53
+
54
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `make test` to run the tests.
55
+
56
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,60 @@
1
+ require 'benchmark'
2
+ require 'securerandom'
3
+
4
+ require 'bundler/setup'
5
+ require 'lightstep'
6
+
7
+ rng = Random.new
8
+
9
+ # Run a quick profile on logging lots of spans
10
+ tracer = LightStep::Tracer.new(
11
+ component_name: 'lightstep/ruby/spec',
12
+ transport: LightStep::Transport::Nil.new
13
+ )
14
+
15
+ Benchmark.bm(32) do |x|
16
+ x.report('Random.bytes.unpack') do
17
+ for i in 1..10_000; rng.bytes(8).unpack('H*')[0]; end
18
+ end
19
+ x.report('Random.bytes.each_byte.map') do
20
+ for i in 1..10_000; rng.bytes(8).each_byte.map { |b| b.to_s(16) }.join; end
21
+ end
22
+ x.report('SecureRandom.hex') do
23
+ for i in 1..10_000; SecureRandom.hex(8); end
24
+ end
25
+
26
+ x.report('start_span(100)') do
27
+ for i in 0..100; tracer.start_span('my_span').finish; end
28
+ end
29
+ x.report('start_span(1000)') do
30
+ for i in 0..1000; tracer.start_span('my_span').finish; end
31
+ end
32
+ x.report('start_span(10000)') do
33
+ for i in 0..10_000; tracer.start_span('my_span').finish; end
34
+ end
35
+
36
+ x.report('log_event(100)') do
37
+ span = tracer.start_span('my_span')
38
+ for i in 0..100; span.log(event: 'event', i: i); end
39
+ span.finish
40
+ end
41
+ x.report('log_event(1000)') do
42
+ span = tracer.start_span('my_span')
43
+ for i in 0..1000; span.log(event: 'event', i: i); end
44
+ span.finish
45
+ end
46
+ x.report('log_event(10000)') do
47
+ span = tracer.start_span('my_span')
48
+ for i in 0..10_000; span.log(event: 'event', i: i); end
49
+ span.finish
50
+ end
51
+
52
+ x.report('inject(10000)') do
53
+ span = tracer.start_span('my_span')
54
+ for i in 0..10_000
55
+ carrier = {}
56
+ tracer.inject(span, LightStep::Tracer::FORMAT_TEXT_MAP, carrier)
57
+ end
58
+ span.finish
59
+ end
60
+ end
@@ -0,0 +1,53 @@
1
+ require 'bundler/setup'
2
+ require 'lightstep'
3
+
4
+ LightStep.configure(component_name: 'lightstep/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} us/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 = LightStep.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!'
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'lightstep'
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,7 @@
1
+ general:
2
+ branches:
3
+ only:
4
+ - master
5
+ machine:
6
+ ruby:
7
+ version: 2.2.3
@@ -0,0 +1,34 @@
1
+ require 'bundler/setup'
2
+ require 'simplecov'
3
+ SimpleCov.command_name 'example.rb'
4
+ SimpleCov.start
5
+ require 'lightstep'
6
+
7
+ access_token = '{your_access_token}'
8
+
9
+ LightStep.configure(component_name: 'lightstep/ruby/example', access_token: access_token)
10
+
11
+ puts 'Starting operation...'
12
+ span = LightStep.start_span('my_span')
13
+ thread1 = Thread.new do
14
+ for i in 1..10
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
+ for i in 1..16
23
+ child = LightStep.start_span('my_child', child_of: span)
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
+
33
+ puts 'Done!'
34
+ puts "https://app.lightstep.com/#{access_token}/trace?span_guid=#{span.guid}&at_micros=#{span.start_micros}"
@@ -0,0 +1,54 @@
1
+ # A simple, manual test ensuring that tracer instances still report after a
2
+ # Process.fork. Currently this requires the tracer instance to be explicitly
3
+ # disabled before the fork and reenabled afterward.
4
+
5
+ require 'bundler/setup'
6
+ require 'lightstep'
7
+
8
+ LightStep.configure(
9
+ component_name: 'lightstep/ruby/examples/fork_children',
10
+ access_token: '{your_access_token}'
11
+ )
12
+
13
+ puts 'Starting...'
14
+ (1..20).each do |k|
15
+ puts "Explicit reset iteration #{k}..."
16
+
17
+ # NOTE: the tracer is disabled and reenabled on either side of the fork
18
+ LightStep.disable
19
+ pid = Process.fork do
20
+ LightStep.enable
21
+ 10.times do
22
+ span = LightStep.start_span("my_forked_span-#{Process.pid}")
23
+ sleep(0.0025 * rand(k))
24
+ span.finish
25
+ end
26
+ end
27
+
28
+ # Also renable the parent process' tracer
29
+ LightStep.enable
30
+
31
+ 10.times do
32
+ span = LightStep.start_span("my_process_span-#{Process.pid}")
33
+ sleep(0.0025 * rand(k))
34
+ span.finish
35
+ end
36
+
37
+ # Make sure redundant enable calls don't cause problems
38
+ 10.times do
39
+ LightStep.disable
40
+ LightStep.enable
41
+ LightStep.disable
42
+ LightStep.disable
43
+ LightStep.enable
44
+ LightStep.enable
45
+ span = LightStep.start_span("my_toggle_span-#{Process.pid}")
46
+ sleep(0.0025 * rand(k))
47
+ span.finish
48
+ end
49
+
50
+ puts "Parent, pid #{Process.pid}, waiting on child pid #{pid}"
51
+ Process.wait
52
+ end
53
+
54
+ puts 'Done!'
@@ -0,0 +1,22 @@
1
+ require 'bundler/setup'
2
+ require 'lightstep'
3
+
4
+ require 'rack'
5
+ require 'rack/server'
6
+
7
+ LightStep.configure(
8
+ component_name: 'lightstep/ruby/examples/rack',
9
+ access_token: '{your_access_token}'
10
+ )
11
+
12
+ class HelloWorldApp
13
+ def self.call(env)
14
+ span = LightStep.start_span('request')
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,35 @@
1
+ require 'forwardable'
2
+
3
+ # LightStep Tracer
4
+ module LightStep
5
+ extend SingleForwardable
6
+
7
+ # Base class for all LightStep errors
8
+ class Error < StandardError; end
9
+
10
+ # Returns the singleton instance of the Tracer.
11
+ def self.instance
12
+ LightStep::GlobalTracer.instance
13
+ end
14
+
15
+ def_delegator :instance, :configure
16
+ def_delegator :instance, :start_span
17
+ def_delegator :instance, :disable
18
+ def_delegator :instance, :enable
19
+ def_delegator :instance, :flush
20
+
21
+ # Convert a time to microseconds
22
+ def self.micros(time)
23
+ (time.to_f * 1E6).floor
24
+ end
25
+
26
+ # Returns a random guid. Note: this intentionally does not use SecureRandom,
27
+ # which is slower and cryptographically secure randomness is not required here.
28
+ def self.guid
29
+ @_rng ||= Random.new
30
+ @_rng.bytes(8).unpack('H*')[0]
31
+ end
32
+ end
33
+
34
+ require 'lightstep/tracer'
35
+ require 'lightstep/global_tracer'
@@ -0,0 +1,27 @@
1
+ require 'singleton'
2
+
3
+ module LightStep
4
+ # GlobalTracer is a singleton version of the LightStep::Tracer.
5
+ #
6
+ # You should access it via `LightStep.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 {LightStep::Tracer#initialize}
17
+ def configure(opts = nil)
18
+ raise ConfigurationError, 'Already configured' if configured
19
+ self.configured = true
20
+ super
21
+ end
22
+
23
+ private
24
+
25
+ attr_accessor :configured
26
+ end
27
+ end
@@ -0,0 +1,148 @@
1
+ require 'concurrent'
2
+
3
+ module LightStep
4
+ # Span represents an OpenTracer Span
5
+ #
6
+ # See http://www.opentracing.io for more information.
7
+ class Span
8
+ # Internal use only
9
+ # @private
10
+ attr_reader :guid, :trace_guid, :start_micros, :end_micros, :baggage, :tags
11
+
12
+ # Creates a new {Span}
13
+ #
14
+ # @param tracer [Tracer] the tracer that created this span
15
+ # @param operation_name [String] the operation name of this span
16
+ # @param child_of_guid [String] the guid of the span this span is a child of
17
+ # @param trace_guid [String] the guid of this span's trace
18
+ # @param start_micros [Numeric] start time of the span in microseconds
19
+ # @return [Span] a new Span
20
+ def initialize(
21
+ tracer:,
22
+ operation_name:,
23
+ child_of_guid: nil,
24
+ trace_guid:,
25
+ start_micros:,
26
+ tags: nil,
27
+ max_log_records:
28
+ )
29
+ @tags = Concurrent::Hash.new(tags)
30
+ @baggage = Concurrent::Hash.new
31
+ @log_records = Concurrent::Array.new
32
+ @dropped_logs = Concurrent::AtomicFixnum.new
33
+ @max_log_records = max_log_records
34
+
35
+ @tracer = tracer
36
+ @guid = LightStep.guid
37
+ self.operation_name = operation_name
38
+ self.start_micros = start_micros
39
+ self.trace_guid = trace_guid
40
+ set_tag(:parent_span_guid, child_of_guid) if !child_of_guid.nil?
41
+ end
42
+
43
+ # Set a tag value on this span
44
+ # @param key [String] the key of the tag
45
+ # @param value [String, Numeric, Boolean] the value of the tag. If it's not
46
+ # a String, Numeric, or Boolean it will be encoded with to_s
47
+ def set_tag(key, value)
48
+ case value
49
+ when String, Fixnum, TrueClass, FalseClass
50
+ tags[key] = value
51
+ else
52
+ tags[key] = value.to_s
53
+ end
54
+ self
55
+ end
56
+
57
+ # TODO(ngauthier@gmail.com) baggage keys have a restricted format according
58
+ # to the spec: http://opentracing.io/documentation/pages/spec#baggage-vs-span-tags
59
+
60
+ # Set a baggage item on the span
61
+ # @param key [String] the key of the baggage item
62
+ # @param value [String] the value of the baggage item
63
+ def set_baggage_item(key, value)
64
+ baggage[key] = value
65
+ self
66
+ end
67
+
68
+ # Get a baggage item
69
+ # @param key [String] the key of the baggage item
70
+ # @return Value of the baggage item
71
+ def get_baggage_item(key)
72
+ baggage[key]
73
+ end
74
+
75
+ # Add a log entry to this span
76
+ # @param event [String] event name for the log
77
+ # @param timestamp [Time] time of the log
78
+ # @param fields [Hash] Additional information to log
79
+ def log(event: nil, timestamp: Time.now, **fields)
80
+ return unless tracer.enabled?
81
+
82
+ record = {
83
+ runtime_guid: tracer.guid,
84
+ timestamp_micros: LightStep.micros(timestamp)
85
+ }
86
+ record[:stable_name] = event.to_s if !event.nil?
87
+
88
+ begin
89
+ record[:payload_json] = JSON.generate(fields, max_nesting: 8)
90
+ rescue
91
+ # TODO: failure to encode a payload as JSON should be recorded in the
92
+ # internal library logs, with catioun not flooding the internal logs.
93
+ end
94
+
95
+ log_records.push(record)
96
+ if log_records.size > @max_log_records
97
+ log_records.shift
98
+ dropped_logs.increment
99
+ end
100
+ end
101
+
102
+ # Finish the {Span}
103
+ # @param end_time [Time] custom end time, if not now
104
+ def finish(end_time: Time.now)
105
+ if end_micros.nil?
106
+ self.end_micros = LightStep.micros(end_time)
107
+ end
108
+ tracer.finish_span(self)
109
+ self
110
+ end
111
+
112
+ # Hash representation of a span
113
+ def to_h
114
+ {
115
+ runtime_guid: tracer.guid,
116
+ span_guid: guid,
117
+ trace_guid: trace_guid,
118
+ span_name: operation_name,
119
+ attributes: tags.map {|key, value|
120
+ {Key: key.to_s, Value: value}
121
+ },
122
+ oldest_micros: start_micros,
123
+ youngest_micros: end_micros,
124
+ error_flag: false,
125
+ dropped_logs: dropped_logs_count,
126
+ log_records: log_records
127
+ }
128
+ end
129
+
130
+ # Internal use only
131
+ # @private
132
+ def dropped_logs_count
133
+ dropped_logs.value
134
+ end
135
+
136
+ # Internal use only
137
+ # @private
138
+ def logs_count
139
+ log_records.size
140
+ end
141
+
142
+ private
143
+
144
+ attr_reader :tracer, :dropped_logs, :log_records
145
+ attr_writer :guid, :trace_guid, :start_micros, :end_micros
146
+ attr_accessor :operation_name
147
+ end
148
+ end
@@ -0,0 +1,309 @@
1
+ require 'json'
2
+ require 'concurrent'
3
+
4
+ require 'lightstep/span'
5
+ require 'lightstep/transport/http_json'
6
+ require 'lightstep/transport/nil'
7
+ require 'lightstep/transport/callback'
8
+
9
+ module LightStep
10
+ class Tracer
11
+ FORMAT_TEXT_MAP = 1
12
+ FORMAT_BINARY = 2
13
+
14
+ CARRIER_TRACER_STATE_PREFIX = 'ot-tracer-'.freeze
15
+ CARRIER_BAGGAGE_PREFIX = 'ot-baggage-'.freeze
16
+
17
+ DEFAULT_MAX_LOG_RECORDS = 1000
18
+ MIN_MAX_LOG_RECORDS = 1
19
+ DEFAULT_MAX_SPAN_RECORDS = 1000
20
+ MIN_MAX_SPAN_RECORDS = 1
21
+ DEFAULT_MIN_REPORTING_PERIOD_SECS = 1.5
22
+ DEFAULT_MAX_REPORTING_PERIOD_SECS = 30.0
23
+
24
+ class Error < LightStep::Error; end
25
+ class ConfigurationError < LightStep::Tracer::Error; end
26
+
27
+ attr_reader :access_token, :guid
28
+
29
+ # Initialize a new tracer. Either an access_token or a transport must be
30
+ # provided. A component_name is always required.
31
+ # @param component_name [String] Component name to use for the tracer
32
+ # @param access_token [String] The project access token when pushing to LightStep
33
+ # @param transport [LightStep::Transport] How the data should be transported
34
+ # @return LightStep::Tracer
35
+ # @raise LightStep::ConfigurationError if the group name or access token is not a valid string.
36
+ def initialize(component_name:, access_token: nil, transport: nil)
37
+ configure(component_name: component_name, access_token: access_token, transport: transport)
38
+ end
39
+
40
+ def max_log_records
41
+ @max_log_records ||= DEFAULT_MAX_LOG_RECORDS
42
+ end
43
+
44
+ def max_log_records=(max)
45
+ @max_log_records = [MIN_MAX_LOG_RECORDS, max].max
46
+ end
47
+
48
+ def max_span_records
49
+ @max_span_records ||= DEFAULT_MAX_SPAN_RECORDS
50
+ end
51
+
52
+ def max_span_records=(max)
53
+ @max_span_records = [MIN_MAX_SPAN_RECORDS, max].max
54
+ end
55
+
56
+ def min_flush_period_micros
57
+ @min_flush_period_micros ||= DEFAULT_MIN_REPORTING_PERIOD_SECS * 1E6
58
+ end
59
+
60
+ def min_reporting_period_secs=(secs)
61
+ @min_flush_period_micros = [DEFAULT_MIN_REPORTING_PERIOD_SECS, secs].max * 1E6
62
+ end
63
+
64
+ def max_flush_period_micros
65
+ @max_flush_period_micros ||= DEFAULT_MAX_REPORTING_PERIOD_SECS * 1E6
66
+ end
67
+
68
+ def max_reporting_period_secs=(secs)
69
+ @max_flush_period_micros = [DEFAULT_MAX_REPORTING_PERIOD_SECS, secs].min * 1E6
70
+ end
71
+
72
+ # TODO(ngauthier@gmail.com) inherit SpanContext from references
73
+
74
+ # Starts a new span.
75
+ # @param operation_name [String] the operation name for the Span
76
+ # @param child_of [Span] Span to inherit from
77
+ # @param start_time [Time] When the Span started, if not now
78
+ # @param tags [Hash] tags for the span
79
+ # @return [Span]
80
+ def start_span(operation_name, child_of: nil, start_time: nil, tags: nil)
81
+ child_of_guid = nil
82
+ trace_guid = nil
83
+ if Span === child_of
84
+ child_of_guid = child_of.guid
85
+ trace_guid = child_of.trace_guid
86
+ else
87
+ trace_guid = LightStep.guid
88
+ end
89
+
90
+ Span.new(
91
+ tracer: self,
92
+ operation_name: operation_name,
93
+ child_of_guid: child_of_guid,
94
+ trace_guid: trace_guid,
95
+ start_micros: start_time.nil? ? LightStep.micros(Time.now) : LightStep.micros(start_time),
96
+ tags: tags,
97
+ max_log_records: max_log_records
98
+ )
99
+ end
100
+
101
+ # Inject a span into the given carrier
102
+ # @param span [Span]
103
+ # @param format [LightStep::Tracer::FORMAT_TEXT_MAP, LightStep::Tracer::FORMAT_BINARY]
104
+ # @param carrier [Hash-like]
105
+ def inject(span, format, carrier)
106
+ case format
107
+ when LightStep::Tracer::FORMAT_TEXT_MAP
108
+ inject_to_text_map(span, carrier)
109
+ when LightStep::Tracer::FORMAT_BINARY
110
+ warn 'Binary inject format not yet implemented'
111
+ else
112
+ warn 'Unknown inject format'
113
+ end
114
+ end
115
+
116
+ # Extract a span from a carrier
117
+ # @param operation_name [String]
118
+ # @param format [LightStep::Tracer::FORMAT_TEXT_MAP, LightStep::Tracer::FORMAT_BINARY]
119
+ # @param carrier [Hash-like]
120
+ # @return [Span]
121
+ def extract(operation_name, format, carrier)
122
+ case format
123
+ when LightStep::Tracer::FORMAT_TEXT_MAP
124
+ extract_from_text_map(operation_name, carrier)
125
+ when LightStep::Tracer::FORMAT_BINARY
126
+ warn 'Binary join format not yet implemented'
127
+ nil
128
+ else
129
+ warn 'Unknown join format'
130
+ nil
131
+ end
132
+ end
133
+
134
+ # @return true if the tracer is enabled
135
+ def enabled?
136
+ @enabled ||= true
137
+ end
138
+
139
+ # Enables the tracer
140
+ def enable
141
+ @enabled = true
142
+ end
143
+
144
+ # Disables the tracer
145
+ # @param discard [Boolean] whether to discard queued data
146
+ def disable(discard: true)
147
+ @enabled = false
148
+ @transport.clear if discard
149
+ @transport.flush
150
+ end
151
+
152
+ # Flush to the Transport
153
+ def flush
154
+ _flush_worker
155
+ end
156
+
157
+ # Internal use only.
158
+ # @private
159
+ def finish_span(span)
160
+ return unless enabled?
161
+ @span_records.push(span.to_h)
162
+ if @span_records.size > max_span_records
163
+ @span_records.shift
164
+ @dropped_spans.increment
165
+ @dropped_span_logs.increment(span.logs_count + span.dropped_logs_count)
166
+ end
167
+ flush_if_needed
168
+ end
169
+
170
+ protected
171
+
172
+ def access_token=(token)
173
+ if !access_token.nil?
174
+ raise ConfigurationError, "access token cannot be changed"
175
+ end
176
+ @access_token = token
177
+ end
178
+
179
+ def configure(component_name:, access_token: nil, transport: nil)
180
+ raise ConfigurationError, "component_name must be a string" unless String === component_name
181
+ raise ConfigurationError, "component_name cannot be blank" if component_name.empty?
182
+
183
+ @span_records = Concurrent::Array.new
184
+ @dropped_spans = Concurrent::AtomicFixnum.new
185
+ @dropped_span_logs = Concurrent::AtomicFixnum.new
186
+
187
+ start_time = LightStep.micros(Time.now)
188
+ @guid = LightStep.guid
189
+ @report_start_time = start_time
190
+ @last_flush_micros = start_time
191
+
192
+ @runtime = {
193
+ guid: guid,
194
+ start_micros: start_time,
195
+ group_name: component_name,
196
+ attrs: [
197
+ {Key: "lightstep.tracer_platform", Value: "ruby"},
198
+ {Key: "lightstep.tracer_version", Value: LightStep::VERSION},
199
+ {Key: "lightstep.tracer_platform_version", Value: RUBY_VERSION}
200
+ ]
201
+ }.freeze
202
+
203
+ if !transport.nil?
204
+ if !(LightStep::Transport::Base === transport)
205
+ raise ConfigurationError, "transport is not a LightStep transport class: #{transport}"
206
+ end
207
+ @transport = transport
208
+ else
209
+ if access_token.nil?
210
+ raise ConfigurationError, "you must provide an access token or a transport"
211
+ end
212
+ @transport = Transport::HTTPJSON.new(access_token: access_token)
213
+ end
214
+
215
+ # At exit, flush this objects data to the transport and close the transport
216
+ # (which in turn will send the flushed data over the network).
217
+ at_exit do
218
+ flush
219
+ @transport.close
220
+ end
221
+ end
222
+
223
+ def flush_if_needed
224
+ return unless enabled?
225
+
226
+ delta = LightStep.micros(Time.now) - @last_flush_micros
227
+ return if delta < min_flush_period_micros
228
+
229
+ if delta > max_flush_period_micros || @span_records.size >= max_span_records / 2
230
+ flush
231
+ end
232
+ end
233
+
234
+ private
235
+
236
+ def inject_to_text_map(span, carrier)
237
+ carrier[CARRIER_TRACER_STATE_PREFIX + 'spanid'] = span.guid
238
+ carrier[CARRIER_TRACER_STATE_PREFIX + 'traceid'] = span.trace_guid unless span.trace_guid.nil?
239
+ carrier[CARRIER_TRACER_STATE_PREFIX + 'sampled'] = 'true'
240
+
241
+ span.baggage.each do |key, value|
242
+ carrier[CARRIER_BAGGAGE_PREFIX + key] = value
243
+ end
244
+ end
245
+
246
+ def extract_from_text_map(operation_name, carrier)
247
+ span = Span.new(
248
+ tracer: self,
249
+ operation_name: operation_name,
250
+ start_micros: LightStep.micros(Time.now),
251
+ child_of_guid: carrier[CARRIER_TRACER_STATE_PREFIX + 'spanid'],
252
+ trace_guid: carrier[CARRIER_TRACER_STATE_PREFIX + 'traceid'],
253
+ max_log_records: max_log_records
254
+ )
255
+
256
+ carrier.each do |key, value|
257
+ next unless key.start_with?(CARRIER_BAGGAGE_PREFIX)
258
+ plain_key = key.to_s[CARRIER_BAGGAGE_PREFIX.length..key.to_s.length]
259
+ span.set_baggage_item(plain_key, value)
260
+ end
261
+ span
262
+ end
263
+
264
+ def _flush_worker
265
+ return unless enabled?
266
+ # The thrift configuration has not yet been set: allow logs and spans
267
+ # to be buffered in this case, but flushes won't yet be possible.
268
+ return if @runtime.nil?
269
+ return if @span_records.empty?
270
+
271
+ now = LightStep.micros(Time.now)
272
+
273
+ span_records = @span_records.slice!(0, @span_records.length)
274
+ dropped_spans = 0
275
+ @dropped_spans.update{|old| dropped_spans = old; 0 }
276
+
277
+ old_dropped_span_logs = 0
278
+ @dropped_span_logs.update{|old| old_dropped_span_logs = old; 0 }
279
+ dropped_logs = old_dropped_span_logs
280
+ dropped_logs = span_records.reduce(dropped_logs) do |memo, span|
281
+ memo += span.delete :dropped_logs
282
+ end
283
+
284
+ report_request = {
285
+ runtime: @runtime,
286
+ oldest_micros: @report_start_time,
287
+ youngest_micros: now,
288
+ span_records: span_records,
289
+ counters: [
290
+ {Name: "dropped_logs", Value: dropped_logs},
291
+ {Name: "dropped_spans", Value: dropped_spans},
292
+ ]
293
+ }
294
+
295
+ @last_flush_micros = now
296
+ @report_start_time = now
297
+
298
+ begin
299
+ @transport.report(report_request)
300
+ rescue LightStep::Transport::HTTPJSON::QueueFullError
301
+ # If the queue is full, add the previous dropped logs to the logs
302
+ # that were going to get reported, as well as the previous dropped
303
+ # spans and spans that would have been recorded
304
+ @dropped_spans.increment(dropped_spans + span_records.length)
305
+ @dropped_span_logs.increment(old_dropped_span_logs)
306
+ end
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,22 @@
1
+ module LightStep
2
+ module Transport
3
+ # Base Transport type
4
+ class Base
5
+ def initialize
6
+ end
7
+
8
+ def report(_report)
9
+ nil
10
+ end
11
+
12
+ def close
13
+ end
14
+
15
+ def clear
16
+ end
17
+
18
+ def flush
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ require 'lightstep/transport/base'
2
+
3
+ module LightStep
4
+ module Transport
5
+ class Callback < Base
6
+ def initialize(callback:)
7
+ @callback = callback
8
+ end
9
+
10
+ def report(report)
11
+ @callback.call(report)
12
+ nil
13
+ end
14
+
15
+ def close
16
+ end
17
+
18
+ def clear
19
+ end
20
+
21
+ def flush
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,111 @@
1
+ require 'net/http'
2
+ require 'lightstep/transport/base'
3
+
4
+ module LightStep
5
+ module Transport
6
+ # HTTPJSON is a transport that sends reports via HTTP in JSON format.
7
+ # It is thread-safe, however it is *not* fork-safe. When forking, all items
8
+ # in the queue will be copied and sent in duplicate.
9
+ #
10
+ # When forking, you should first `disable` the tracer, then `enable` it from
11
+ # within the fork (and in the parent post-fork). See
12
+ # `examples/fork_children/main.rb` for an example.
13
+ class HTTPJSON < Base
14
+ LIGHTSTEP_HOST = "collector.lightstep.com"
15
+ LIGHTSTEP_PORT = 443
16
+ QUEUE_SIZE = 16
17
+
18
+ ENCRYPTION_TLS = 'tls'
19
+ ENCRYPTION_NONE = 'none'
20
+
21
+ class QueueFullError < LightStep::Error; end
22
+
23
+ # Initialize the transport
24
+ # @param host [String] host of the domain to the endpoind to push data
25
+ # @param port [Numeric] port on which to connect
26
+ # @param verbose [Numeric] verbosity level. Right now 0-3 are supported
27
+ # @param secure [Boolean]
28
+ # @param access_token [String] access token for LightStep server
29
+ # @return [HTTPJSON]
30
+ def initialize(host: LIGHTSTEP_HOST, port: LIGHTSTEP_PORT, verbose: 0, encryption: ENCRYPTION_TLS, access_token:)
31
+ @host = host
32
+ @port = port
33
+ @verbose = verbose
34
+ @encryption = encryption
35
+
36
+ raise ConfigurationError, "access_token must be a string" unless String === access_token
37
+ raise ConfigurationError, "access_token cannot be blank" if access_token.empty?
38
+ @access_token = access_token
39
+
40
+ start_queue
41
+ end
42
+
43
+ # Queue a report for sending
44
+ def report(report)
45
+ p report if @verbose >= 3
46
+ # TODO(ngauthier@gmail.com): the queue could be full here if we're
47
+ # lagging, which would cause this to block!
48
+ @queue.push({
49
+ host: @host,
50
+ port: @port,
51
+ encryption: @encryption,
52
+ access_token: @access_token,
53
+ content: report,
54
+ verbose: @verbose
55
+ }, true)
56
+ nil
57
+ rescue ThreadError
58
+ raise QueueFullError
59
+ end
60
+
61
+ # Flush the current queue
62
+ def flush
63
+ close
64
+ start_queue
65
+ end
66
+
67
+ # Clear the current queue, deleting pending items
68
+ def clear
69
+ @queue.clear
70
+ end
71
+
72
+ # Close the transport. No further data can be sent!
73
+ def close
74
+ @queue.close
75
+ @thread.join
76
+ end
77
+
78
+ private
79
+
80
+ def start_queue
81
+ @queue = SizedQueue.new(QUEUE_SIZE)
82
+ @thread = start_thread(@queue)
83
+ end
84
+
85
+ # TODO(ngauthier@gmail.com) abort on exception?
86
+ def start_thread(queue)
87
+ Thread.new do
88
+ while item = queue.pop
89
+ post_report(item)
90
+ end
91
+ end
92
+ end
93
+
94
+ def post_report(params)
95
+ https = Net::HTTP.new(params[:host], params[:port])
96
+ https.use_ssl = params[:encryption] == ENCRYPTION_TLS
97
+ req = Net::HTTP::Post.new('/api/v0/reports')
98
+ req['LightStep-Access-Token'] = params[:access_token]
99
+ req['Content-Type'] = 'application/json'
100
+ req['Connection'] = 'keep-alive'
101
+ req.body = params[:content].to_json
102
+ res = https.request(req)
103
+
104
+ puts res.to_s if params[:verbose] >= 3
105
+
106
+ # TODO(ngauthier@gmail.com): log unknown commands
107
+ # TODO(ngauthier@gmail.com): log errors from server
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,9 @@
1
+ require 'lightstep/transport/base'
2
+
3
+ module LightStep
4
+ module Transport
5
+ # Empty transport, primarily for unit testing purposes
6
+ class Nil < Base
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module LightStep
2
+ VERSION = '0.9.2'.freeze
3
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'lightstep/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'lightstep'
8
+ spec.version = LightStep::VERSION
9
+ spec.authors = ['bcronin']
10
+ spec.email = ['support@lightstep.com']
11
+
12
+ spec.summary = 'LightStep OpenTracing Ruby bindings'
13
+ spec.homepage = 'https://github.com/lightstep/lightstep-tracer-ruby'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_dependency 'concurrent-ruby', '~> 1.0.0'
20
+ spec.add_development_dependency 'rake', '~> 11.3.0'
21
+ spec.add_development_dependency 'rack', '~> 2.0.0'
22
+ spec.add_development_dependency 'rspec', '~> 3.0'
23
+ spec.add_development_dependency 'bump', '~> 0.5'
24
+ spec.add_development_dependency 'simplecov', '~> 0.12.0'
25
+ end
@@ -0,0 +1,5 @@
1
+ lib = File.expand_path('../../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'lightstep/version'
4
+
5
+ print LightStep::VERSION
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lightstep
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.2
5
+ platform: ruby
6
+ authors:
7
+ - bcronin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 11.3.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 11.3.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.0.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bump
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.12.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.12.0
97
+ description:
98
+ email:
99
+ - support@lightstep.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - Makefile
109
+ - README.md
110
+ - Rakefile
111
+ - benchmark/bench.rb
112
+ - benchmark/threading/thread_test.rb
113
+ - bin/console
114
+ - bin/setup
115
+ - circle.yml
116
+ - example.rb
117
+ - examples/fork_children/main.rb
118
+ - examples/rack/hello.rb
119
+ - lib/lightstep.rb
120
+ - lib/lightstep/global_tracer.rb
121
+ - lib/lightstep/span.rb
122
+ - lib/lightstep/tracer.rb
123
+ - lib/lightstep/transport/base.rb
124
+ - lib/lightstep/transport/callback.rb
125
+ - lib/lightstep/transport/http_json.rb
126
+ - lib/lightstep/transport/nil.rb
127
+ - lib/lightstep/version.rb
128
+ - lightstep-tracer.gemspec
129
+ - scripts/version.rb
130
+ homepage: https://github.com/lightstep/lightstep-tracer-ruby
131
+ licenses:
132
+ - MIT
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 2.5.1
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: LightStep OpenTracing Ruby bindings
154
+ test_files: []