summaryrefslogtreecommitdiff
path: root/lib/gitlab/tracing
diff options
context:
space:
mode:
authorAndrew Newdigate <andrew@gitlab.com>2019-01-07 12:40:54 +0200
committerAndrew Newdigate <andrew@gitlab.com>2019-01-17 12:32:44 +0200
commit57a8859a11fe0c2cbd68deb83a2d2857c245fd2f (patch)
tree26728a2ac8d0dd6937de95e1de0711640de788d7 /lib/gitlab/tracing
parentf383c4032a6b3f2ee2c9e5aa50425fc3a04bc1b4 (diff)
downloadgitlab-ce-57a8859a11fe0c2cbd68deb83a2d2857c245fd2f.tar.gz
Conditionally initialize the global opentracing tracer
This change will instantiate an OpenTracing tracer and configure it as the global tracer when the GITLAB_TRACING environment variable is configured. GITLAB_TRACING takes a "connection string"-like value, encapsulating the driver (eg jaeger, etc) and options for the driver. Since each service, whether it's written in Ruby or Golang, uses the same connection-string, it should be very easy to configure all services in a cluster, or even a single development machine to be setup to use tracing. Note that this change does not include instrumentation or propagation changes as this is a way of breaking a previous larger change into components. The instrumentation and propagation changes will follow in separate changes.
Diffstat (limited to 'lib/gitlab/tracing')
-rw-r--r--lib/gitlab/tracing/factory.rb61
-rw-r--r--lib/gitlab/tracing/jaeger_factory.rb97
2 files changed, 158 insertions, 0 deletions
diff --git a/lib/gitlab/tracing/factory.rb b/lib/gitlab/tracing/factory.rb
new file mode 100644
index 00000000000..fc714164353
--- /dev/null
+++ b/lib/gitlab/tracing/factory.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require "cgi"
+
+module Gitlab
+ module Tracing
+ class Factory
+ OPENTRACING_SCHEME = "opentracing"
+
+ def self.create_tracer(service_name, connection_string)
+ return unless connection_string.present?
+
+ begin
+ opentracing_details = parse_connection_string(connection_string)
+ driver_name = opentracing_details[:driver_name]
+
+ case driver_name
+ when "jaeger"
+ JaegerFactory.create_tracer(service_name, opentracing_details[:options])
+ else
+ raise "Unknown driver: #{driver_name}"
+ end
+ rescue => e
+ # Can't create the tracer? Warn and continue sans tracer
+ warn "Unable to instantiate tracer: #{e}"
+ nil
+ end
+ end
+
+ def self.parse_connection_string(connection_string)
+ parsed = URI.parse(connection_string)
+
+ unless valid_uri?(parsed)
+ raise "Invalid tracing connection string"
+ end
+
+ {
+ driver_name: parsed.host,
+ options: parse_query(parsed.query)
+ }
+ end
+ private_class_method :parse_connection_string
+
+ def self.parse_query(query)
+ return {} unless query
+
+ CGI.parse(query).symbolize_keys.transform_values(&:first)
+ end
+ private_class_method :parse_query
+
+ def self.valid_uri?(uri)
+ return false unless uri
+
+ uri.scheme == OPENTRACING_SCHEME &&
+ uri.host.to_s =~ /^[a-z0-9_]+$/ &&
+ uri.path.empty?
+ end
+ private_class_method :valid_uri?
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/jaeger_factory.rb b/lib/gitlab/tracing/jaeger_factory.rb
new file mode 100644
index 00000000000..0726f6b67f4
--- /dev/null
+++ b/lib/gitlab/tracing/jaeger_factory.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'jaeger/client'
+
+module Gitlab
+ module Tracing
+ class JaegerFactory
+ # When the probabilistic sampler is used, by default 0.1% of requests will be traced
+ DEFAULT_PROBABILISTIC_RATE = 0.001
+
+ # The default port for the Jaeger agent UDP listener
+ DEFAULT_UDP_PORT = 6831
+
+ # Reduce this from default of 10 seconds as the Ruby jaeger
+ # client doesn't have overflow control, leading to very large
+ # messages which fail to send over UDP (max packet = 64k)
+ # Flush more often, with smaller packets
+ FLUSH_INTERVAL = 5
+
+ def self.create_tracer(service_name, options)
+ kwargs = {
+ service_name: service_name,
+ sampler: get_sampler(options[:sampler], options[:sampler_param]),
+ reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint])
+ }
+
+ extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug) # rubocop: disable CodeReuse/ActiveRecord
+ if extra_params.present?
+ message = "jaeger tracer: invalid option: #{extra_params.keys.join(", ")}"
+
+ if options[:strict_parsing]
+ raise message
+ else
+ warn message
+ end
+ end
+
+ Jaeger::Client.build(kwargs)
+ end
+
+ def self.get_sampler(sampler_type, sampler_param)
+ case sampler_type
+ when "probabilistic"
+ sampler_rate = sampler_param ? sampler_param.to_f : DEFAULT_PROBABILISTIC_RATE
+ Jaeger::Samplers::Probabilistic.new(rate: sampler_rate)
+ when "const"
+ const_value = sampler_param == "1"
+ Jaeger::Samplers::Const.new(const_value)
+ else
+ nil
+ end
+ end
+ private_class_method :get_sampler
+
+ def self.get_reporter(service_name, http_endpoint, udp_endpoint)
+ encoder = Jaeger::Encoders::ThriftEncoder.new(service_name: service_name)
+
+ if http_endpoint.present?
+ sender = get_http_sender(encoder, http_endpoint)
+ elsif udp_endpoint.present?
+ sender = get_udp_sender(encoder, udp_endpoint)
+ else
+ return nil
+ end
+
+ Jaeger::Reporters::RemoteReporter.new(
+ sender: sender,
+ flush_interval: FLUSH_INTERVAL
+ )
+ end
+ private_class_method :get_reporter
+
+ def self.get_http_sender(encoder, address)
+ Jaeger::HttpSender.new(
+ url: address,
+ encoder: encoder,
+ logger: Logger.new(STDOUT)
+ )
+ end
+ private_class_method :get_http_sender
+
+ def self.get_udp_sender(encoder, address)
+ pair = address.split(":", 2)
+ host = pair[0]
+ port = pair[1] ? pair[1].to_i : DEFAULT_UDP_PORT
+
+ Jaeger::UdpSender.new(
+ host: host,
+ port: port,
+ encoder: encoder,
+ logger: Logger.new(STDOUT)
+ )
+ end
+ private_class_method :get_udp_sender
+ end
+ end
+end