├── AUTHORS ├── docs ├── .gitignore ├── 404.html ├── Gemfile ├── _config.yml ├── _layouts │ └── default.html └── index.md ├── Gemfile ├── examples ├── hello-world │ ├── Gemfile │ ├── README.md │ └── hello.rb └── stats │ └── quickstart.rb ├── bin ├── setup └── console ├── .yardopts ├── .gitignore ├── lib ├── opencensus │ ├── tags │ │ ├── formatters.rb │ │ ├── config.rb │ │ ├── tag_map.rb │ │ └── formatters │ │ │ └── binary.rb │ ├── stats │ │ ├── aggregation │ │ │ ├── sum.rb │ │ │ ├── count.rb │ │ │ ├── last_value.rb │ │ │ └── distribution.rb │ │ ├── aggregation_data.rb │ │ ├── aggregation.rb │ │ ├── aggregation_data │ │ │ ├── last_value.rb │ │ │ ├── sum.rb │ │ │ ├── count.rb │ │ │ └── distribution.rb │ │ ├── measurement.rb │ │ ├── exporters.rb │ │ ├── exemplar.rb │ │ ├── view.rb │ │ ├── view_data.rb │ │ ├── measure_registry.rb │ │ ├── exporters │ │ │ ├── multi.rb │ │ │ └── logger.rb │ │ ├── config.rb │ │ ├── measure.rb │ │ └── recorder.rb │ ├── version.rb │ ├── trace │ │ ├── trace_context_data.rb │ │ ├── integrations.rb │ │ ├── formatters.rb │ │ ├── time_event.rb │ │ ├── samplers │ │ │ ├── always_sample.rb │ │ │ ├── never_sample.rb │ │ │ ├── rate_limiting.rb │ │ │ └── probability.rb │ │ ├── exporters.rb │ │ ├── annotation.rb │ │ ├── exporters │ │ │ ├── multi.rb │ │ │ └── logger.rb │ │ ├── truncatable_string.rb │ │ ├── formatters │ │ │ ├── binary.rb │ │ │ ├── cloud_trace.rb │ │ │ └── trace_context.rb │ │ ├── samplers.rb │ │ ├── message_event.rb │ │ ├── link.rb │ │ ├── integrations │ │ │ └── rack_middleware.rb │ │ └── config.rb │ ├── common.rb │ ├── config.rb │ ├── tags.rb │ ├── context.rb │ └── stats.rb └── opencensus.rb ├── test ├── stats │ ├── aggregation │ │ ├── sum_test.rb │ │ ├── count_test.rb │ │ ├── last_value_test.rb │ │ └── distribution_test.rb │ ├── aggregation_data │ │ ├── count_test.rb │ │ ├── sum_test.rb │ │ ├── last_value_test.rb │ │ └── distribution_test.rb │ ├── view_test.rb │ ├── exemplar_test.rb │ ├── measure_test.rb │ ├── exporters │ │ └── logger_test.rb │ └── measure_registry_test.rb ├── tags_test.rb ├── opencensus_test.rb ├── test_helper.rb ├── trace │ ├── samplers │ │ ├── always_sample_test.rb │ │ ├── never_sample_test.rb │ │ ├── probability_test.rb │ │ └── rate_limiting_test.rb │ ├── integrations │ │ ├── rails51_app_template.rb │ │ ├── rails_test.rb │ │ ├── rails_integration_test.rb │ │ └── rack_middleware_test.rb │ ├── exporters │ │ ├── multi_test.rb │ │ └── logger_test.rb │ └── formatters │ │ ├── cloud_trace_test.rb │ │ ├── binary_test.rb │ │ └── trace_context_test.rb ├── tags │ ├── formatters │ │ └── binary_test.rb │ └── tag_map_test.rb ├── trace_test.rb └── stats_test.rb ├── Rakefile ├── .rubocop.yml ├── opencensus.gemspec ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── CHANGELOG.md /AUTHORS: -------------------------------------------------------------------------------- 1 | Google Inc. 2 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-metadata 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in opencensus.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /examples/hello-world/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "faraday", "~> 0.14" 4 | gem "opencensus", "~> 0.3" 5 | gem "sinatra", "~> 2.0" 6 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 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 9 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --no-private 2 | --title=OpenCensus 3 | --markup markdown 4 | ./lib/**/*.rb 5 | - 6 | README.md 7 | CHANGELOG.md 8 | CODE_OF_CONDUCT.md 9 | CONTRIBUTING.md 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc/ 3 | /docs/ 4 | /examples/*/Gemfile.lock 5 | /pkg/ 6 | /tmp/ 7 | 8 | /Gemfile.lock 9 | 10 | # IntelliJ IDEA 11 | .idea 12 | *.iml 13 | .editorconfig -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "opencensus" 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(__FILE__) 15 | -------------------------------------------------------------------------------- /lib/opencensus/tags/formatters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "opencensus/tags/formatters/binary" 4 | 5 | module OpenCensus 6 | module Tags 7 | ## 8 | # The Formatters module contains several implementations of cross-service 9 | # context propagation. Each formatter can serialize and deserialize a 10 | # {OpenCensus::Tags::TagMap} instance. 11 | # 12 | module Formatters 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation/sum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | module Aggregation 7 | # Sum aggregation type 8 | class Sum 9 | # Create new aggregation data container to store sum value 10 | # values. 11 | # @return [AggregationData::Sum] 12 | def create_aggregation_data 13 | AggregationData::Sum.new 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation/count.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | module Aggregation 7 | # Count aggregation type 8 | class Count 9 | # Create new aggregation data container to store count value. 10 | # values. 11 | # @return [AggregationData::Count] 12 | def create_aggregation_data 13 | AggregationData::Count.new 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 18 | 19 |
20 |

404

21 | 22 |

Page not found :(

23 |

The requested page could not be found.

24 |
25 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation/last_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | module Aggregation 7 | # Last value aggregation type 8 | class LastValue 9 | # Create new aggregation data container to store last value. 10 | # values. 11 | # @return [AggregationData::LastValue] 12 | def create_aggregation_data 13 | AggregationData::LastValue.new 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/stats/aggregation/sum_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::Aggregation::Sum do 4 | describe "create_aggregation_data" do 5 | it "create sum aggregation data instance with default value" do 6 | sum_aggregation = OpenCensus::Stats::Aggregation::Sum.new 7 | aggregation_data = sum_aggregation.create_aggregation_data 8 | aggregation_data.must_be_kind_of OpenCensus::Stats::AggregationData::Sum 9 | aggregation_data.value.must_equal 0 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/stats/aggregation/count_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::Aggregation::Count do 4 | describe "create_aggregation_data" do 5 | it "create count aggregation data instance with default value" do 6 | count_aggregation = OpenCensus::Stats::Aggregation::Count.new 7 | aggregation_data = count_aggregation.create_aggregation_data 8 | aggregation_data.must_be_kind_of OpenCensus::Stats::AggregationData::Count 9 | aggregation_data.value.must_equal 0 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/tags_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Tags do 4 | before{ 5 | OpenCensus::Tags.unset_tag_map_context 6 | } 7 | 8 | it "can be set and unset tag map context" do 9 | tag_map = OpenCensus::Tags::TagMap.new({ "frontend" => "mobile-1.0"}) 10 | OpenCensus::Tags.tag_map_context.must_be_nil 11 | OpenCensus::Tags.tag_map_context = tag_map 12 | OpenCensus::Tags.tag_map_context.must_equal tag_map 13 | OpenCensus::Tags.unset_tag_map_context 14 | OpenCensus::Tags.tag_map_context.must_be_nil 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/stats/aggregation/last_value_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::Aggregation::LastValue do 4 | describe "create_aggregation_data" do 5 | it "create last value aggregation data instance with default value" do 6 | last_value_aggregation = OpenCensus::Stats::Aggregation::LastValue.new 7 | aggregation_data = last_value_aggregation.create_aggregation_data 8 | aggregation_data.must_be_kind_of OpenCensus::Stats::AggregationData::LastValue 9 | aggregation_data.value.must_be_nil 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | require "yard" 4 | 5 | require "rubocop/rake_task" 6 | RuboCop::RakeTask.new 7 | 8 | Rake::TestTask.new :test do |t| 9 | t.libs << "test" 10 | t.libs << "lib" 11 | t.test_files = FileList["test/**/*_test.rb"] 12 | end 13 | 14 | YARD::Rake::YardocTask.new do |t| 15 | t.files = ['lib/**/*.rb'] # optional 16 | t.options = ['--output-dir', 'docs/api'] # optional 17 | t.stats_options = ['--list-undoc'] # optional 18 | end 19 | 20 | task :faster do 21 | ENV["FASTER_TESTS"] = "true" 22 | end 23 | 24 | task :default => [:test, :rubocop, :yard] 25 | -------------------------------------------------------------------------------- /test/stats/aggregation_data/count_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::AggregationData::Count do 4 | it "create count aggregation data with default value" do 5 | aggregation_data = OpenCensus::Stats::AggregationData::Count.new 6 | aggregation_data.value.must_equal 0 7 | end 8 | 9 | describe "add" do 10 | it "add value" do 11 | aggregation_data = OpenCensus::Stats::AggregationData::Count.new 12 | 13 | time = Time.now 14 | aggregation_data.add nil, time 15 | aggregation_data.value.must_equal 1 16 | aggregation_data.time.must_equal time 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation_data.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | require "opencensus/stats/aggregation_data/sum" 5 | require "opencensus/stats/aggregation_data/count" 6 | require "opencensus/stats/aggregation_data/last_value" 7 | require "opencensus/stats/aggregation_data/distribution" 8 | 9 | module OpenCensus 10 | module Stats 11 | # # AggregationData 12 | # 13 | # AggregationData to store collected stats data. 14 | # Aggregation data container type: 15 | # - Sum 16 | # - Count 17 | # - Last Value 18 | # - Distribution - calcualate sum, count, min, max, mean, 19 | # sum of squared deviation 20 | # 21 | module AggregationData 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/opencensus/version.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | ## Current OpenCensus version 18 | VERSION = "0.5.0".freeze 19 | end 20 | -------------------------------------------------------------------------------- /test/stats/aggregation_data/sum_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::AggregationData::Sum do 4 | it "create sum aggregation data with default value" do 5 | aggregation_data = OpenCensus::Stats::AggregationData::Sum.new 6 | aggregation_data.value.must_equal 0 7 | end 8 | 9 | describe "add" do 10 | it "add value" do 11 | aggregation_data = OpenCensus::Stats::AggregationData::Sum.new 12 | 13 | time = Time.now 14 | aggregation_data.add 10, time 15 | aggregation_data.value.must_equal 10 16 | aggregation_data.time.must_equal time 17 | 18 | time = Time.now 19 | aggregation_data.add 15, time 20 | aggregation_data.value.must_equal 25 21 | aggregation_data.time.must_equal time 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | require "opencensus/stats/aggregation/sum" 5 | require "opencensus/stats/aggregation/count" 6 | require "opencensus/stats/aggregation/last_value" 7 | require "opencensus/stats/aggregation/distribution" 8 | require "opencensus/stats/aggregation_data" 9 | require "opencensus/stats/exemplar" 10 | 11 | module OpenCensus 12 | module Stats 13 | # # Aggregation 14 | # 15 | # Aggregation types to describes how the data collected based on aggregation 16 | # type. 17 | # Aggregation types are. 18 | # - Sum 19 | # - Count 20 | # - Last Value 21 | # - Distribution - calcualate min, max, mean, sum, count, 22 | # sum of squared deviation 23 | # 24 | module Aggregation 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/stats/aggregation_data/last_value_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::AggregationData::LastValue do 4 | it "create count aggregation data with default value" do 5 | aggregation_data = OpenCensus::Stats::AggregationData::LastValue.new 6 | aggregation_data.value.must_be_nil 7 | end 8 | 9 | describe "add" do 10 | it "add value" do 11 | aggregation_data = OpenCensus::Stats::AggregationData::LastValue.new 12 | 13 | time = Time.now 14 | aggregation_data.add 1, time 15 | aggregation_data.value.must_equal 1 16 | aggregation_data.time.must_equal time 17 | 18 | time = Time.now 19 | aggregation_data.add 10, time 20 | aggregation_data.value.must_equal 10 21 | aggregation_data.time.must_equal time 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/opencensus_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | class OpenCensusTest < Minitest::Test 18 | def test_that_it_has_a_version_number 19 | refute_nil ::OpenCensus::VERSION 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/opencensus/trace/trace_context_data.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2018 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | ## 19 | # Struct that holds parsed trace context data. 20 | # 21 | TraceContextData = Struct.new :trace_id, :span_id, :trace_options 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/opencensus/common.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/common/config" 17 | 18 | module OpenCensus 19 | ## 20 | # The Common module contains common infrastructure that can be shared between 21 | # Trace and Stats (not yet implemented) 22 | # 23 | module Common 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/stats/view_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::View do 4 | let(:measure){ 5 | OpenCensus::Stats::Measure.new( 6 | name: "latency", 7 | unit: "ms", 8 | type: OpenCensus::Stats::Measure::INT64_TYPE, 9 | description: "latency desc" 10 | ) 11 | } 12 | let(:aggregation){ OpenCensus::Stats::Aggregation::Sum.new } 13 | let(:columns) { ["frontend"]} 14 | 15 | it "create view instance and populate properties" do 16 | view = OpenCensus::Stats::View.new( 17 | name: "test.view", 18 | measure: measure, 19 | aggregation: aggregation, 20 | description: "Test view", 21 | columns: columns 22 | ) 23 | 24 | view.name.must_equal "test.view" 25 | view.measure.must_equal measure 26 | view.aggregation.must_equal aggregation 27 | view.description.must_equal "Test view" 28 | view.columns.must_equal columns 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) 16 | require "opencensus" 17 | 18 | require "minitest/autorun" 19 | require "minitest/focus" 20 | 21 | class TestRandom < Array 22 | alias_method :rand, :shift 23 | end 24 | 25 | class TestTimeClass < Array 26 | alias_method :now, :shift 27 | end 28 | -------------------------------------------------------------------------------- /test/stats/exemplar_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::Exemplar do 4 | describe "create" do 5 | it "create instance with attachments" do 6 | value = 100.1 7 | time = Time.now.utc 8 | attachments = { "test" => "value" } 9 | 10 | exemplar = OpenCensus::Stats::Exemplar.new( 11 | value: value, 12 | time: time, 13 | attachments: attachments 14 | ) 15 | 16 | exemplar.value.must_equal value 17 | exemplar.time.must_equal time 18 | exemplar.attachments.must_equal attachments 19 | end 20 | 21 | it "attachments can not be nil" do 22 | value = 100.2 23 | time = Time.now.utc 24 | attachments = nil 25 | 26 | proc { 27 | OpenCensus::Stats::Exemplar.new( 28 | value: value, 29 | time: time, 30 | attachments: attachments 31 | ) 32 | }.must_raise ArgumentError 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/trace/samplers/always_sample_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Samplers::AlwaysSample do 18 | let(:sampler) { OpenCensus::Trace::Samplers::AlwaysSample.new } 19 | 20 | describe ".call" do 21 | it "should always return true" do 22 | sampler.call.must_equal true 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/trace/samplers/never_sample_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Samplers::NeverSample do 18 | let(:sampler) { OpenCensus::Trace::Samplers::NeverSample.new } 19 | 20 | describe ".call" do 21 | it "should always return false" do 22 | sampler.call.must_equal false 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation_data/last_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | module AggregationData 7 | # # LastValue 8 | # 9 | # Represents the last recorded value. 10 | class LastValue 11 | # @return [Integer,Float] Last recorded value. 12 | attr_reader :value 13 | 14 | # @return [Time] The latest time at new data point was recorded 15 | attr_reader :time 16 | 17 | # rubocop:disable Lint/UnusedMethodArgument 18 | 19 | # Set last value 20 | # @param [Integer,Float] value 21 | # @param [Time] time Time of data point was recorded 22 | # @param [Hash] attachments Attachments are not in use 23 | def add value, time, attachments: nil 24 | @time = time 25 | @value = value 26 | end 27 | 28 | # rubocop:enable Lint/UnusedMethodArgument 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation_data/sum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | module AggregationData 7 | # Sum 8 | # 9 | # Accumulate measurement values. 10 | class Sum 11 | # @return [Integer,Float] The current sum value. 12 | attr_reader :value 13 | 14 | # @return [Time] The latest time at new data point was recorded 15 | attr_reader :time 16 | 17 | # @private 18 | def initialize 19 | @value = 0 20 | end 21 | 22 | # rubocop:disable Lint/UnusedMethodArgument 23 | 24 | # Add value 25 | # @param [Integer,Float] value 26 | # @param [Time] time Time of data point was recorded 27 | # @param [Hash] attachments Attachments are not in use. 28 | def add value, time, attachments: nil 29 | @time = time 30 | @value += value 31 | end 32 | 33 | # rubocop:enable Lint/UnusedMethodArgument 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation_data/count.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | module AggregationData 7 | # # Count 8 | # 9 | # Counts number of measurements recorded. 10 | class Count 11 | # @return [Integer,Float] Current count value. 12 | attr_reader :value 13 | 14 | # @return [Time] The latest timestamp a new data point was recorded 15 | attr_reader :time 16 | 17 | # @private 18 | def initialize 19 | @value = 0 20 | end 21 | 22 | # rubocop:disable Lint/UnusedMethodArgument 23 | 24 | # Increment counter. 25 | # @param [Value] value 26 | # @param [Time] time Time of data point was recorded 27 | # @param [Hash] attachments Attachments are not in use. 28 | def add value, time, attachments: nil 29 | @time = time 30 | @value += 1 31 | end 32 | 33 | # rubocop:enable Lint/UnusedMethodArgument 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /examples/hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello World example 2 | 3 | This example application demonstrates how to use OpenCensus to record traces for a Sinatra-based web application. 4 | 5 | ## Prerequisites 6 | 7 | Ruby 2.2 or later is required. Make sure you have Bundler installed as well. 8 | 9 | gem install bundler 10 | 11 | ## Installation 12 | 13 | Get the example from the OpenCensus Ruby repository on Github, and cd into the example application directory. 14 | 15 | git clone https://github.com/census-instrumentation/opencensus-ruby.git 16 | cd opencensus-ruby/examples/hello-world 17 | 18 | Install the dependencies using Bundler. 19 | 20 | bundle install 21 | 22 | ## Running the example 23 | 24 | Run the application locally on your workstation with: 25 | 26 | bundle exec ruby hello.rb 27 | 28 | This will run on port 4567 by default, and display application logs on the terminal. From a separate shell, you can send requests using a tool such as curl: 29 | 30 | curl http://localhost:4567/ 31 | curl http://localhost:4567/lengthy 32 | 33 | The running application will log the captured traces. 34 | -------------------------------------------------------------------------------- /lib/opencensus/trace/integrations.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | ## 19 | # The Integrations module contains implementations of integrations with 20 | # popular gems such as Rails and Faraday. 21 | # 22 | # Integrations are not loaded by default. To use an integration, 23 | # require it explicitly. e.g.: 24 | # 25 | # require "opencensus/trace/integrations/rack_middleware" 26 | # 27 | module Integrations 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/opencensus/stats/measurement.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | # Measurement 7 | # 8 | # Describes a data point to be collected for a Measure. 9 | class Measurement 10 | # @return [Measure] A measure to which the value is applied. 11 | attr_reader :measure 12 | 13 | # @return [Integer,Float] The recorded value 14 | attr_reader :value 15 | 16 | # @return [TagMap] The tags to which the value is applied 17 | attr_reader :tags 18 | 19 | # @return [Time] The time when measurement was created. 20 | attr_reader :time 21 | 22 | # Create a instance of measurement 23 | # 24 | # @param [Measure] measure A measure to which the value is applied. 25 | # @param [Integer,Float] value Measurement value. 26 | # @param [Hash] tags The tags to which the value is applied 27 | def initialize measure:, value:, tags: 28 | @measure = measure 29 | @value = value 30 | @tags = Tags::TagMap.new tags 31 | @time = Time.now.utc 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /examples/stats/quickstart.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) 4 | require "opencensus" 5 | 6 | MIB = 1 << 20 7 | fontend_key = "my.org/keys/frontend" 8 | video_size_measure = OpenCensus::Stats.measure_int( 9 | name: "my.org/measures/video_size", 10 | unit: "By", 11 | description: "size of processed videos" 12 | ) 13 | video_size_distribution = OpenCensus::Stats.distribution_aggregation( 14 | [0.0, 16.0 * MIB, 256.0 * MIB] 15 | ) 16 | 17 | video_size_view = OpenCensus::Stats.create_view( 18 | name: "my.org/views/video_size", 19 | measure: video_size_measure, 20 | aggregation: video_size_distribution, 21 | description: "processed video size over time", 22 | columns: [fontend_key] 23 | ) 24 | 25 | stats_recorder = OpenCensus::Stats.recorder 26 | stats_recorder.register_view(video_size_view) 27 | 28 | tag_map = OpenCensus::Tags::TagMap.new(fontend_key => "mobile-ios9.3.5") 29 | 30 | stats_recorder.record( 31 | video_size_measure.measurement(25 * MIB), 32 | tags: tag_map 33 | ) 34 | 35 | view_data = stats_recorder.view_data video_size_view.name 36 | 37 | p view_data.data 38 | -------------------------------------------------------------------------------- /lib/opencensus/trace/formatters.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/trace/formatters/binary" 17 | require "opencensus/trace/formatters/cloud_trace" 18 | require "opencensus/trace/formatters/trace_context" 19 | 20 | module OpenCensus 21 | module Trace 22 | ## 23 | # The Formatters module contains several implementations of cross-service 24 | # context propagation. Each formatter can serialize and deserialize a 25 | # {OpenCensus::Trace::TraceContextData} instance. 26 | # 27 | module Formatters 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - "acceptance/**/*" 4 | - "integration/**/*" 5 | - "opencensus.gemspec" 6 | - "Rakefile" 7 | - "support/**/*" 8 | - "test/**/*" 9 | - "docs/**/*" 10 | - "tmp/**/*" 11 | 12 | Documentation: 13 | Enabled: false 14 | 15 | Layout/EmptyLineAfterGuardClause: 16 | Enabled: false 17 | Layout/EmptyLines: 18 | Enabled: false 19 | Layout/SpaceAroundOperators: 20 | Enabled: false 21 | Metrics/AbcSize: 22 | Max: 25 23 | Metrics/ClassLength: 24 | Enabled: false 25 | Metrics/CyclomaticComplexity: 26 | Max: 10 27 | Metrics/MethodLength: 28 | Max: 20 29 | Metrics/ParameterLists: 30 | Enabled: false 31 | Metrics/PerceivedComplexity: 32 | Max: 10 33 | Style/CaseEquality: 34 | Enabled: false 35 | Style/ClassVars: 36 | Enabled: false 37 | Style/EmptyElse: 38 | Enabled: false 39 | Style/GuardClause: 40 | Enabled: false 41 | Style/MethodDefParentheses: 42 | EnforcedStyle: require_no_parentheses 43 | Style/NumericLiterals: 44 | Enabled: false 45 | Style/RescueModifier: 46 | Enabled: false 47 | Style/StringLiterals: 48 | EnforcedStyle: double_quotes 49 | Style/TrivialAccessors: 50 | Enabled: false 51 | -------------------------------------------------------------------------------- /lib/opencensus/trace/time_event.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | ## 19 | # A time-stamped annotation or message event in the Span. 20 | # 21 | class TimeEvent 22 | ## 23 | # The time the event occurred. 24 | # 25 | # @return [Time] 26 | # 27 | attr_reader :time 28 | 29 | ## 30 | # Create a TimeEvent object 31 | # 32 | # @private 33 | # 34 | def initialize time: nil 35 | @time = time || ::Time.now.utc 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source "https://rubygems.org" 3 | 4 | # Hello! This is where you manage which Jekyll version is used to run. 5 | # When you want to use a different version, change it below, save the 6 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 7 | # 8 | # bundle exec jekyll serve 9 | # 10 | # This will help ensure the proper Jekyll version is running. 11 | # Happy Jekylling! 12 | gem "jekyll", "~> 3.6.2" 13 | 14 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 15 | # gem "minima", "~> 2.0" 16 | gem "jekyll-theme-minimal", "~> 0.1" 17 | # gem "minimal" 18 | 19 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 20 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 21 | # gem "github-pages", group: :jekyll_plugins 22 | 23 | # If you have any plugins, put them here! 24 | group :jekyll_plugins do 25 | gem "jekyll-feed", "~> 0.6" 26 | end 27 | 28 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 29 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 30 | 31 | gem "github-pages", group: :jekyll_plugins 32 | -------------------------------------------------------------------------------- /lib/opencensus/trace/samplers/always_sample.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | module Samplers 19 | ## 20 | # The AlwaysSample sampler always returns true. 21 | # 22 | class AlwaysSample 23 | ## 24 | # Implements the sampler contract. Checks to see whether a sample 25 | # should be taken at this time. 26 | # 27 | # @return [boolean] Whether to sample at this time. 28 | # 29 | def call _opts = {} 30 | true 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/opencensus/trace/samplers/never_sample.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | module Samplers 19 | ## 20 | # The NeverSample sampler always returns false. 21 | # 22 | class NeverSample 23 | ## 24 | # Implements the sampler contract. Checks to see whether a sample 25 | # should be taken at this time. 26 | # 27 | # @return [boolean] Whether to sample at this time. 28 | # 29 | def call _opts = {} 30 | false 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/trace/integrations/rails51_app_template.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | base_dir = File.absolute_path File.dirname File.dirname File.dirname __dir__ 16 | gem "opencensus", path: base_dir 17 | 18 | application "require 'opencensus/trace/integrations/rails'" 19 | application "OpenCensus::Trace.configure.exporter = OpenCensus::Trace::Exporters::Logger.new(::Logger.new(STDERR, ::Logger::INFO))" 20 | 21 | route "root to: 'home#index'" 22 | 23 | file "app/controllers/home_controller.rb", <<-CODE 24 | class HomeController < ApplicationController 25 | def index 26 | render plain: "OK" 27 | end 28 | end 29 | CODE 30 | -------------------------------------------------------------------------------- /lib/opencensus/stats/exporters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "opencensus/stats/exporters/logger" 4 | 5 | module OpenCensus 6 | module Stats 7 | ## 8 | # The Exporters module provides integrations for exporting collected stats 9 | # to an external or local service. Exporter classes may be put inside 10 | # this module, but are not required to be located here. 11 | # 12 | # An exporter is an object that must respond to the following method: 13 | # 14 | # def export(views_data) 15 | # 16 | # Where `views_data` is an array of {OpenCensus::Stats::ViewData} objects 17 | # to export. 18 | # 19 | # The method return value is not defined. 20 | # 21 | # The exporter object may interpret the `export` message in whatever way it 22 | # deems appropriate. For example, it may write the data to a log file, it 23 | # may transmit it to a monitoring service, or it may do nothing at all. 24 | # An exporter may also queue the request for later asynchronous processing, 25 | # and indeed this is recommended if the export involves time consuming 26 | # operations such as remote API calls. 27 | # 28 | module Exporters 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/opencensus.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | ## 17 | # OpenCensus is a vendor-agnostic single distribution of libraries to provide 18 | # metrics collection and tracing for your services. See https://opencensus.io/ 19 | # for general information on OpenCensus. 20 | # 21 | # The OpenCensus module provides a namespace for the Ruby implementation of 22 | # OpenCensus, including the core libraries for OpenCensus metrics and tracing. 23 | # 24 | module OpenCensus 25 | end 26 | 27 | require "opencensus/common" 28 | require "opencensus/config" 29 | require "opencensus/context" 30 | require "opencensus/stats" 31 | require "opencensus/tags" 32 | require "opencensus/trace" 33 | require "opencensus/version" 34 | -------------------------------------------------------------------------------- /lib/opencensus/stats/exemplar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | ## 7 | # Exemplars are example points that may be used to annotate aggregated 8 | # Distribution values. They are metadata that gives information about a 9 | # particular value added to a Distribution bucket. 10 | # 11 | class Exemplar 12 | # Value of the exemplar point. It determines which bucket the exemplar 13 | # belongs to 14 | # @return [Integer,Float] 15 | attr_reader :value 16 | 17 | # The observation (sampling) time of the above value 18 | # @return [Time] 19 | attr_reader :time 20 | 21 | # Contextual information about the example value 22 | # @return [Hash] 23 | attr_reader :attachments 24 | 25 | # Create instance of the exemplar 26 | # @param [Integer,Float] value 27 | # @param [Time] time 28 | # @param [Hash] attachments. Attachments are key-value 29 | # pairs that describe the context in which the exemplar was recored. 30 | def initialize value:, time:, attachments: 31 | @value = value 32 | @time = time 33 | 34 | raise ArgumentError, "attachments can not be empty" if attachments.nil? 35 | 36 | @attachments = attachments 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation/distribution.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | module Aggregation 7 | # Distribution aggregation type 8 | class Distribution 9 | # Invalid bucket types. 10 | class InvalidBucketsError < StandardError; end 11 | 12 | # @return [Array,Array] Bucket boundries. 13 | attr_reader :buckets 14 | 15 | # @private 16 | # @param [Array,Array,] buckets Buckets boundries 17 | # for distribution 18 | # aggregation. 19 | # @raise [InvalidBucketsError] If any bucket value is nil. 20 | def initialize buckets 21 | if buckets.nil? || buckets.empty? 22 | raise InvalidBucketsError, "buckets should not be nil or empty" 23 | end 24 | 25 | if buckets.any?(&:nil?) 26 | raise InvalidBucketsError, "buckets value should not be nil" 27 | end 28 | 29 | @buckets = buckets.reject { |v| v < 0 } 30 | end 31 | 32 | # Create new aggregation data container to store distribution values. 33 | # values. 34 | # @return [AggregationData::Distribution] 35 | def create_aggregation_data 36 | AggregationData::Distribution.new @buckets 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /opencensus.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "opencensus/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "opencensus" 8 | spec.version = OpenCensus::VERSION 9 | spec.authors = ["Jeff Ching", "Daniel Azuma"] 10 | spec.email = ["chingor@google.com", "dazuma@google.com"] 11 | 12 | spec.summary = "A stats collection and distributed tracing framework" 13 | spec.description = "A stats collection and distributed tracing framework" 14 | spec.homepage = "https://github.com/census-instrumentation/opencensus-ruby" 15 | spec.license = "Apache-2.0" 16 | 17 | spec.files = ::Dir.glob("lib/**/*.rb") + 18 | ::Dir.glob("*.md") + 19 | ["AUTHORS", "LICENSE", ".yardopts"] 20 | spec.require_paths = ["lib"] 21 | spec.required_ruby_version = ">= 2.2.0" 22 | 23 | spec.add_development_dependency "bundler", ">= 1.17" 24 | spec.add_development_dependency "rake", "~> 12.0" 25 | spec.add_development_dependency "minitest", "~> 5.0" 26 | spec.add_development_dependency "minitest-focus", "~> 1.1" 27 | spec.add_development_dependency "faraday", "~> 0.13" 28 | spec.add_development_dependency "rails", "~> 5.1.4" 29 | spec.add_development_dependency "rubocop", "~> 0.59.2" 30 | spec.add_development_dependency "yard", "~> 0.9" 31 | spec.add_development_dependency "yard-doctest", "~> 0.1.6" 32 | end 33 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | 11 | # Site settings 12 | # These are used to personalize your new site. If you look in the HTML files, 13 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 14 | # You can create any custom variable you would like, and they will be accessible 15 | # in the templates via {{ site.myvariable }}. 16 | title: OpenCensus for Ruby 17 | email: your-email@example.com 18 | description: A stats collection and distributed tracing framework 19 | baseurl: "/opencensus-ruby" # the subpath of your site, e.g. /blog 20 | url: "http://opencensus.io" # the base hostname & protocol for your site, e.g. http://example.com 21 | github_username: census-instrumentation 22 | 23 | # Build settings 24 | markdown: kramdown 25 | theme: jekyll-theme-minimal 26 | plugins: 27 | - jekyll-feed 28 | 29 | # Exclude from processing. 30 | # The following items will not be processed, by default. Create a custom list 31 | # to override the default setting. 32 | # exclude: 33 | # - Gemfile 34 | # - Gemfile.lock 35 | # - node_modules 36 | # - vendor/bundle/ 37 | # - vendor/cache/ 38 | # - vendor/gems/ 39 | # - vendor/ruby/ 40 | -------------------------------------------------------------------------------- /test/trace/integrations/rails_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | require "opencensus/trace/integrations/rails" 18 | 19 | describe OpenCensus::Trace::Integrations::Rails do 20 | describe "#setup_notifications" do 21 | let(:railtie) { OpenCensus::Trace::Integrations::Rails.instance } 22 | after { 23 | ActiveSupport::Notifications.notifier = ActiveSupport::Notifications::Fanout.new 24 | OpenCensus::Trace.unset_span_context 25 | } 26 | 27 | it "creates spans for sql notifications" do 28 | railtie.setup_notifications 29 | OpenCensus::Trace.start_request_trace 30 | ActiveSupport::Notifications.instrument("sql.active_record", query: "hello") do 31 | end 32 | spans = OpenCensus::Trace.span_context.build_contained_spans 33 | spans.size.must_equal 1 34 | span = spans.first 35 | span.name.value.must_equal "sql.active_record" 36 | span.attributes.size.must_equal 1 37 | span.attributes["rails/query"].value.must_equal "hello" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an [individual CLA] 13 | (https://developers.google.com/open-source/cla/individual). 14 | * If you work for a company that wants to allow you to contribute your work, 15 | then you'll need to sign a [corporate CLA] 16 | (https://developers.google.com/open-source/cla/corporate). 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the repo in question. 25 | 1. The repo owner will respond to your issue promptly. 26 | 1. If your proposed change is accepted, and you haven't already done so, sign a 27 | Contributor License Agreement (see details above). 28 | 1. Fork the desired repo, develop and test your code changes. 29 | 1. Ensure that your code adheres to the existing style in the sample to which 30 | you are contributing. Refer to the 31 | [Google Cloud Platform Samples Style Guide] 32 | (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the 33 | recommended coding standards for this organization. 34 | 1. Submit a pull request. 35 | -------------------------------------------------------------------------------- /test/trace/exporters/multi_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Exporters::Multi do 18 | describe "export" do 19 | let(:spans) { ["span1", "span2"] } 20 | 21 | it "should delegate to exporters" do 22 | mock1 = Minitest::Mock.new 23 | mock1.expect :export, nil, [spans] 24 | mock2 = Minitest::Mock.new 25 | mock2.expect :export, nil, [spans] 26 | 27 | exporter = OpenCensus::Trace::Exporters::Multi.new mock1, mock2 28 | exporter.export spans 29 | 30 | mock1.verify 31 | mock2.verify 32 | end 33 | 34 | it "should allow empty" do 35 | exporter = OpenCensus::Trace::Exporters::Multi.new 36 | exporter.export spans 37 | end 38 | end 39 | 40 | describe "array management" do 41 | let(:exporter1) { "exporter1" } 42 | let(:exporter2) { "exporter2" } 43 | 44 | it "allows adding to the multi and retrieving by index" do 45 | exporter = OpenCensus::Trace::Exporters::Multi.new 46 | exporter << exporter1 47 | exporter.push exporter2 48 | exporter[0].must_equal exporter1 49 | exporter[1].must_equal exporter2 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/opencensus/stats/view.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | # View 7 | # 8 | # A View specifies an aggregation and a set of tag keys. 9 | # The aggregation will be broken down by the unique set of matching tag 10 | # values for each measure. 11 | class View 12 | # @return [String] Name of the view 13 | attr_reader :name 14 | 15 | # @return [Measure] Associated measure definition instance. 16 | attr_reader :measure 17 | 18 | # @return [Aggregation] Associated aggregation definition instance. 19 | attr_reader :aggregation 20 | 21 | # Columns (a.k.a Tag Keys) to match with the 22 | # associated Measure. Measure will be recorded in a "greedy" way. 23 | # That is, every view aggregates every measure. 24 | # This is similar to doing a GROUPBY on view columns. Columns must be 25 | # unique. 26 | # @return [Array] 27 | attr_reader :columns 28 | 29 | # @return [String] Detailed description 30 | attr_reader :description 31 | 32 | # @return [Time] View creation time. 33 | attr_reader :time 34 | 35 | # Create instance of the view 36 | # 37 | # @param [String] name 38 | # @param [Measure] measure 39 | # @param [Aggregation] aggregation 40 | # @param [Array] columns 41 | # @param [String] description 42 | def initialize \ 43 | name:, 44 | measure:, 45 | aggregation:, 46 | columns:, 47 | description: nil 48 | @name = name 49 | @measure = measure 50 | @aggregation = aggregation 51 | @columns = columns 52 | @description = description 53 | @time = Time.now.utc 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/opencensus/stats/view_data.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | # ViewData is a container to store stats. 7 | class ViewData 8 | # @return [View] 9 | attr_reader :view 10 | 11 | # @return [Time, nil] 12 | attr_reader :start_time 13 | 14 | # @return [Time, nil] 15 | attr_reader :end_time 16 | 17 | # @return [Hash>,AggregationData] Recorded stats data 18 | # against view columns. 19 | attr_reader :data 20 | 21 | # @private 22 | # Create instance of view 23 | # 24 | # @param [View] view 25 | # @param [Time] start_time 26 | # @param [Time] end_time 27 | def initialize view, start_time: nil, end_time: nil 28 | @view = view 29 | @start_time = start_time 30 | @end_time = end_time 31 | @data = {} 32 | end 33 | 34 | # Set start time. 35 | def start 36 | @start_time = Time.now.utc 37 | end 38 | 39 | # Set stop time. 40 | def stop 41 | @end_time = Time.now.utc 42 | end 43 | 44 | # Record a measurement. 45 | # 46 | # @param [Measurement] measurement 47 | # @param [Hash] attachments 48 | def record measurement, attachments: nil 49 | tag_values = @view.columns.map { |column| measurement.tags[column] } 50 | 51 | unless @data.key? tag_values 52 | @data[tag_values] = @view.aggregation.create_aggregation_data 53 | end 54 | 55 | @data[tag_values].add( 56 | measurement.value, 57 | measurement.time, 58 | attachments: attachments 59 | ) 60 | end 61 | 62 | # Clear recorded ata 63 | def clear 64 | data.clear 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/stats/measure_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::Measurement do 4 | describe "create" do 5 | it "int64 type measure" do 6 | measure = OpenCensus::Stats::Measure.new( 7 | name: "latency", 8 | unit: "ms", 9 | type: OpenCensus::Stats::Measure::INT64_TYPE, 10 | description: "latency desc" 11 | ) 12 | 13 | measure.name.must_equal "latency" 14 | measure.unit.must_equal "ms" 15 | measure.type.must_equal OpenCensus::Stats::Measure::INT64_TYPE 16 | measure.description.must_equal "latency desc" 17 | measure.int64?.must_equal true 18 | measure.double?.must_equal false 19 | end 20 | 21 | it "double type measure" do 22 | measure = OpenCensus::Stats::Measure.new( 23 | name: "storage", 24 | unit: "kb", 25 | type: OpenCensus::Stats::Measure::DOUBLE_TYPE, 26 | description: "storage desc" 27 | ) 28 | 29 | measure.name.must_equal "storage" 30 | measure.unit.must_equal "kb" 31 | measure.type.must_equal OpenCensus::Stats::Measure::DOUBLE_TYPE 32 | measure.description.must_equal "storage desc" 33 | measure.double?.must_equal true 34 | measure.int64?.must_equal false 35 | end 36 | end 37 | 38 | it "create measurement instance" do 39 | measure = OpenCensus::Stats::Measure.new( 40 | name: "storage", 41 | unit: "kb", 42 | type: OpenCensus::Stats::Measure::DOUBLE_TYPE, 43 | description: "storage desc" 44 | ) 45 | tags = { "key1" => "val1" } 46 | measurement = measure.create_measurement value: 10.10, tags: tags 47 | measurement.value.must_equal 10.10 48 | measurement.measure.must_equal measure 49 | measurement.tags.must_be_kind_of OpenCensus::Tags::TagMap 50 | measurement.tags.to_h.must_equal tags 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/opencensus/config.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/common/config" 17 | 18 | module OpenCensus 19 | # The OpenCensus overall configuration. 20 | @config = Common::Config.new 21 | 22 | class << self 23 | ## 24 | # Configure OpenCensus. Most configuration parameters are defined in 25 | # subconfigurations that live under this main configuration. See, for 26 | # example, {OpenCensus::Trace.configure}. 27 | # 28 | # If the OpenCensus Railtie is installed in a Rails application, the 29 | # toplevel configuration object is also exposed as `config.opencensus`. 30 | # 31 | # Generally, you should configure this once at process initialization, 32 | # but it can be modified at any time. 33 | # 34 | # Example: 35 | # 36 | # OpenCensus.configure do |config| 37 | # config.trace.default_sampler = 38 | # OpenCensus::Trace::Samplers::RateLimiting.new 39 | # config.trace.default_max_attributes = 16 40 | # end 41 | # 42 | def configure 43 | if block_given? 44 | yield @config 45 | else 46 | @config 47 | end 48 | end 49 | 50 | ## 51 | # Get the current configuration. 52 | # @private 53 | # 54 | attr_reader :config 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/trace/formatters/cloud_trace_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Formatters::CloudTrace do 18 | let(:formatter) { OpenCensus::Trace::Formatters::CloudTrace.new } 19 | 20 | describe "deserialize" do 21 | it "should return nil on invalid format" do 22 | data = formatter.deserialize "badvalue" 23 | data.must_be_nil 24 | end 25 | 26 | it "should parse a valid format" do 27 | data = formatter.deserialize "123456789012345678901234567890ab/1234;o=1" 28 | data.wont_be_nil 29 | data.trace_id.must_equal "123456789012345678901234567890ab" 30 | data.span_id.must_equal "00000000000004d2" 31 | data.trace_options.must_equal 1 32 | end 33 | end 34 | 35 | describe "serialize" do 36 | let(:trace_data) do 37 | OpenCensus::Trace::SpanContext::TraceData.new( 38 | "123456789012345678901234567890ab", 39 | {} 40 | ) 41 | end 42 | let(:span_context) do 43 | OpenCensus::Trace::SpanContext.new trace_data, nil, "00000000000004d2", 1, nil 44 | end 45 | 46 | it "should serialize a SpanContext object" do 47 | header = formatter.serialize span_context 48 | header.must_equal "123456789012345678901234567890ab/1234;o=1" 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/trace/formatters/binary_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Formatters::Binary do 18 | let(:formatter) { OpenCensus::Trace::Formatters::Binary.new } 19 | 20 | describe "deserialize" do 21 | it "should return nil on invalid format" do 22 | data = formatter.deserialize "badvalue" 23 | data.must_be_nil 24 | end 25 | 26 | it "should parse a valid format" do 27 | data = formatter.deserialize ["0000123456789012345678901234567890ab0100000000000004d20201"].pack("H*") 28 | data.wont_be_nil 29 | data.trace_id.must_equal "123456789012345678901234567890ab" 30 | data.span_id.must_equal "00000000000004d2" 31 | data.trace_options.must_equal 1 32 | end 33 | end 34 | 35 | describe "serialize" do 36 | let(:trace_data) do 37 | OpenCensus::Trace::SpanContext::TraceData.new( 38 | "123456789012345678901234567890ab", 39 | {} 40 | ) 41 | end 42 | let(:span_context) do 43 | OpenCensus::Trace::SpanContext.new trace_data, nil, "00000000000004d2", 1, nil 44 | end 45 | 46 | it "should serialize a SpanContext object" do 47 | header = formatter.serialize span_context 48 | header.must_equal ["0000123456789012345678901234567890ab0100000000000004d20201"].pack("H*") 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/opencensus/trace/exporters.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/trace/exporters/logger" 17 | require "opencensus/trace/exporters/multi" 18 | 19 | module OpenCensus 20 | module Trace 21 | ## 22 | # The Exporters module provides integrations for exporting collected trace 23 | # spans to an external or local service. Exporter classes may be put inside 24 | # this module, but are not required to be located here. 25 | # 26 | # An exporter is an object that must respond to the following method: 27 | # 28 | # def export(spans) 29 | # 30 | # Where `spans` is an array of {OpenCensus::Trace::Span} objects to export. 31 | # The method _must_ tolerate any number of spans in the `spans` array, 32 | # including an empty array. 33 | # 34 | # The method return value is not defined. 35 | # 36 | # The exporter object may interpret the `export` message in whatever way it 37 | # deems appropriate. For example, it may write the data to a log file, it 38 | # may transmit it to a monitoring service, or it may do nothing at all. 39 | # An exporter may also queue the request for later asynchronous processing, 40 | # and indeed this is recommended if the export involves time consuming 41 | # operations such as remote API calls. 42 | # 43 | module Exporters 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/opencensus/stats/measure_registry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | require "singleton" 5 | require "opencensus/stats/measure" 6 | 7 | module OpenCensus 8 | module Stats 9 | # #MeasureRegistry 10 | # 11 | # Measure registry is a collection of uniq measures. 12 | # 13 | class MeasureRegistry 14 | include Singleton 15 | 16 | # @return [Hash] 17 | attr_reader :measures 18 | 19 | # @private 20 | def initialize 21 | @measures = {} 22 | end 23 | 24 | class << self 25 | # Register measure. 26 | # 27 | # @param [String] name Name of measure 28 | # @param [String] unit Unit name of measure 29 | # @param [String] type Date type unit of measure. integer or float. 30 | # @param [String] description Description of measure 31 | # @return [Measure, nil] 32 | # 33 | def register name:, unit:, type:, description: nil 34 | return if instance.measures.key? name 35 | 36 | measure = Measure.new( 37 | name: name, 38 | unit: unit, 39 | type: type, 40 | description: description 41 | ) 42 | 43 | instance.measures[name] = measure 44 | end 45 | 46 | # Un register measure 47 | # 48 | # @param [String] name Name of the registered view 49 | def unregister name 50 | instance.measures.delete name 51 | end 52 | 53 | # Clear measures registry 54 | def clear 55 | instance.measures.clear 56 | end 57 | 58 | # Get registered measure 59 | # @return [Measure] 60 | def get name 61 | instance.measures[name] 62 | end 63 | 64 | # List of registered measures 65 | # @return [Array] 66 | def measures 67 | instance.measures.values 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/opencensus/tags.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Copyright 2017 OpenCensus Authors 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may 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, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | require "opencensus/tags/config" 19 | require "opencensus/tags/tag_map" 20 | require "opencensus/tags/formatters" 21 | 22 | module OpenCensus 23 | ## 24 | # The Tags module contains support for OpenCensus tags. Tags are key-value 25 | # pairs. Tags provide additional cardinality to the OpenCensus instrumentation 26 | # data. 27 | # 28 | module Tags 29 | ## 30 | # Internal key for storing the current TagMap in the thread local 31 | # Context 32 | # 33 | # @private 34 | # 35 | TAG_MAP_CONTEXT_KEY = :__tag_map_context__ 36 | 37 | class << self 38 | ## 39 | # Sets the current thread-local TagMap, which used in Stats data 40 | # recording. 41 | # 42 | # @param [TagMap] context 43 | # 44 | def tag_map_context= context 45 | OpenCensus::Context.set TAG_MAP_CONTEXT_KEY, context 46 | end 47 | 48 | # Unsets the current thread-local TagMap context 49 | # 50 | def unset_tag_map_context 51 | OpenCensus::Context.unset TAG_MAP_CONTEXT_KEY 52 | end 53 | 54 | # Returns the current thread-local TagMap object. 55 | # 56 | # @return [TagMap, nil] 57 | # 58 | def tag_map_context 59 | OpenCensus::Context.get TAG_MAP_CONTEXT_KEY 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/stats/exporters/logger_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::Exporters::Logger do 4 | before{ 5 | OpenCensus::Tags.unset_tag_map_context 6 | } 7 | 8 | let(:logger) { 9 | logger = ::Logger.new STDOUT 10 | logger.level = ::Logger::INFO 11 | logger.formatter = -> (_, _, _, msg) { msg } 12 | logger 13 | } 14 | let(:measure){ 15 | OpenCensus::Stats::Measure.new( 16 | name: "latency", 17 | unit: "ms", 18 | type: OpenCensus::Stats::Measure::INT64_TYPE, 19 | description: "latency desc" 20 | ) 21 | } 22 | let(:aggregation){ OpenCensus::Stats::Aggregation::Sum.new } 23 | let(:tag_keys) { ["frontend"]} 24 | let(:view) { 25 | OpenCensus::Stats::View.new( 26 | name: "test.view", 27 | measure: measure, 28 | aggregation: aggregation, 29 | description: "Test view", 30 | columns: tag_keys 31 | ) 32 | } 33 | let(:tag_map) { 34 | OpenCensus::Tags::TagMap.new(tag_keys.first => "mobile-ios9.3.5") 35 | } 36 | let(:tags){ 37 | { tag_keys.first => "mobile-ios9.3.5" } 38 | } 39 | let(:view_data){ 40 | recorder = OpenCensus::Stats::Recorder.new 41 | recorder.register_view view 42 | recorder.record measure.create_measurement(value: 10, tags: tags) 43 | view_data = recorder.view_data view.name 44 | [view_data] 45 | } 46 | 47 | describe "export" do 48 | it "should emit data for a covered log level" do 49 | exporter = OpenCensus::Stats::Exporters::Logger.new logger, level: ::Logger::INFO 50 | out, _err = capture_subprocess_io do 51 | exporter.export view_data 52 | end 53 | 54 | out.wont_be_empty 55 | end 56 | 57 | it "should not emit data for a low log level" do 58 | exporter = OpenCensus::Stats::Exporters::Logger.new logger, level: ::Logger::DEBUG 59 | out, _err = capture_subprocess_io do 60 | exporter.export view_data 61 | end 62 | 63 | out.must_be_empty 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/opencensus/trace/annotation.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/trace/time_event" 17 | 18 | module OpenCensus 19 | module Trace 20 | ## 21 | # A text annotation with a set of attributes. 22 | # 23 | class Annotation < TimeEvent 24 | ## 25 | # A user-supplied message describing the event. 26 | # 27 | # @return [TruncatableString] 28 | # 29 | attr_reader :description 30 | 31 | ## 32 | # A set of attributes on the annotation. 33 | # 34 | # @return [Hash] 35 | # 36 | attr_reader :attributes 37 | 38 | ## 39 | # The number of attributes that were discarded. Attributes can be 40 | # discarded because their keys are too long or because there are too 41 | # many attributes. If this value is 0, then no attributes were dropped. 42 | # 43 | # @return [Integer] 44 | # 45 | attr_reader :dropped_attributes_count 46 | 47 | ## 48 | # Create an Annotation object. 49 | # 50 | # @private 51 | # 52 | def initialize description, attributes: {}, dropped_attributes_count: 0, 53 | time: nil 54 | super time: time 55 | @description = description 56 | @attributes = attributes 57 | @dropped_attributes_count = dropped_attributes_count 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/opencensus/trace/exporters/multi.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2018 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "delegate" 17 | 18 | module OpenCensus 19 | module Trace 20 | module Exporters 21 | ## 22 | # The Multi exporter multiplexes captured spans to a set of delegate 23 | # exporters. It is useful if you need to export to more than one 24 | # destination. You may also use it as a "null" exporter by providing 25 | # no delegates. 26 | # 27 | # Multi delegates to an array of the exporter objects. You can manage 28 | # the list of exporters using any method of Array. For example: 29 | # 30 | # multi = OpenCensus::Trace::Exporters::Multi.new 31 | # multi.export(spans) # Does nothing 32 | # multi << OpenCensus::Trace::Exporters::Logger.new 33 | # multi.export(spans) # Exports to the logger 34 | # 35 | class Multi < SimpleDelegator 36 | ## 37 | # Create a new Multi exporter 38 | # 39 | # @param [Array<#export>] delegates An array of exporters 40 | # 41 | def initialize *delegates 42 | super(delegates.flatten) 43 | end 44 | 45 | ## 46 | # Pass the captured spans to the delegates. 47 | # 48 | # @param [Array] spans The captured spans. 49 | # 50 | def export spans 51 | each { |delegate| delegate.export spans } 52 | nil 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/opencensus/stats/exporters/multi.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2018 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "delegate" 17 | 18 | module OpenCensus 19 | module Stats 20 | module Exporters 21 | ## 22 | # The Multi exporter multiplexes captured stats to a set of delegate 23 | # exporters. It is useful if you need to export to more than one 24 | # destination. You may also use it as a "null" exporter by providing 25 | # no delegates. 26 | # 27 | # Multi delegates to an array of the exporter objects. You can manage 28 | # the list of exporters using any method of Array. For example: 29 | # 30 | # multi = OpenCensus::Stats::Exporters::Multi.new 31 | # multi.export(views_data) # Does nothing 32 | # multi << OpenCensus::Stats::Exporters::Logger.new 33 | # multi.export(views_data) # Exports to the logger 34 | # 35 | class Multi < SimpleDelegator 36 | ## 37 | # Create a new Multi exporter 38 | # 39 | # @param [Array<#export>] delegates An array of exporters 40 | # 41 | def initialize *delegates 42 | super(delegates.flatten) 43 | end 44 | 45 | ## 46 | # Pass the captured stats view data to the delegates. 47 | # 48 | # @param [Array] views_data The captured stats. 49 | # 50 | def export views_data 51 | each { |delegate| delegate.export views_data } 52 | nil 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/trace/formatters/trace_context_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Formatters::TraceContext do 18 | let(:formatter) { OpenCensus::Trace::Formatters::TraceContext.new } 19 | 20 | describe "deserialize" do 21 | it "should return nil on invalid format" do 22 | data = formatter.deserialize "badvalue" 23 | data.must_be_nil 24 | end 25 | 26 | it "should return nil on unsupported version" do 27 | data = formatter.deserialize "ff-0123456789abcdef0123456789abcdef-0123456789abcdef-01" 28 | data.must_be_nil 29 | end 30 | 31 | it "should parse a valid format" do 32 | data = formatter.deserialize "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" 33 | data.wont_be_nil 34 | data.trace_id.must_equal "0123456789abcdef0123456789abcdef" 35 | data.span_id.must_equal "0123456789abcdef" 36 | data.trace_options.must_equal 1 37 | end 38 | end 39 | 40 | describe "serialize" do 41 | let(:trace_data) do 42 | OpenCensus::Trace::SpanContext::TraceData.new( 43 | "0123456789abcdef0123456789abcdef", 44 | {} 45 | ) 46 | end 47 | let(:span_context) do 48 | OpenCensus::Trace::SpanContext.new trace_data, nil, "0123456789abcdef", 1, nil 49 | end 50 | 51 | it "should serialize a SpanContext object" do 52 | header = formatter.serialize span_context 53 | header.must_equal "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 4 | and in the interest of fostering an open and welcoming community, 5 | we pledge to respect all people who contribute through reporting issues, 6 | posting feature requests, updating documentation, 7 | submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project 10 | a harassment-free experience for everyone, 11 | regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, 13 | body size, race, ethnicity, age, religion, or nationality. 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery 18 | * Personal attacks 19 | * Trolling or insulting/derogatory comments 20 | * Public or private harassment 21 | * Publishing other's private information, 22 | such as physical or electronic 23 | addresses, without explicit permission 24 | * Other unethical or unprofessional conduct. 25 | 26 | Project maintainers have the right and responsibility to remove, edit, or reject 27 | comments, commits, code, wiki edits, issues, and other contributions 28 | that are not aligned to this Code of Conduct. 29 | By adopting this Code of Conduct, 30 | project maintainers commit themselves to fairly and consistently 31 | applying these principles to every aspect of managing this project. 32 | Project maintainers who do not follow or enforce the Code of Conduct 33 | may be permanently removed from the project team. 34 | 35 | This code of conduct applies both within project spaces and in public spaces 36 | when an individual is representing the project or its community. 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior 39 | may be reported by opening an issue 40 | or contacting one or more of the project maintainers. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, 43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 44 | -------------------------------------------------------------------------------- /lib/opencensus/trace/truncatable_string.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | ## 19 | # A string that might be shortened to a specified length. 20 | # 21 | class TruncatableString 22 | ## 23 | # The shortened string. For example, if the original string was 500 bytes 24 | # long and the limit of the string was 128 bytes, then this value contains 25 | # the first 128 bytes of the 500-byte string. Note that truncation always 26 | # happens on a character boundary, to ensure that a truncated string is 27 | # still valid UTF-8. Because it may contain multi-byte characters, the 28 | # size of the truncated string may be less than the truncation limit. 29 | # 30 | # @return [String] 31 | # 32 | attr_reader :value 33 | 34 | ## 35 | # The number of bytes removed from the original string. If this value is 36 | # 0, then the string was not shortened. 37 | # 38 | # @return [Integer] 39 | # 40 | attr_reader :truncated_byte_count 41 | 42 | ## 43 | # Create an empty TruncatableString object. 44 | # 45 | # @private 46 | # 47 | def initialize value, truncated_byte_count: 0 48 | @value = value 49 | @truncated_byte_count = truncated_byte_count 50 | end 51 | 52 | ## 53 | # Override the default to_s implementation. 54 | # 55 | # @private 56 | # 57 | def to_s 58 | @value 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/opencensus/context.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | ## 18 | # The Context module provides per-thread storage. 19 | # 20 | module Context 21 | ## 22 | # Thread local storage key under which all OpenCensus context data is 23 | # stored. 24 | # 25 | # @private 26 | # 27 | THREAD_KEY = :__opencensus_context__ 28 | 29 | class << self 30 | ## 31 | # Store a value in the context. 32 | # 33 | # @param [String, Symbol] key The name of the context value to store. 34 | # @param [Object] value The value associated with the key. 35 | def set key, value 36 | storage[key] = value 37 | end 38 | 39 | ## 40 | # Return a value from the context. Returns nil if no value is set. 41 | # 42 | # @param [String, Symbol] key The name of the context value to fetch. 43 | # @return [Object, nil] The fetched value. 44 | # 45 | def get key 46 | storage[key] 47 | end 48 | 49 | ## 50 | # Unsets a value from the context. 51 | # 52 | # @param [String, Symbol] key The name of the context value to unset. 53 | # @return [Object, nil] The value of the context value just unset. 54 | # 55 | def unset key 56 | storage.delete key 57 | end 58 | 59 | ## 60 | # Clears all values from the context. 61 | # 62 | def reset! 63 | Thread.current[THREAD_KEY] = {} 64 | end 65 | 66 | private 67 | 68 | def storage 69 | Thread.current[THREAD_KEY] ||= {} 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /test/trace/samplers/probability_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Samplers::Probability do 18 | describe ".call" do 19 | it "should return true if rng < rate" do 20 | rng = TestRandom.new [0.2] 21 | sampler = OpenCensus::Trace::Samplers::Probability.new 0.5, rng: rng 22 | sampler.call.must_equal true 23 | end 24 | 25 | it "should return true if rng == rate" do 26 | rng = TestRandom.new [0.5] 27 | sampler = OpenCensus::Trace::Samplers::Probability.new 0.5, rng: rng 28 | sampler.call.must_equal true 29 | end 30 | 31 | it "should return false if rng < rate" do 32 | rng = TestRandom.new [0.7] 33 | sampler = OpenCensus::Trace::Samplers::Probability.new 0.5, rng: rng 34 | sampler.call.must_equal false 35 | end 36 | 37 | describe "with 1.0 rate" do 38 | let(:sampler) { OpenCensus::Trace::Samplers::Probability.new 1.0 } 39 | 40 | it "should always return true" do 41 | sampler.call.must_equal true 42 | end 43 | end 44 | 45 | describe "with 0.0 rate" do 46 | let(:sampler) { OpenCensus::Trace::Samplers::Probability.new 0.0 } 47 | 48 | it "should return false" do 49 | sampler.call.must_equal false 50 | end 51 | 52 | it "should return true anyway if the span context is sampled" do 53 | trace_context = OpenCensus::Trace::TraceContextData.new "1", "2", 1 54 | span_context = OpenCensus::Trace::SpanContext.create_root \ 55 | trace_context: trace_context 56 | sampler.call(span_context: span_context).must_equal true 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/stats/aggregation/distribution_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::Aggregation::Distribution do 4 | let(:buckets) { 5 | [1,5,10] 6 | } 7 | 8 | describe "create" do 9 | it "create instance with buckets" do 10 | distribution_aggregation = 11 | OpenCensus::Stats::Aggregation::Distribution.new buckets 12 | distribution_aggregation.buckets.must_equal buckets 13 | end 14 | 15 | it "raise error if buckets value nil" do 16 | expect { 17 | OpenCensus::Stats::Aggregation::Distribution.new nil 18 | }.must_raise OpenCensus::Stats::Aggregation::Distribution::InvalidBucketsError 19 | end 20 | 21 | it "raise error if buckets are empty" do 22 | expect { 23 | OpenCensus::Stats::Aggregation::Distribution.new [] 24 | }.must_raise OpenCensus::Stats::Aggregation::Distribution::InvalidBucketsError 25 | end 26 | 27 | it "raise error if any bucket value is nil " do 28 | expect { 29 | OpenCensus::Stats::Aggregation::Distribution.new [1, nil] 30 | }.must_raise OpenCensus::Stats::Aggregation::Distribution::InvalidBucketsError 31 | end 32 | 33 | it "reject bucket value if value is less then zero" do 34 | distribution_aggregation = 35 | OpenCensus::Stats::Aggregation::Distribution.new [1, -1, 10, -20] 36 | distribution_aggregation.buckets.must_equal [1, 10] 37 | end 38 | end 39 | 40 | describe "create_aggregation_data" do 41 | it "create distribution aggregation data instance with default values" do 42 | distribution_aggregation = 43 | OpenCensus::Stats::Aggregation::Distribution.new buckets 44 | aggregation_data = distribution_aggregation.create_aggregation_data 45 | aggregation_data.must_be_kind_of OpenCensus::Stats::AggregationData::Distribution 46 | aggregation_data.count.must_equal 0 47 | aggregation_data.sum.must_equal 0 48 | aggregation_data.max.must_equal(-Float::INFINITY) 49 | aggregation_data.min.must_equal Float::INFINITY 50 | aggregation_data.mean.must_equal 0 51 | aggregation_data.sum_of_squared_deviation.must_equal 0 52 | aggregation_data.buckets.must_equal buckets 53 | aggregation_data.bucket_counts.must_equal [0,0,0,0] 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/opencensus/stats/exporters/logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | require "logger" 5 | require "json" 6 | 7 | module OpenCensus 8 | module Stats 9 | module Exporters 10 | ## 11 | # The Logger exporter exports captured stats view data to a standard 12 | # Ruby Logger interface. 13 | # 14 | class Logger 15 | ## 16 | # Create a new Logger exporter 17 | # 18 | # @param [#log] logger The logger to write to. 19 | # @param [Integer] level The log level. This should be a log level 20 | # defined by the Logger standard library. Default is 21 | # `::Logger::INFO`. 22 | # 23 | def initialize logger, level: ::Logger::INFO 24 | @logger = logger 25 | @level = level 26 | end 27 | 28 | ## 29 | # Export the captured stats to the configured logger. 30 | # 31 | # @param [Array] views_data The captured stats data. 32 | # 33 | def export views_data 34 | stats_data = views_data.map { |vd| format_view_data(vd) } 35 | @logger.log @level, stats_data.to_json 36 | nil 37 | end 38 | 39 | private 40 | 41 | def format_view_data view_data 42 | { 43 | view: format_view(view_data.view), 44 | measure: format_measure(view_data.view.measure), 45 | aggregation: format_aggregation(view_data) 46 | } 47 | end 48 | 49 | def format_view view 50 | { 51 | name: view.name, 52 | columns: view.columns, 53 | description: view.description 54 | } 55 | end 56 | 57 | def format_measure measure 58 | { 59 | name: measure.name, 60 | unit: measure.unit, 61 | type: measure.type, 62 | description: measure.description 63 | } 64 | end 65 | 66 | def format_aggregation view_data 67 | { 68 | type: view_data.view.aggregation.class.name.downcase, 69 | start_time: view_data.start_time, 70 | end_time: view_data.end_time, 71 | data: view_data.data 72 | } 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /examples/hello-world/hello.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2018 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # This is a simple Sinatra application demonstrating how to record traces 17 | # using OpenCensus. 18 | # 19 | # It uses the OpenCensus Rack middleware to trace all incoming requests. It 20 | # also demonstrates how to use the Faraday middleware to trace outgoing HTTP 21 | # requests, as well as how to instrument your code to capture custom spans. 22 | # 23 | # By default, the resulting trace data is logged in JSON format. You can also 24 | # configure OpenCensus to report traces to a backend such as Stackdriver or 25 | # Zipkin using an exporter plugin library. 26 | 27 | require "sinatra" 28 | 29 | # Install the Rack middleware to trace incoming requests. 30 | require "opencensus/trace/integrations/rack_middleware" 31 | use OpenCensus::Trace::Integrations::RackMiddleware 32 | 33 | # Access the Faraday middleware which will be used to trace outgoing HTTP 34 | # requests. 35 | require "opencensus/trace/integrations/faraday_middleware" 36 | 37 | # Each request will be traced automatically by the middleware. 38 | get "/" do 39 | "Hello world!" 40 | end 41 | 42 | # Traces for this request will also include sub-spans as indicated below. 43 | get "/lengthy" do 44 | # Configure this Faraday connection with a middleware to trace outgoing 45 | # requests. 46 | conn = Faraday.new(url: "http://www.google.com") do |c| 47 | c.use OpenCensus::Trace::Integrations::FaradayMiddleware 48 | c.adapter Faraday.default_adapter 49 | end 50 | conn.get "/" 51 | 52 | # You may instrument your code to create custom spans for long-running 53 | # operations. 54 | OpenCensus::Trace.in_span "long task" do 55 | sleep rand 56 | end 57 | 58 | "Done!" 59 | end 60 | -------------------------------------------------------------------------------- /lib/opencensus/trace/formatters/binary.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | module Formatters 19 | ## 20 | # This formatter serializes and deserializes span context according to 21 | # the OpenCensus' BinaryEncoding specification. See 22 | # [documentation](https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md). 23 | # 24 | class Binary 25 | ## 26 | # Internal format used to (un)pack binary data 27 | # 28 | # @private 29 | # 30 | BINARY_FORMAT = "CCH32CH16CC".freeze 31 | 32 | ## 33 | # Deserialize a trace context header into a TraceContext object. 34 | # 35 | # @param [String] binary 36 | # @return [TraceContextData, nil] 37 | # 38 | def deserialize binary 39 | data = binary.unpack(BINARY_FORMAT) 40 | if data[0].zero? && data[1].zero? && data[3] == 1 && data[5] == 2 41 | TraceContextData.new data[2], data[4], data[6] 42 | else 43 | nil 44 | end 45 | end 46 | 47 | ## 48 | # Serialize a TraceContextData object. 49 | # 50 | # @param [TraceContextData] trace_context 51 | # @return [String] 52 | # 53 | def serialize trace_context 54 | [ 55 | 0, # version 56 | 0, # field 0 57 | trace_context.trace_id, 58 | 1, # field 1 59 | trace_context.span_id, 60 | 2, # field 2 61 | trace_context.trace_options 62 | ].pack(BINARY_FORMAT) 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/tags/formatters/binary_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Tags::Formatters::Binary do 4 | let(:formatter) { OpenCensus::Tags::Formatters::Binary.new } 5 | 6 | describe "serialize" do 7 | it "return serialize tag map binary data" do 8 | tag_map = OpenCensus::Tags::TagMap.new({ 9 | "key1" => "val1", 10 | "key2" => "val2", 11 | "key3" => "val3", 12 | "key4" => "val4", 13 | }) 14 | 15 | binary = formatter.serialize(tag_map) 16 | expected_binary = "\x00\x00\x04key1\x04val1\x00\x04key2\x04val2\x00\x04key3\x04val3\x00\x04key4\x04val4" 17 | binary.must_equal expected_binary 18 | end 19 | 20 | it "return nil if tag map serialize data size more then 8192 bytes" do 21 | tag_map = OpenCensus::Tags::TagMap.new 22 | 23 | 500.times do |i| 24 | tag_map["key#{i}"] = "value#{i}" 25 | end 26 | 27 | binary = formatter.serialize(tag_map) 28 | binary.must_be_nil 29 | end 30 | end 31 | 32 | describe "deserialize" do 33 | it "deserialize binary data" do 34 | binary = "\x00\x00\x04key1\x04val1\x00\x04key2\x04val2\x00\x04key3\x04val3\x00\x04key4\x04val4" 35 | 36 | tag_map = formatter.deserialize binary 37 | tag_map.length.must_equal 4 38 | 39 | expected_tag_map_values = { 40 | "key1" => "val1", 41 | "key2" => "val2", 42 | "key3" => "val3", 43 | "key4" => "val4", 44 | } 45 | tag_map.to_h.must_equal expected_tag_map_values 46 | end 47 | 48 | it "returns empty tag map for empty string" do 49 | tag_map = formatter.deserialize "" 50 | tag_map.length.must_equal 0 51 | end 52 | 53 | it "returns empty tag map for nil" do 54 | tag_map = formatter.deserialize nil 55 | tag_map.length.must_equal 0 56 | end 57 | 58 | it "raise an error for invalid verion id" do 59 | binary = "\x01\x00\x04key1\x04val1" 60 | 61 | error = expect { 62 | formatter.deserialize binary 63 | }.must_raise OpenCensus::Tags::Formatters::Binary::BinaryFormatterError 64 | error.message.must_match(/invalid version id/i) 65 | end 66 | 67 | it "stop parsing subsequent tags if found invalid field tag id" do 68 | binary = "\x00\x00\x04key1\x04val1\x01\x04key2\x04val2\x00\x04key3\x04val3" 69 | 70 | tag_map = formatter.deserialize binary 71 | tag_map.to_h.must_equal({ "key1" => "val1" }) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/opencensus/trace/samplers.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/trace/samplers/always_sample" 17 | require "opencensus/trace/samplers/never_sample" 18 | require "opencensus/trace/samplers/probability" 19 | require "opencensus/trace/samplers/rate_limiting" 20 | 21 | module OpenCensus 22 | module Trace 23 | ## 24 | # A sampler determines whether a given request's latency trace should 25 | # actually be reported. It is usually not necessary to trace every 26 | # request, especially for an application serving heavy traffic. You may 27 | # use a sampler to decide, for a given request, whether to report its 28 | # trace. 29 | # 30 | # The OpenCensus specification defines four samplers: 31 | # {OpenCensus::Trace::Samplers::AlwaysSample}, 32 | # {OpenCensus::Trace::Samplers::NeverSample}, 33 | # {OpenCensus::Trace::Samplers::Probability}, and 34 | # {OpenCensus::Trace::Samplers::RateLimiting}. 35 | # 36 | # A sampler is a `Proc` that takes a hash of environment information and 37 | # returns a boolean indicating whether or not to sample the current 38 | # request. Alternately, it could be an object that duck-types the `Proc` 39 | # interface by implementing the `call` method. The hash passed to `call` 40 | # may contain the following keys, all of which are optional. Samplers must 41 | # adjust their behavior to account for the availability or absence of any 42 | # environment information: 43 | # 44 | # * `span_context` The {OpenCensus::Trace::SpanContext} that created the 45 | # span being sampled. 46 | # * `rack_env` The hash of Rack environment information 47 | # 48 | # Applications may set a default sampler in the config. In addition, the 49 | # sampler may be overridden whenever a span is created. 50 | # 51 | module Samplers 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/trace/samplers/rate_limiting_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Samplers::RateLimiting do 18 | let(:start_time) { ::Time.at 10000000.0 } 19 | let(:time_200ms) { ::Time.at 10000000.2 } 20 | let(:time_400ms) { ::Time.at 10000000.4 } 21 | let(:env) { {} } 22 | 23 | describe ".call" do 24 | it "returns true if rng < elapsed" do 25 | rng = TestRandom.new [0.1] 26 | time_class = TestTimeClass.new [start_time, time_200ms] 27 | sampler = OpenCensus::Trace::Samplers::RateLimiting.new \ 28 | 1.0, rng: rng, time_class: time_class 29 | sampler.call.must_equal true 30 | end 31 | 32 | it "returns false if rng > elapsed" do 33 | rng = TestRandom.new [0.3] 34 | time_class = TestTimeClass.new [start_time, time_200ms] 35 | sampler = OpenCensus::Trace::Samplers::RateLimiting.new \ 36 | 1.0, rng: rng, time_class: time_class 37 | sampler.call.must_equal false 38 | end 39 | 40 | it "keeps track of last time" do 41 | rng = TestRandom.new [0.1, 0.3] 42 | time_class = TestTimeClass.new [start_time, time_200ms, time_400ms] 43 | sampler = OpenCensus::Trace::Samplers::RateLimiting.new \ 44 | 1.0, rng: rng, time_class: time_class 45 | sampler.call.must_equal true 46 | sampler.call.must_equal false 47 | end 48 | 49 | it "returns true if span context was sampled" do 50 | rng = TestRandom.new [0.3] 51 | time_class = TestTimeClass.new [start_time, time_200ms] 52 | sampler = OpenCensus::Trace::Samplers::RateLimiting.new \ 53 | 1.0, rng: rng, time_class: time_class 54 | trace_context = OpenCensus::Trace::TraceContextData.new "1", "2", 1 55 | span_context = OpenCensus::Trace::SpanContext.create_root \ 56 | trace_context: trace_context 57 | sampler.call(span_context: span_context).must_equal true 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/opencensus/tags/config.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/config" 17 | require "opencensus/tags/formatters" 18 | 19 | module OpenCensus 20 | module Tags 21 | # Schema of the Tags configuration. See Tags#configure for more info. 22 | @config = Common::Config.new do |config| 23 | default_formatter = Formatters::Binary.new 24 | config.add_option! :binary_formatter, default_formatter do |value| 25 | value.respond_to?(:serialize) && value.respond_to?(:deserialize) 26 | end 27 | end 28 | 29 | # Expose the tags config as a subconfig under the main config. 30 | OpenCensus.configure do |config| 31 | config.add_alias! :tags, config: @config 32 | end 33 | 34 | class << self 35 | ## 36 | # Configure OpenCensus Tags. These configuration fields include 37 | # parameters formatter. 38 | # 39 | # This configuration is also available as the `tags` subconfig under the 40 | # main configuration `OpenCensus.configure`. If the OpenCensus Railtie is 41 | # installed in a Rails application, the configuration object is also 42 | # exposed as `config.opencensus.tags`. 43 | # 44 | # Generally, you should configure this once at process initialization, 45 | # but it can be modified at any time. 46 | # 47 | # Supported fields are: 48 | # 49 | # * `binary_formatter` The tags context propagation formatter to use. 50 | # Must be a formatter, an object with `serialize`, `deserialize`, 51 | # methods. See {OpenCensus::Tags::Formatters::Binary}. 52 | # 53 | # @example 54 | # 55 | # OpenCensus::Tags.configure do |config| 56 | # config.binary_formatter = OpenCensus::Tags::Formatters::Binary.new 57 | # end 58 | # 59 | def configure 60 | if block_given? 61 | yield @config 62 | else 63 | @config 64 | end 65 | end 66 | 67 | ## 68 | # Get the current configuration 69 | # @private 70 | # 71 | attr_reader :config 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% seo %} 8 | 9 | 10 | 11 | 14 | 15 | 16 |
17 |
18 |

{{ site.title | default: site.github.repository_name }}

19 |

{{ site.description | default: site.github.project_tagline }}

20 | 21 | {% if site.github.is_project_page %} 22 |

View the Project on GitHub {{ github_name }}

23 | {% endif %} 24 |

View the API documentation 25 | 26 | {% if site.github.is_user_page %} 27 |

View My GitHub Profile

28 | {% endif %} 29 | 30 | {% if site.show_downloads %} 31 | 36 | {% endif %} 37 |
38 |
39 | 40 | {{ content }} 41 | 42 |
43 | 49 |
50 | 51 | 52 | 53 | {% if site.google_analytics %} 54 | 63 | {% endif %} 64 | 65 | 66 | -------------------------------------------------------------------------------- /lib/opencensus/trace/message_event.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/trace/time_event" 17 | 18 | module OpenCensus 19 | module Trace 20 | ## 21 | # An event describing a message sent/received between Spans. 22 | # 23 | class MessageEvent < TimeEvent 24 | ## 25 | # An event type, indicating the type is unknown. 26 | # @return [Symbol] 27 | # 28 | TYPE_UNSPECIFIED = :TYPE_UNSPECIFIED 29 | 30 | ## 31 | # An event type, indicating a sent message. 32 | # @return [Symbol] 33 | # 34 | SENT = :SENT 35 | 36 | ## 37 | # An event type, indicating a received message. 38 | # @return [Symbol] 39 | # 40 | RECEIVED = :RECEIVED 41 | 42 | ## 43 | # The type of MessageEvent. Indicates whether the message was sent or 44 | # received. You should use the type constants provided by this class. 45 | # 46 | # @return [Symbol] 47 | # 48 | attr_reader :type 49 | 50 | ## 51 | # An identifier for the MessageEvent's message that can be used to match 52 | # SENT and RECEIVED MessageEvents. For example, this field could 53 | # represent a sequence ID for a streaming RPC. It is recommended to be 54 | # unique within a Span. 55 | # 56 | # @return [Integer] 57 | # 58 | attr_reader :id 59 | 60 | ## 61 | # The number of uncompressed bytes sent or received. 62 | # 63 | # @return [Integer] 64 | # 65 | attr_reader :uncompressed_size 66 | 67 | ## 68 | # The number of compressed bytes sent or received. If zero, assumed to 69 | # be the same size as uncompressed. 70 | # 71 | # @return [Integer] 72 | # 73 | attr_reader :compressed_size 74 | 75 | ## 76 | # Create a new MessageEvent object. 77 | # 78 | # @private 79 | # 80 | def initialize type, id, uncompressed_size, compressed_size: 0, 81 | time: nil 82 | super time: time 83 | @type = type 84 | @id = id 85 | @uncompressed_size = uncompressed_size 86 | @compressed_size = compressed_size 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/opencensus/trace/samplers/rate_limiting.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "monitor" 17 | 18 | module OpenCensus 19 | module Trace 20 | module Samplers 21 | ## 22 | # The RateLimiting sampler delays a minimum amount of time between each 23 | # sample, enforcing a maximum QPS across traces that use this sampler. 24 | # 25 | class RateLimiting 26 | ## 27 | # Default rate in samples per second. 28 | # 29 | DEFAULT_RATE = 0.1 30 | 31 | ## 32 | # Create a sampler for the given QPS. 33 | # 34 | # @param [Number] qps Samples per second. Default is {DEFAULT_RATE}. 35 | # @param [#rand] rng The random number generator to use. Default is a 36 | # new `::Random` instance. 37 | # @param [#now] time_class The time class to use. Default is `::Time`. 38 | # Generally used for testing. 39 | # 40 | def initialize qps = DEFAULT_RATE, rng: nil, time_class: nil 41 | @qps = qps 42 | @time_class = time_class || Time 43 | @rng = rng || Random.new 44 | @last_time = @time_class.now.to_f 45 | @lock = Monitor.new 46 | end 47 | 48 | ## 49 | # Implements the sampler contract. Checks to see whether a sample 50 | # should be taken at this time. 51 | # 52 | # @param [Hash] opts The options to sample with. 53 | # @option opts [SpanContext] :span_context If provided, the span context 54 | # will be checked and the parent's sampling decision will be 55 | # propagated if the parent was sampled. 56 | # @return [boolean] Whether to sample at this time. 57 | # 58 | def call opts = {} 59 | span_context = opts[:span_context] 60 | return true if span_context && span_context.sampled? 61 | @lock.synchronize do 62 | time = @time_class.now.to_f 63 | elapsed = time - @last_time 64 | @last_time = time 65 | @rng.rand <= elapsed * @qps 66 | end 67 | end 68 | end 69 | 70 | # MaxQPS is an older, deprecated name for the RateLimiting sampler. 71 | MaxQPS = RateLimiting 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/opencensus/stats/config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Copyright 2017 OpenCensus Authors 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may 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, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | require "opencensus/config" 19 | require "opencensus/stats/exporters" 20 | 21 | module OpenCensus 22 | module Stats 23 | # Schema of the Trace configuration. See Stats#configure for more info. 24 | @config = Common::Config.new do |config| 25 | exporter_logger = ::Logger.new STDOUT, ::Logger::INFO 26 | default_exporter = Exporters::Logger.new exporter_logger 27 | 28 | config.add_option! :exporter, default_exporter do |value| 29 | value.respond_to? :export 30 | end 31 | end 32 | 33 | # Expose the stats config as a subconfig under the main config. 34 | OpenCensus.configure do |config| 35 | config.add_alias! :stats, config: @config 36 | end 37 | 38 | class << self 39 | ## 40 | # Configure OpenCensus Stats. These configuration fields include 41 | # parameters governing aggregation, exporting. 42 | # 43 | # This configuration is also available as the `stats` subconfig under the 44 | # main configuration `OpenCensus.configure`. If the OpenCensus Railtie is 45 | # installed in a Rails application, the configuration object is also 46 | # exposed as `config.opencensus.stats`. 47 | # 48 | # Generally, you should configure this once at process initialization, 49 | # but it can be modified at any time. 50 | # 51 | # Supported fields are: 52 | # 53 | # * `exporter` The exporter to use. Must be an exporter, an object with 54 | # an export method that takes an array of ViewData objects. See 55 | # {OpenCensus::Stats::Exporters}. The initial value is a 56 | # {OpenCensus::Stats::Exporters::Logger} that logs to STDOUT. 57 | # 58 | # @example: 59 | # 60 | # OpenCensus::Stats.configure do |config| 61 | # config.exporter = OpenCensus::Stats::Exporters::Logger.new 62 | # end 63 | # 64 | def configure 65 | if block_given? 66 | yield @config 67 | else 68 | @config 69 | end 70 | end 71 | 72 | ## 73 | # Get the current configuration 74 | # @private 75 | # 76 | attr_reader :config 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/opencensus/trace/samplers/probability.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "monitor" 17 | 18 | module OpenCensus 19 | module Trace 20 | module Samplers 21 | ## 22 | # The Probability sampler uses a random number generator against a 23 | # configured rate to determine whether or not to sample. 24 | # 25 | class Probability 26 | ## 27 | # The default sampling probability, equal to 1/10000. 28 | # 29 | DEFAULT_RATE = 0.0001 30 | 31 | ## 32 | # Create a sampler for the given probability. 33 | # 34 | # @param [Number] rate Probability that we will sample. This value must 35 | # be between 0 and 1. 36 | # @param [#rand] rng The random number generator to use. Default is a 37 | # new Random instance. 38 | # 39 | def initialize rate, rng: nil 40 | if rate > 1 || rate < 0 41 | raise ArgumentError, "Invalid rate - must be between 0 and 1." 42 | end 43 | @rate = rate 44 | @rng = rng || Random.new 45 | @lock = Monitor.new 46 | end 47 | 48 | ## 49 | # Implements the sampler contract. Checks to see whether a sample 50 | # should be taken at this time. 51 | # 52 | # @param [Hash] opts The options to sample with. 53 | # @option opts [SpanContext] :span_context If provided, the span context 54 | # will be checked and the parent's sampling decision will be 55 | # propagated if the parent was sampled. The span context will 56 | # also be used to generate a deterministic value in place of the 57 | # pseudo-random number generator. 58 | # @return [boolean] Whether to sample at this time. 59 | # 60 | def call opts = {} 61 | span_context = opts[:span_context] 62 | return true if span_context && span_context.sampled? 63 | value = 64 | if span_context 65 | (span_context.trace_id.to_i(16) % 0x10000000000000000).to_f / 66 | 0x10000000000000000 67 | else 68 | @lock.synchronize do 69 | @rng.rand 70 | end 71 | end 72 | value <= @rate 73 | end 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Release History 2 | 3 | ### 0.5.0 / 2019-10-14 4 | 5 | This update brings experimental support for the OpenCensus Stats API. Note that 6 | Stats support is currently partial and not well-tested. 7 | 8 | Other changes: 9 | 10 | * Attributes in the Trace API can now be floats. 11 | 12 | ### 0.4.0 / 2018-10-22 13 | 14 | This is an important update that includes a number of fixes and brings the 15 | library up to date with the latest OpenCensus specification. 16 | 17 | Backward-incompatible changes: 18 | 19 | * Local parent span's sampling decision is propagated to children by default, to match the current sampling spec. 20 | * It is no longer possible to change a sampling decision after a SpanBuilder has been created, to match the current sampling spec. 21 | * Renamed MaxQPS sampler to RateLimiting, and modified the implementation to match the current spec. 22 | * Probability sampler defaults to 1 in 10,000. 23 | * `true` or `false` may be passed as a sampler, as shortcuts for AlwaysSample or NeverSample. 24 | * Standard samplers are now thread-safe. 25 | * HTTP status codes are properly translated to OpenCensus status codes, in particular for spans generated by the Faraday and Rack middleware. 26 | * Faraday and Rack middleware produce attributes matching the OpenCensus HTTP spec instead of the Stackdriver Trace service spec. The Stackdriver exporter now translates these attributes accordingly. 27 | * Update standard trace context HTTP header to "traceparent" following updated spec. 28 | 29 | Other fixes: 30 | 31 | * Allow requiring "opencensus/trace" directly. 32 | * Return SpanContext objects when block not given. 33 | * FaradayMiddleware closes span if Faraday raises an exception. 34 | 35 | ### 0.3.1 / 2018-04-13 36 | 37 | * Clean unneeded files from the gem 38 | 39 | ### 0.3.0 / 2018-03-26 40 | 41 | * SpanContext#build_contained_spans honors sampling bit. 42 | * Use AlwaysSample as the default sampler. 43 | * Support the Span.kind field. 44 | 45 | ### 0.2.2 / 2018-03-09 46 | 47 | * Railtie now adds the middleware at the end of the stack by default, and provides a config that can customize the position 48 | * Provided a multi exporter 49 | * Document exporter interface 50 | * Fix some broken links in the documentation 51 | 52 | ### 0.2.1 / 2018-03-05 53 | 54 | * Clarify Ruby version requirement (2.2+) 55 | * Fix exceptions in the config library on Ruby 2.2 and 2.3. 56 | * Automatically require opencensus base library from standard integrations. 57 | 58 | ### 0.2.0 / 2018-02-13 59 | 60 | * Span creation sets the "same_process_as_parent_span" field if possible. 61 | * The "stack_trace_hash_id" field was missing from the interfaces. Fixed. 62 | * Nullability of a few fields did not match the proto specs. Fixed. 63 | * Fixed some documentation errors and omissions. 64 | 65 | ### 0.1.0 / 2018-01-12 66 | 67 | Initial release of the core library, including: 68 | 69 | * Trace interfaces 70 | * Rack integration 71 | * Rails integration 72 | * Faraday integration 73 | * Logging exporter 74 | -------------------------------------------------------------------------------- /lib/opencensus/stats/measure.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | require "opencensus/stats/measurement" 5 | 6 | module OpenCensus 7 | module Stats 8 | # Measure 9 | # 10 | # The definition of the Measurement. Describes the type of the individual 11 | # values/measurements recorded by an application. It includes information 12 | # such as the type of measurement, the units of measurement and descriptive 13 | # names for the data. This provides th fundamental type used for recording 14 | # data. 15 | class Measure 16 | # Describes the unit used for the Measure. 17 | # Should follows the format described by 18 | # http://unitsofmeasure.org/ucum.html 19 | # Unit name for general counts 20 | # @return [String] 21 | UNIT_NONE = "1".freeze 22 | 23 | # Unit name for bytes 24 | # @return [String] 25 | BYTE = "By".freeze 26 | 27 | # Unit name for Kilobytes 28 | # @return [String] 29 | KBYTE = "kb".freeze 30 | 31 | # Unit name for Seconds 32 | # @return [String] 33 | SEC = "s".freeze 34 | 35 | # Unit name for Milli seconds 36 | # @return [String] 37 | MS = "ms".freeze 38 | 39 | # Unit name for Micro seconds 40 | # @return [String] 41 | US = "us".freeze 42 | 43 | # Unit name for Nano seconds 44 | # @return [String] 45 | NS = "ns".freeze 46 | 47 | # Measure int64 type 48 | # @return [String] 49 | INT64_TYPE = "INT64".freeze 50 | 51 | # Measure double type 52 | # @return [String] 53 | DOUBLE_TYPE = "DOUBLE".freeze 54 | 55 | # @return [String] 56 | attr_reader :name 57 | 58 | # @return [String] 59 | attr_reader :description 60 | 61 | # Unit type of the measurement. i.e "kb", "ms" etc 62 | # @return [String] 63 | attr_reader :unit 64 | 65 | # Data type of the measure. 66 | # @return [String] Valid types are {INT64_TYPE}, {DOUBLE_TYPE}. 67 | attr_reader :type 68 | 69 | # @private 70 | # Create instance of the measure. 71 | def initialize name:, unit:, type:, description: nil 72 | @name = name 73 | @unit = unit 74 | @type = type 75 | @description = description 76 | end 77 | 78 | # Create new measurement 79 | # @param [Integer, Float] value 80 | # @param [Hash] tags Tags to which the value is recorded 81 | # @return [Measurement] 82 | def create_measurement value:, tags: 83 | Measurement.new measure: self, value: value, tags: tags 84 | end 85 | 86 | # Is int64 data type 87 | # @return [Boolean] 88 | def int64? 89 | type == INT64_TYPE 90 | end 91 | 92 | # Is float data type 93 | # @return [Boolean] 94 | def double? 95 | type == DOUBLE_TYPE 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/opencensus/stats/recorder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | require "opencensus/stats/view" 5 | require "opencensus/stats/view_data" 6 | 7 | module OpenCensus 8 | module Stats 9 | # Stats recorder. 10 | # 11 | # Recorder record measurement against measure for registered views. 12 | class Recorder 13 | # @private 14 | # @return [Hash] Hash of view name and View object. 15 | attr_reader :views 16 | 17 | # @private 18 | # @return [Hash] Hash of measure name and Measure object. 19 | attr_reader :measures 20 | 21 | # @private 22 | # @return [Hash>] 23 | # Hash of view name and View data objects array. 24 | attr_reader :measure_views_data 25 | 26 | # @private 27 | # Create instance of the recorder. 28 | def initialize 29 | @views = {} 30 | @measures = {} 31 | @measure_views_data = {} 32 | @time = Time.now.utc 33 | end 34 | 35 | # Register view 36 | # 37 | # @param [View] view 38 | # @return [View] 39 | def register_view view 40 | return if @views.key? view.name 41 | 42 | @views[view.name] = view 43 | @measures[view.measure.name] = view.measure 44 | 45 | unless @measure_views_data.key? view.measure.name 46 | @measure_views_data[view.measure.name] = [] 47 | end 48 | 49 | @measure_views_data[view.measure.name] << ViewData.new( 50 | view, 51 | start_time: @time, 52 | end_time: @time 53 | ) 54 | view 55 | end 56 | 57 | # Record measurements 58 | # 59 | # @param [Array, Measurement] measurements 60 | # @param [Hash] attachments 61 | def record *measurements, attachments: nil 62 | return if measurements.any? { |m| m.value < 0 } 63 | 64 | measurements.each do |measurement| 65 | views_data = @measure_views_data[measurement.measure.name] 66 | next unless views_data 67 | 68 | views_data.each do |view_data| 69 | view_data.record measurement, attachments: attachments 70 | end 71 | end 72 | end 73 | 74 | # Get recorded data for given view name 75 | # 76 | # @param [String] view_name View name 77 | # @return [ViewData] 78 | def view_data view_name 79 | view = @views[view_name] 80 | return unless view 81 | 82 | views_data = @measure_views_data[view.measure.name] 83 | views_data.find { |view_data| view_data.view.name == view.name } 84 | end 85 | 86 | # Get all views data list 87 | # @return [Array] 88 | def views_data 89 | @measure_views_data.values.flatten 90 | end 91 | 92 | # Clear recorded stats. 93 | def clear_stats 94 | @measure_views_data.each_value do |views_data| 95 | views_data.each(&:clear) 96 | end 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/opencensus/trace/formatters/cloud_trace.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | module Formatters 19 | ## 20 | # This formatter serializes and deserializes span context according to 21 | # the Google X-Cloud-Trace header specification. 22 | # 23 | class CloudTrace 24 | ## 25 | # Internal regex used to parse fields 26 | # 27 | # @private 28 | # 29 | HEADER_FORMAT = %r{([0-9a-fA-F]{32})(?:\/(\d+))?(?:;o=(\d+))?} 30 | 31 | ## 32 | # The outgoing header used for the Google Cloud Trace header 33 | # specification. 34 | # 35 | # @private 36 | # 37 | HEADER_NAME = "X-Cloud-Trace".freeze 38 | 39 | ## 40 | # The rack environment header used the the Google Cloud Trace header 41 | # specification. 42 | # 43 | # @private 44 | # 45 | RACK_HEADER_NAME = "HTTP_X_CLOUD_TRACE".freeze 46 | 47 | ## 48 | # Returns the name of the header used for context propagation. 49 | # 50 | # @return [String] 51 | # 52 | def header_name 53 | HEADER_NAME 54 | end 55 | 56 | ## 57 | # Returns the name of the rack_environment header to use when parsing 58 | # context from an incoming request. 59 | # 60 | # @return [String] 61 | # 62 | def rack_header_name 63 | RACK_HEADER_NAME 64 | end 65 | 66 | ## 67 | # Deserialize a trace context header into a TraceContext object. 68 | # 69 | # @param [String] header 70 | # @return [TraceContextData, nil] 71 | # 72 | def deserialize header 73 | match = HEADER_FORMAT.match(header) 74 | if match 75 | trace_id = match[1].downcase 76 | span_id = format("%016x", match[2].to_i) 77 | trace_options = match[3].to_i 78 | TraceContextData.new trace_id, span_id, trace_options 79 | else 80 | nil 81 | end 82 | end 83 | 84 | ## 85 | # Serialize a TraceContextData object. 86 | # 87 | # @param [TraceContextData] trace_context 88 | # @return [String] 89 | # 90 | def serialize trace_context 91 | trace_context.trace_id.dup.tap do |ret| 92 | if trace_context.span_id 93 | ret << "/" << trace_context.span_id.to_i(16).to_s 94 | end 95 | if trace_context.trace_options 96 | ret << ";o=" << trace_context.trace_options.to_s 97 | end 98 | end 99 | end 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/opencensus/trace/link.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | ## 19 | # A pointer from the current span to another span in the same trace or in 20 | # a different trace. For example, this can be used in batching operations, 21 | # where a single batch handler processes multiple requests from different 22 | # traces or when the handler receives a request from a different project. 23 | # 24 | class Link 25 | ## 26 | # A link type, indicating the relationship of the two spans is unknown, 27 | # or is known but is other than parent-child. 28 | # @return [Symbol] 29 | # 30 | TYPE_UNSPECIFIED = :TYPE_UNSPECIFIED 31 | 32 | ## 33 | # A link type, indicating the linked span is a child of the current span. 34 | # @return [Symbol] 35 | # 36 | CHILD_LINKED_SPAN = :CHILD_LINKED_SPAN 37 | 38 | ## 39 | # A link type, indicating the linked span is a parent of the current span. 40 | # @return [Symbol] 41 | # 42 | PARENT_LINKED_SPAN = :PARENT_LINKED_SPAN 43 | 44 | ## 45 | # A unique identifier for a trace. All spans from the same trace share 46 | # the same `trace_id`. The ID is a 16-byte represented as a hexadecimal 47 | # string. 48 | # 49 | # @return [String] 50 | # 51 | attr_reader :trace_id 52 | 53 | ## 54 | # A unique identifier for a span within a trace, assigned when the span 55 | # is created. The ID is a 16-byte represented as a hexadecimal string. 56 | # 57 | # @return [String] 58 | # 59 | attr_reader :span_id 60 | 61 | ## 62 | # The relationship of the current span relative to the linked span. You 63 | # should use the type constants provided by this class. 64 | # 65 | # @return [Symbol] 66 | # 67 | attr_reader :type 68 | 69 | ## 70 | # A set of attributes on the link. 71 | # 72 | # @return [Hash] 73 | # 74 | attr_reader :attributes 75 | 76 | ## 77 | # The number of attributes that were discarded. Attributes can be 78 | # discarded because their keys are too long or because there are too 79 | # many attributes. If this value is 0, then no attributes were dropped. 80 | # 81 | # @return [Integer] 82 | # 83 | attr_reader :dropped_attributes_count 84 | 85 | ## 86 | # Create a Link object. 87 | # 88 | # @private 89 | # 90 | def initialize trace_id, span_id, type: nil, 91 | attributes: {}, dropped_attributes_count: 0 92 | @trace_id = trace_id 93 | @span_id = span_id 94 | @type = type 95 | @attributes = attributes 96 | @dropped_attributes_count = dropped_attributes_count 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /test/stats/measure_registry_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::MeasureRegistry do 4 | before{ 5 | OpenCensus::Stats::MeasureRegistry.clear 6 | } 7 | 8 | describe "register" do 9 | it "register measure" do 10 | OpenCensus::Stats::MeasureRegistry.register( 11 | name: "latency", 12 | unit: "ms", 13 | type: OpenCensus::Stats::Measure::INT64_TYPE, 14 | description: "latency desc" 15 | ).must_be_kind_of OpenCensus::Stats::Measure 16 | 17 | measure = OpenCensus::Stats::MeasureRegistry.get "latency" 18 | measure.must_be_kind_of OpenCensus::Stats::Measure 19 | measure.name.must_equal "latency" 20 | measure.unit.must_equal "ms" 21 | measure.type.must_equal OpenCensus::Stats::Measure::INT64_TYPE 22 | measure.description.must_equal "latency desc" 23 | measure.int64?.must_equal true 24 | end 25 | 26 | it "won't register dublicate measure" do 27 | measure1 = OpenCensus::Stats::MeasureRegistry.register( 28 | name: "latency-1", 29 | unit: "ms", 30 | type: OpenCensus::Stats::Measure::INT64_TYPE, 31 | description: "latency desc" 32 | ) 33 | measure1.must_be_kind_of OpenCensus::Stats::Measure 34 | 35 | OpenCensus::Stats::MeasureRegistry.register( 36 | name: "latency-1", 37 | unit: "ms", 38 | type: OpenCensus::Stats::Measure::INT64_TYPE, 39 | description: "latency desc" 40 | ).must_be_nil 41 | 42 | OpenCensus::Stats::MeasureRegistry.measures.length.must_equal 1 43 | OpenCensus::Stats::MeasureRegistry.get("latency-1").must_equal measure1 44 | end 45 | end 46 | 47 | describe "get" do 48 | it "get measure" do 49 | measure = OpenCensus::Stats::MeasureRegistry.register( 50 | name: "latency", 51 | unit: "ms", 52 | type: OpenCensus::Stats::Measure::INT64_TYPE, 53 | description: "latency desc" 54 | ) 55 | 56 | OpenCensus::Stats::MeasureRegistry.get("latency").must_equal measure 57 | end 58 | end 59 | 60 | describe "list" do 61 | it "list all measures" do 62 | OpenCensus::Stats::MeasureRegistry.register( 63 | name: "latency", 64 | unit: "ms", 65 | type: OpenCensus::Stats::Measure::INT64_TYPE, 66 | description: "latency desc" 67 | ) 68 | 69 | OpenCensus::Stats::MeasureRegistry.measures.length.must_equal 1 70 | end 71 | end 72 | 73 | describe "unregister" do 74 | it "unregister measure by name" do 75 | OpenCensus::Stats::MeasureRegistry.register( 76 | name: "latency", 77 | unit: "ms", 78 | type: OpenCensus::Stats::Measure::INT64_TYPE, 79 | description: "latency desc" 80 | ) 81 | 82 | OpenCensus::Stats::MeasureRegistry.measures.length.must_equal 1 83 | OpenCensus::Stats::MeasureRegistry.unregister "latency" 84 | OpenCensus::Stats::MeasureRegistry.measures.length.must_equal 0 85 | end 86 | end 87 | 88 | describe "clear" do 89 | it "remove all measures from registry" do 90 | OpenCensus::Stats::MeasureRegistry.register( 91 | name: "latency", 92 | unit: "ms", 93 | type: OpenCensus::Stats::Measure::INT64_TYPE, 94 | description: "latency desc" 95 | ) 96 | 97 | OpenCensus::Stats::MeasureRegistry.measures.length.must_equal 1 98 | OpenCensus::Stats::MeasureRegistry.clear 99 | OpenCensus::Stats::MeasureRegistry.measures.length.must_equal 0 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /test/stats/aggregation_data/distribution_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats::AggregationData::Distribution do 4 | let(:buckets) { 5 | [2,4,6] 6 | } 7 | 8 | it "create distribution aggregation data instance with default values" do 9 | aggregation_data = OpenCensus::Stats::AggregationData::Distribution.new( 10 | buckets 11 | ) 12 | aggregation_data.count.must_equal 0 13 | aggregation_data.sum.must_equal 0 14 | aggregation_data.max.must_equal(-Float::INFINITY) 15 | aggregation_data.min.must_equal Float::INFINITY 16 | aggregation_data.mean.must_equal 0 17 | aggregation_data.sum_of_squared_deviation.must_equal 0 18 | aggregation_data.buckets.must_equal buckets 19 | aggregation_data.bucket_counts.must_equal [0,0,0,0] 20 | aggregation_data.exemplars.must_be_empty 21 | end 22 | 23 | describe "add" do 24 | it "add value without attachments" do 25 | aggregation_data = OpenCensus::Stats::AggregationData::Distribution.new( 26 | buckets 27 | ) 28 | 29 | values = [1, 3, 5, 10, 4] 30 | time = nil 31 | values.each do |value| 32 | time = Time.now 33 | aggregation_data.add value, time 34 | end 35 | 36 | aggregation_data.time.must_equal time 37 | aggregation_data.count.must_equal values.length 38 | aggregation_data.sum.must_equal values.inject(:+) 39 | aggregation_data.max.must_equal values.max 40 | aggregation_data.min.must_equal values.min 41 | 42 | mean = ( values.inject(:+).to_f / values.length ) 43 | aggregation_data.mean.must_equal mean 44 | 45 | sum_of_squared_deviation = values.map { |v| (v - mean)**2 }.inject(:+) 46 | aggregation_data.sum_of_squared_deviation.must_equal sum_of_squared_deviation 47 | aggregation_data.buckets.must_equal buckets 48 | aggregation_data.bucket_counts.must_equal [1, 1, 2, 1] 49 | aggregation_data.exemplars.must_be_empty 50 | end 51 | 52 | it "add value with attachments and record value to exampler" do 53 | buckets = [1, 3, 10] 54 | aggregation_data = OpenCensus::Stats::AggregationData::Distribution.new( 55 | buckets 56 | ) 57 | 58 | attachments = { "trace_id" => "1111-1111" } 59 | values = [1, 5, 10, 2] 60 | time = Time.now 61 | values.each do |value| 62 | aggregation_data.add value, time, attachments: attachments 63 | end 64 | 65 | aggregation_data.time.must_equal time 66 | aggregation_data.count.must_equal values.length 67 | aggregation_data.sum.must_equal values.inject(:+) 68 | aggregation_data.max.must_equal values.max 69 | aggregation_data.min.must_equal values.min 70 | 71 | mean = ( values.inject(:+).to_f / values.length ) 72 | aggregation_data.mean.must_equal mean 73 | 74 | sum_of_squared_deviation = values.map { |v| (v - mean)**2 }.inject(:+) 75 | aggregation_data.sum_of_squared_deviation.must_equal sum_of_squared_deviation 76 | aggregation_data.buckets.must_equal buckets 77 | 78 | expected_bucket_counts = [0, 2, 1, 1] 79 | aggregation_data.bucket_counts.must_equal expected_bucket_counts 80 | 81 | expected_examples_values = { 82 | 0 => nil, 83 | 1 => 2, 84 | 2 => 5, 85 | 3 => 10 86 | } 87 | 88 | expected_examples_values.each do |k, v| 89 | if v.nil? 90 | aggregation_data.exemplars[k].must_be_nil 91 | else 92 | aggregation_data.exemplars[k].value.must_equal v 93 | aggregation_data.exemplars[k].time.must_equal time 94 | aggregation_data.exemplars[k].attachments.must_equal attachments 95 | end 96 | end 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/opencensus/stats/aggregation_data/distribution.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | module OpenCensus 5 | module Stats 6 | module AggregationData 7 | # # Distribution 8 | # 9 | # This AggregationData contains a histogram of the collected values 10 | class Distribution 11 | # @return [Array,Array] Buckets boundries 12 | attr_reader :buckets 13 | 14 | # @return [Integer] Count of recorded values 15 | attr_reader :count 16 | 17 | # @return [Integer,Float] Sum of recorded values 18 | attr_reader :sum 19 | 20 | # @return [Integer,Float] Maximum recorded value 21 | attr_reader :max 22 | 23 | # @return [Integer,Float] Minimum recorded value 24 | attr_reader :min 25 | 26 | # @return [Integer,Float] Mean of recorded values 27 | attr_reader :mean 28 | 29 | # @return [Integer,Float] Sum of squared deviation of recorded values 30 | attr_reader :sum_of_squared_deviation 31 | 32 | # @return [Array] Count of number of recorded values in buckets 33 | attr_reader :bucket_counts 34 | 35 | # @return [Time] Time of first recorded value 36 | attr_reader :start_time 37 | 38 | # @return [Time] The latest time a new value was recorded 39 | attr_reader :time 40 | 41 | # @return [Array] Exemplars are points associated with each 42 | # bucket in the distribution giving an example of what was aggregated 43 | # into the bucket. 44 | attr_reader :exemplars 45 | 46 | # @private 47 | # @param [Array,Array] buckets Buckets boundries 48 | # for distribution 49 | def initialize buckets 50 | @buckets = buckets 51 | @count = 0 52 | @sum = 0 53 | @max = -Float::INFINITY 54 | @min = Float::INFINITY 55 | @mean = 0 56 | @sum_of_squared_deviation = 0 57 | @bucket_counts = Array.new(buckets.length + 1, 0) 58 | @exemplars = [] 59 | @start_time = Time.now.utc 60 | end 61 | 62 | # @private 63 | # Add value to distribution 64 | # @param [Integer,Float] value 65 | # @param [Time] time Time of data point was recorded 66 | # @param [Hash,nil] attachments Attachments are key-value 67 | # pairs that describe the context in which the exemplar was recored. 68 | def add value, time, attachments: nil 69 | @time = time 70 | @count += 1 71 | @sum += value 72 | @max = value if value > @max 73 | @min = value if value < @min 74 | 75 | delta_from_mean = (value - @mean).to_f 76 | @mean += delta_from_mean / @count 77 | @sum_of_squared_deviation += delta_from_mean * (value - @mean) 78 | 79 | bucket_index = @buckets.find_index { |b| b > value } || 80 | @buckets.length 81 | @bucket_counts[bucket_index] += 1 82 | 83 | if attachments 84 | @exemplars[bucket_index] = Exemplar.new( 85 | value: value, 86 | time: time, 87 | attachments: attachments 88 | ) 89 | end 90 | end 91 | 92 | # Get distribution result values. 93 | # @return [Hash] 94 | def value 95 | { 96 | start_time: @start_time, 97 | count: @count, 98 | sum: @sum, 99 | max: @max, 100 | min: @min, 101 | sum_of_squared_deviation: @sum_of_squared_deviation, 102 | buckets: @buckets, 103 | bucket_counts: @bucket_counts 104 | } 105 | end 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/opencensus/tags/tag_map.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | 4 | require "forwardable" 5 | 6 | module OpenCensus 7 | module Tags 8 | # # TagMap 9 | # 10 | # Collection of tag key and value. 11 | # @example 12 | # 13 | # tag_map = OpenCensus::Tags::OpenCensus.new 14 | # 15 | # # Add or update 16 | # tag_map["key1"] = "value1" 17 | # tag_map["key2"] = "value2" 18 | # 19 | # # Get value 20 | # tag_map["key1"] # value1 21 | # 22 | # # Delete 23 | # tag_map.delete "key1" 24 | # 25 | # # Iterate 26 | # tag_map.each do |key, value| 27 | # p key 28 | # p value 29 | # end 30 | # 31 | # # Length 32 | # tag_map.length # 1 33 | # 34 | # @example Create tag map from hash 35 | # 36 | # tag_map = OpenCensus::Tags::OpenCensus.new({ "key1" => "value1"}) 37 | # 38 | class TagMap 39 | extend Forwardable 40 | 41 | # The maximum length for a tag key and tag value 42 | MAX_LENGTH = 255 43 | 44 | # Create a tag map. It is a map of tags from key to value. 45 | # @param [Hash{String=>String}] tags Tags hash with string key and value. 46 | # 47 | def initialize tags = {} 48 | @tags = {} 49 | 50 | tags.each do |key, value| 51 | self[key] = value 52 | end 53 | end 54 | 55 | # Set tag key value 56 | # 57 | # @param [String] key Tag key 58 | # @param [String] value Tag value 59 | # @raise [InvalidTagError] If invalid tag key or value. 60 | # 61 | def []= key, value 62 | validate_key! key 63 | validate_value! value 64 | 65 | @tags[key] = value 66 | end 67 | 68 | # Convert tag map to binary string format. 69 | # @see [documentation](https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md#tag-context) 70 | # @return [String] Binary string 71 | # 72 | def to_binary 73 | Formatters::Binary.new.serialize self 74 | end 75 | 76 | # Create a tag map from the binary string. 77 | # @param [String] data Binary string data 78 | # @return [TagMap] 79 | # 80 | def self.from_binary data 81 | Formatters::Binary.new.deserialize data 82 | end 83 | 84 | # @!method [] 85 | # @see Hash#[] 86 | # @!method each 87 | # @see Hash#each 88 | # @!method delete 89 | # @see Hash#delete 90 | # @!method delete_if 91 | # @see Hash#delete_if 92 | # @!method length 93 | # @see Hash#length 94 | # @!method to_h 95 | # @see Hash#to_h 96 | # @!method empty? 97 | # @see Hash#empty? 98 | def_delegators :@tags, :[], :each, :delete, :delete_if, :length, \ 99 | :to_h, :empty? 100 | 101 | # Invalid tag error. 102 | class InvalidTagError < StandardError; end 103 | 104 | private 105 | 106 | # Validate tag key. 107 | # @param [String] key 108 | # @raise [InvalidTagError] If key is empty, length grater then 255 109 | # characters or contains non printable characters 110 | # 111 | def validate_key! key 112 | if key.empty? || key.length > MAX_LENGTH || !printable_str?(key) 113 | raise InvalidTagError, "Invalid tag key #{key}" 114 | end 115 | end 116 | 117 | # Validate tag value. 118 | # @param [String] value 119 | # @raise [InvalidTagError] If value length grater then 255 characters 120 | # or contains non printable characters 121 | # 122 | def validate_value! value 123 | if (value && value.length > MAX_LENGTH) || !printable_str?(value) 124 | raise InvalidTagError, "Invalid tag value #{value}" 125 | end 126 | end 127 | 128 | # Check string is printable. 129 | # @param [String] str 130 | # @return [Boolean] 131 | # 132 | def printable_str? str 133 | str.bytes.none? { |b| b < 32 || b > 126 } 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/opencensus/trace/formatters/trace_context.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | module OpenCensus 17 | module Trace 18 | module Formatters 19 | ## 20 | # This formatter serializes and deserializes span context according to 21 | # the TraceContext specification. See 22 | # [documentation](https://github.com/TraceContext/tracecontext-spec/blob/master/trace_context/HTTP_HEADER_FORMAT.md). 23 | # 24 | class TraceContext 25 | ## 26 | # Internal regex used to identify the TraceContext version 27 | # 28 | # @private 29 | # 30 | VERSION_PATTERN = /^([0-9a-fA-F]{2})-(.+)$/ 31 | 32 | ## 33 | # Internal regex used to parse fields in version 0 34 | # 35 | # @private 36 | # 37 | HEADER_V0_PATTERN = 38 | /^([0-9a-fA-F]{32})-([0-9a-fA-F]{16})(-([0-9a-fA-F]{2}))?$/ 39 | 40 | ## 41 | # The outgoing header used for the TraceContext header specification. 42 | # 43 | # @private 44 | # 45 | HEADER_NAME = "traceparent".freeze 46 | 47 | ## 48 | # The rack environment header used for the TraceContext header 49 | # specification 50 | # 51 | # @private 52 | # 53 | RACK_HEADER_NAME = "HTTP_TRACEPARENT".freeze 54 | 55 | ## 56 | # Returns the name of the header used for context propagation. 57 | # 58 | # @return [String] 59 | # 60 | def header_name 61 | HEADER_NAME 62 | end 63 | 64 | ## 65 | # Returns the name of the rack_environment header to use when parsing 66 | # context from an incoming request. 67 | # 68 | # @return [String] 69 | # 70 | def rack_header_name 71 | RACK_HEADER_NAME 72 | end 73 | 74 | ## 75 | # Deserialize a trace context header into a TraceContext object. 76 | # 77 | # @param [String] header 78 | # @return [TraceContextData, nil] 79 | # 80 | def deserialize header 81 | match = VERSION_PATTERN.match(header) 82 | if match 83 | version = match[1].to_i(16) 84 | version_format = match[2] 85 | case version 86 | when 0 87 | parse_trace_context_header_version_0 version_format 88 | else 89 | nil 90 | end 91 | else 92 | nil 93 | end 94 | end 95 | 96 | ## 97 | # Serialize a TraceContextData object. 98 | # 99 | # @param [TraceContextData] trace_context 100 | # @return [String] 101 | # 102 | def serialize trace_context 103 | format( 104 | "%02d-%s-%s-%02d", 105 | version: 0, # version 0, 106 | trace_id: trace_context.trace_id, 107 | span_id: trace_context.span_id, 108 | trace_options: trace_context.trace_options 109 | ) 110 | end 111 | 112 | private 113 | 114 | def parse_trace_context_header_version_0 str 115 | match = HEADER_V0_PATTERN.match(str) 116 | if match 117 | TraceContextData.new match[1].downcase, 118 | match[2].downcase, 119 | match[4].to_i(16) 120 | end 121 | end 122 | end 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /test/trace_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace do 18 | let(:simple_context) { OpenCensus::Trace::SpanContext.create_root } 19 | let(:header) { "00-0123456789abcdef0123456789abcdef-0123456789abcdef-00" } 20 | let(:trace_context) do 21 | OpenCensus::Trace::TraceContextData.new \ 22 | "0123456789abcdef0123456789abcdef", "0123456789abcdef", 1 23 | end 24 | 25 | describe "span context" do 26 | after { 27 | OpenCensus::Trace.unset_span_context 28 | } 29 | 30 | it "can be set and unset" do 31 | OpenCensus::Trace.span_context.must_be_nil 32 | OpenCensus::Trace.span_context = simple_context 33 | OpenCensus::Trace.span_context.must_equal simple_context 34 | OpenCensus::Trace.unset_span_context 35 | OpenCensus::Trace.span_context.must_be_nil 36 | end 37 | 38 | it "is initialized when a request trace starts" do 39 | OpenCensus::Trace.start_request_trace trace_context: trace_context 40 | OpenCensus::Trace.span_context.trace_id.must_equal \ 41 | "0123456789abcdef0123456789abcdef" 42 | OpenCensus::Trace.span_context.span_id.must_equal \ 43 | "0123456789abcdef" 44 | OpenCensus::Trace.span_context.root?.must_equal true 45 | end 46 | 47 | it "is cleared after a request trace block" do 48 | OpenCensus::Trace.start_request_trace trace_context: trace_context do |ctx| 49 | OpenCensus::Trace.span_context.must_equal ctx 50 | end 51 | OpenCensus::Trace.span_context.must_be_nil 52 | end 53 | end 54 | 55 | describe "default context" do 56 | before { 57 | OpenCensus::Trace.start_request_trace trace_context: trace_context 58 | } 59 | after { 60 | OpenCensus::Trace.unset_span_context 61 | } 62 | 63 | it "can start a span which changes the context" do 64 | OpenCensus::Trace.start_span "the span" 65 | OpenCensus::Trace.span_context.trace_id.must_equal \ 66 | "0123456789abcdef0123456789abcdef" 67 | OpenCensus::Trace.span_context.span_id.wont_equal \ 68 | "0123456789abcdef" 69 | OpenCensus::Trace.span_context.parent.span_id.must_equal \ 70 | "0123456789abcdef" 71 | end 72 | 73 | it "can end the current span which restores the root context" do 74 | span = OpenCensus::Trace.start_span "the span" 75 | OpenCensus::Trace.end_span span 76 | OpenCensus::Trace.span_context.trace_id.must_equal \ 77 | "0123456789abcdef0123456789abcdef" 78 | OpenCensus::Trace.span_context.span_id.must_equal \ 79 | "0123456789abcdef" 80 | OpenCensus::Trace.span_context.root?.must_equal true 81 | end 82 | 83 | it "cannot end a foreign span" do 84 | foreign_span = simple_context.start_span "foreign span" 85 | OpenCensus::Trace.start_span "the span" 86 | -> () { 87 | OpenCensus::Trace.end_span foreign_span 88 | }.must_raise "The given span doesn't match the currently active span" 89 | end 90 | 91 | it "can start and end a span in a block" do 92 | OpenCensus::Trace.in_span "the span" do |span| 93 | OpenCensus::Trace.span_context.trace_id.must_equal \ 94 | "0123456789abcdef0123456789abcdef" 95 | OpenCensus::Trace.span_context.span_id.wont_equal \ 96 | "0123456789abcdef" 97 | OpenCensus::Trace.span_context.parent.span_id.must_equal \ 98 | "0123456789abcdef" 99 | end 100 | OpenCensus::Trace.span_context.trace_id.must_equal \ 101 | "0123456789abcdef0123456789abcdef" 102 | OpenCensus::Trace.span_context.span_id.must_equal \ 103 | "0123456789abcdef" 104 | OpenCensus::Trace.span_context.root?.must_equal true 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /test/trace/integrations/rails_integration_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "fileutils" 16 | require "open3" 17 | require "timeout" 18 | 19 | require "faraday" 20 | require "test_helper" 21 | require "opencensus/trace/integrations/rails" 22 | 23 | describe "Rails integration" do 24 | module RailsTestHelper 25 | APP_NAME = "railstest" 26 | TEMPLATE_PATH = File.absolute_path(File.join __dir__, "rails51_app_template.rb") 27 | BASE_DIR = File.dirname File.dirname File.dirname __dir__ 28 | TMP_DIR = File.join BASE_DIR, "tmp" 29 | APP_DIR = File.join TMP_DIR, APP_NAME 30 | RAILS_OPTIONS = %w( 31 | --skip-yarn 32 | --skip-action-mailer 33 | --skip-active-record 34 | --skip-action-cable 35 | --skip-puma 36 | --skip-sprockets 37 | --skip-spring 38 | --skip-listen 39 | --skip-coffee 40 | --skip-javascript 41 | --skip-turbolinks 42 | --skip-test 43 | --skip-system-test 44 | --skip-bundle 45 | ).join ' ' 46 | 47 | class << self 48 | def create_rails_app 49 | puts "**** Creating test Rails app..." 50 | FileUtils.mkdir_p TMP_DIR 51 | FileUtils.rm_rf APP_DIR 52 | Dir.chdir TMP_DIR do 53 | system "bundle exec rails new #{APP_NAME} #{RAILS_OPTIONS} -m #{TEMPLATE_PATH}" 54 | end 55 | Dir.chdir APP_DIR do 56 | Bundler.with_original_env do 57 | original_gemfile = ENV.delete "BUNDLE_GEMFILE" 58 | begin 59 | system "bundle lock" 60 | system "bundle install --deployment" 61 | ensure 62 | ENV["BUNDLE_GEMFILE"] = original_gemfile if original_gemfile 63 | end 64 | end 65 | end 66 | puts "**** Finished creating test Rails app" 67 | end 68 | 69 | def run_rails_app timeout: 5 70 | Dir.chdir APP_DIR do 71 | Bundler.with_original_env do 72 | Open3.popen2e "bundle exec rails s -p 3000" do |_in, out, thr| 73 | begin 74 | Timeout.timeout timeout do 75 | loop do 76 | line = out.gets 77 | break if !line || line =~ /WEBrick::HTTPServer#start/ 78 | end 79 | yield out if block_given? 80 | end 81 | ensure 82 | Process.kill("INT", thr.pid) 83 | end 84 | end 85 | end 86 | end 87 | end 88 | 89 | def capture_in_rails_context cmd, timeout: 6 90 | result = nil 91 | Dir.chdir APP_DIR do 92 | Bundler.with_original_env do 93 | Timeout.timeout timeout do 94 | result = `#{cmd}` 95 | end 96 | end 97 | end 98 | result 99 | end 100 | 101 | def rails_request path 102 | resp = Faraday.get "http://localhost:3000#{path}" 103 | resp.body 104 | end 105 | end 106 | end 107 | 108 | RailsTestHelper.create_rails_app unless ENV["FASTER_TESTS"] 109 | 110 | it "traces incoming requests" do 111 | skip if ENV["FASTER_TESTS"] 112 | RailsTestHelper.run_rails_app do |stream| 113 | result = RailsTestHelper.rails_request "/" 114 | result.must_equal "OK" 115 | loop do 116 | line = stream.gets 117 | break if !line || line =~ /"trace_id":"\w{32}"/ 118 | end 119 | end 120 | end 121 | 122 | it "inserts middleware at the end" do 123 | skip if ENV["FASTER_TESTS"] 124 | result = RailsTestHelper.capture_in_rails_context "bundle exec bin/rails middleware" 125 | result = result.split("\n") 126 | result[-2].must_equal "use OpenCensus::Trace::Integrations::RackMiddleware" 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/opencensus/trace/integrations/rack_middleware.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus" 17 | 18 | module OpenCensus 19 | module Trace 20 | module Integrations 21 | ## 22 | # # Rack integration 23 | # 24 | # This is a middleware for Rack applications: 25 | # 26 | # * It wraps all incoming requests in a root span 27 | # * It exports the captured spans at the end of the request. 28 | # 29 | # Example: 30 | # 31 | # require "opencensus/trace/integrations/rack_middleware" 32 | # 33 | # use OpenCensus::Trace::Integrations::RackMiddleware 34 | # 35 | class RackMiddleware 36 | ## 37 | # List of trace context formatters we use to parse the parent span 38 | # context. 39 | # 40 | # @private 41 | # 42 | AUTODETECTABLE_FORMATTERS = [ 43 | Formatters::CloudTrace.new, 44 | Formatters::TraceContext.new 45 | ].freeze 46 | 47 | ## 48 | # Create the Rack middleware. 49 | # 50 | # @param [#call] app Next item on the middleware stack 51 | # @param [#export] exporter The exported used to export captured spans 52 | # at the end of the request. Optional: If omitted, uses the exporter 53 | # in the current config. 54 | # 55 | def initialize app, exporter: nil 56 | @app = app 57 | @exporter = exporter || OpenCensus::Trace.config.exporter 58 | end 59 | 60 | ## 61 | # Run the Rack middleware. 62 | # 63 | # @param [Hash] env The rack environment 64 | # @return [Array] The rack response. An array with 3 elements: the HTTP 65 | # response code, a Hash of the response headers, and the response 66 | # body which must respond to `each`. 67 | # 68 | def call env 69 | formatter = AUTODETECTABLE_FORMATTERS.detect do |f| 70 | env.key? f.rack_header_name 71 | end 72 | if formatter 73 | context = formatter.deserialize env[formatter.rack_header_name] 74 | end 75 | 76 | Trace.start_request_trace \ 77 | trace_context: context, 78 | same_process_as_parent: false do |span_context| 79 | begin 80 | Trace.in_span get_path(env) do |span| 81 | start_request span, env 82 | @app.call(env).tap do |response| 83 | finish_request span, response 84 | end 85 | end 86 | ensure 87 | @exporter.export span_context.build_contained_spans 88 | end 89 | end 90 | end 91 | 92 | private 93 | 94 | def get_path env 95 | path = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}" 96 | path = "/#{path}" unless path.start_with? "/" 97 | path 98 | end 99 | 100 | def get_host env 101 | env["HTTP_HOST"] || env["SERVER_NAME"] 102 | end 103 | 104 | def start_request span, env 105 | span.kind = SpanBuilder::SERVER 106 | span.put_attribute "http.host", get_host(env) 107 | span.put_attribute "http.path", get_path(env) 108 | span.put_attribute "http.method", env["REQUEST_METHOD"].to_s.upcase 109 | if env["HTTP_USER_AGENT"] 110 | span.put_attribute "http.user_agent", env["HTTP_USER_AGENT"] 111 | end 112 | end 113 | 114 | def finish_request span, response 115 | if response.is_a?(::Array) && response.size == 3 116 | http_status = response[0] 117 | span.set_http_status http_status 118 | span.put_attribute "http.status_code", http_status 119 | end 120 | end 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /lib/opencensus/tags/formatters/binary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OpenCensus 4 | module Tags 5 | module Formatters 6 | ## 7 | # This formatter serializes and deserializes tags context according to 8 | # the OpenCensus' BinaryEncoding specification. See 9 | # [documentation](https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md). 10 | # 11 | # @example Serialize 12 | # 13 | # formatter = OpenCensus::Tags::Formatters::Binary.new 14 | # 15 | # tag_map = OpenCensus::Tags::TagMap.new({\"key1" => \"val1"}) 16 | # binary = formatter.serialize tag_map # "\x00\x00\x04key1\x04val1" 17 | # 18 | # @example Deserialize 19 | # 20 | # formatter = OpenCensus::Tags::Formatters::Binary.new 21 | # 22 | # binary = "\x00\x00\x04key1\x04val1" 23 | # tag_map = formatter.deserialize binary 24 | # 25 | class Binary 26 | # Binary format error for tags serialize/deserialize. 27 | class BinaryFormatterError < StandardError; end 28 | 29 | # @private 30 | # 31 | # Seralization version 32 | VERSION_ID = 0 33 | 34 | # @private 35 | # 36 | # Tag field id 37 | TAG_FIELD_ID = 0 38 | 39 | # @private 40 | # 41 | # Serialized tag context limit 42 | TAG_MAP_SERIALIZED_SIZE_LIMIT = 8192 43 | 44 | # Serialize TagMap object 45 | # 46 | # @param [TagMap] tags_context 47 | # 48 | def serialize tags_context 49 | binary = [int_to_varint(VERSION_ID)] 50 | 51 | tags_context.each do |key, value| 52 | binary << int_to_varint(TAG_FIELD_ID) 53 | binary << int_to_varint(key.length) 54 | binary << key.encode(Encoding::UTF_8) 55 | binary << int_to_varint(value ? value.length : 0) 56 | binary << value.to_s.encode(Encoding::UTF_8) 57 | end 58 | 59 | binary = binary.join 60 | binary.length > TAG_MAP_SERIALIZED_SIZE_LIMIT ? nil : binary 61 | end 62 | 63 | # Deserialize binary data into a TagMap object. 64 | # 65 | # @param [String] binary 66 | # @return [TagMap] 67 | # @raise [BinaryFormatterError] If deserialized version id not valid or 68 | # tag key, value size in varint more then then unsigned int32. 69 | # 70 | def deserialize binary 71 | return TagMap.new if binary.nil? || binary.empty? 72 | 73 | io = StringIO.new binary 74 | version_id = io.getc.unpack("C").first 75 | unless version_id == VERSION_ID 76 | raise BinaryFormatterError, "invalid version id" 77 | end 78 | 79 | tag_map = TagMap.new 80 | 81 | loop do 82 | break if io.eof? 83 | tag_field_id = io.getc.unpack("C").first 84 | break unless tag_field_id == TAG_FIELD_ID 85 | 86 | key_length = varint_to_int io 87 | key = io.gets key_length 88 | value_length = varint_to_int io 89 | value = io.gets value_length 90 | tag_map[key] = value 91 | end 92 | 93 | io.close 94 | tag_map 95 | end 96 | 97 | private 98 | 99 | # Convert integer to Varint. 100 | # @see https://developers.google.com/protocol-buffers/docs/encoding#varints 101 | # 102 | # @param [Integer] int_val 103 | # @return [String] 104 | def int_to_varint int_val 105 | result = [] 106 | loop do 107 | bits = int_val & 0x7F 108 | int_val >>= 7 109 | if int_val.zero? 110 | result << bits 111 | break 112 | else 113 | result << (0x80 | bits) 114 | end 115 | end 116 | result.pack "C*" 117 | end 118 | 119 | # Convert Varint bytes format to integer 120 | # @see https://developers.google.com/protocol-buffers/docs/encoding#varints 121 | # 122 | # @param [StringIO] io 123 | # @return [Integer] 124 | # @raise [BinaryFormatterError] If varint size more then unsigned int32 125 | # 126 | def varint_to_int io 127 | int_val = 0 128 | shift = 0 129 | 130 | loop do 131 | raise BinaryFormatterError, "varint too long" if shift >= 32 132 | byte = io.getbyte 133 | int_val |= (byte & 0x7F) << shift 134 | shift += 7 135 | return int_val if (byte & 0x80).zero? 136 | end 137 | end 138 | end 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /test/trace/exporters/logger_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | describe OpenCensus::Trace::Exporters::Logger do 18 | let(:logger) { 19 | logger = ::Logger.new STDOUT 20 | logger.level = ::Logger::INFO 21 | logger.formatter = -> (_, _, _, msg) { msg } 22 | logger 23 | } 24 | 25 | describe "export" do 26 | let(:spans) { [OpenCensus::Trace::Span.new("traceid", "spanid", "name", Time.new, Time.new)] } 27 | 28 | it "should emit data for a covered log level" do 29 | exporter = OpenCensus::Trace::Exporters::Logger.new logger, level: ::Logger::INFO 30 | out, _err = capture_subprocess_io do 31 | exporter.export spans 32 | end 33 | 34 | out.wont_be_empty 35 | end 36 | 37 | it "should not emit data for a low log level" do 38 | exporter = OpenCensus::Trace::Exporters::Logger.new logger, level: ::Logger::DEBUG 39 | out, _err = capture_subprocess_io do 40 | exporter.export spans 41 | end 42 | 43 | out.must_be_empty 44 | end 45 | end 46 | 47 | describe "span format" do 48 | let(:root_context) { OpenCensus::Trace::SpanContext.create_root } 49 | let(:span1) do 50 | root_context 51 | .start_span("hello", kind: OpenCensus::Trace::SpanBuilder::SERVER) 52 | .put_attribute("foo", "bar") 53 | .put_annotation("some annotation", {"key" => "value"}) 54 | .put_message_event(OpenCensus::Trace::SpanBuilder::SENT, 1234, 2345) 55 | .put_link("traceid", "spanid", OpenCensus::Trace::SpanBuilder::CHILD_LINKED_SPAN, {"key2" => "value2"}) 56 | .set_http_status(200, "OK") 57 | .update_stack_trace 58 | .finish! 59 | end 60 | let(:exporter) { OpenCensus::Trace::Exporters::Logger.new logger, level: ::Logger::INFO } 61 | let(:output) do 62 | out, _err = capture_subprocess_io do 63 | exporter.export [span1.to_span] 64 | end 65 | JSON.parse(out).first 66 | end 67 | 68 | it "should serialize name" do 69 | output["name"].wont_be_empty 70 | output["name"].must_equal "hello" 71 | end 72 | 73 | it "should serialize kind" do 74 | output["kind"].wont_be_empty 75 | output["kind"].must_equal "SERVER" 76 | end 77 | 78 | it "should serialize attributes" do 79 | output["attributes"].must_equal({"foo" => "bar"}) 80 | output["dropped_attributes_count"].must_equal 0 81 | end 82 | 83 | it "should serialize annotations" do 84 | output["time_events"].wont_be_empty 85 | output["time_events"].must_be_kind_of Array 86 | annotation = output["time_events"].first 87 | annotation.must_be_kind_of Hash 88 | annotation["time"].wont_be_empty 89 | annotation["description"].must_equal "some annotation" 90 | annotation["attributes"].must_equal({"key" => "value"}) 91 | output["dropped_annotations_count"].must_equal 0 92 | end 93 | 94 | it "should serialize message events" do 95 | output["time_events"].wont_be_empty 96 | output["time_events"].must_be_kind_of Array 97 | message_event = output["time_events"].last 98 | message_event.must_be_kind_of Hash 99 | message_event["time"].wont_be_empty 100 | message_event["type"].must_equal "SENT" 101 | message_event["id"].must_equal 1234 102 | message_event["uncompressed_size"].must_equal 2345 103 | message_event["compressed_size"].must_be_nil 104 | output["dropped_message_events_count"].must_equal 0 105 | end 106 | 107 | it "should serialize links" do 108 | output["links"].wont_be_empty 109 | output["links"].must_be_kind_of Array 110 | link = output["links"].first 111 | link.must_be_kind_of Hash 112 | link["trace_id"].must_equal "traceid" 113 | link["span_id"].must_equal "spanid" 114 | link["type"].must_equal "CHILD_LINKED_SPAN" 115 | link["attributes"].must_equal({"key2" => "value2"}) 116 | output["dropped_links_count"].must_equal 0 117 | end 118 | 119 | it "should serialize status" do 120 | output["status"].wont_be_nil 121 | output["status"].must_be_kind_of Hash 122 | output["status"]["code"].must_equal OpenCensus::Trace::Status::OK 123 | output["status"]["message"].must_equal "OK" 124 | end 125 | 126 | it "should serialize stack trace" do 127 | output["stack_trace"].wont_be_empty 128 | output["stack_trace"].must_be_kind_of Array 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | # OpenCensus for Ruby 6 | 7 | OpenCensus provides a framework to measure a server's resource usage and 8 | collect performance stats. The `opencensus` Rubygem contains the core 9 | OpenCensus APIs and basic integrations with Rails, Faraday, and GRPC. 10 | 11 | The library is in alpha stage, and the API is subject to change. 12 | 13 | ## Quick Start 14 | 15 | ### Installation 16 | 17 | Install the gem directly: 18 | 19 | ```sh 20 | $ gem install opencensus 21 | ``` 22 | 23 | Or install through Bundler: 24 | 25 | 1. Add the `opencensus` gem to your Gemfile: 26 | 27 | ```ruby 28 | gem "opencensus" 29 | ``` 30 | 31 | 2. Use Bundler to install the gem: 32 | 33 | ```sh 34 | $ bundle install 35 | ``` 36 | 37 | ### Getting started with Ruby on Rails 38 | 39 | The OpenCensus library provides a Railtie that integrates with Ruby On Rails, 40 | automatically tracing incoming requests in the application. It also 41 | automatically traces key processes in your application such as database queries 42 | and view rendering. 43 | 44 | To enable Rails integration, require this file during application startup: 45 | 46 | ```ruby 47 | # In config/application.rb 48 | require "opencensus/trace/integrations/rails" 49 | ``` 50 | 51 | ### Getting started with other Rack-based frameworks 52 | 53 | Other Rack-based frameworks, such as Sinatra, can use the Rack Middleware 54 | integration, which automatically traces incoming requests. To enable the 55 | integration for a non-Rails Rack framework, add the middleware to your 56 | middleware stack. 57 | 58 | ```ruby 59 | # In config.ru or similar Rack configuration file 60 | require "opencensus/trace/integrations/rack_middleware" 61 | use OpenCensus::Trace::Integrations::RackMiddleware 62 | ``` 63 | 64 | ## Instrumentation features 65 | 66 | ### Tracing outgoing HTTP requests 67 | 68 | If your app uses the [Faraday](https://github.com/lostisland/faraday) library 69 | to make outgoing HTTP requests, consider installing the Faraday Middleware 70 | integration. This integration creates a span for each outgoing Faraday request, 71 | tracking the latency of that request, and propagates distributed trace headers 72 | into the request so you can potentially connect your request trace with that of 73 | the remote service. Here is an example: 74 | 75 | ```ruby 76 | conn = Faraday.new(url: "http://www.example.com") do |c| 77 | c.use OpenCensus::Trace::Integrations::FaradayMiddleware 78 | c.adapter Faraday.default_adapter 79 | end 80 | conn.get "/" 81 | ``` 82 | 83 | See the documentation for the 84 | [FaradayMiddleware](http://opencensus.io/opencensus-ruby/api/OpenCensus/Trace/Integrations/FaradayMiddleware.html) 85 | class for more info. 86 | 87 | ### Adding Custom Trace Spans 88 | 89 | In addition to the spans added by the Rails integration (e.g. for database 90 | queries) and by Faraday integration for outgoing HTTP requests, you can add 91 | additional custom spans to the request trace: 92 | 93 | ```ruby 94 | OpenCensus::Trace.in_span "my_task" do |span| 95 | # Do stuff... 96 | 97 | OpenCensus::Trace.in_span "my_subtask" do |subspan| 98 | # Do other stuff 99 | end 100 | end 101 | ``` 102 | 103 | See the documentation for the 104 | [OpenCensus::Trace](http://opencensus.io/opencensus-ruby/api/OpenCensus/Trace.html) 105 | module for more info. 106 | 107 | ### Exporting traces 108 | 109 | By default, OpenCensus will log request trace data as JSON. To export traces to 110 | your favorite analytics backend, install an export plugin. There are plugins 111 | currently being developed for Stackdriver, Zipkin, and other services. 112 | 113 | ### Configuring the library 114 | 115 | OpenCensus allows configuration of a number of aspects via the configuration 116 | class. The following example illustrates how that looks: 117 | 118 | ```ruby 119 | OpenCensus.configure do |c| 120 | c.trace.default_sampler = OpenCensus::Trace::Samplers::AlwaysSample.new 121 | c.trace.default_max_attributes = 16 122 | end 123 | ``` 124 | 125 | If you are using Rails, you can equivalently use the Rails config: 126 | 127 | ```ruby 128 | config.opencensus.trace.default_sampler = 129 | OpenCensus::Trace::Samplers::AlwaysSample.new 130 | config.opencensus.trace.default_max_attributes = 16 131 | ``` 132 | 133 | You can configure a variety of core OpenCensuys options, including: 134 | 135 | * Sampling, which controls how often a request is traced. 136 | * Exporting, which controls how trace information is reported. 137 | * Formatting, which controls how distributed request trace headers are 138 | constructed 139 | * Size maximums, which control when trace data is truncated. 140 | 141 | Additionally, integrations and other plugins might have their own 142 | configurations. 143 | 144 | For more information, consult the documentation for 145 | [OpenCensus.configure](http://opencensus.io/opencensus-ruby/api/OpenCensus.html#configure-class_method) 146 | and 147 | [OpenCensus::Trace.configure](http://opencensus.io/opencensus-ruby/api/OpenCensus/Trace.html#configure-class_method). 148 | 149 | ## Supported Ruby Versions 150 | 151 | This library is supported on Ruby 2.0+. 152 | -------------------------------------------------------------------------------- /lib/opencensus/trace/config.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/config" 17 | require "opencensus/trace/exporters" 18 | require "opencensus/trace/formatters" 19 | require "opencensus/trace/samplers" 20 | 21 | module OpenCensus 22 | module Trace 23 | # Schema of the Trace configuration. See Trace#configure for more info. 24 | @config = Common::Config.new do |config| 25 | default_sampler = Samplers::AlwaysSample.new 26 | exporter_logger = ::Logger.new STDOUT, ::Logger::INFO 27 | default_exporter = Exporters::Logger.new exporter_logger 28 | default_formatter = Formatters::TraceContext.new 29 | 30 | config.add_option! :default_sampler, default_sampler do |value| 31 | value.respond_to? :call 32 | end 33 | config.add_option! :exporter, default_exporter do |value| 34 | value.respond_to? :export 35 | end 36 | config.add_option! :http_formatter, default_formatter do |value| 37 | value.respond_to?(:serialize) && 38 | value.respond_to?(:deserialize) && 39 | value.respond_to?(:header_name) && 40 | value.respond_to?(:rack_header_name) 41 | end 42 | config.add_option! :default_max_attributes, 32 43 | config.add_option! :default_max_stack_frames, 32 44 | config.add_option! :default_max_annotations, 32 45 | config.add_option! :default_max_message_events, 128 46 | config.add_option! :default_max_links, 128 47 | config.add_option! :default_max_string_length, 1024 48 | end 49 | 50 | # Expose the trace config as a subconfig under the main config. 51 | OpenCensus.configure do |config| 52 | config.add_alias! :trace, config: @config 53 | end 54 | 55 | class << self 56 | ## 57 | # Configure OpenCensus Trace. These configuration fields include 58 | # parameters governing sampling, span creation, and exporting. 59 | # 60 | # This configuration is also available as the `trace` subconfig under the 61 | # main configuration `OpenCensus.configure`. If the OpenCensus Railtie is 62 | # installed in a Rails application, the configuration object is also 63 | # exposed as `config.opencensus.trace`. 64 | # 65 | # Generally, you should configure this once at process initialization, 66 | # but it can be modified at any time. 67 | # 68 | # Example: 69 | # 70 | # OpenCensus::Trace.configure do |config| 71 | # config.default_sampler = 72 | # OpenCensus::Trace::Samplers::RateLimiting.new 73 | # config.default_max_attributes = 16 74 | # end 75 | # 76 | # Supported fields are: 77 | # 78 | # * `default_sampler` The default sampler to use. Must be a sampler, 79 | # an object with a `call` method that takes a single options hash. 80 | # See {OpenCensus::Trace::Samplers}. The initial value is an instance 81 | # of {OpenCensus::Trace::Samplers::AlwaysSample}. 82 | # * `exporter` The exporter to use. Must be an exporter, an object with 83 | # an export method that takes an array of Span objects. See 84 | # {OpenCensus::Trace::Exporters}. The initial value is a 85 | # {OpenCensus::Trace::Exporters::Logger} that logs to STDOUT. 86 | # * `http_formatter` The trace context propagation formatter to use. 87 | # Must be a formatter, an object with `serialize`, `deserialize`, 88 | # `header_name`, and `rack_header_name` methods. See 89 | # {OpenCensus::Trace::Formatters}. The initial value is a 90 | # {OpenCensus::Trace::Formatters::TraceContext}. 91 | # * `default_max_attributes` The maximum number of attributes to add to 92 | # a span. Initial value is 32. Use 0 for no maximum. 93 | # * `default_max_stack_frames` The maximum number of stack frames to 94 | # represent in a span's stack trace. Initial value is 32. Use 0 for 95 | # no maximum. 96 | # * `default_max_annotations` The maximum number of annotations to add 97 | # to a span. Initial value is 32. Use 0 for no maximum. 98 | # * `default_max_message_events` The maximum number of message events 99 | # to add to a span. Initial value is 128. Use 0 for no maximum. 100 | # * `default_max_links` The maximum number of links to add to a span. 101 | # Initial value is 128. Use 0 for no maximum. 102 | # * `default_max_string_length` The maximum length of string fields. 103 | # Initial value is 1024. Use 0 for no maximum. 104 | # 105 | def configure 106 | if block_given? 107 | yield @config 108 | else 109 | @config 110 | end 111 | end 112 | 113 | ## 114 | # Get the current configuration 115 | # @private 116 | # 117 | attr_reader :config 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/opencensus/trace/exporters/logger.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "logger" 17 | require "json" 18 | 19 | module OpenCensus 20 | module Trace 21 | module Exporters 22 | ## 23 | # The Logger exporter exports captured spans to a standard Ruby Logger 24 | # interface. 25 | # 26 | class Logger 27 | ## 28 | # Create a new Logger exporter 29 | # 30 | # @param [#log] logger The logger to write to. 31 | # @param [Integer] level The log level. This should be a log level 32 | # defined by the Logger standard library. Default is 33 | # `::Logger::INFO`. 34 | # 35 | def initialize logger, level: ::Logger::INFO 36 | @logger = logger 37 | @level = level 38 | end 39 | 40 | ## 41 | # Export the captured spans to the configured logger. 42 | # 43 | # @param [Array] spans The captured spans. 44 | # 45 | def export spans 46 | @logger.log @level, spans.map { |span| format_span(span) }.to_json 47 | nil 48 | end 49 | 50 | private 51 | 52 | # rubocop:disable Metrics/MethodLength 53 | # rubocop:disable Metrics/AbcSize 54 | 55 | def format_span span 56 | { 57 | name: format_value(span.name), 58 | kind: span.kind, 59 | trace_id: span.trace_id, 60 | span_id: span.span_id, 61 | parent_span_id: span.parent_span_id, 62 | start_time: span.start_time, 63 | end_time: span.end_time, 64 | attributes: format_attributes(span.attributes), 65 | dropped_attributes_count: span.dropped_attributes_count, 66 | stack_trace: span.stack_trace, 67 | dropped_frames_count: span.dropped_frames_count, 68 | stack_trace_hash_id: span.stack_trace_hash_id, 69 | time_events: span.time_events.map { |te| format_time_event(te) }, 70 | dropped_annotations_count: span.dropped_annotations_count, 71 | dropped_message_events_count: span.dropped_message_events_count, 72 | links: span.links.map { |link| format_link(link) }, 73 | dropped_links_count: span.dropped_links_count, 74 | status: format_status(span.status), 75 | same_process_as_parent_span: span.same_process_as_parent_span, 76 | child_span_count: span.child_span_count 77 | } 78 | end 79 | 80 | # rubocop:enable Metrics/MethodLength 81 | # rubocop:enable Metrics/AbcSize 82 | 83 | def format_time_event time_event 84 | case time_event 85 | when Annotation 86 | format_annotation time_event 87 | when MessageEvent 88 | format_message_event time_event 89 | end 90 | end 91 | 92 | def format_annotation annotation 93 | { 94 | description: format_value(annotation.description), 95 | attributes: format_attributes(annotation.attributes), 96 | dropped_attributes_count: annotation.dropped_attributes_count, 97 | time: annotation.time 98 | } 99 | end 100 | 101 | def format_message_event message_event 102 | { 103 | type: message_event.type, 104 | id: message_event.id, 105 | uncompressed_size: message_event.uncompressed_size, 106 | compressed_size: message_event.compressed_size, 107 | time: message_event.time 108 | } 109 | end 110 | 111 | def format_link link 112 | { 113 | trace_id: link.trace_id, 114 | span_id: link.span_id, 115 | type: link.type, 116 | attributes: format_attributes(link.attributes), 117 | dropped_attributes_count: link.dropped_attributes_count 118 | } 119 | end 120 | 121 | def format_status status 122 | return nil if status.nil? 123 | 124 | { 125 | code: status.code, 126 | message: status.message 127 | } 128 | end 129 | 130 | def format_attributes attrs 131 | result = {} 132 | attrs.each do |k, v| 133 | result[k] = format_value v 134 | end 135 | result 136 | end 137 | 138 | def format_value value 139 | case value 140 | when String, Integer, true, false 141 | value 142 | when TruncatableString 143 | if value.truncated_byte_count.zero? 144 | value.value 145 | else 146 | { 147 | value: value.value, 148 | truncated_byte_count: value.truncated_byte_count 149 | } 150 | end 151 | else 152 | nil 153 | end 154 | end 155 | end 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /test/stats_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Stats do 4 | let(:tags) { 5 | { "tag1" => "value1", "tag2" => "value2"} 6 | } 7 | 8 | describe "stats context" do 9 | before { 10 | OpenCensus::Stats.unset_recorder_context 11 | } 12 | 13 | it "can be set and unset stats context" do 14 | stats_recorder = OpenCensus::Stats::Recorder.new 15 | 16 | OpenCensus::Stats.recorder_context.must_be_nil 17 | OpenCensus::Stats.recorder_context = stats_recorder 18 | OpenCensus::Stats.recorder_context.must_equal stats_recorder 19 | OpenCensus::Stats.unset_recorder_context 20 | OpenCensus::Stats.recorder_context.must_be_nil 21 | end 22 | 23 | it "create stats recorder and set into local thread" do 24 | stats_recorder = OpenCensus::Stats.ensure_recorder 25 | stats_recorder.must_be_kind_of OpenCensus::Stats::Recorder 26 | OpenCensus::Stats.ensure_recorder.must_equal stats_recorder 27 | end 28 | end 29 | 30 | describe "measure" do 31 | before { 32 | OpenCensus::Stats::MeasureRegistry.clear 33 | } 34 | 35 | it "create int measure" do 36 | measure = OpenCensus::Stats.create_measure_int( 37 | name: "Latency", 38 | unit: "ms", 39 | description: "Test description" 40 | ) 41 | 42 | measure.must_be_kind_of OpenCensus::Stats::Measure 43 | measure.int64?.must_equal true 44 | measure.name.must_equal "Latency" 45 | measure.unit.must_equal "ms" 46 | measure.description.must_equal "Test description" 47 | OpenCensus::Stats::MeasureRegistry.get(measure.name).must_equal measure 48 | end 49 | 50 | it "create double type measure" do 51 | measure = OpenCensus::Stats.create_measure_double( 52 | name: "Storage", 53 | unit: "kb", 54 | description: "Test description" 55 | ) 56 | measure.must_be_kind_of OpenCensus::Stats::Measure 57 | measure.double?.must_equal true 58 | measure.name.must_equal "Storage" 59 | measure.unit.must_equal "kb" 60 | measure.description.must_equal "Test description" 61 | OpenCensus::Stats::MeasureRegistry.get(measure.name).must_equal measure 62 | end 63 | 64 | it "get list of registered measure" do 65 | measure = OpenCensus::Stats.create_measure_double( 66 | name: "Storage-1", 67 | unit: "kb", 68 | description: "Test description" 69 | ) 70 | OpenCensus::Stats.registered_measures.first.must_equal measure 71 | end 72 | 73 | it "prevents dublicate measure registration" do 74 | measure_name = "Storage-2" 75 | measure = OpenCensus::Stats.create_measure_double( 76 | name: measure_name, 77 | unit: "kb", 78 | description: "Test description" 79 | ) 80 | 81 | OpenCensus::Stats.create_measure_double( 82 | name: measure_name, 83 | unit: "kb", 84 | description: "Test description" 85 | ).must_be_nil 86 | 87 | OpenCensus::Stats.registered_measures.length.must_equal 1 88 | OpenCensus::Stats::MeasureRegistry.get(measure_name).must_equal measure 89 | end 90 | end 91 | 92 | describe "create_measurement" do 93 | before { 94 | OpenCensus::Stats::MeasureRegistry.clear 95 | } 96 | it "create measurement" do 97 | measure = OpenCensus::Stats.create_measure_int( 98 | name: "latency", 99 | unit: "ms", 100 | description: "Test description" 101 | ) 102 | 103 | measurement = OpenCensus::Stats.create_measurement( 104 | name: "latency", 105 | value: 10, 106 | tags: tags 107 | ) 108 | measurement.measure.must_equal measure 109 | measurement.value.must_equal 10 110 | end 111 | 112 | it "raise an error if measure not found in registry" do 113 | expect { 114 | OpenCensus::Stats.create_measurement( 115 | name: "latency-#{Time.now.to_i}", 116 | value: 10, 117 | tags: tags 118 | ) 119 | }.must_raise ArgumentError 120 | end 121 | end 122 | 123 | describe "aggregation" do 124 | it "create count aggregation" do 125 | aggregation = OpenCensus::Stats.create_count_aggregation 126 | aggregation.must_be_kind_of OpenCensus::Stats::Aggregation::Count 127 | end 128 | 129 | it "create sum aggregation" do 130 | aggregation = OpenCensus::Stats.create_sum_aggregation 131 | aggregation.must_be_kind_of OpenCensus::Stats::Aggregation::Sum 132 | end 133 | 134 | it "create last value aggregation" do 135 | aggregation = OpenCensus::Stats.create_last_value_aggregation 136 | aggregation.must_be_kind_of OpenCensus::Stats::Aggregation::LastValue 137 | end 138 | 139 | it "create distribution aggregation" do 140 | aggregation = OpenCensus::Stats.create_distribution_aggregation [1, 10] 141 | aggregation.must_be_kind_of OpenCensus::Stats::Aggregation::Distribution 142 | aggregation.buckets.must_equal [1, 10] 143 | end 144 | end 145 | 146 | describe "create_and_register_view" do 147 | let(:measure){ 148 | OpenCensus::Stats.create_measure_double( 149 | name: "Storage", 150 | unit: "kb", 151 | description: "Test description" 152 | ) 153 | } 154 | let(:aggregation){ OpenCensus::Stats.create_sum_aggregation } 155 | 156 | it "create and register view" do 157 | view = OpenCensus::Stats.create_and_register_view( 158 | name: "test.view", 159 | measure: measure, 160 | aggregation: aggregation, 161 | description: "Test description", 162 | columns: ["frontend"] 163 | ) 164 | 165 | view.must_be_kind_of OpenCensus::Stats::View 166 | view.name.must_equal "test.view" 167 | view.measure.must_equal measure 168 | view.aggregation.must_equal aggregation 169 | view.columns.must_equal ["frontend"] 170 | OpenCensus::Stats.ensure_recorder.views[view.name].must_equal view 171 | end 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /test/trace/integrations/rack_middleware_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "test_helper" 16 | 17 | require "opencensus/trace/integrations/rack_middleware" 18 | 19 | describe OpenCensus::Trace::Integrations::RackMiddleware do 20 | RACK_APP_RESPONSE = [200, {}, ["Hello World!"]] 21 | 22 | class TestRackApp 23 | def call env 24 | RACK_APP_RESPONSE 25 | end 26 | end 27 | 28 | class TestExporter 29 | attr_reader :spans 30 | 31 | def initialize 32 | @spans = [] 33 | end 34 | 35 | def export spans 36 | @spans += spans 37 | end 38 | end 39 | 40 | let(:app) { TestRackApp.new } 41 | let(:exporter) { TestExporter.new } 42 | 43 | describe "basic request" do 44 | let(:middleware) { OpenCensus::Trace::Integrations::RackMiddleware.new app, exporter: exporter } 45 | let(:response) { 46 | env = { 47 | "SCRIPT_NAME" => "", 48 | "PATH_INFO" => "/hello/world", 49 | "HTTP_HOST" => "www.google.com", 50 | "REQUEST_METHOD" => "GET", 51 | "rack.url_scheme" => "https", 52 | "SERVER_PROTOCOL" => "HTTP/1.1", 53 | "HTTP_USER_AGENT" => "Google Chrome", 54 | } 55 | middleware.call env 56 | } 57 | let(:spans) do 58 | response # make sure the request is processed 59 | exporter.spans 60 | end 61 | let(:root_span) { spans.first } 62 | 63 | it "captures spans" do 64 | spans.wont_be_empty 65 | spans.count.must_equal 1 66 | end 67 | 68 | it "parses the request path" do 69 | root_span.name.value.must_equal "/hello/world" 70 | end 71 | 72 | it "captures the response status code" do 73 | root_span.status.wont_be_nil 74 | root_span.status.code.must_equal OpenCensus::Trace::Status::OK 75 | end 76 | 77 | it "adds attributes to the span" do 78 | root_span.kind.must_equal :SERVER 79 | root_span.attributes["http.method"].value.must_equal "GET" 80 | root_span.attributes["http.path"].value.must_equal "/hello/world" 81 | root_span.attributes["http.host"].value.must_equal "www.google.com" 82 | root_span.attributes["http.user_agent"].value.must_equal "Google Chrome" 83 | end 84 | end 85 | 86 | describe "global configuration" do 87 | describe "default exporter" do 88 | let(:middleware) { OpenCensus::Trace::Integrations::RackMiddleware.new app } 89 | 90 | it "should use the default Logger exporter" do 91 | env = { 92 | "SCRIPT_NAME" => "", 93 | "PATH_INFO" => "/hello/world" 94 | } 95 | out, _err = capture_subprocess_io do 96 | middleware.call env 97 | end 98 | out.wont_be_empty 99 | end 100 | end 101 | 102 | describe "custom exporter" do 103 | before do 104 | @original_exporter = OpenCensus::Trace.config.exporter 105 | OpenCensus::Trace.config.exporter = exporter 106 | end 107 | after do 108 | OpenCensus::Trace.config.exporter = @original_exporter 109 | end 110 | let(:middleware) { OpenCensus::Trace::Integrations::RackMiddleware.new app } 111 | 112 | it "should capture the request" do 113 | env = { 114 | "SCRIPT_NAME" => "", 115 | "PATH_INFO" => "/hello/world" 116 | } 117 | middleware.call env 118 | 119 | spans = exporter.spans 120 | spans.wont_be_empty 121 | spans.count.must_equal 1 122 | end 123 | end 124 | end 125 | 126 | describe "trace context formatting" do 127 | let(:middleware) { OpenCensus::Trace::Integrations::RackMiddleware.new app, exporter: exporter } 128 | it "parses trace-context header from rack environment" do 129 | env = { 130 | "HTTP_TRACEPARENT" => 131 | "00-0123456789ABCDEF0123456789abcdef-0123456789ABCdef-01" 132 | } 133 | resp = middleware.call env 134 | root_span = exporter.spans.first 135 | 136 | resp.must_equal RACK_APP_RESPONSE 137 | root_span.trace_id.must_equal "0123456789abcdef0123456789abcdef" 138 | root_span.parent_span_id.must_equal "0123456789abcdef" 139 | end 140 | 141 | it "parses x-cloud-trace header from rack environment" do 142 | env = { 143 | "HTTP_X_CLOUD_TRACE" => 144 | "0123456789ABCDEF0123456789abcdef/81985529216486895;o=1" 145 | } 146 | resp = middleware.call env 147 | root_span = exporter.spans.first 148 | 149 | resp.must_equal RACK_APP_RESPONSE 150 | root_span.trace_id.must_equal "0123456789abcdef0123456789abcdef" 151 | root_span.parent_span_id.must_equal "0123456789abcdef" 152 | end 153 | 154 | it "falls back to default for missing header" do 155 | env = { 156 | "HTTP_TRACE_CONTEXT1" => 157 | "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01" 158 | } 159 | resp = middleware.call env 160 | root_span = exporter.spans.first 161 | 162 | resp.must_equal RACK_APP_RESPONSE 163 | root_span.trace_id.must_match %r{^[0-9a-f]{32}$} 164 | root_span.parent_span_id.must_be_empty 165 | end 166 | 167 | it "falls back to default for invalid trace-context version" do 168 | env = { 169 | "HTTP_TRACE_CONTEXT" => 170 | "ff-0123456789abcdef0123456789abcdef-0123456789abcdef-01" 171 | } 172 | resp = middleware.call env 173 | root_span = exporter.spans.first 174 | 175 | resp.must_equal RACK_APP_RESPONSE 176 | root_span.trace_id.must_match %r{^[0-9a-f]{32}$} 177 | root_span.parent_span_id.must_be_empty 178 | end 179 | end 180 | end 181 | -------------------------------------------------------------------------------- /lib/opencensus/stats.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2017 OpenCensus Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | require "opencensus/stats/config" 17 | require "opencensus/stats/recorder" 18 | require "opencensus/stats/view" 19 | require "opencensus/stats/aggregation" 20 | require "opencensus/stats/measure_registry" 21 | require "opencensus/stats/exporters" 22 | 23 | module OpenCensus 24 | ## 25 | # The Stats module contains support for OpenCensus stats collection. 26 | # 27 | # OpenCensus allows users to create typed measures, record measurements, 28 | # aggregate the collected data, and export the aggregated data. 29 | # 30 | # 31 | module Stats 32 | ## 33 | # Internal key for storing the current stats recorder in the thread local 34 | # context. 35 | # 36 | # @private 37 | RECORDER_CONTEXT_KEY = :__recorder_context__ 38 | 39 | class << self 40 | ## 41 | # Sets the current thread-local Recorder, which governs the behavior 42 | # of the recorder creation methods of OpenCensus::Stats::Recorder. 43 | # 44 | # @param [Recorder] context 45 | def recorder_context= context 46 | OpenCensus::Context.set RECORDER_CONTEXT_KEY, context 47 | end 48 | 49 | ## 50 | # Unsets the current thread-local SpanContext, disabling stats recorder 51 | # creation methods of OpenCensus::Stats::Recorder 52 | def unset_recorder_context 53 | OpenCensus::Context.unset RECORDER_CONTEXT_KEY 54 | end 55 | 56 | # Get the current thread-local stats recorder context/ 57 | # Returns `nil` if there is no current SpanContext. 58 | # 59 | # @return [Recorder, nil] 60 | def recorder_context 61 | OpenCensus::Context.get RECORDER_CONTEXT_KEY 62 | end 63 | 64 | # Get recorder from the stats context. If stats context nil then create 65 | # new recorder and set into stats context. 66 | # @return [Recorder] 67 | def ensure_recorder 68 | self.recorder_context ||= Recorder.new 69 | end 70 | 71 | # Create and register int64 type measure into measure registry. 72 | # 73 | # @param [String] name Name of the measure. 74 | # @param [String] unit Unit of the measure. i.e "kb", "s", "ms" 75 | # @param [String] description Detail description 76 | # @return [Measure] 77 | def create_measure_int name:, unit:, description: nil 78 | MeasureRegistry.register( 79 | name: name, 80 | unit: unit, 81 | type: Measure::INT64_TYPE, 82 | description: description 83 | ) 84 | end 85 | 86 | # Create and register double type measure into measure registry. 87 | # 88 | # @param [String] name Name of the measure. 89 | # @param [String] unit Unit of the measure. i.e "kb", "s", "ms" 90 | # @param [String] description Detail description 91 | # @return [Measure] 92 | def create_measure_double name:, unit:, description: nil 93 | MeasureRegistry.register( 94 | name: name, 95 | unit: unit, 96 | type: Measure::DOUBLE_TYPE, 97 | description: description 98 | ) 99 | end 100 | 101 | # Get list of registered measures 102 | # @return [Array] 103 | def registered_measures 104 | MeasureRegistry.measures 105 | end 106 | 107 | # Create measurement value for registered measure. 108 | # 109 | # @param [String] name Name of the registered measure 110 | # @param [Integer, Float] value Value of the measurement 111 | # @param [Hash] tags Tags to which the value is recorded 112 | # @raise [ArgumentError] if givem measure is not register 113 | def create_measurement name:, value:, tags: 114 | measure = MeasureRegistry.get name 115 | return measure.create_measurement(value: value, tags: tags) if measure 116 | raise ArgumentError, "#{name} measure is not registered" 117 | end 118 | 119 | # Create and register a view to current stats recorder context. 120 | # 121 | # @param [String] name 122 | # @param [Measure] measure 123 | # @param [Aggregation] aggregation 124 | # @param [Array] columns 125 | # @param [String] description 126 | def create_and_register_view \ 127 | name:, 128 | measure:, 129 | aggregation:, 130 | columns: nil, 131 | description: nil 132 | view = View.new( 133 | name: name, 134 | measure: measure, 135 | aggregation: aggregation, 136 | description: description, 137 | columns: columns 138 | ) 139 | ensure_recorder.register_view view 140 | end 141 | 142 | # Create aggregation defination instance with type sum. 143 | # @return [Aggregation] 144 | def create_sum_aggregation 145 | Aggregation::Sum.new 146 | end 147 | 148 | # Create aggregation defination instance with type count. 149 | # @return [Aggregation] 150 | def create_count_aggregation 151 | Aggregation::Count.new 152 | end 153 | 154 | # Create aggregation defination instance with type distribution. 155 | # @param [Array,Array] buckets Value boundries for 156 | # distribution. 157 | # @return [Aggregation] 158 | def create_distribution_aggregation buckets 159 | Aggregation::Distribution.new buckets 160 | end 161 | 162 | # Create aggregation defination instance with type last value. 163 | # @return [Aggregation] 164 | def create_last_value_aggregation 165 | Aggregation::LastValue.new 166 | end 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /test/tags/tag_map_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe OpenCensus::Tags::TagMap do 4 | describe "create" do 5 | it "create tags map with defaults" do 6 | tag_map = OpenCensus::Tags::TagMap.new 7 | tag_map.length.must_equal 0 8 | end 9 | 10 | it "create tags map with tags key, values" do 11 | tag_map = OpenCensus::Tags::TagMap.new({ "frontend" => "mobile-1.0"}) 12 | tag_map.length.must_equal 1 13 | tag_map["frontend"].must_equal "mobile-1.0" 14 | end 15 | 16 | it "create tag map with empty value" do 17 | tag_map = OpenCensus::Tags::TagMap.new({ "frontend" => ""}) 18 | tag_map["frontend"].must_equal "" 19 | end 20 | 21 | describe "tag key validation" do 22 | it "raise error for empty key" do 23 | expect { 24 | raise OpenCensus::Tags::TagMap.new({ "" => "mobile-1.0"}) 25 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 26 | end 27 | 28 | it "raise error if key length more then 255 chars" do 29 | key = "k" * 256 30 | expect { 31 | raise OpenCensus::Tags::TagMap.new({ key => "mobile-1.0"}) 32 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 33 | end 34 | 35 | it "raise error if key contains non printable chars less then 32 ascii code" do 36 | key = "key#{[31].pack('c')}-test" 37 | expect { 38 | raise OpenCensus::Tags::TagMap.new({ key => "mobile-1.0"}) 39 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 40 | end 41 | 42 | it "raise error if key contains non printable chars greater then 126 ascii code" do 43 | key = "key#{[127].pack('c')}-test" 44 | expect { 45 | raise OpenCensus::Tags::TagMap.new({ key => "mobile-1.0"}) 46 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 47 | end 48 | end 49 | 50 | describe "tag value validation" do 51 | it "raise error if value length more then 255 chars" do 52 | value = "v" * 256 53 | expect { 54 | OpenCensus::Tags::TagMap.new({ "frontend" => value }) 55 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 56 | end 57 | 58 | it "raise error if value contains non printable chars less then 32 ascii code" do 59 | value = "value#{[31].pack('c')}-test" 60 | expect { 61 | OpenCensus::Tags::TagMap.new({ "frontend" => value}) 62 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 63 | end 64 | 65 | it "raise error if value contains non printable chars greater then 126 ascii code" do 66 | value = "value#{[127].pack('c')}-test" 67 | expect { 68 | OpenCensus::Tags::TagMap.new({ "frontend" => value }) 69 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 70 | end 71 | end 72 | end 73 | 74 | describe "add tag to tag map" do 75 | it "set tag key value" do 76 | tag_map = OpenCensus::Tags::TagMap.new 77 | tag_map["frontend"] = "mobile-1.0" 78 | tag_map["frontend"].must_equal "mobile-1.0" 79 | end 80 | 81 | it "allow empty tag value" do 82 | tag_map = OpenCensus::Tags::TagMap.new 83 | tag_map["frontend"] = "" 84 | tag_map["frontend"].must_equal "" 85 | end 86 | 87 | describe "tag key validation" do 88 | let(:tag_map) { OpenCensus::Tags::TagMap.new } 89 | 90 | it "raise error for empty key" do 91 | expect { 92 | tag_map[""] = "mobile-1.0" 93 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 94 | end 95 | 96 | it "raise error if key length more then 255 chars" do 97 | key = "k" * 256 98 | expect { 99 | tag_map[key] = "mobile-1.0" 100 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 101 | end 102 | 103 | it "raise error if key contains non printable chars less then 32 ascii code" do 104 | key = "key#{[31].pack('c')}-test" 105 | expect { 106 | tag_map[key] = "mobile-1.0" 107 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 108 | end 109 | 110 | it "raise error if key contains non printable chars greater then 126 ascii code" do 111 | key = "key#{[127].pack('c')}-test" 112 | expect { 113 | tag_map[key] = "mobile-1.0" 114 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 115 | end 116 | end 117 | 118 | describe "tag value validation" do 119 | let(:tag_map) { OpenCensus::Tags::TagMap.new } 120 | 121 | it "raise error if value length more then 255 chars" do 122 | value = "v" * 256 123 | expect { 124 | tag_map["frontend"] = value 125 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 126 | end 127 | 128 | it "raise error if value contains non printable chars less then 32 ascii code" do 129 | value = "value#{[31].pack('c')}-test" 130 | expect { 131 | tag_map["frontend"] = value 132 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 133 | end 134 | 135 | it "raise error if value contains non printable chars greater then 126 ascii code" do 136 | value = "value#{[127].pack('c')}-test" 137 | expect { 138 | tag_map["frontend"] = value 139 | }.must_raise OpenCensus::Tags::TagMap::InvalidTagError 140 | end 141 | end 142 | end 143 | 144 | it "delete" do 145 | tag_map = OpenCensus::Tags::TagMap.new({ "frontend" => "mobile-1.0"}) 146 | 147 | tag_map.delete "frontend" 148 | tag_map["frontend"].must_be_nil 149 | tag_map.length.must_equal 0 150 | end 151 | 152 | describe "binary formatter" do 153 | it "serialize tag map to binary format" do 154 | tag_map = OpenCensus::Tags::TagMap.new({ 155 | "key1" => "val1", 156 | "key2" => "val2" 157 | }) 158 | 159 | expected_binary = "\x00\x00\x04key1\x04val1\x00\x04key2\x04val2" 160 | tag_map.to_binary.must_equal expected_binary 161 | end 162 | 163 | it "deserialize binary format and create tag map" do 164 | binary = "\x00\x00\x04key1\x04val1\x00\x04key2\x04val2" 165 | 166 | tag_map = OpenCensus::Tags::TagMap.from_binary binary 167 | expected_value = { 168 | "key1" => "val1", 169 | "key2" => "val2" 170 | } 171 | tag_map.to_h.must_equal expected_value 172 | end 173 | end 174 | end 175 | --------------------------------------------------------------------------------