├── .circleci └── config.yml ├── .dir-locals.el ├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── doc ├── Makefile ├── README.md ├── build.sh ├── edoc-info ├── erlang.png ├── github-pandoc.css ├── index.md ├── logo-sm.svg ├── oc_producer.md ├── oc_producer_registry.md ├── oc_reporter.md ├── oc_reporter_noop.md ├── oc_reporter_sequential.md ├── oc_reporter_stdout.md ├── oc_reporter_zipkin.md ├── oc_sampler.md ├── oc_sampler_always.md ├── oc_sampler_impl.md ├── oc_sampler_never.md ├── oc_sampler_period_or_count.md ├── oc_sampler_probability.md ├── oc_self_producer.md ├── oc_server.md ├── oc_span.md ├── oc_span_ctx_binary.md ├── oc_span_ctx_header.md ├── oc_span_sweeper.md ├── oc_span_transform.md ├── oc_stat.md ├── oc_stat_aggregation.md ├── oc_stat_aggregation_count.md ├── oc_stat_aggregation_distribution.md ├── oc_stat_aggregation_latest.md ├── oc_stat_aggregation_sum.md ├── oc_stat_config.md ├── oc_stat_exporter.md ├── oc_stat_exporter_prometheus.md ├── oc_stat_exporter_stdout.md ├── oc_stat_measure.md ├── oc_stat_transform.md ├── oc_stat_unit.md ├── oc_stat_view.md ├── oc_std_encoder.md ├── oc_tag_ctx_binary.md ├── oc_tag_ctx_header.md ├── oc_tags.md ├── oc_trace.md ├── oc_trace_pb.md ├── oc_transform.md ├── ocp.md ├── opencensus.md ├── opencensus_app.md ├── opencensus_sup.md ├── overview.edoc ├── span.md ├── stylesheet.css └── tpl.html ├── elvis.config ├── examples └── helloworld │ ├── README.md │ ├── config │ └── sys.config │ ├── rebar.config │ └── src │ ├── helloworld.app.src │ ├── helloworld.erl │ ├── helloworld_app.erl │ └── helloworld_sup.erl ├── include ├── oc_metrics.hrl └── opencensus.hrl ├── rebar.config ├── rebar.lock ├── src ├── oc_logger.hrl ├── oc_producer.erl ├── oc_producer_registry.erl ├── oc_propagation_binary.erl ├── oc_propagation_http_b3.erl ├── oc_propagation_http_tracecontext.erl ├── oc_reporter.erl ├── oc_reporter_pid.erl ├── oc_reporter_stdout.erl ├── oc_sampler.erl ├── oc_sampler_always.erl ├── oc_sampler_impl.erl ├── oc_sampler_never.erl ├── oc_sampler_period_or_count.erl ├── oc_sampler_probability.erl ├── oc_self_producer.erl ├── oc_server.erl ├── oc_span.erl ├── oc_span_sweeper.erl ├── oc_span_transform.erl ├── oc_stat.erl ├── oc_stat_aggregation.erl ├── oc_stat_aggregation_count.erl ├── oc_stat_aggregation_distribution.erl ├── oc_stat_aggregation_latest.erl ├── oc_stat_aggregation_sum.erl ├── oc_stat_config.erl ├── oc_stat_exporter.erl ├── oc_stat_exporter_stdout.erl ├── oc_stat_measure.erl ├── oc_stat_transform.erl ├── oc_stat_unit.erl ├── oc_stat_view.erl ├── oc_tag_ctx_binary.erl ├── oc_tag_ctx_header.erl ├── oc_tags.erl ├── oc_trace.erl ├── oc_tracestate.erl ├── ocp.erl ├── opencensus.app.src ├── opencensus.erl ├── opencensus_app.erl └── opencensus_sup.erl └── test ├── oc_metrics_SUITE.erl ├── oc_reporters_SUITE.erl ├── oc_sampler_SUITE.erl ├── oc_sampler_period_or_count_SUITE.erl ├── oc_span_SUITE.erl ├── oc_span_ctx_SUITE.erl ├── oc_stat_SUITE.erl ├── oc_stat_aggregation_SUITE.erl ├── oc_stat_aggregation_pid.erl ├── oc_stat_exporter_pid.erl ├── oc_stat_pt_user.erl ├── oc_stat_unit_SUITE.erl ├── oc_sweeper_SUITE.erl ├── oc_tab_reporter.erl ├── oc_tags_SUITE.erl ├── oc_test_utils.hrl ├── oc_transform_SUITE.erl ├── ocp_SUITE.erl ├── opencensus_SUITE.erl └── prop_period_or_count.erl /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | rebar3: tsloughter/rebar3@0.6.3 5 | codecov: codecov/codecov@1.0.4 6 | 7 | jobs: 8 | build_and_test: 9 | parameters: 10 | tag: 11 | description: The Erlang/OTP docker image tag to use 12 | type: string 13 | codecov_flag: 14 | description: String the coverage reports are grouped by 15 | type: string 16 | executor: 17 | name: rebar3/erlang 18 | tag: <> 19 | steps: 20 | - checkout 21 | - rebar3/with_deps_cache: 22 | cache_key_postfix: -<> 23 | steps: 24 | - rebar3/compile 25 | - rebar3/dialyzer 26 | - run: rebar3 as test xref 27 | - rebar3/ct 28 | 29 | - store_test_results: 30 | path: ~/project/_build/test/logs/ 31 | - store_artifacts: 32 | path: ~/project/_build/test/logs 33 | destination: common_test 34 | 35 | - rebar3/cover 36 | - run: rebar3 as test covertool generate 37 | - codecov/upload: 38 | file: _build/test/covertool/opencensus.covertool.xml 39 | flags: <> 40 | 41 | workflows: 42 | run_all: 43 | jobs: 44 | - build_and_test: 45 | name: "otp-21" 46 | tag: "21.2" 47 | codecov_flag: "otp21" 48 | - build_and_test: 49 | name: "otp-20" 50 | tag: "20" 51 | codecov_flag: "otp20" 52 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((erlang-mode . ((erlang-indent-level . 4)))) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/*.html 2 | !doc/tpl.html 3 | priv/opencensus-proto 4 | .rebar3 5 | _* 6 | .eunit 7 | *.o 8 | *.beam 9 | *.plt 10 | *.swp 11 | *.swo 12 | .erlang.cookie 13 | ebin 14 | log 15 | erl_crash.dump 16 | .rebar 17 | logs 18 | _build 19 | .idea 20 | *.iml 21 | rebar3.crashdump 22 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Google Inc. 2 | Tristan Sloughter 3 | Ilya Khaprov -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | SOURCE_DOCS := $(wildcard *.md) 2 | 3 | EXPORTED_DOCS=\ 4 | $(SOURCE_DOCS:.md=.html) 5 | 6 | RM=/bin/rm 7 | 8 | PANDOC=pandoc 9 | 10 | PANDOC_HTML_OPTIONS=--standalone --highlight-style=tango --template tpl.html -f gfm --to html5 11 | 12 | %.html : %.md 13 | $(PANDOC) $(PANDOC_HTML_OPTIONS) -o $@ $< 14 | 15 | 16 | .PHONY: all clean 17 | 18 | all : $(EXPORTED_DOCS) 19 | 20 | clean: 21 | - $(RM) -f $(EXPORTED_DOCS) 22 | -------------------------------------------------------------------------------- /doc/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $(realpath $0)) 4 | 5 | cp ../README.md index.md 6 | 7 | make clean 8 | make 9 | 10 | # fix internal doc links 11 | sed -i 's/https:\/\/github\.com\/doc\/\(.*\)\.md/\1.html/g' *.html 12 | sed -i 's/\"\([a-zA-Z_-]*\)\.md\([a-zA-Z_-#]*\)\"/\"\1.html\2\"/g' *.html 13 | sed -i 's/\"doc\/\([a-zA-Z_-]*\)\.md\"/\"\1.html\"/g' *.html 14 | sed -i 's/\"\([a-zA-Z_-]*\)\.md\"/\"\1.html\"/g' *.html 15 | sed -i 's/
//g' *.html 16 | 17 | # sed -i 's/\"\(.*\)\.md\"/\"\1.html\"/g' *.html 18 | # sed -i 's/\"\(.*\)\.md#\(.*\)\"/\"\1.html#\2\"/g' *.html 19 | 20 | # fix external doc links 21 | sed -i 's/maps\.html\#/http:\/\/erlang.org\/doc\/man\/maps\.html#/g' *.html 22 | sed -i 's/unicode\.html\#/http:\/\/erlang.org\/doc\/man\/unicode\.html#/g' *.html 23 | sed -i 's/maps\.md\#/http:\/\/erlang.org\/doc\/man\/maps\.html#/g' *.html 24 | sed -i 's/unicode\.md\#/http:\/\/erlang.org\/doc\/man\/unicode\.html#/g' *.html 25 | 26 | # cleans up the indentation of code blocks 27 | sed -i 's/ 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /doc/oc_producer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_producer # 4 | * [Description](#description) 5 | 6 | Producer is a source of metrics. 7 | 8 | __This module defines the `oc_producer` behaviour.__
Required callback functions: `read/2`. 9 | 10 |
11 | 12 | ## Description ## 13 | 14 | Callbacks: 15 | 16 | - `read(Registry, Callback) -> ok` - called by exporters. 17 | Read should call `Callback` with the current values of all metrics 18 | supported by this metric provider. 19 | The metrics should be unique for each combination of name and resource. 20 | 21 | - `cleanup(Registry) -> ok` - optional. 22 | Called when producer removed from `Registry` 23 | -------------------------------------------------------------------------------- /doc/oc_producer_registry.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_producer_registry # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Registry maintains a set of metric producers for exporting. 9 | 10 | 11 | 12 | ## Description ## 13 | Most users will rely on the DefaultRegistry. 14 | 15 | ## Function Index ## 16 | 17 | 18 |
add_producer/1
add_producer/2 19 | Adds Producer to the Registry.
read_all/1Equivalent to read_all(default).
read_all/2 20 | Calls Callback for each metric produced by the metric producers in the Registry.
read_to_list/0
read_to_list/1
remove_producer/1Equivalent to remove_producer(default, Producer).
remove_producer/2
21 | 22 | 23 | 24 | 25 | ## Function Details ## 26 | 27 | 28 | 29 | ### add_producer/1 ### 30 | 31 | `add_producer(Producer) -> any()` 32 | 33 | 34 | 35 | ### add_producer/2 ### 36 | 37 | `add_producer(Registry, Producer) -> any()` 38 | 39 | Adds `Producer` to the `Registry`. 40 | 41 | 42 | 43 | ### read_all/1 ### 44 | 45 | `read_all(Callback) -> any()` 46 | 47 | Equivalent to [`read_all(default)`](#read_all-1). 48 | 49 | 50 | 51 | ### read_all/2 ### 52 | 53 | `read_all(Registry, Callback) -> any()` 54 | 55 | Calls `Callback` for each metric produced by the metric producers in the `Registry`. 56 | 57 | 58 | 59 | ### read_to_list/0 ### 60 | 61 | `read_to_list() -> any()` 62 | 63 | 64 | 65 | ### read_to_list/1 ### 66 | 67 | `read_to_list(Registry) -> any()` 68 | 69 | 70 | 71 | ### remove_producer/1 ### 72 | 73 | `remove_producer(Producer) -> any()` 74 | 75 | Equivalent to [`remove_producer(default, Producer)`](#remove_producer-2). 76 | 77 | 78 | 79 | ### remove_producer/2 ### 80 | 81 | `remove_producer(Registry, Producer) -> any()` 82 | 83 | -------------------------------------------------------------------------------- /doc/oc_reporter.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_reporter # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | This module has the behaviour that each reporter must implement 9 | and creates the buffer of trace spans to be reported. 10 | 11 | __Behaviours:__ [`gen_server`](gen_server.md). 12 | 13 | __This module defines the `oc_reporter` behaviour.__
Required callback functions: `init/1`, `report/2`. 14 | 15 | 16 | 17 | ## Function Index ## 18 | 19 | 20 |
code_change/3
handle_call/3
handle_cast/2
handle_info/2
init/1
start_link/0
store_span/1
terminate/2
21 | 22 | 23 | 24 | 25 | ## Function Details ## 26 | 27 | 28 | 29 | ### code_change/3 ### 30 | 31 | `code_change(X1, State, X3) -> any()` 32 | 33 | 34 | 35 | ### handle_call/3 ### 36 | 37 | `handle_call(X1, From, State) -> any()` 38 | 39 | 40 | 41 | ### handle_cast/2 ### 42 | 43 | `handle_cast(X1, State) -> any()` 44 | 45 | 46 | 47 | ### handle_info/2 ### 48 | 49 | `handle_info(X1, State) -> any()` 50 | 51 | 52 | 53 | ### init/1 ### 54 | 55 | `init(Args) -> any()` 56 | 57 | 58 | 59 | ### start_link/0 ### 60 | 61 | `start_link() -> any()` 62 | 63 | 64 | 65 | ### store_span/1 ### 66 | 67 |

68 | store_span(Span::opencensus:span()) -> true | {error, invalid_span} | {error, no_report_buffer}
69 | 
70 |
71 | 72 | 73 | 74 | ### terminate/2 ### 75 | 76 | `terminate(X1, State) -> any()` 77 | 78 | -------------------------------------------------------------------------------- /doc/oc_reporter_noop.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_reporter_noop # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | A no-op reporter that does nothing. 9 | 10 | __Behaviours:__ [`oc_reporter`](oc_reporter.md). 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
init/1
report/2
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### init/1 ### 27 | 28 | `init(Opts) -> any()` 29 | 30 | 31 | 32 | ### report/2 ### 33 | 34 | `report(Spans, Opts) -> any()` 35 | 36 | -------------------------------------------------------------------------------- /doc/oc_reporter_sequential.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_reporter_sequential # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | This module allows sequential execution of multiple reporters. 10 | 11 | 12 | 13 | ## Data Types ## 14 | 15 | 16 | 17 | 18 | ### opts() ### 19 | 20 | 21 |

22 | opts() = [{reporter(), reporter_opts()}]
23 | 
24 | 25 | 26 | 27 | 28 | ### reporter() ### 29 | 30 | 31 |

32 | reporter() = atom()
33 | 
34 | 35 | 36 | 37 | 38 | ### reporter_opts() ### 39 | 40 | 41 |

42 | reporter_opts() = term()
43 | 
44 | 45 | 46 | 47 | ## Function Index ## 48 | 49 | 50 |
init/1
report/2
51 | 52 | 53 | 54 | 55 | ## Function Details ## 56 | 57 | 58 | 59 | ### init/1 ### 60 | 61 |

62 | init(Config::[{reporter(), reporter_opts()}]) -> opts()
63 | 
64 |
65 | 66 | 67 | 68 | ### report/2 ### 69 | 70 |

71 | report(Spans::[opencensus:spans(), ...], Config::opts()) -> ok
72 | 
73 |
74 | 75 | -------------------------------------------------------------------------------- /doc/oc_reporter_stdout.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_reporter_stdout # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
init/1
report/2
13 | 14 | 15 | 16 | 17 | ## Function Details ## 18 | 19 | 20 | 21 | ### init/1 ### 22 | 23 | `init(X1) -> any()` 24 | 25 | 26 | 27 | ### report/2 ### 28 | 29 | `report(Spans, X2) -> any()` 30 | 31 | -------------------------------------------------------------------------------- /doc/oc_reporter_zipkin.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_reporter_zipkin # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Exports spans to Zipkin. 9 | 10 | __Behaviours:__ [`oc_reporter`](oc_reporter.md). 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
init/1
report/2
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### init/1 ### 27 | 28 | `init(Opts) -> any()` 29 | 30 | 31 | 32 | ### report/2 ### 33 | 34 | `report(Spans, X2) -> any()` 35 | 36 | -------------------------------------------------------------------------------- /doc/oc_sampler.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_sampler # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Behaviour each sampler must implement. 9 | 10 | __This module defines the `oc_sampler` behaviour.__
Required callback functions: `init/1`, `should_sample/4`. 11 | 12 | 13 | 14 | ## Description ## 15 | The function `should_trace` 16 | is given the trace id of the current trace, if one exists, the span id 17 | of what would be the parent of any new span in this trace and a boolean 18 | for whether the trace was enabled in the process that propagated this 19 | context. 20 | 21 | ## Function Index ## 22 | 23 | 24 |
init/1Called at the start of a trace.
should_sample/3
25 | 26 | 27 | 28 | 29 | ## Function Details ## 30 | 31 | 32 | 33 | ### init/1 ### 34 | 35 | `init(X1) -> any()` 36 | 37 | Called at the start of a trace. 38 | 39 | 40 | 41 | ### should_sample/3 ### 42 | 43 | `should_sample(TraceId, SpanId, Enabled) -> any()` 44 | 45 | -------------------------------------------------------------------------------- /doc/oc_sampler_always.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_sampler_always # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | A sampler that always returns true. 9 | 10 | __Behaviours:__ [`oc_sampler`](oc_sampler.md). 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
init/1
should_sample/4
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### init/1 ### 27 | 28 | `init(X1) -> any()` 29 | 30 | 31 | 32 | ### should_sample/4 ### 33 | 34 | `should_sample(X1, X2, X3, X4) -> any()` 35 | 36 | -------------------------------------------------------------------------------- /doc/oc_sampler_impl.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_sampler_impl # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
should_sample/3
13 | 14 | 15 | 16 | 17 | ## Function Details ## 18 | 19 | 20 | 21 | ### should_sample/3 ### 22 | 23 | `should_sample(TraceId, SpanId, Enabled) -> any()` 24 | 25 | -------------------------------------------------------------------------------- /doc/oc_sampler_never.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_sampler_never # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | A sampler that always returns false. 9 | 10 | __Behaviours:__ [`oc_sampler`](oc_sampler.md). 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
init/1
should_sample/4
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### init/1 ### 27 | 28 | `init(X1) -> any()` 29 | 30 | 31 | 32 | ### should_sample/4 ### 33 | 34 | `should_sample(X1, X2, X3, X4) -> any()` 35 | 36 | -------------------------------------------------------------------------------- /doc/oc_sampler_period_or_count.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_sampler_period_or_count # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | This sampler makes sure you will have at least 1 sample during `period` 9 | or `1/count` samples otherwise. 10 | 11 | __Behaviours:__ [`oc_sampler`](oc_sampler.md). 12 | 13 | 14 | 15 | ## Function Index ## 16 | 17 | 18 |
init/1
should_sample/4
19 | 20 | 21 | 22 | 23 | ## Function Details ## 24 | 25 | 26 | 27 | ### init/1 ### 28 | 29 | `init(Opts) -> any()` 30 | 31 | 32 | 33 | ### should_sample/4 ### 34 | 35 | `should_sample(TraceId, X2, X3, X4) -> any()` 36 | 37 | -------------------------------------------------------------------------------- /doc/oc_sampler_probability.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_sampler_probability # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | This sampler assumes the lower 64 bits of the trace id are 9 | randomly distributed around the whole (long) range. 10 | 11 | __Behaviours:__ [`oc_sampler`](oc_sampler.md). 12 | 13 | 14 | 15 | ## Description ## 16 | The sampler creates 17 | an upper bound id based on the configured probability and compares the 18 | lower 64 bits of the trace id to for the sampling decision. 19 | 20 | ## Function Index ## 21 | 22 | 23 |
init/1
should_sample/4
24 | 25 | 26 | 27 | 28 | ## Function Details ## 29 | 30 | 31 | 32 | ### init/1 ### 33 | 34 | `init(Opts) -> any()` 35 | 36 | 37 | 38 | ### should_sample/4 ### 39 | 40 | `should_sample(TraceId, X2, X3, IdUpperBound) -> any()` 41 | 42 | -------------------------------------------------------------------------------- /doc/oc_self_producer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_self_producer # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Produces metrics for internal state like spans queue size, views count, etc. 9 | 10 | 11 | 12 | ## Function Index ## 13 | 14 | 15 |
read/2
16 | 17 | 18 | 19 | 20 | ## Function Details ## 21 | 22 | 23 | 24 | ### read/2 ### 25 | 26 | `read(Registry, Callback) -> any()` 27 | 28 | -------------------------------------------------------------------------------- /doc/oc_server.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_server # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Server with no logic, simply owns the span ets table. 9 | 10 | 11 | 12 | ## Function Index ## 13 | 14 | 15 |
handle_call/3
handle_cast/2
init/1
start_link/0
16 | 17 | 18 | 19 | 20 | ## Function Details ## 21 | 22 | 23 | 24 | ### handle_call/3 ### 25 | 26 | `handle_call(X1, From, State) -> any()` 27 | 28 | 29 | 30 | ### handle_cast/2 ### 31 | 32 | `handle_cast(X1, State) -> any()` 33 | 34 | 35 | 36 | ### init/1 ### 37 | 38 | `init(X1) -> any()` 39 | 40 | 41 | 42 | ### start_link/0 ### 43 | 44 | `start_link() -> any()` 45 | 46 | -------------------------------------------------------------------------------- /doc/oc_span_ctx_binary.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_span_ctx_binary # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Functions to support the binary format trace context serialization. 10 | 11 | 12 | 13 | ## Description ## 14 | Implements the spec found here 15 | [`https://github.com/census-instrumentation/opencensus-specs/blob/7b426409/encodings/BinaryEncoding.md`](https://github.com/census-instrumentation/opencensus-specs/blob/7b426409/encodings/BinaryEncoding.md) 16 | 17 | 18 | ## Data Types ## 19 | 20 | 21 | 22 | 23 | ### maybe() ### 24 | 25 | 26 |

27 | maybe(T) = T | undefined
28 | 
29 | 30 | 31 | 32 | ## Function Index ## 33 | 34 | 35 |
decode/1
encode/1
36 | 37 | 38 | 39 | 40 | ## Function Details ## 41 | 42 | 43 | 44 | ### decode/1 ### 45 | 46 |

47 | decode(X1::binary()) -> maybe(opencensus:span_ctx())
48 | 
49 |
50 | 51 | 52 | 53 | ### encode/1 ### 54 | 55 |

56 | encode(Span_ctx::opencensus:span_ctx()) -> maybe(binary())
57 | 
58 |
59 | 60 | -------------------------------------------------------------------------------- /doc/oc_span_ctx_header.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_span_ctx_header # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Functions to support the http header format of the tracecontext spec 10 | Implements the spec found here. 11 | 12 | 13 | 14 | ## Data Types ## 15 | 16 | 17 | 18 | 19 | ### maybe() ### 20 | 21 | 22 |

23 | maybe(T) = T | undefined
24 | 
25 | 26 | 27 | 28 | ## Function Index ## 29 | 30 | 31 |
decode/1
encode/1
field_name/0
32 | 33 | 34 | 35 | 36 | ## Function Details ## 37 | 38 | 39 | 40 | ### decode/1 ### 41 | 42 |

43 | decode(TraceContext::iodata()) -> maybe(opencensus:span_ctx())
44 | 
45 |
46 | 47 | 48 | 49 | ### encode/1 ### 50 | 51 |

52 | encode(Span_ctx::opencensus:span_ctx()) -> maybe(iolist())
53 | 
54 |
55 | 56 | 57 | 58 | ### field_name/0 ### 59 | 60 | `field_name() -> any()` 61 | 62 | -------------------------------------------------------------------------------- /doc/oc_span_sweeper.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_span_sweeper # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`gen_statem`](gen_statem.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
callback_mode/0
code_change/4
handle_event/4
init/1
start_link/0
storage_size/0
terminate/3
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### callback_mode/0 ### 24 | 25 | `callback_mode() -> any()` 26 | 27 | 28 | 29 | ### code_change/4 ### 30 | 31 | `code_change(X1, State, Data, X4) -> any()` 32 | 33 | 34 | 35 | ### handle_event/4 ### 36 | 37 | `handle_event(X1, X2, X3, Data) -> any()` 38 | 39 | 40 | 41 | ### init/1 ### 42 | 43 | `init(X1) -> any()` 44 | 45 | 46 | 47 | ### start_link/0 ### 48 | 49 | `start_link() -> any()` 50 | 51 | 52 | 53 | ### storage_size/0 ### 54 | 55 | `storage_size() -> any()` 56 | 57 | 58 | 59 | ### terminate/3 ### 60 | 61 | `terminate(Reason, State, Data) -> any()` 62 | 63 | -------------------------------------------------------------------------------- /doc/oc_span_transform.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_span_transform # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | oc_transform provides a parse transform for wrapping a function in 9 | a start and a finish of a span. 10 | 11 | 12 | 13 | ## Function Index ## 14 | 15 | 16 |
format_error/1
parse_transform/2
17 | 18 | 19 | 20 | 21 | ## Function Details ## 22 | 23 | 24 | 25 | ### format_error/1 ### 26 | 27 | `format_error(X1) -> any()` 28 | 29 | 30 | 31 | ### parse_transform/2 ### 32 | 33 | `parse_transform(Ast, Options) -> any()` 34 | 35 | -------------------------------------------------------------------------------- /doc/oc_stat.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | OpenCensus Stats package. 9 | 10 | 11 | 12 | ## Function Index ## 13 | 14 | 15 |
export/0Exports view_data of all subscribed views.
record/2 16 | Records multiple measurements at once.
record/3 17 | Records one or multiple measurements with the same tags at once.
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### export/0 ### 27 | 28 |

29 | export() -> [oc_stat_view:view_data()]
30 | 
31 |
32 | 33 | Exports view_data of all subscribed views 34 | 35 | 36 | 37 | ### record/2 ### 38 | 39 |

40 | record(Tags::ctx:t() | oc_tags:tags(), Measures::[{oc_stat_measure:name(), number()}]) -> ok
41 | 
42 |
43 | 44 | Records multiple measurements at once. 45 | 46 | Can be optimized with `oc_stat_measure` parse transform. 47 | 48 | Raises `{unknown_measure, MeasureName}` if measure doesn't exist. 49 | 50 | 51 | 52 | ### record/3 ### 53 | 54 |

55 | record(Tags::ctx:t() | oc_tags:tags(), MeasureName::oc_stat_measure:name(), Value::number()) -> ok
56 | 
57 |
58 | 59 | Records one or multiple measurements with the same tags at once. 60 | If there are any tags in the context, measurements will be tagged with them. 61 | 62 | Can be optimized with `oc_stat_measure` parse transform. 63 | 64 | Raises `{unknown_measure, MeasureName}` if measure doesn't exist. 65 | 66 | -------------------------------------------------------------------------------- /doc/oc_stat_aggregation.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_aggregation # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | 7 | Aggregation represents a data aggregation method. 8 | 9 | __This module defines the `oc_stat_aggregation` behaviour.__
Required callback functions: `init/3`, `type/0`, `add_sample/4`, `export/2`, `clear_rows/2`. 10 | 11 | 12 | 13 | ## Data Types ## 14 | 15 | 16 | 17 | 18 | ### data() ### 19 | 20 | 21 |

22 | data() = data(latest, number()) | data(count, number()) | data(sum, #{count => non_neg_integer(), mean => number(), sum => number()}) | data(distribution, #{count => non_neg_integer(), mean => number(), sum => number(), buckets => [{number(), non_neg_integer()}]})
23 | 
24 | 25 | 26 | 27 | 28 | ### data() ### 29 | 30 | 31 |

32 | data(Type, AggregationValue) = #{type => Type, rows => data_rows(AggregationValue)}
33 | 
34 | 35 | 36 | 37 | 38 | ### data_rows() ### 39 | 40 | 41 |

42 | data_rows(AggregationValue) = [#{tags => tv(), value => AggregationValue}]
43 | 
44 | 45 | 46 | 47 | 48 | ### tv() ### 49 | 50 | 51 |

52 | tv() = [oc_tags:value()]
53 | 
54 | 55 | -------------------------------------------------------------------------------- /doc/oc_stat_aggregation_count.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_aggregation_count # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Count indicates that data collected and aggregated 9 | with this method will be turned into a count value. 10 | 11 | 12 | 13 | ## Description ## 14 | For example, total number of accepted requests can be 15 | aggregated by using Count. 16 | 17 | ## Function Index ## 18 | 19 | 20 |
add_sample/4
clear_rows/2
export/2
init/3
type/0
21 | 22 | 23 | 24 | 25 | ## Function Details ## 26 | 27 | 28 | 29 | ### add_sample/4 ### 30 | 31 |

32 | add_sample(Name::oc_stat_view:name(), Tags::oc_tags:tags(), Value::number(), Options::any()) -> ok
33 | 
34 |
35 | 36 | 37 | 38 | ### clear_rows/2 ### 39 | 40 | `clear_rows(Name, Options) -> any()` 41 | 42 | 43 | 44 | ### export/2 ### 45 | 46 | `export(Name, Options) -> any()` 47 | 48 | 49 | 50 | ### init/3 ### 51 | 52 | `init(Name, Keys, Options) -> any()` 53 | 54 | 55 | 56 | ### type/0 ### 57 | 58 | `type() -> any()` 59 | 60 | -------------------------------------------------------------------------------- /doc/oc_stat_aggregation_distribution.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_aggregation_distribution # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Distribution indicates that the desired aggregation is 9 | a histogram distribution. 10 | 11 | 12 | 13 | ## Function Index ## 14 | 15 | 16 |
add_sample/4
clear_rows/2
export/2
init/3
type/0
17 | 18 | 19 | 20 | 21 | ## Function Details ## 22 | 23 | 24 | 25 | ### add_sample/4 ### 26 | 27 |

28 | add_sample(Name::oc_stat_view:name(), Tags::oc_tags:tags(), Value::number(), Buckets::any()) -> ok
29 | 
30 |
31 | 32 | 33 | 34 | ### clear_rows/2 ### 35 | 36 | `clear_rows(Name, Options) -> any()` 37 | 38 | 39 | 40 | ### export/2 ### 41 | 42 | `export(Name, Buckets) -> any()` 43 | 44 | 45 | 46 | ### init/3 ### 47 | 48 | `init(Name, Keys, Options) -> any()` 49 | 50 | 51 | 52 | ### type/0 ### 53 | 54 | `type() -> any()` 55 | 56 | -------------------------------------------------------------------------------- /doc/oc_stat_aggregation_latest.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_aggregation_latest # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Latest indicates that only last data point is saved. 9 | 10 | 11 | 12 | ## Function Index ## 13 | 14 | 15 |
add_sample/4
clear_rows/2
export/2
init/3
type/0
16 | 17 | 18 | 19 | 20 | ## Function Details ## 21 | 22 | 23 | 24 | ### add_sample/4 ### 25 | 26 |

27 | add_sample(Name::oc_stat_view:name(), Tags::oc_tags:tags(), Value::number(), Options::any()) -> ok
28 | 
29 |
30 | 31 | 32 | 33 | ### clear_rows/2 ### 34 | 35 | `clear_rows(Name, Options) -> any()` 36 | 37 | 38 | 39 | ### export/2 ### 40 | 41 | `export(Name, Options) -> any()` 42 | 43 | 44 | 45 | ### init/3 ### 46 | 47 | `init(Name, Keys, Options) -> any()` 48 | 49 | 50 | 51 | ### type/0 ### 52 | 53 | `type() -> any()` 54 | 55 | -------------------------------------------------------------------------------- /doc/oc_stat_aggregation_sum.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_aggregation_sum # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Sum indicates that data collected and aggregated 9 | with this method will be summed up. 10 | 11 | 12 | 13 | ## Description ## 14 | For example, accumulated request bytes can be aggregated by using 15 | Sum. 16 | 17 | ## Function Index ## 18 | 19 | 20 |
add_sample/4
clear_rows/2
export/2
init/3
type/0
21 | 22 | 23 | 24 | 25 | ## Function Details ## 26 | 27 | 28 | 29 | ### add_sample/4 ### 30 | 31 |

32 | add_sample(Name::oc_stat_view:name(), Tags::oc_tags:tags(), Value::number(), Options::any()) -> ok
33 | 
34 |
35 | 36 | 37 | 38 | ### clear_rows/2 ### 39 | 40 | `clear_rows(Name, Options) -> any()` 41 | 42 | 43 | 44 | ### export/2 ### 45 | 46 | `export(Name, Options) -> any()` 47 | 48 | 49 | 50 | ### init/3 ### 51 | 52 | `init(Name, Keys, Options) -> any()` 53 | 54 | 55 | 56 | ### type/0 ### 57 | 58 | `type() -> any()` 59 | 60 | -------------------------------------------------------------------------------- /doc/oc_stat_config.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_config # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | stats configuration. 9 | 10 | 11 | 12 | ## Function Index ## 13 | 14 | 15 |
export_interval/0
exporters/0
views/0
16 | 17 | 18 | 19 | 20 | ## Function Details ## 21 | 22 | 23 | 24 | ### export_interval/0 ### 25 | 26 | `export_interval() -> any()` 27 | 28 | 29 | 30 | ### exporters/0 ### 31 | 32 | `exporters() -> any()` 33 | 34 | 35 | 36 | ### views/0 ### 37 | 38 | `views() -> any()` 39 | 40 | -------------------------------------------------------------------------------- /doc/oc_stat_exporter.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_exporter # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Exporter exports the collected records as view data. 10 | 11 | __This module defines the `oc_stat_exporter` behaviour.__
Required callback functions: `export/2`. 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### exporter() ### 21 | 22 | 23 |

 24 | exporter() = module()
 25 | 
26 | 27 | 28 | 29 | ## Function Index ## 30 | 31 | 32 |
batch_register/1 33 | Register many Exporters at once.
code_change/3
deregister/1 34 | Deregisters an Exporter.
handle_call/3
handle_cast/2
handle_info/2
init/1
register/1Equivalent to register(Exporter, []).
register/2 35 | Registers an Exporter with Config.
registered/1 36 | Checks whether Exporter is registered.
start_link/0
terminate/2
37 | 38 | 39 | 40 | 41 | ## Function Details ## 42 | 43 | 44 | 45 | ### batch_register/1 ### 46 | 47 |

 48 | batch_register(Exporters) -> ok
 49 | 
50 | 51 | 52 | 53 | Register many `Exporters` at once. 54 | 55 | 56 | 57 | ### code_change/3 ### 58 | 59 | `code_change(OldVsn, State, Extra) -> any()` 60 | 61 | 62 | 63 | ### deregister/1 ### 64 | 65 |

 66 | deregister(Exporter) -> ok
 67 | 
68 | 69 | 70 | 71 | Deregisters an `Exporter`. 72 | 73 | 74 | 75 | ### handle_call/3 ### 76 | 77 | `handle_call(X1, From, State) -> any()` 78 | 79 | 80 | 81 | ### handle_cast/2 ### 82 | 83 | `handle_cast(X1, State) -> any()` 84 | 85 | 86 | 87 | ### handle_info/2 ### 88 | 89 | `handle_info(X1, State) -> any()` 90 | 91 | 92 | 93 | ### init/1 ### 94 | 95 | `init(Args) -> any()` 96 | 97 | 98 | 99 | ### register/1 ### 100 | 101 |

102 | register(Exporter) -> ok
103 | 
104 | 105 | 106 | 107 | Equivalent to [`register(Exporter, [])`](#register-2). 108 | 109 | 110 | 111 | ### register/2 ### 112 | 113 |

114 | register(Exporter, Config) -> ok
115 | 
116 | 117 | 118 | 119 | Registers an `Exporter` with `Config`. 120 | Collected data will be reported via all the 121 | registered exporters. Once you no longer 122 | want data to be exported, invoke [`deregister/1`](#deregister-1) 123 | with the previously registered exporter. 124 | 125 | 126 | 127 | ### registered/1 ### 128 | 129 |

130 | registered(Exporter) -> boolean()
131 | 
132 | 133 | 134 | 135 | Checks whether `Exporter` is registered. 136 | 137 | 138 | 139 | ### start_link/0 ### 140 | 141 | `start_link() -> any()` 142 | 143 | 144 | 145 | ### terminate/2 ### 146 | 147 | `terminate(X1, State) -> any()` 148 | 149 | -------------------------------------------------------------------------------- /doc/oc_stat_exporter_prometheus.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_exporter_prometheus # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`prometheus_collector`](prometheus_collector.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
collect_mf/2
deregister_cleanup/1
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### collect_mf/2 ### 24 | 25 | `collect_mf(Registry, Callback) -> any()` 26 | 27 | 28 | 29 | ### deregister_cleanup/1 ### 30 | 31 | `deregister_cleanup(Registry) -> any()` 32 | 33 | -------------------------------------------------------------------------------- /doc/oc_stat_exporter_stdout.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_exporter_stdout # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
export/2
13 | 14 | 15 | 16 | 17 | ## Function Details ## 18 | 19 | 20 | 21 | ### export/2 ### 22 | 23 | `export(ViewData, X2) -> any()` 24 | 25 | -------------------------------------------------------------------------------- /doc/oc_stat_measure.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_measure # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Measure represents a type of metric to be tracked and recorded. 10 | 11 | 12 | 13 | ## Description ## 14 | 15 | For example, latency, request Mb/s, and response Mb/s are measures 16 | to collect from a server. 17 | 18 | Measure is a generic interface for recording values in aggregations 19 | via subscribed views. 20 | When recording a value, we have to obtain the list of all subscribed views 21 | and call respective aggregations. We use code generation to optimize this. 22 | When a view subscribed or unsubscribed we regenerate unrolled loop in a 23 | special module (one for each measure). Module names generated from measurement 24 | names (1-to-1). If we know a measure name at the compile time, we can eliminate 25 | the module name lookup and inject remote call directly, replacing `oc_stat:record` 26 | with `:record`. 27 | For that {parse_transform, oc_stat_measure} option must be used. 28 | 29 | 30 | ## Data Types ## 31 | 32 | 33 | 34 | 35 | ### description() ### 36 | 37 | 38 |

 39 | description() = binary() | string()
 40 | 
41 | 42 | 43 | 44 | 45 | ### measure() ### 46 | 47 | 48 |

 49 | measure() = #measure{name = name(), module = module(), description = description(), unit = unit()}
 50 | 
51 | 52 | 53 | 54 | 55 | ### name() ### 56 | 57 | 58 |

 59 | name() = atom() | binary() | string()
 60 | 
61 | 62 | 63 | 64 | 65 | ### unit() ### 66 | 67 | 68 |

 69 | unit() = atom()
 70 | 
71 | 72 | 73 | 74 | ## Function Index ## 75 | 76 | 77 |
exists/1 78 | Returns a measure with the Name or false..
new/3 79 | Creates and registers a measure.
unit/1
80 | 81 | 82 | 83 | 84 | ## Function Details ## 85 | 86 | 87 | 88 | ### exists/1 ### 89 | 90 |

 91 | exists(Measure::name() | measure()) -> measure() | false
 92 | 
93 |
94 | 95 | Returns a measure with the `Name` or `false`.. 96 | 97 | 98 | 99 | ### new/3 ### 100 | 101 |

102 | new(Name::name(), Description::description(), Unit::unit()) -> oc_stat_view:measure()
103 | 
104 |
105 | 106 | Creates and registers a measure. If a measure with the same name 107 | already exists, old measure returned. 108 | 109 | 110 | 111 | ### unit/1 ### 112 | 113 |

114 | unit(Measure::measure()) -> unit()
115 | 
116 |
117 | 118 | -------------------------------------------------------------------------------- /doc/oc_stat_transform.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_transform # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
parse_transform/2 13 | oc_stat_transform is a parse transform that can detect oc_stat:record calls 14 | with constant measure names and generate remote measure module call from that.
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### parse_transform/2 ### 24 | 25 | `parse_transform(Forms, Options) -> any()` 26 | 27 | `oc_stat_transform` is a parse transform that can detect `oc_stat:record` calls 28 | with constant measure names and generate remote measure module call from that. 29 | At the run-time this means we don't have to do a lookup for the module name and 30 | if measure doesn't exist, `{unknown_measure, Name}` error will be thrown. 31 | 32 | -------------------------------------------------------------------------------- /doc/oc_stat_unit.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_stat_unit # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
convert/3
is_comparable/2
must_convert/1
13 | 14 | 15 | 16 | 17 | ## Function Details ## 18 | 19 | 20 | 21 | ### convert/3 ### 22 | 23 | `convert(Value, From, To) -> any()` 24 | 25 | 26 | 27 | ### is_comparable/2 ### 28 | 29 | `is_comparable(VUnit, MUnit) -> any()` 30 | 31 | 32 | 33 | ### must_convert/1 ### 34 | 35 | `must_convert(X1) -> any()` 36 | 37 | -------------------------------------------------------------------------------- /doc/oc_std_encoder.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_std_encoder # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
to_proto/1
to_std_records/1
13 | 14 | 15 | 16 | 17 | ## Function Details ## 18 | 19 | 20 | 21 | ### to_proto/1 ### 22 | 23 | `to_proto(Span) -> any()` 24 | 25 | 26 | 27 | ### to_std_records/1 ### 28 | 29 | `to_std_records(Message_event) -> any()` 30 | 31 | -------------------------------------------------------------------------------- /doc/oc_tag_ctx_binary.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_tag_ctx_binary # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | ## Function Index ## 10 | 11 | 12 |
decode/1
encode/1
format_error/1
13 | 14 | 15 | 16 | 17 | ## Function Details ## 18 | 19 | 20 | 21 | ### decode/1 ### 22 | 23 |

24 | decode(X1::binary()) -> {ok, oc_tags:tags()} | {error, any()}
25 | 
26 |
27 | 28 | 29 | 30 | ### encode/1 ### 31 | 32 |

33 | encode(TagContext::#{}) -> {ok, iolist()} | {error, any()}
34 | 
35 |
36 | 37 | 38 | 39 | ### format_error/1 ### 40 | 41 | `format_error(X1) -> any()` 42 | 43 | -------------------------------------------------------------------------------- /doc/oc_tag_ctx_header.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_tag_ctx_header # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | Functions to support sending tags over http as an http header. 10 | 11 | 12 | 13 | ## Data Types ## 14 | 15 | 16 | 17 | 18 | ### maybe() ### 19 | 20 | 21 |

22 | maybe(T) = T | undefined
23 | 
24 | 25 | 26 | 27 | ## Function Index ## 28 | 29 | 30 |
decode/1
encode/1
field_name/0
format_error/1
31 | 32 | 33 | 34 | 35 | ## Function Details ## 36 | 37 | 38 | 39 | ### decode/1 ### 40 | 41 |

42 | decode(Thing::iodata()) -> {ok, oc_tags:tags()} | {error, any()}
43 | 
44 |
45 | 46 | 47 | 48 | ### encode/1 ### 49 | 50 |

51 | encode(Tags::oc_tags:tags()) -> maybe(iodata())
52 | 
53 |
54 | 55 | 56 | 57 | ### field_name/0 ### 58 | 59 | `field_name() -> any()` 60 | 61 | 62 | 63 | ### format_error/1 ### 64 | 65 | `format_error(X1) -> any()` 66 | 67 | -------------------------------------------------------------------------------- /doc/oc_tags.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_tags # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | ## Data Types ## 11 | 12 | 13 | 14 | 15 | ### key() ### 16 | 17 | 18 |

 19 | key() = atom() | binary() | unicode:latin1_charlist()
 20 | 
21 | 22 | 23 | 24 | 25 | ### tags() ### 26 | 27 | 28 |

 29 | tags() = #{key() => value()}
 30 | 
31 | 32 | 33 | 34 | 35 | ### value() ### 36 | 37 | 38 |

 39 | value() = binary() | unicode:latin1_charlist()
 40 | 
41 | 42 | 43 | 44 | ## Function Index ## 45 | 46 | 47 |
format_error/1
from_ctx/1
new/0
new/1
new/2
new_ctx/2
put/3
to_map/1
update/2
verify_key/1
verify_value/1
48 | 49 | 50 | 51 | 52 | ## Function Details ## 53 | 54 | 55 | 56 | ### format_error/1 ### 57 | 58 | `format_error(X1) -> any()` 59 | 60 | 61 | 62 | ### from_ctx/1 ### 63 | 64 |

 65 | from_ctx(Ctx::ctx:t()) -> tags()
 66 | 
67 |
68 | 69 | 70 | 71 | ### new/0 ### 72 | 73 |

 74 | new() -> tags()
 75 | 
76 |
77 | 78 | 79 | 80 | ### new/1 ### 81 | 82 |

 83 | new(Map::maps:map()) -> tags()
 84 | 
85 |
86 | 87 | 88 | 89 | ### new/2 ### 90 | 91 |

 92 | new(Ctx::ctx:t(), Map::maps:map()) -> ctx:t()
 93 | 
94 |
95 | 96 | 97 | 98 | ### new_ctx/2 ### 99 | 100 |

101 | new_ctx(Ctx::ctx:t(), Tags::tags()) -> ctx:t()
102 | 
103 |
104 | 105 | 106 | 107 | ### put/3 ### 108 | 109 |

110 | put(Key::key(), Value::value(), Tags::tags()) -> {ok, tags()} | {error, any()}
111 | 
112 |
113 | 114 | 115 | 116 | ### to_map/1 ### 117 | 118 |

119 | to_map(Tags::tags()) -> maps:map()
120 | 
121 |
122 | 123 | 124 | 125 | ### update/2 ### 126 | 127 |

128 | update(Tags::tags(), Map::maps:map()) -> tags()
129 | 
130 |
131 | 132 | 133 | 134 | ### verify_key/1 ### 135 | 136 | `verify_key(Key) -> any()` 137 | 138 | 139 | 140 | ### verify_value/1 ### 141 | 142 | `verify_value(Value) -> any()` 143 | 144 | -------------------------------------------------------------------------------- /doc/oc_transform.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module oc_transform # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | oc_transform provides a parse transform for wrapping a function in 9 | a start and a finish of a span. 10 | 11 | 12 | 13 | ## Function Index ## 14 | 15 | 16 |
format_error/1
parse_transform/2
17 | 18 | 19 | 20 | 21 | ## Function Details ## 22 | 23 | 24 | 25 | ### format_error/1 ### 26 | 27 | `format_error(X1) -> any()` 28 | 29 | 30 | 31 | ### parse_transform/2 ### 32 | 33 | `parse_transform(Ast, Options) -> any()` 34 | 35 | -------------------------------------------------------------------------------- /doc/opencensus_app.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module opencensus_app # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | opencensus application. 9 | 10 | __Behaviours:__ [`application`](application.md). 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
start/2
stop/1
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### start/2 ### 27 | 28 | `start(StartType, StartArgs) -> any()` 29 | 30 | 31 | 32 | ### stop/1 ### 33 | 34 | `stop(State) -> any()` 35 | 36 | -------------------------------------------------------------------------------- /doc/opencensus_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module opencensus_sup # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | opencensus top level supervisor. 9 | 10 | __Behaviours:__ [`supervisor`](supervisor.md). 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
init/1
start_link/0
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### init/1 ### 27 | 28 | `init(X1) -> any()` 29 | 30 | 31 | 32 | ### start_link/0 ### 33 | 34 | `start_link() -> any()` 35 | 36 | -------------------------------------------------------------------------------- /doc/span.md: -------------------------------------------------------------------------------- 1 | # Creating Spans # 2 | 3 | Span data is stored and manipulated in an ETS table. Span context must be tracked in order to create child span's, propagate span context across process or node boundaries and for the library to which span data to manipulate in the context it is called. 4 | 5 | `opencensus` provides two methods for tracking this context, the process dictionary and a variable holding a `ctx` record. 6 | 7 |
Process Dictionary as Context
8 | 9 | With `ocp` the span context is tracked in the current process`s process dictionary. 10 | 11 | ```erlang 12 | some_fun() -> 13 | ocp:with_child_span(<<"some_fun/0">>, 14 | fun() -> 15 | ... body .. 16 | end). 17 | ``` 18 | 19 |
Parse Transform
20 | 21 | The parse transform provides an attribute to decorate functions with that will start a span, wrap the contents in a `try` and finish the span in an `after` clause. Add the parse transform to the compile opts in `rebar.config`: 22 | 23 | ```erlang 24 | {erl_opts, [{parse_transform, oc_transform}]}. 25 | ``` 26 | 27 | And use: 28 | 29 | ```erlang 30 | -span([]). 31 | function_to_trace() -> 32 | ... 33 | SpanCtx = ocp:current_span(), %% ocp works in a decorated function too 34 | ... 35 | ``` 36 | 37 | Since the tranformed functions use the process dictionary to store the context you can interact with the current span the same way as you do with `ocp`, covered in the previous section. 38 | 39 |
Manual Context Tracking
40 | 41 | `ctx` is a generic context library for Erlang. OpenCensus provides the option to use it in place of the process dictionary for tracking the span context. 42 | 43 | In this example a function is passed a `ctx` variable `Ctx` that some instrumented library could have set the span context based on the incoming metadata of a request, like HTTP headers. The `oc_trace:new_span` function will check `Ctx` for a span context and create a child span of that span context if it exists, otherwise a root span will be created. We can pass the span context to another function, we could also createa a new `ctx` to pass (`oc_trace:with_span(Ctx, SpanCtx)`), to be further updated or have new children created: 44 | 45 | ```erlang 46 | handler(Ctx, NextHandler) -> 47 | SpanCtx = oc_trace:with_child_span(Ctx, <<"span-name">>), 48 | try 49 | oc_trace:put_attribute(<<"key">>, <<"value">>, SpanCtx), 50 | {Code, Message} = NextHandler(SpanCtx), 51 | oc_trace:set_status(Code, Message, SpanCtx) 52 | after 53 | oc_trace:finish_span(SpanCtx) 54 | end. 55 | ``` 56 | 57 |
Manual Span Data Handling
58 | 59 | The module `oc_span` has the functional span data manipulation functions, meaning ETS is not involved. Most users will not need this, but for potential alternative span data stores or context trackers they are necessary. 60 | 61 | 62 | #### Working with Spans #### 63 | 64 |
Attributes
65 | 66 | A span has a map of attributes providing details about the span. The key is a binary string and the value of the attribute can be a binary string, integer, or boolean. 67 | 68 | ```erlang 69 | Span1 = oc_trace:put_attribute(<<"/instance_id">>, <<"my-instance">>, SpanCtx), 70 | ``` 71 | 72 |
Time Events
73 | 74 | A time event is a timestamped annotation with user-supplied key-value pairs or a message event to represent a message (not specificly an Erlang message) sent to or received from another span. 75 | 76 | The `message_event` consists of a type, identifier and size of the message. `Id` is an identifier for the event's message that can be used to match `SENT` and `RECEIVED` `message_event`s. For example, this field could represent a sequence ID for a streaming RPC. It is recommended to be unique within a Span. If `CompressedSize` is `0` it is assumed to be the same as `UncompressedSize`. 77 | 78 | ```erlang 79 | Event = opencensus:message_event(?MESSAGE_EVENT_TYPE_SENT, Id, UncompressedSize, CompressedSize) 80 | oc_trace:add_time_event(Event, SpanCtx), 81 | ``` 82 | 83 |
Links
84 | 85 | Links are useful in cases like a job queue. A job is created with a span context and when run wants to report a new span. The job isn't a direct child of the span that inserted it into the queue, but it is related. The job creates a link to the span that created it. 86 | 87 | ```erlang 88 | SpanCtx = oc_trace:with_child_span(Ctx, <<"running job">>), 89 | Link = oc_trace:link(?LINK_TYPE_PARENT_LINKED_SPAN, TraceId, ParentSpanId, #{}), 90 | oc_trace:add_link(Link, SpanCtx), 91 | ... run job ... 92 | oc_trace:finish_span(SpanCtx). 93 | ``` 94 | -------------------------------------------------------------------------------- /doc/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* standard EDoc style sheet */ 2 | body { 3 | font-family: Verdana, Arial, Helvetica, sans-serif; 4 | margin-left: .25in; 5 | margin-right: .2in; 6 | margin-top: 0.2in; 7 | margin-bottom: 0.2in; 8 | color: #000000; 9 | background-color: #ffffff; 10 | } 11 | h1,h2 { 12 | margin-left: -0.2in; 13 | } 14 | div.navbar { 15 | background-color: #add8e6; 16 | padding: 0.2em; 17 | } 18 | h2.indextitle { 19 | padding: 0.4em; 20 | background-color: #add8e6; 21 | } 22 | h3.function,h3.typedecl { 23 | background-color: #add8e6; 24 | padding-left: 1em; 25 | } 26 | div.spec { 27 | margin-left: 2em; 28 | background-color: #eeeeee; 29 | } 30 | a.module { 31 | text-decoration:none 32 | } 33 | a.module:hover { 34 | background-color: #eeeeee; 35 | } 36 | ul.definitions { 37 | list-style-type: none; 38 | } 39 | ul.index { 40 | list-style-type: none; 41 | background-color: #eeeeee; 42 | } 43 | 44 | /* 45 | * Minor style tweaks 46 | */ 47 | ul { 48 | list-style-type: square; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | } 53 | td { 54 | padding: 3 55 | } 56 | -------------------------------------------------------------------------------- /doc/tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /elvis.config: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | elvis, 4 | [ 5 | {config, 6 | [#{dirs => ["src"], 7 | filter => "*.erl", 8 | ignore => ["src/oc_trace_pb.erl", "src/oc_trace_config_pb.erl"], 9 | rules => [{elvis_style, god_modules, #{ignore => [ocp]}}, 10 | {elvis_style, no_debug_call, #{ignore => [oc_reporter_stdout, 11 | oc_stat_exporter_stdout]}}, 12 | {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}, 13 | {elvis_style, line_length, #{limit => 120}}, 14 | {elvis_style, state_record_and_type, disable}, 15 | {elvis_style, no_if_expression, disable}, 16 | {elvis_style, function_naming_convention, #{regex => "^_{0,2}([a-z][a-z0-9]*_?)*_{0,2}$"}}, 17 | %% sequential reporter calls other reporters dynamically 18 | %% stat view proxies measures 19 | {elvis_style, invalid_dynamic_call, #{ignore => [oc_reporter_sequential, %% loops over reporters 20 | oc_stat_view, %% calls aggragtions 21 | oc_stat_measure, %% calls generated modules 22 | oc_stat_exporter, %% loops over exporters, 23 | oc_producer_registry %% loops over metric producers 24 | ]}}], 25 | ruleset => erl_files 26 | }, 27 | #{dirs => ["test"], 28 | filter => "*.erl", 29 | rules => [{elvis_style, god_modules, #{ignore => [egithub]}}, 30 | {elvis_style, dont_repeat_yourself, #{min_complexity => 30}}, 31 | {elvis_style, line_length, #{limit => 120}}, 32 | {elvis_style, no_if_expression, disable}, 33 | %% be a bit more lax on naming for tests 34 | {elvis_style, variable_naming_convention, #{regex => "^_{0,2}([A-Z][0-9a-zA-Z]*)$"}}, 35 | {elvis_style, state_record_and_type, disable}], 36 | ruleset => erl_files 37 | }, 38 | #{dirs => ["."], 39 | filter => "Makefile", 40 | rules => [{elvis_project, 41 | protocol_for_deps_erlang_mk, 42 | #{regex => "(https://.*|[0-9]+([.][0-9]+)*)"}}], 43 | ruleset => makefiles 44 | }, 45 | #{dirs => ["."], 46 | filter => "rebar.config", 47 | ruleset => rebar_config 48 | }, 49 | #{dirs => ["."], 50 | filter => "elvis.config", 51 | ruleset => elvis_config 52 | } 53 | ] 54 | } 55 | ] 56 | } 57 | ]. 58 | -------------------------------------------------------------------------------- /examples/helloworld/README.md: -------------------------------------------------------------------------------- 1 | ## Quickstart 2 | 3 | The example demonstrates how to record stats and traces for a video processing system. It records data with the "frontend" tag so that collected data can be broken by the frontend user who initiated the video processing. Code for this example can be found under the `examples/helloworld` directory of the [OpenCensus Erlang repo](https://github.com/census-instrumentation/opencensus-erlang). 4 | 5 | ### API Documentation 6 | 7 | The OpenCensus Erlang API artifact is available here: https://hexdocs.pm/opencensus/0.3.0/index.html 8 | 9 | ### Example 10 | 11 | #### Prerequisites 12 | 13 | [Erlang/OTP 20](https://erlang.org) and [rebar3](https://rebar3.org) are required. 14 | 15 | #### Using 16 | 17 | Create a new Erlang application with `rebar3 new` named `helloworld`: 18 | 19 | ```shell 20 | $ rebar3 new app helloworld 21 | ===> Writing helloworld/src/helloworld_app.erl 22 | ===> Writing helloworld/src/helloworld_sup.erl 23 | ===> Writing helloworld/src/helloworld.app.src 24 | ===> Writing helloworld/rebar.config 25 | ===> Writing helloworld/.gitignore 26 | ===> Writing helloworld/LICENSE 27 | ===> Writing helloworld/README.md 28 | $ cd helloworld 29 | ``` 30 | 31 | Add `opencensus` as a dependency in `rebar.config`. For development purposes it is also useful to include the `shell` section of the config which tells rebar3 to boot the application and load configuration when running `rebar3 shell`: 32 | 33 | ```erlang 34 | {erl_opts, [debug_info]}. 35 | {deps, [opencensus]}. 36 | 37 | {shell, [{apps, [helloworld]}, 38 | {config, "config/sys.config"}]}. 39 | ``` 40 | 41 | ```erlang 42 | [ 43 | {opencensus, [{sampler, {oc_sampler_always, []}}, 44 | {reporters, [{oc_reporter_stdout, []}]}, 45 | {stat, [{exporters, [{oc_stat_exporter_stdout, []}]}]} 46 | ]. 47 | ``` 48 | 49 | `opencensus` is a runtime dependency so it is added to the applications list in `src/helloworld.app.src`, ensuring it is included and started in a release of `helloworld`: 50 | 51 | ```erlang 52 | {application, helloworld, 53 | [{description, "Example OpenCensus application"}, 54 | {vsn, "0.1.0"}, 55 | {registered, []}, 56 | {mod, { helloworld_app, []}}, 57 | {applications, 58 | [kernel, 59 | stdlib, 60 | opencensus 61 | ]}, 62 | {env,[]}, 63 | {modules, []}, 64 | 65 | {maintainers, []}, 66 | {licenses, ["Apache 2.0"]}, 67 | {links, []} 68 | ]}. 69 | ``` 70 | 71 | Building the application with `rebar3 compile` will fetch the OpenCensus Erlang library and its dependencies. 72 | 73 | When our application starts it needs to create and subscribe to the statistics that we'll record. So a call to `create_measures/0` and `subscribe_views/0` is added to the application start function, `helloworld_app:start/2`: 74 | 75 | ```erlang 76 | create_measures() -> 77 | oc_stat_measure:new('my.org/measure/video_size', "Size of a processed video", bytes). 78 | 79 | subscribe_views() -> 80 | oc_stat_view:subscribe(#{name => "video_size", 81 | description => "size of processed videos", 82 | tags => ['frontend'], 83 | measure => 'my.org/measure/video_size', 84 | aggregation => default_size_distribution()}). 85 | 86 | default_size_distribution() -> 87 | {oc_stat_aggregation_distribution, [{buckets, [0, 1 bsl 16, 1 bsl 32]}]}. 88 | ``` 89 | 90 | The main module called to actually do the video processing is `helloworld`. It creates a tag for who made the process request to include with the record statistic and creates a span for the duration of the video processing (a random sleep between 0 and 10 seconds): 91 | 92 | ```erlang 93 | -module(helloworld). 94 | 95 | -export([process/0]). 96 | 97 | process() -> 98 | %% create a tag for who sent the request and start a child span 99 | Tags = oc_tags:new(#{'frontend' => "mobile-ios9.3.5"}), 100 | ocp:with_child_span(<<"my.org/ProcessVideo">>), 101 | 102 | %% sleep for 0-10 seconds to simulate processing time 103 | timer:sleep(timer:seconds(rand:uniform(10))), 104 | 105 | %% finish the span 106 | ocp:finish_span(), 107 | 108 | %% record the size of the video 109 | oc_stat:record(Tags, 'my.org/measure/video_size', 25648). 110 | ``` 111 | 112 | Run the application with `rebar3 shell` and see the stats and span reported to the console: 113 | 114 | ```erlang 115 | $ rebar3 shell 116 | ... 117 | ===> Booted opencensus 118 | ===> Booted helloworld 119 | > helloworld:process(). 120 | ok 121 | {span,<<"my.org/ProcessVideo">>,1201374966367397737078249396493886473, 122 | 10421649746227310879,undefined,1, 123 | {-576460748652616660,2097430124176280981}, 124 | {-576460740651723247,2097430124176280981}, 125 | #{},undefined,[],[],undefined,undefined,undefined} 126 | video_size: #{rows => 127 | [#{tags => #{"frontend" => "mobile-ios9.3.5"}, 128 | value => 129 | #{buckets => 130 | [{0,0},{65536,1},{4294967296,0},{infinity,0}], 131 | count => 1,mean => 25648.0,sum => 25648}}], 132 | type => distribution} 133 | ``` 134 | -------------------------------------------------------------------------------- /examples/helloworld/config/sys.config: -------------------------------------------------------------------------------- 1 | [ 2 | {opencensus, [{sampler, {oc_sampler_always, []}}, 3 | {reporters, [{oc_reporter_stdout, []}]}, 4 | 5 | {stat, [{exporters, [{oc_stat_exporter_stdout, []}]}]}, 6 | 7 | {sweeper, #{interval => 300000, 8 | strategy => drop, 9 | span_ttl => 300000}}]}, 10 | 11 | {kernel, 12 | [{logger, 13 | [{handler, default, logger_std_h, 14 | #{level => debug, 15 | formatter => {logger_formatter, 16 | #{template => [time, " ", pid, " ", 17 | {[span_ctx, trace_id], ["trace_id=", [span_ctx, trace_id], " "], []}, 18 | {[span_ctx, span_id], ["span_id=", [span_ctx, span_id], " "], []}, 19 | {[span_ctx, trace_options], ["trace_options=", [span_ctx, trace_options], " "], []}, 20 | msg, "\n"]}} 21 | }}]}]} 22 | ]. 23 | -------------------------------------------------------------------------------- /examples/helloworld/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [opencensus]}. 3 | 4 | {shell, [{apps, [helloworld]}, 5 | {config, "config/sys.config"}]}. 6 | -------------------------------------------------------------------------------- /examples/helloworld/src/helloworld.app.src: -------------------------------------------------------------------------------- 1 | {application, helloworld, 2 | [{description, "Example OpenCensus application"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {mod, { helloworld_app, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib, 9 | opencensus 10 | ]}, 11 | {env,[]}, 12 | {modules, []}, 13 | 14 | {maintainers, []}, 15 | {licenses, ["Apache 2.0"]}, 16 | {links, []} 17 | ]}. 18 | -------------------------------------------------------------------------------- /examples/helloworld/src/helloworld.erl: -------------------------------------------------------------------------------- 1 | -module(helloworld). 2 | 3 | -export([process/0]). 4 | 5 | process() -> 6 | %% create a tag for who sent the request and start a child span 7 | Tags = oc_tags:new(#{'frontend' => "mobile-ios9.3.5"}), 8 | ocp:with_child_span(<<"my.org/ProcessVideo">>), 9 | 10 | %% sleep for 0-10 seconds to simulate processing time 11 | timer:sleep(timer:seconds(rand:uniform(10))), 12 | 13 | %% finish the span 14 | ocp:finish_span(), 15 | 16 | %% record the size of the video 17 | oc_stat:record(Tags, 'my.org/measure/video_size', 25648). 18 | -------------------------------------------------------------------------------- /examples/helloworld/src/helloworld_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc helloworld public API 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(helloworld_app). 7 | 8 | -behaviour(application). 9 | 10 | %% Application callbacks 11 | -export([start/2, stop/1]). 12 | 13 | %%==================================================================== 14 | %% API 15 | %%==================================================================== 16 | 17 | start(_StartType, _StartArgs) -> 18 | create_measures(), 19 | subscribe_views(), 20 | helloworld_sup:start_link(). 21 | 22 | %%-------------------------------------------------------------------- 23 | stop(_State) -> 24 | ok. 25 | 26 | %%==================================================================== 27 | %% Internal functions 28 | %%==================================================================== 29 | 30 | create_measures() -> 31 | oc_stat_measure:new('my.org/measure/video_size', "Size of a processed video", bytes). 32 | 33 | subscribe_views() -> 34 | oc_stat_view:subscribe(#{name => "video_size", 35 | description => "size of processed videos", 36 | tags => ['frontend'], 37 | measure => 'my.org/measure/video_size', 38 | aggregation => default_size_distribution()}). 39 | 40 | default_size_distribution() -> 41 | {oc_stat_aggregation_distribution, [{buckets, [0, 1 bsl 16, 1 bsl 32]}]}. 42 | -------------------------------------------------------------------------------- /examples/helloworld/src/helloworld_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc helloworld top level supervisor. 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(helloworld_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([start_link/0]). 12 | 13 | %% Supervisor callbacks 14 | -export([init/1]). 15 | 16 | -define(SERVER, ?MODULE). 17 | 18 | %%==================================================================== 19 | %% API functions 20 | %%==================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 24 | 25 | %%==================================================================== 26 | %% Supervisor callbacks 27 | %%==================================================================== 28 | 29 | %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} 30 | init([]) -> 31 | {ok, { {one_for_all, 0, 1}, []} }. 32 | 33 | %%==================================================================== 34 | %% Internal functions 35 | %%==================================================================== 36 | -------------------------------------------------------------------------------- /include/oc_metrics.hrl: -------------------------------------------------------------------------------- 1 | -type oc_string() :: iodata(). 2 | -type oc_float() :: float() | integer() | infinity | '-infinity' | nan. 3 | -type oc_double() :: float() | integer() | infinity | '-infinity' | nan. 4 | -type oc_int64() :: integer(). 5 | -type oc_uint64() :: non_neg_integer(). 6 | -type oc_int32() :: integer(). 7 | -type oc_uint32() :: integer(). 8 | 9 | -type registry() :: atom(). 10 | 11 | %% Defines a label key associated with a metric descriptor. 12 | -record(oc_label_key, 13 | {key = <<>> :: oc_string(), 14 | description = <<>> :: oc_string() 15 | }). 16 | 17 | -type oc_label_key() :: #oc_label_key{}. 18 | 19 | %% Represents the value of label 20 | %% @type oc_label_value(v) = #oc_label_value{value = binary() | iolist(), 21 | %% present = boolean()} 22 | -record(oc_label_value, 23 | {value :: oc_string() | undefined, %% the value of the label 24 | present :: boolean() %% if false the value field is ignored and considered not set; 25 | %% This is used to differentiate a missing label from an empty string. 26 | }). 27 | 28 | -type oc_label_value() :: #oc_label_value{}. 29 | 30 | -record(oc_explicit_bucket_options, 31 | {bounds = [] :: [oc_double()] 32 | }). 33 | 34 | -type oc_explicit_bucket_options() :: #oc_explicit_bucket_options{}. 35 | 36 | -record(exemplar, 37 | {value = 0.0 :: oc_double(), 38 | timestamp = undefined :: wts:timestamp() | undefined, 39 | attachments = #{} :: #{oc_string() := oc_string()} 40 | }). 41 | 42 | -type exemplar() :: #exemplar{}. 43 | 44 | -record(oc_bucket, 45 | {count = 0 :: oc_int64(), 46 | exemplar = undefined :: oc_metrics:exemplar() | undefined 47 | }). 48 | 49 | -type oc_bucket() :: #oc_bucket{}. 50 | 51 | -record(oc_distribution, 52 | {count = 0 :: oc_int32(), 53 | sum = 0.0 :: oc_double(), 54 | sum_of_squared_deviation = 0.0 :: oc_double(), 55 | bucket_options = undefined :: oc_explicit_bucket_options() | undefined, 56 | buckets = undefined :: [oc_bucket()] | undefined 57 | }). 58 | 59 | -type oc_distribution() :: #oc_distribution{}. 60 | 61 | -record(oc_value_at_percentile, 62 | {percentile = 0.0 :: oc_double(), 63 | value = 0.0 :: oc_double() 64 | }). 65 | 66 | -type oc_value_at_percentile() :: #oc_value_at_percentile{}. 67 | 68 | -record(oc_snapshot, 69 | {count = undefined :: oc_int64() | undefined, 70 | sum = undefined :: oc_double() | undefined, 71 | percentile_values = [] :: [oc_value_at_percentile()] | undefined 72 | }). 73 | 74 | -type oc_snapshot() :: #oc_snapshot{}. 75 | 76 | -record(oc_summary, 77 | {count = 0 :: oc_int64(), 78 | sum = 0.0 :: oc_double(), 79 | snapshot = undefined :: oc_snapshot() | undefined 80 | }). 81 | 82 | -type oc_summary() :: #oc_summary{}. 83 | 84 | -record(oc_point, 85 | {timestamp = undefined :: wts:timestamp() | undefined, 86 | value :: oc_int64() | oc_double() | 87 | oc_distribution() | oc_summary() 88 | }). 89 | 90 | -type oc_point() :: #oc_point{}. 91 | 92 | -record(oc_time_series, 93 | {start_timestamp = undefined :: wts:timestamp() | undefined, 94 | label_values = [] :: [oc_label_value()], 95 | points = [] :: [oc_point()] 96 | }). 97 | 98 | -type oc_time_series() :: #oc_time_series{}. 99 | -record(oc_metric_descriptor, 100 | {name = <<>> :: oc_string(), 101 | description = <<>> :: oc_string(), 102 | unit = <<"1">> :: oc_string(), 103 | type = 'UNSPECIFIED' :: 'UNSPECIFIED' | 104 | 'GAUGE_INT64' | 105 | 'GAUGE_DOUBLE' | 106 | 'GAUGE_DISTRIBUTION' | 107 | 'CUMULATIVE_INT64' | 108 | 'CUMULATIVE_DOUBLE' | 109 | 'CUMULATIVE_DISTRIBUTION' | 110 | 'SUMMARY', 111 | label_keys = [] :: [oc_label_key()] %% The label keys associated with the metric descriptor. 112 | }). 113 | 114 | -type oc_metric_descriptor() :: #oc_metric_descriptor{}. 115 | 116 | -record(oc_resource, 117 | {type = <<>> :: oc_string(), 118 | labels = #{} :: #{oc_string() := oc_string()} 119 | }). 120 | 121 | -type oc_resource() :: #oc_resource{}. 122 | 123 | -record(oc_metric, 124 | {descriptor :: oc_metric_descriptor() | oc_string(), 125 | timeseries = [] :: [oc_time_series()], 126 | resource = undefined :: oc_resource() | undefined 127 | }). 128 | 129 | -type oc_metric() :: #oc_metric{}. 130 | 131 | -type metric_callback() :: 132 | fun((oc_producer_registry:registry(), oc_metric()) -> any()). 133 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, 2 | [debug_info, 3 | warnings_as_errors, 4 | warn_untyped_record]}. 5 | 6 | {deps, [{wts, "~> 0.3"}, 7 | {ctx, "~> 0.5"}, 8 | {counters, "~> 0.2.1"}]}. 9 | 10 | {project_plugins, [covertool, 11 | rebar3_lint]}. 12 | 13 | {plugins, [rebar3_proper]}. 14 | 15 | {profiles, [ 16 | {test, [{erl_opts, [nowarn_export_all]}, 17 | {deps, [{meck, "0.8.11"}, 18 | {proper, "1.3.0"}]}]}, 19 | {docs, [{deps, [{edown, "0.8.1"}]}, 20 | {edoc_opts, 21 | [{doclet, edown_doclet}, 22 | {preprocess, true}, 23 | {dir, "doc"}, 24 | {subpackages, true}]}]}, 25 | {benchmark, [{src_dirs, ["src", "benchmark"]}, 26 | {deps, [{'erlang-color', 27 | {git, "https://github.com/julianduque/erlang-color", {branch, "master"}}}]}]}]}. 28 | 29 | {xref_checks, [undefined_function_calls, undefined_functions, 30 | deprecated_function_calls, deprecated_functions]}. 31 | {xref_ignores, [{oc_sampler_impl, should_sample, 3}]}. 32 | 33 | {cover_enabled, true}. 34 | {cover_opts, [verbose]}. 35 | {cover_excl_mods, [oc_trace_pb, oc_trace_config_pb, oc_span_transform, oc_stat_transform]}. 36 | {cover_export_enabled, true}. 37 | {covertool, [{coverdata_files, ["ct.coverdata"]}]}. 38 | 39 | {ct_opts, [{ct_hooks, [cth_surefire]}]}. 40 | 41 | {shell, [{apps, [opencensus]}]}. 42 | 43 | {post_hooks, [{edoc, "doc/build.sh"}]}. 44 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.1.0", 2 | [{<<"counters">>,{pkg,<<"counters">>,<<"0.2.1">>},0}, 3 | {<<"ctx">>,{pkg,<<"ctx">>,<<"0.5.0">>},0}, 4 | {<<"rfc3339">>,{pkg,<<"rfc3339">>,<<"0.9.0">>},1}, 5 | {<<"wts">>,{pkg,<<"wts">>,<<"0.3.0">>},0}]}. 6 | [ 7 | {pkg_hash,[ 8 | {<<"counters">>, <<"AA3D97E88F92573488987193D0F48EFCE0F3B2CD1443BF4EE760BC7F99322F0C">>}, 9 | {<<"ctx">>, <<"78E0F16712E12D707A7F34277381B8E193D7C71EAA24D37330DC02477C09EDA5">>}, 10 | {<<"rfc3339">>, <<"2075653DC9407541C84B1E15F8BDA2ABE95FB17C9694025E079583F2D19C1060">>}, 11 | {<<"wts">>, <<"5CDF22C775CB1EBAE24C326A5DB6074D753C42F4BD12A9AA47CC62D3E2C71AD1">>}]} 12 | ]. 13 | -------------------------------------------------------------------------------- /src/oc_logger.hrl: -------------------------------------------------------------------------------- 1 | -ifdef(OTP_RELEASE). 2 | -define(WITH_STACKTRACE(T, R, S), T:R:S ->). 3 | -else. 4 | -define(WITH_STACKTRACE(T, R, S), T:R -> S = erlang:get_stacktrace(),). 5 | -endif. 6 | 7 | -ifdef(OTP_RELEASE). 8 | -include_lib("kernel/include/logger.hrl"). 9 | -else. 10 | -define(LOG_INFO(Format, Args), error_logger:info_msg(Format, Args)). 11 | -define(LOG_ERROR(Format, Args), error_logger:error_msg(Format, Args)). 12 | -endif. 13 | 14 | -ifdef(OTP_RELEASE). 15 | -define(SET_LOG_METADATA(SpanCtx), 16 | case SpanCtx of 17 | undefined -> 18 | logger:update_process_metadata(#{span_ctx => undefined}); 19 | _ -> 20 | logger:update_process_metadata(#{span_ctx => #{trace_id => io_lib:format("~32.16.0b", [SpanCtx#span_ctx.trace_id]), 21 | span_id => io_lib:format("~16.16.0b", [SpanCtx#span_ctx.span_id]), 22 | trace_options => integer_to_list(SpanCtx#span_ctx.trace_options)}}) 23 | end). 24 | -else. 25 | -define(SET_LOG_METADATA(SpanCtx), skip). 26 | -endif. 27 | -------------------------------------------------------------------------------- /src/oc_producer.erl: -------------------------------------------------------------------------------- 1 | %% @doc 2 | %% Producer is a source of metrics. 3 | %% 4 | %% Callbacks: 5 | %% 6 | %% - `read(Registry, Callback) -> ok' - called by exporters. 7 | %% Read should call `Callback' with the current values of all metrics 8 | %% supported by this metric provider. 9 | %% The metrics should be unique for each combination of name and resource. 10 | %% 11 | %% - `cleanup(Registry) -> ok' - optional. 12 | %% Called when producer removed from `Registry' 13 | %% 14 | %% @end 15 | -module(oc_producer). 16 | 17 | -include("oc_metrics.hrl"). 18 | 19 | -callback read(Registry, Callback) -> ok when 20 | Registry :: oc_producer_registry:registry(), 21 | Callback :: metric_callback(). 22 | 23 | -callback cleanup(Registry) -> ok when 24 | Registry :: oc_producer_registry:registry(). 25 | 26 | -optional_callbacks([cleanup/1]). 27 | -------------------------------------------------------------------------------- /src/oc_producer_registry.erl: -------------------------------------------------------------------------------- 1 | %% @doc 2 | %% Registry maintains a set of metric producers for exporting. 3 | %% Most users will rely on the DefaultRegistry. 4 | %% @end 5 | -module(oc_producer_registry). 6 | 7 | -export([read_to_list/0, 8 | read_to_list/1, 9 | 10 | read_all/1, 11 | read_all/2, 12 | 13 | add_producer/1, 14 | add_producer/2, 15 | 16 | remove_producer/1, 17 | remove_producer/2]). 18 | 19 | %% @equiv read_all(default) 20 | read_all(Callback) -> 21 | read_all(default, Callback). 22 | 23 | %% @doc 24 | %% Calls `Callback' for each metric produced by the metric producers in the `Registry'. 25 | %% @end 26 | read_all(Registry, Callback) -> 27 | [Producer:read(Registry, Callback) || {_, Producer} <- ets:lookup(?MODULE, Registry)], 28 | ok. 29 | 30 | %% 31 | read_to_list() -> 32 | read_to_list(default). 33 | 34 | read_to_list(Registry) -> 35 | Ref = make_ref(), 36 | try 37 | Callback = fun (M) -> 38 | put(Ref, [M|get_list(Ref)]) 39 | end, 40 | read_all(Registry, Callback), 41 | 42 | get_list(Ref) 43 | after 44 | erase(Ref) 45 | end. 46 | 47 | %% @equiv(default, Producer) 48 | add_producer(Producer) -> 49 | add_producer(default, Producer). 50 | 51 | %% @doc 52 | %% Adds `Producer' to the `Registry'. 53 | %% @end 54 | add_producer(Registry, Producer) -> 55 | ets:insert(?MODULE, {Registry, Producer}), 56 | ok. 57 | 58 | %% @equiv remove_producer(default, Producer) 59 | remove_producer(Producer) -> 60 | remove_producer(default, Producer). 61 | 62 | remove_producer(Registry, Producer) -> 63 | ets:delete_object(?MODULE, {Registry, Producer}), 64 | case erlang:function_exported(Producer, cleanup, 1) of 65 | true -> Producer:cleanup(Registry); 66 | _ -> ok 67 | end, 68 | ok. 69 | 70 | get_list(Key) -> 71 | case get(Key) of 72 | undefined -> 73 | []; 74 | Value -> 75 | Value 76 | end. 77 | -------------------------------------------------------------------------------- /src/oc_propagation_binary.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------- 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc Functions to support the binary format trace context serialization. 16 | %% Implements the spec found here 17 | %% [https://github.com/census-instrumentation/opencensus-specs/blob/7b426409/encodings/BinaryEncoding.md] 18 | %% @end 19 | %%%------------------------------------------------------------------------- 20 | -module(oc_propagation_binary). 21 | 22 | -export([encode/1, 23 | decode/1]). 24 | 25 | -include("opencensus.hrl"). 26 | 27 | -define(VERSION, 0). 28 | 29 | -define(TRACE_ID_FIELD_NUM, 0). 30 | -define(SPAN_ID_FIELD_NUM, 1). 31 | -define(TRACE_OPTIONS_FIELD_NUM, 2). 32 | 33 | -spec encode(opencensus:span_ctx()) -> maybe(binary()). 34 | encode(#span_ctx{trace_id=TraceId, 35 | span_id=SpanId}) when TraceId =:= 0 36 | ; SpanId =:= 0 -> 37 | undefined; 38 | encode(#span_ctx{trace_id=TraceId, 39 | span_id=SpanId, 40 | trace_options=TraceOptions}) -> 41 | Options = case TraceOptions band 1 of 1 -> <<1:8>>; _ -> <<0:8>> end, 42 | <>. 43 | 44 | -spec decode(binary()) -> maybe(opencensus:span_ctx()). 45 | decode(<<0:8/integer, VersionFormat/binary>>) -> 46 | decode_v0(VersionFormat, #span_ctx{}). 47 | 48 | decode_v0(<<>>, TraceContext) -> 49 | TraceContext; 50 | decode_v0(<>, _) 51 | when TraceId =:= 0 -> 52 | undefined; 53 | decode_v0(<>, TraceContext) -> 54 | decode_v0(Rest, TraceContext#span_ctx{trace_id=TraceId}); 55 | decode_v0(<>, _) 56 | when SpanId =:= 0 -> 57 | undefined; 58 | decode_v0(<>, TraceContext) -> 59 | decode_v0(Rest, TraceContext#span_ctx{span_id=SpanId}); 60 | decode_v0(<>, TraceContext) -> 61 | decode_v0(Rest, TraceContext#span_ctx{trace_options=TraceOptions}); 62 | decode_v0(_, TraceContext) -> 63 | TraceContext. 64 | -------------------------------------------------------------------------------- /src/oc_propagation_http_b3.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------- 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc Functions to support the http header format of the tracecontext spec 16 | %% Implements the spec found here https://github.com/openzipkin/b3-propagation 17 | %% @end 18 | %%%------------------------------------------------------------------------- 19 | -module(oc_propagation_http_b3). 20 | 21 | -export([to_headers/1, 22 | from_headers/1]). 23 | 24 | -include("opencensus.hrl"). 25 | 26 | -define(B3_TRACE_ID, <<"X-B3-TraceId">>). 27 | -define(B3_SPAN_ID, <<"X-B3-SpanId">>). 28 | -define(B3_SAMPLED, <<"X-B3-Sampled">>). 29 | 30 | -define(IS_SAMPLED(S), S =:= "1" orelse S =:= <<"1">> orelse S =:= "true" orelse S =:= <<"true">>). 31 | 32 | -spec to_headers(opencensus:span_ctx()) -> maybe(list()). 33 | to_headers(#span_ctx{trace_id=TraceId, 34 | span_id=SpanId}) when TraceId =:= 0 35 | ; SpanId =:= 0 -> 36 | []; 37 | to_headers(#span_ctx{trace_id=TraceId, 38 | span_id=SpanId, 39 | trace_options=TraceOptions}) -> 40 | Options = case TraceOptions band 1 of 1 -> "1"; _ -> "0" end, 41 | %% iolist_to_binary only needed for versions before otp-21 42 | EncodedTraceId = iolist_to_binary(io_lib:format("~32.16.0b", [TraceId])), 43 | EncodedSpanId = iolist_to_binary(io_lib:format("~16.16.0b", [SpanId])), 44 | [{?B3_TRACE_ID, EncodedTraceId}, 45 | {?B3_SPAN_ID, EncodedSpanId}, 46 | {?B3_SAMPLED, Options}]; 47 | to_headers(undefined) -> 48 | []. 49 | 50 | -spec from_headers(list() | map()) -> maybe(opencensus:span_ctx()). 51 | from_headers(Headers) when is_map(Headers) -> 52 | from_headers(maps:to_list(Headers)); 53 | from_headers(Headers) when is_list(Headers) -> 54 | try 55 | TraceId = trace_id(Headers), 56 | SpanId = span_id(Headers), 57 | Sampled = lookup(?B3_SAMPLED, Headers), 58 | #span_ctx{trace_id=string_to_integer(TraceId, 16), 59 | span_id=string_to_integer(SpanId, 16), 60 | trace_options=case Sampled of True when ?IS_SAMPLED(True) -> 1; _ -> 0 end} 61 | catch 62 | throw:invalid -> 63 | undefined; 64 | 65 | %% thrown if _to_integer fails 66 | error:badarg -> 67 | undefined 68 | end; 69 | from_headers(_) -> 70 | undefined. 71 | 72 | trace_id(Headers) -> 73 | case lookup(?B3_TRACE_ID, Headers) of 74 | TraceId when is_list(TraceId) orelse is_binary(TraceId) -> 75 | case string:length(TraceId) =:= 32 orelse string:length(TraceId) =:= 16 of 76 | true -> 77 | TraceId; 78 | _ -> 79 | throw(invalid) 80 | end; 81 | _ -> 82 | throw(invalid) 83 | end. 84 | 85 | span_id(Headers) -> 86 | case lookup(?B3_SPAN_ID, Headers) of 87 | SpanId when is_list(SpanId) orelse is_binary(SpanId) -> 88 | case string:length(SpanId) =:= 16 of 89 | true -> 90 | SpanId; 91 | _ -> 92 | throw(invalid) 93 | end; 94 | _ -> 95 | throw(invalid) 96 | end. 97 | 98 | %% find a header in a list, ignoring case 99 | lookup(_, []) -> 100 | undefined; 101 | lookup(Header, [{H, Value} | Rest]) -> 102 | case string:equal(Header, H, true, none) of 103 | true -> 104 | Value; 105 | false -> 106 | lookup(Header, Rest) 107 | end. 108 | 109 | string_to_integer(S, Base) when is_binary(S) -> 110 | binary_to_integer(S, Base); 111 | string_to_integer(S, Base) when is_list(S) -> 112 | list_to_integer(S, Base). 113 | -------------------------------------------------------------------------------- /src/oc_propagation_http_tracecontext.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------- 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc Functions to support the http header format of the tracecontext spec 16 | %% Implements the spec found here https://www.w3.org/TR/trace-context/ 17 | %% @end 18 | %%%------------------------------------------------------------------------- 19 | -module(oc_propagation_http_tracecontext). 20 | 21 | -export([to_headers/1, 22 | encode/1, 23 | from_headers/1, 24 | decode/1]). 25 | 26 | -include("opencensus.hrl"). 27 | 28 | -define(VERSION, "00"). 29 | 30 | -define(ZERO_TRACEID, <<"00000000000000000000000000000000">>). 31 | -define(ZERO_SPANID, <<"0000000000000000">>). 32 | 33 | -define(HEADER_KEY, <<"traceparent">>). 34 | -define(STATE_HEADER_KEY, <<"tracestate">>). 35 | 36 | -spec to_headers(opencensus:span_ctx() | undefined) -> [{binary(), iolist()}]. 37 | to_headers(#span_ctx{trace_id=TraceId, 38 | span_id=SpanId}) 39 | when TraceId =:= 0 orelse SpanId =:= 0 -> 40 | []; 41 | to_headers(SpanCtx=#span_ctx{}) -> 42 | EncodedValue = encode(SpanCtx), 43 | [{?HEADER_KEY, EncodedValue} | encode_tracestate(SpanCtx)]; 44 | to_headers(undefined) -> 45 | []. 46 | 47 | -spec encode(opencensus:span_ctx()) -> iolist(). 48 | encode(#span_ctx{trace_id=TraceId, 49 | span_id=SpanId, 50 | trace_options=TraceOptions}) -> 51 | Options = case TraceOptions band 1 of 1 -> <<"01">>; _ -> <<"00">> end, 52 | EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]), 53 | EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]), 54 | [?VERSION, "-", EncodedTraceId, "-", EncodedSpanId, "-", Options]. 55 | 56 | encode_tracestate(#span_ctx{tracestate=undefined}) -> 57 | []; 58 | encode_tracestate(#span_ctx{tracestate=#tracestate{entries=Entries}}) -> 59 | StateHeaderValue = lists:join($,, [[Key, $=, Value] || {Key, Value} <- Entries]), 60 | [{?STATE_HEADER_KEY, StateHeaderValue}]. 61 | 62 | -spec from_headers(list() | map()) -> maybe(opencensus:span_ctx()). 63 | from_headers(Headers) when is_map(Headers) -> 64 | decode(maps:get(?HEADER_KEY, Headers, undefined)); 65 | from_headers(Headers) when is_list(Headers) -> 66 | case lists:keyfind(?HEADER_KEY, 1, Headers) of 67 | {_, Value} -> 68 | case decode(Value) of 69 | undefined -> 70 | undefined; 71 | SpanCtx -> 72 | Tracestate = tracestate_from_headers(Headers), 73 | SpanCtx#span_ctx{tracestate=Tracestate} 74 | end; 75 | _ -> 76 | undefined 77 | end. 78 | 79 | tracestate_from_headers(Headers) -> 80 | %% could be multiple tracestate headers. Combine them all with comma separators 81 | case combine_headers(?STATE_HEADER_KEY, Headers) of 82 | [] -> 83 | undefined; 84 | FieldValue -> 85 | tracestate_decode(FieldValue) 86 | end. 87 | 88 | combine_headers(Key, Headers) -> 89 | lists:foldl(fun({K, V}, Acc) -> 90 | case string:equal(K, Key) of 91 | true -> 92 | [V, $, | Acc]; 93 | false -> 94 | Acc 95 | end 96 | end, [], Headers). 97 | 98 | tracestate_decode(Value) -> 99 | %% TODO: the 512 byte limit should not include optional white space that can 100 | %% appear between list members. 101 | case iolist_size(Value) of 102 | Size when Size =< 512 -> 103 | #tracestate{entries=[split(Pair) || Pair <- string:lexemes(Value, [$,])]}; 104 | _ -> 105 | undefined 106 | end. 107 | 108 | split(Pair) -> 109 | case string:split(Pair, "=") of 110 | [Key, Value] -> 111 | {iolist_to_binary(Key), iolist_to_binary(Value)}; 112 | [Key] -> 113 | {iolist_to_binary(Key), <<>>} 114 | end. 115 | 116 | decode(TraceContext) when is_list(TraceContext) -> 117 | decode(list_to_binary(TraceContext)); 118 | decode(<>) 119 | when TraceId =:= ?ZERO_TRACEID orelse SpanId =:= ?ZERO_SPANID -> 120 | undefined; 121 | decode(<>) -> 122 | try 123 | #span_ctx{trace_id=binary_to_integer(TraceId, 16), 124 | span_id=binary_to_integer(SpanId, 16), 125 | trace_options=case Opts of <<"01">> -> 1; _ -> 0 end} 126 | catch 127 | %% to integer from base 16 string failed 128 | error:badarg -> 129 | undefined 130 | end; 131 | decode(_) -> 132 | undefined. 133 | -------------------------------------------------------------------------------- /src/oc_reporter_pid.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc A test reporter for sending trace spans to an Erlang PID as message. 16 | %% @end 17 | %%%----------------------------------------------------------------------- 18 | -module(oc_reporter_pid). 19 | 20 | -behaviour(oc_reporter). 21 | 22 | -export([init/1, 23 | report/2]). 24 | 25 | init(Pid) -> Pid. 26 | 27 | report(Spans, Pid) -> 28 | [Pid ! {span, Span} || Span <- Spans], 29 | ok. 30 | -------------------------------------------------------------------------------- /src/oc_reporter_stdout.erl: -------------------------------------------------------------------------------- 1 | -module(oc_reporter_stdout). 2 | 3 | -export([init/1, 4 | report/2]). 5 | 6 | init(_) -> 7 | ok. 8 | 9 | report(Spans, _) -> 10 | [io:format("~p~n", [Span]) || Span <- Spans]. 11 | -------------------------------------------------------------------------------- /src/oc_sampler.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc Behaviour each sampler must implement. The function `should_trace' 16 | %% is given the trace id of the current trace, if one exists, the span id 17 | %% of what would be the parent of any new span in this trace and a boolean 18 | %% for whether the trace was enabled in the process that propagated this 19 | %% context. 20 | %% @end 21 | %%%------------------------------------------------------------------------ 22 | 23 | -module(oc_sampler). 24 | 25 | -export([init/1, 26 | should_sample/3]). 27 | 28 | -dialyzer({nowarn_function, should_sample/3}). 29 | 30 | -include_lib("syntax_tools/include/merl.hrl"). 31 | 32 | -callback init(term()) -> term(). 33 | 34 | %% @doc Called at the start of a trace. 35 | -callback should_sample(TraceId, SpanId, Enabled, Opts) -> boolean() when 36 | TraceId :: opencensus:trace_id() | undefined, 37 | SpanId :: opencensus:span_id() | undefined, 38 | Enabled :: boolean() | undefined, 39 | Opts :: term(). 40 | 41 | init({SamplerModule, SamplerInitArgs}) -> 42 | SamplerOpts = SamplerModule:init(SamplerInitArgs), 43 | 44 | ImplModule = ?Q(["-module(oc_sampler_impl).", 45 | "-export([should_sample/3]).", 46 | "should_sample(TraceId, SpanId, Enabled) -> ", 47 | " _@SamplerModule@:should_sample(TraceId, SpanId, Enabled, _@SamplerOpts@)."]), 48 | merl:compile_and_load(ImplModule), 49 | ok. 50 | 51 | should_sample(TraceId, SpanId, Enabled) -> 52 | oc_sampler_impl:should_sample(TraceId, SpanId, Enabled). 53 | -------------------------------------------------------------------------------- /src/oc_sampler_always.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc A sampler that always returns true. 16 | %% @end 17 | %%%----------------------------------------------------------------------- 18 | -module(oc_sampler_always). 19 | 20 | -behaviour(oc_sampler). 21 | 22 | -export([init/1, 23 | should_sample/4]). 24 | 25 | init(_) -> 26 | ok. 27 | 28 | should_sample(_, _, _, _) -> 29 | true. 30 | -------------------------------------------------------------------------------- /src/oc_sampler_impl.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc 16 | %% @end 17 | %%%------------------------------------------------------------------------ 18 | 19 | 20 | -module(oc_sampler_impl). 21 | 22 | -export([should_sample/3]). 23 | 24 | -dialyzer({nowarn_function, should_sample/3}). 25 | 26 | should_sample(_TraceId, _SpanId, _Enabled) -> 27 | erlang:error("Please start opencensus app first"). 28 | -------------------------------------------------------------------------------- /src/oc_sampler_never.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc A sampler that always returns false. 16 | %% @end 17 | %%%----------------------------------------------------------------------- 18 | -module(oc_sampler_never). 19 | 20 | -behaviour(oc_sampler). 21 | 22 | -export([init/1, 23 | should_sample/4]). 24 | 25 | init(_) -> 26 | ok. 27 | 28 | should_sample(_, _, _, _) -> 29 | false. 30 | -------------------------------------------------------------------------------- /src/oc_sampler_period_or_count.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc 16 | %% This sampler makes sure you will have at least 1 sample during `period' 17 | %% or `1/count' samples otherwise. 18 | %% @end 19 | %%%----------------------------------------------------------------------- 20 | -module(oc_sampler_period_or_count). 21 | 22 | -behaviour(oc_sampler). 23 | 24 | -export([init/1, 25 | should_sample/4]). 26 | 27 | -define(DEFAULT_PERIOD, 10). 28 | -define(DEFAULT_COUNT, 1000). 29 | -define(ETS_TABLE, sampler_period_or_count). 30 | 31 | %% public 32 | 33 | init(Opts) -> 34 | _ = ets:new(?ETS_TABLE, [named_table, public]), 35 | 36 | Period0 = proplists:get_value(period, Opts, ?DEFAULT_PERIOD), 37 | %% TODO: check Period0 is a non-negative integer 38 | Count = proplists:get_value(count, Opts, ?DEFAULT_COUNT), 39 | %% TODO: check Counter is a non-negative? integer 40 | 41 | CountThreshold = Count, 42 | Period = erlang:convert_time_unit(Period0, second, native), 43 | 44 | _ = ets:insert(?ETS_TABLE, {sampler, erlang:monotonic_time(), CountThreshold}), 45 | {Period, CountThreshold}. 46 | 47 | should_sample(_TraceId, _, _, {Period, CountThreshold}) -> 48 | should_sample(Period, CountThreshold). 49 | 50 | 51 | %% private 52 | 53 | -define(COUNT_PART(Count, Now), {{sampler, '$1', '$2'}, 54 | [{'>=', '$2', CountThreshold}], 55 | [{{sampler, Now, 0}}]}). 56 | 57 | -define(PERIOD_PART(Period, Now), {{sampler, '$1', '$2'}, 58 | [{'>=', {'-', Now, '$1'}, Period}], 59 | [{{sampler, Now, '$2'}}]}). 60 | 61 | should_sample(0, 0) -> 62 | false; 63 | should_sample(_, 1) -> 64 | true; 65 | should_sample(0, CountThreshold) -> 66 | ets:update_counter(?ETS_TABLE, sampler, {3, 1, CountThreshold, 1}) =:= 1; 67 | should_sample(Period, 0) -> 68 | Now = erlang:monotonic_time(), 69 | ets:select_replace(?ETS_TABLE, 70 | [?PERIOD_PART(Period, Now)]) > 0; 71 | should_sample(Period, CountThreshold) -> 72 | Now = erlang:monotonic_time(), 73 | Res = ets:select_replace(?ETS_TABLE, 74 | [?COUNT_PART(CountThreshold, Now), 75 | ?PERIOD_PART(Period, Now) 76 | ]) > 0, 77 | 78 | ets:update_counter(?ETS_TABLE, sampler, {3, 1}), 79 | Res. 80 | -------------------------------------------------------------------------------- /src/oc_sampler_probability.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc This sampler assumes the lower 64 bits of the trace id are 16 | %% randomly distributed around the whole (long) range. The sampler creates 17 | %% an upper bound id based on the configured probability and compares the 18 | %% lower 64 bits of the trace id to for the sampling decision. 19 | %% @end 20 | %%%----------------------------------------------------------------------- 21 | -module(oc_sampler_probability). 22 | 23 | -behaviour(oc_sampler). 24 | 25 | -include_lib("syntax_tools/include/merl.hrl"). 26 | 27 | -export([init/1, 28 | should_sample/4]). 29 | 30 | -define(MAX_VALUE, 9223372036854775807). 31 | 32 | -define(DEFAULT_PROBABILITY, 0.5). 33 | 34 | init(Opts) -> 35 | case proplists:get_value(probability, Opts, ?DEFAULT_PROBABILITY) of 36 | P when P =:= 0.0 -> 37 | IdUpperBound = 0; 38 | P when P =:= 1.0 -> 39 | IdUpperBound = ?MAX_VALUE; 40 | P when P >= 0.0 41 | , P =< 1.0 -> 42 | IdUpperBound = (P * ?MAX_VALUE) 43 | end, 44 | IdUpperBound. 45 | 46 | %% probability sampler keeps parent decision if it is true 47 | should_sample(_, _, true, _) -> 48 | true; 49 | should_sample(TraceId, _, _, IdUpperBound) -> 50 | Lower64Bits = TraceId band ?MAX_VALUE, 51 | erlang:abs(Lower64Bits) < IdUpperBound. 52 | -------------------------------------------------------------------------------- /src/oc_self_producer.erl: -------------------------------------------------------------------------------- 1 | %% @doc 2 | %% Produces metrics for internal state like spans queue size, views count, etc. 3 | -module(oc_self_producer). 4 | 5 | -export([read/2]). 6 | 7 | -include("oc_metrics.hrl"). 8 | -include("opencensus.hrl"). 9 | 10 | read(_Registry, Callback) -> 11 | spans_buffer_metrics(Callback). 12 | 13 | spans_buffer_metrics(Callback) -> 14 | {Count, Bytes} = oc_span_sweeper:storage_size(), 15 | Callback(#oc_metric{descriptor = #oc_metric_descriptor{ 16 | name = "oc_span_buffer_bytes", 17 | description = "Size of the spans ETS table", 18 | type = 'GAUGE_INT64' 19 | }, 20 | timeseries = [#oc_time_series{ 21 | points = [#oc_point{timestamp = wts:timestamp(), 22 | value = Bytes}] 23 | } 24 | ] 25 | }), 26 | Callback(#oc_metric{descriptor = #oc_metric_descriptor{ 27 | name = "oc_span_buffer_size", 28 | description = "Count of spans in the ETS table", 29 | type = 'GAUGE_INT64' 30 | }, 31 | timeseries = [#oc_time_series{ 32 | points = [#oc_point{timestamp = wts:timestamp(), 33 | value = Count}] 34 | } 35 | ] 36 | }). 37 | -------------------------------------------------------------------------------- /src/oc_server.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc Server with no logic, simply owns the span ets table. 16 | %% @end 17 | %%%------------------------------------------------------------------------- 18 | -module(oc_server). 19 | 20 | -export([start_link/0]). 21 | 22 | -export([init/1, 23 | handle_call/3, 24 | handle_cast/2]). 25 | 26 | -include("opencensus.hrl"). 27 | 28 | -record(state, {}). 29 | 30 | start_link() -> 31 | maybe_init_ets(), 32 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 33 | 34 | init([]) -> 35 | {ok, #state{}}. 36 | 37 | handle_call(_, _From, State) -> 38 | {noreply, State}. 39 | 40 | handle_cast(_, State) -> 41 | {noreply, State}. 42 | 43 | maybe_init_ets() -> 44 | case ets:info(?SPAN_TAB, name) of 45 | undefined -> 46 | ets:new(?SPAN_TAB, [named_table, public, {write_concurrency, true}, {keypos, #span.span_id}]); 47 | _ -> 48 | ok 49 | end. 50 | -------------------------------------------------------------------------------- /src/oc_span_sweeper.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | -module(oc_span_sweeper). 16 | 17 | -behaviour(gen_statem). 18 | 19 | -export([start_link/0]). 20 | 21 | -export([init/1, 22 | callback_mode/0, 23 | handle_event/4, 24 | code_change/4, 25 | terminate/3]). 26 | 27 | -export([storage_size/0]). 28 | 29 | -include("opencensus.hrl"). 30 | -include("oc_logger.hrl"). 31 | 32 | -record(data, {interval :: integer() | infinity, 33 | strategy :: drop | finish | failed_attribute_and_finish | fun((opencensus:span()) -> ok), 34 | ttl :: integer() | infinity, 35 | storage_size :: integer() | infinity}). 36 | 37 | storage_size() -> 38 | {ets:info(?SPAN_TAB, size), ets:info(?SPAN_TAB, memory) * erlang:system_info({wordsize, external})}. 39 | 40 | start_link() -> 41 | gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []). 42 | 43 | init([]) -> 44 | SweeperConfig = application:get_env(opencensus, sweeper, #{}), 45 | 46 | Interval = maps:get(interval, SweeperConfig, timer:minutes(5)), 47 | Strategy = maps:get(strategy, SweeperConfig, drop), 48 | TTL = maps:get(span_ttl, SweeperConfig, timer:minutes(5)), 49 | StorageSize = maps:get(storage_size, SweeperConfig, infinity), 50 | {ok, ready, #data{interval=Interval, 51 | strategy=Strategy, 52 | ttl=maybe_convert_time_unit(TTL), 53 | storage_size=StorageSize}, 54 | [hibernate, {state_timeout, Interval, sweep}]}. 55 | 56 | 57 | maybe_convert_time_unit(infinity) -> 58 | infinity; 59 | maybe_convert_time_unit(TTL) -> 60 | erlang:convert_time_unit(TTL, millisecond, native). 61 | 62 | callback_mode() -> 63 | handle_event_function. 64 | 65 | handle_event(state_timeout, sweep, _, #data{interval=Interval} = Data) -> 66 | do_gc(Data), 67 | {keep_state_and_data, [hibernate, {state_timeout, Interval, sweep}]}; 68 | handle_event(_, _, _, _Data) -> 69 | keep_state_and_data. 70 | 71 | code_change(_, State, Data, _) -> 72 | {ok, State, Data}. 73 | 74 | terminate(_Reason, _State, _Data) -> 75 | ok. 76 | 77 | %% 78 | do_gc(#data{strategy=Strategy, 79 | ttl=TTL, 80 | storage_size=infinity}) -> 81 | sweep_spans(Strategy, TTL); 82 | do_gc(#data{strategy=Strategy, 83 | ttl=TTL, 84 | storage_size=MaxSize}) -> 85 | 86 | {_, StorageSize} = storage_size(), 87 | 88 | if 89 | StorageSize >= 2 * MaxSize -> 90 | %% High overload kill storage. 91 | ets:delete_all_objects(?SPAN_TAB); 92 | StorageSize >= MaxSize -> 93 | %% Low overload, reduce TTL 94 | sweep_spans(Strategy, overload_ttl(TTL)); 95 | true -> 96 | sweep_spans(Strategy, TTL) 97 | end. 98 | 99 | overload_ttl(infinity) -> 100 | infinity; 101 | overload_ttl(TTL) -> 102 | TTL div 10. 103 | 104 | sweep_spans(_, infinity) -> 105 | ok; 106 | sweep_spans(drop, TTL) -> 107 | TooOld = erlang:monotonic_time() - TTL, 108 | case ets:select_delete(?SPAN_TAB, expired_match_spec(TooOld, true)) of 109 | 0 -> 110 | ok; 111 | NumDeleted -> 112 | ?LOG_INFO("sweep old spans: ttl=~p num_dropped=~p", [TTL, NumDeleted]) 113 | end; 114 | sweep_spans(finish, TTL) -> 115 | Expired = select_expired(TTL), 116 | [finish_span(Span) || Span <- Expired], 117 | ok; 118 | sweep_spans(failed_attribute_and_finish, TTL) -> 119 | Expired = select_expired(TTL), 120 | [finish_span(oc_span:put_attribute(<<"finished_by_sweeper">>, true, Span)) || Span <- Expired], 121 | ok; 122 | sweep_spans(Fun, TTL) when is_function(Fun) -> 123 | Expired = select_expired(TTL), 124 | [Fun(Span) || Span <- Expired], 125 | ok. 126 | 127 | %% ignore these functions because dialyzer doesn't like match spec use of '_' 128 | -dialyzer({nowarn_function, expired_match_spec/2}). 129 | -dialyzer({nowarn_function, finish_span/1}). 130 | -dialyzer({nowarn_function, select_expired/1}). 131 | 132 | expired_match_spec(Time, Return) -> 133 | [{#span{start_time={'$1', '_'}, _='_'}, 134 | [{'<', '$1', Time}], 135 | [Return]}]. 136 | 137 | finish_span(S=#span{span_id=SpanId, 138 | tracestate=Tracestate}) -> 139 | %% hack to not lose tracestate when finishing without span ctx 140 | oc_span:finish_span(#span_ctx{tracestate=Tracestate}, S), 141 | ets:delete(?SPAN_TAB, SpanId). 142 | 143 | select_expired(TTL) -> 144 | TooOld = erlang:monotonic_time() - TTL, 145 | ets:select(?SPAN_TAB, expired_match_spec(TooOld, '$_')). 146 | -------------------------------------------------------------------------------- /src/oc_span_transform.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc oc_transform provides a parse transform for wrapping a function in 16 | %% a start and a finish of a span. 17 | %% @end 18 | %%%----------------------------------------------------------------------- 19 | 20 | -module(oc_span_transform). 21 | 22 | -export([parse_transform/2, 23 | format_error/1]). 24 | 25 | parse_transform(Ast, _Options)-> 26 | try lists:mapfoldl(fun form/2, {false, [], []}, Ast) of 27 | {Ast1, _} -> 28 | lists:flatten(lists:filter(fun(Node) -> Node =/= nil end, Ast1)) 29 | catch 30 | throw:E -> 31 | E 32 | end. 33 | 34 | form(Node={attribute, _Line, module, Module}, _) -> 35 | {Node, {false, Module, []}}; 36 | form({attribute, _Line, span, Args}, {_, Module, _}) -> 37 | {nil, {true, Module, Args}}; 38 | form(Node={function, _Line, _FuncName, _Arity, _Clauses}, {false, Module, []}) -> 39 | {Node, {false, Module, []}}; 40 | form(Node={function, _Line, _FuncName, _Arity, _Clauses}, {true, Module, Args}) -> 41 | {trace(Node, Module, Args), {false, Module, []}}; 42 | form(Node, Trace) -> 43 | {Node, Trace}. 44 | 45 | trace({function, Line, Name, Arity, Clauses}, Module, Args) -> 46 | case args_proplist(Args) of 47 | {error, Reason} -> 48 | {error, {Line, ?MODULE, Reason}}; 49 | ArgsPropList -> 50 | SpanName = proplists:get_value(name, ArgsPropList, io_lib:format("~s:~s/~w", [Module, Name, Arity])), 51 | Clauses1 = trace_clauses(Clauses, SpanName), 52 | {function, Line, Name, Arity, Clauses1} 53 | end. 54 | 55 | trace_clauses([], _) -> 56 | []; 57 | trace_clauses([{clause, Line, H, G, B} | Cs], Name) -> 58 | CurrentSpan = make_varname("CurrentSpan", Line), 59 | StartSpan = [{match, Line, {var, Line, CurrentSpan}, 60 | {call, Line, 61 | {remote, Line, {atom, Line, ocp}, {atom, Line, current_span_ctx}}, 62 | []}}, 63 | {call, Line, 64 | {remote, Line, {atom, Line, ocp}, {atom, Line, with_child_span}}, 65 | [{string, Line, to_binary(Name)}]}], 66 | FinishSpan = [{call, Line, 67 | {remote, Line, {atom, Line, ocp}, {atom, Line, finish_span}}, 68 | []}, 69 | {call, Line, 70 | {remote, Line, {atom, Line, ocp}, {atom, Line, with_span_ctx}}, 71 | [{var, Line, CurrentSpan}]}], 72 | Trace = StartSpan ++ [{'try', Line, B, [], [], FinishSpan}], 73 | [{clause, Line, H, G, Trace} | trace_clauses(Cs, Name)]. 74 | 75 | make_varname(Prefix, Line) -> 76 | list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)). 77 | 78 | to_binary(X) when is_binary(X) -> 79 | X; 80 | to_binary(X) when is_list(X) -> 81 | list_to_binary(X); 82 | to_binary(X) when is_atom(X) -> 83 | atom_to_binary(X, utf8). 84 | 85 | args_proplist(A) when is_binary(A) -> 86 | [{name, A}]; 87 | args_proplist(A) when is_atom(A) -> 88 | [{name, A}]; 89 | args_proplist([]) -> 90 | []; 91 | args_proplist(A) when is_list(A) -> 92 | try unicode:characters_to_nfc_binary(A) of 93 | B -> 94 | [{name, B}] 95 | catch 96 | %% must not be a string, use as a proplist 97 | error:function_clause -> 98 | A 99 | end; 100 | args_proplist(A) -> 101 | {error, {bad_trace_args, A}}. 102 | 103 | format_error({bad_trace_args, Args}) -> 104 | io_lib:format("Bad trace arguments. Must be binary, atom, list string or proplist. Got: ~p", [Args]); 105 | format_error({bad_name, Args}) -> 106 | io_lib:format("Bad span name. Name must be an atom, binary or printable list. Got: ~p", [Args]). 107 | -------------------------------------------------------------------------------- /src/oc_stat.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc OpenCensus Stats package 16 | %% @end 17 | %%%----------------------------------------------------------------------- 18 | -module(oc_stat). 19 | 20 | -export([record/2, 21 | record/3, 22 | export/0]). 23 | 24 | -export([start_link/0, 25 | init/1, 26 | handle_call/3, 27 | handle_cast/2, 28 | handle_info/2, 29 | code_change/3, 30 | terminate/2]). 31 | 32 | -record(state, {}). 33 | 34 | -include("opencensus.hrl"). 35 | 36 | -define(RECORD(Tags, MeasureName, Value), 37 | begin 38 | Module = oc_stat_measure:measure_module(MeasureName), 39 | Module:record(Tags, Value), 40 | ok 41 | end). 42 | 43 | %% @doc 44 | %% Records one or multiple measurements with the same tags at once. 45 | %% If there are any tags in the context, measurements will be tagged with them. 46 | %% 47 | %% Can be optimized with `oc_stat_measure' parse transform. 48 | %% 49 | %% Raises `{unknown_measure, MeasureName}' if measure doesn't exist. 50 | %% @end 51 | -spec record(ctx:t() | oc_tags:tags(), oc_stat_measure:name(), number()) -> ok. 52 | record(Tags, MeasureName, Value) when is_map(Tags) -> 53 | ?RECORD(Tags, MeasureName, Value); 54 | record(Ctx, MeasureName, Value)-> 55 | Tags = oc_tags:from_ctx(Ctx), 56 | ?RECORD(Tags, MeasureName, Value). 57 | 58 | %% @doc 59 | %% Records multiple measurements at once. 60 | %% 61 | %% Can be optimized with `oc_stat_measure' parse transform. 62 | %% 63 | %% Raises `{unknown_measure, MeasureName}' if measure doesn't exist. 64 | %% @end 65 | -spec record(ctx:t() | oc_tags:tags(), [{oc_stat_measure:name(), number()}]) -> ok. 66 | record(Tags, Measures) when is_map(Tags) -> 67 | [?RECORD(Tags, MeasureName, Value) || {MeasureName, Value} <- Measures], 68 | ok; 69 | record(Ctx, Measures) -> 70 | Tags = oc_tags:from_ctx(Ctx), 71 | record(Tags, Measures). 72 | 73 | %% @doc Exports view_data of all subscribed views 74 | -spec export() -> [oc_stat_view:view_data()]. 75 | export() -> 76 | [oc_stat_view:export(View) || View <- oc_stat_view:all_subscribed_()]. 77 | 78 | %% gen_server implementation 79 | 80 | %% @private 81 | start_link() -> 82 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 83 | 84 | %% @private 85 | init(_Args) -> 86 | process_flag(trap_exit, true), 87 | ok = oc_stat_view:'__init_backend__'(), 88 | ok = oc_stat_measure:'__init_backend__'(), 89 | {ok, #state{}}. 90 | 91 | %% @private 92 | handle_call({measure_register, Measure}, _From, State) -> 93 | {reply, oc_stat_measure:register_(Measure), State}; 94 | handle_call({view_register_subscribe, View}, _From, State) -> 95 | {reply, oc_stat_view:register_subscribe_(View), State}; 96 | handle_call({view_register, View}, _From, State) -> 97 | {reply, oc_stat_view:register_(View), State}; 98 | handle_call({view_deregister, Name}, _From, State) -> 99 | oc_stat_view:deregister_(Name), 100 | {reply, ok, State}; 101 | handle_call({view_subscribe, Name}, _From, State) -> 102 | {reply, oc_stat_view:subscribe_(Name), State}; 103 | handle_call({view_unsubscribe, Name}, _From, State) -> 104 | {reply, oc_stat_view:unsubscribe_(Name), State}; 105 | handle_call(_, _From, State) -> 106 | {noreply, State}. 107 | 108 | %% @private 109 | handle_cast(_, State) -> 110 | {noreply, State}. 111 | 112 | %% @private 113 | handle_info(_, State) -> 114 | {noreply, State}. 115 | 116 | %% @private 117 | code_change(_OldVsn, State, _Extra) -> 118 | {ok, State}. 119 | 120 | %% @private 121 | terminate(_, _) -> 122 | oc_stat_measure:terminate_(), 123 | ok. 124 | -------------------------------------------------------------------------------- /src/oc_stat_aggregation.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc 16 | %% Aggregation represents a data aggregation method. 17 | %% @end 18 | %%%----------------------------------------------------------------------- 19 | 20 | -module(oc_stat_aggregation). 21 | 22 | -export([convert/3]). 23 | 24 | -export_type([data/0]). 25 | 26 | -type data_rows(AggregationValue) :: [#{tags := tv(), 27 | value := AggregationValue}]. 28 | 29 | -type data(Type, AggregationValue) :: #{type := Type, 30 | rows := data_rows(AggregationValue)}. 31 | 32 | -type data() :: data(latest, number()) 33 | | data(count, number()) 34 | | data(sum, #{count := non_neg_integer(), 35 | mean := number(), 36 | sum := number()}) 37 | | data(distribution, #{count := non_neg_integer(), 38 | mean := number(), 39 | sum := number(), 40 | buckets := [{number(), non_neg_integer()}]}). 41 | 42 | -type keys() :: [oc_tags:key()]. 43 | -type tv() :: [oc_tags:value()]. 44 | 45 | -callback init(oc_stat_view:name(), keys(), any()) -> any(). 46 | 47 | -callback type() -> latest | count | sum | distribution. 48 | 49 | -callback add_sample(oc_stat_view:name(), oc_tags:tags(), number(), any()) -> ok. 50 | 51 | -callback export(oc_stat_view:name(), any()) -> data(). 52 | 53 | -callback clear_rows(oc_stat_view:name(), any()) -> ok. 54 | 55 | %% @private 56 | convert(Data, _From, undefined) -> 57 | Data; 58 | convert(#{type := Type, 59 | rows := Rows}, From, To) -> 60 | #{type => Type, 61 | rows => convert_rows(Type, Rows, From, To)}. 62 | 63 | convert_rows(count, Rows, _From, _To) -> 64 | Rows; 65 | convert_rows(latest, Rows, From, To) -> 66 | [Row#{value => oc_stat_unit:convert(Value, From, To)} 67 | || #{value := Value}=Row <- Rows]; 68 | convert_rows(sum, Rows, From, To) -> 69 | [Row#{value => Value#{sum => oc_stat_unit:convert(Sum, From, To), 70 | mean => oc_stat_unit:convert(Mean, From, To)}} 71 | || #{value := #{sum := Sum, 72 | mean := Mean}=Value}=Row <- Rows]; 73 | convert_rows(distribution, Rows, From, To) -> 74 | [Row#{value => Value#{sum => oc_stat_unit:convert(Sum, From, To), 75 | mean => oc_stat_unit:convert(Mean, From, To), 76 | buckets => convert_buckets(Buckets, From, To)}} 77 | || #{value := #{sum := Sum, 78 | mean := Mean, 79 | buckets := Buckets}=Value}=Row <- Rows]. 80 | 81 | convert_buckets(Buckets, From, To) -> 82 | [{maybe_convert_bound(Bound, From, To), Counter} || {Bound, Counter} <- Buckets]. 83 | 84 | maybe_convert_bound(infinity, _From, _To) -> 85 | infinity; 86 | maybe_convert_bound(Bound, From, To) -> 87 | oc_stat_unit:convert(Bound, From, To). 88 | -------------------------------------------------------------------------------- /src/oc_stat_aggregation_count.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc 16 | %% Count indicates that data collected and aggregated 17 | %% with this method will be turned into a count value. 18 | %% For example, total number of accepted requests can be 19 | %% aggregated by using Count. 20 | %% @end 21 | %%%----------------------------------------------------------------------- 22 | 23 | -module(oc_stat_aggregation_count). 24 | 25 | -include("opencensus.hrl"). 26 | 27 | -export([init/3, 28 | type/0, 29 | add_sample/4, 30 | export/2, 31 | clear_rows/2]). 32 | 33 | init(_Name, _Keys, Options) -> 34 | Options. 35 | 36 | type() -> 37 | count. 38 | 39 | -spec add_sample(oc_stat_view:name(), oc_tags:tags(), number(), any()) -> ok. 40 | add_sample(Name, Tags, _Value, Options) -> 41 | case counters_simple:inc(Name, Tags, 1) of 42 | unknown -> 43 | case counters_simple:new(Name, Tags, 1) of 44 | ok -> ok; 45 | false -> 46 | add_sample(Name, Tags, 1, Options) 47 | end; 48 | _ -> 49 | ok 50 | end. 51 | 52 | export(Name, _Options) -> 53 | Rows = maps:values(maps:map(fun(Tags, Value) -> 54 | #{tags => Tags, 55 | value => Value} 56 | end, 57 | counters_simple:value(Name))), 58 | #{type => type(), 59 | rows => Rows}. 60 | 61 | clear_rows(Name, _Options) -> 62 | counters_simple:remove(Name), 63 | ok. 64 | -------------------------------------------------------------------------------- /src/oc_stat_aggregation_distribution.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc 16 | %% Distribution indicates that the desired aggregation is 17 | %% a histogram distribution. 18 | %% @end 19 | %%%----------------------------------------------------------------------- 20 | 21 | -module(oc_stat_aggregation_distribution). 22 | 23 | -export([init/3, 24 | type/0, 25 | add_sample/4, 26 | export/2, 27 | clear_rows/2]). 28 | 29 | -behavior(oc_stat_aggregation). 30 | 31 | -include("opencensus.hrl"). 32 | 33 | init(_Name, _Keys, Options) -> 34 | Buckets = counters_buckets:new(proplists:get_value(buckets, Options, default)), 35 | Buckets. 36 | 37 | type() -> 38 | distribution. 39 | 40 | -spec add_sample(oc_stat_view:name(), oc_tags:tags(), number(), any()) -> ok. 41 | add_sample(Name, Tags, Value, Buckets) -> 42 | Bound = counters_buckets:bound(Buckets, Value), 43 | case counters_distribution:observe(Name, Tags, Bound, Value) of 44 | unknown -> 45 | case counters_distribution:new(Name, Tags, Bound, 1, Value) of 46 | ok -> ok; 47 | false -> 48 | add_sample(Name, Tags, Value, Buckets) 49 | end; 50 | _ -> 51 | ok 52 | end. 53 | 54 | export(Name, Buckets) -> 55 | Rows = maps:values( 56 | maps:map(fun(Tags, BMap) -> 57 | {Count, Sum} = maps:fold(fun(_Bound, {C, S}, {Ca, Sa}) -> 58 | {Ca + C, Sa + S} 59 | end, {0, 0}, BMap), 60 | 61 | BucketsE = [begin 62 | {C, _} = maps:get(Bound, BMap, {0, 0}), 63 | {Bound, C} 64 | end || Bound <- Buckets], 65 | 66 | Mean = case Count of 67 | 0 -> 0; 68 | _ -> Sum / Count 69 | end, 70 | #{tags => Tags, 71 | value => #{count => Count, 72 | sum => Sum, 73 | mean => Mean, 74 | buckets => BucketsE}} 75 | end, 76 | counters_distribution:value(Name))), 77 | #{type => type(), 78 | rows => Rows}. 79 | 80 | clear_rows(Name, _Options) -> 81 | counters_distribution:remove(Name), 82 | ok. 83 | -------------------------------------------------------------------------------- /src/oc_stat_aggregation_latest.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc 16 | %% Latest indicates that only last data point is saved. 17 | %% @end 18 | %%%----------------------------------------------------------------------- 19 | 20 | -module(oc_stat_aggregation_latest). 21 | 22 | -export([init/3, 23 | type/0, 24 | add_sample/4, 25 | export/2, 26 | clear_rows/2]). 27 | 28 | -behavior(oc_stat_aggregation). 29 | 30 | init(_Name, _Keys, Options) -> 31 | Options. 32 | 33 | type() -> 34 | latest. 35 | 36 | -spec add_sample(oc_stat_view:name(), oc_tags:tags(), number(), any()) -> ok. 37 | add_sample(Name, Tags, Value, Options) -> 38 | case counters_counter:set(Name, Tags, Value) of 39 | unknown -> 40 | case counters_counter:new(Name, Tags, Value) of 41 | ok -> ok; 42 | false -> 43 | add_sample(Name, Tags, Value, Options) 44 | end; 45 | _ -> 46 | ok 47 | end. 48 | 49 | export(Name, _Options) -> 50 | Rows = maps:values(maps:map(fun(Tags, Value) -> 51 | #{tags => Tags, 52 | value => Value} 53 | end, 54 | counters_counter:value(Name))), 55 | #{type => type(), 56 | rows => Rows}. 57 | 58 | clear_rows(Name, _Options) -> 59 | counters_counter:remove(Name), 60 | ok. 61 | -------------------------------------------------------------------------------- /src/oc_stat_aggregation_sum.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc 16 | %% Sum indicates that data collected and aggregated 17 | %% with this method will be summed up. 18 | %% For example, accumulated request bytes can be aggregated by using 19 | %% Sum. 20 | %% @end 21 | %%%----------------------------------------------------------------------- 22 | 23 | -module(oc_stat_aggregation_sum). 24 | 25 | -export([init/3, 26 | type/0, 27 | add_sample/4, 28 | export/2, 29 | clear_rows/2]). 30 | 31 | -behavior(oc_stat_aggregation). 32 | 33 | init(_Name, _Keys, Options) -> 34 | Options. 35 | 36 | type() -> 37 | sum. 38 | 39 | -spec add_sample(oc_stat_view:name(), oc_tags:tags(), number(), any()) -> ok. 40 | add_sample(Name, Tags, Value, Options) -> 41 | case counters_sum:observe(Name, Tags, Value) of 42 | unknown -> 43 | case counters_sum:new(Name, Tags, 1, Value) of 44 | ok -> ok; 45 | false -> 46 | add_sample(Name, Tags, Value, Options) 47 | end; 48 | _ -> 49 | ok 50 | end. 51 | 52 | export(Name, _Options) -> 53 | Rows = maps:values(maps:map(fun(Tags, {Count, Sum}) -> 54 | Mean = case Count of 55 | 0 -> 0; 56 | _ -> Sum / Count 57 | end, 58 | #{tags => Tags, 59 | value => #{count => Count, 60 | sum => Sum, 61 | mean => Mean}} 62 | end, 63 | counters_sum:value(Name))), 64 | #{type => type(), 65 | rows => Rows}. 66 | 67 | clear_rows(Name, _Options) -> 68 | counters_sum:remove(Name), 69 | ok. 70 | -------------------------------------------------------------------------------- /src/oc_stat_config.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc stats configuration 16 | %% @end 17 | %%%------------------------------------------------------------------------ 18 | 19 | -module(oc_stat_config). 20 | 21 | -export([views/0, 22 | export_interval/0, 23 | exporters/0]). 24 | 25 | -define(DEFAULT_VIEWS, []). 26 | -define(DEFAULT_EXPORTERS, []). 27 | -define(DEFAULT_EXPORT_INTERVAL, 5000). 28 | 29 | views() -> 30 | proplists:get_value(views, stat_conf(), ?DEFAULT_VIEWS). 31 | 32 | export_interval() -> 33 | proplists:get_value(export_interval, stat_conf(), ?DEFAULT_EXPORT_INTERVAL). 34 | 35 | exporters() -> 36 | proplists:get_value(exporters, stat_conf(), ?DEFAULT_EXPORTERS). 37 | 38 | stat_conf() -> 39 | application:get_env(opencensus, stat, []). 40 | -------------------------------------------------------------------------------- /src/oc_stat_exporter_stdout.erl: -------------------------------------------------------------------------------- 1 | -module(oc_stat_exporter_stdout). 2 | 3 | -export([export/2]). 4 | 5 | export(ViewData, _) -> 6 | [io:format("~s: ~p~n", [Name, Data]) || #{name := Name, 7 | data := Data} <- ViewData]. 8 | -------------------------------------------------------------------------------- /src/oc_stat_transform.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc 16 | %% @end 17 | %%%----------------------------------------------------------------------- 18 | -module(oc_stat_transform). 19 | 20 | -export([parse_transform/2]). 21 | 22 | %% @doc 23 | %% `oc_stat_transform' is a parse transform that can detect `oc_stat:record' calls 24 | %% with constant measure names and generate remote measure module call from that. 25 | %% At the run-time this means we don't have to do a lookup for the module name and 26 | %% if measure doesn't exist, `{unknown_measure, Name}' error will be thrown. 27 | %% @end 28 | parse_transform(Forms, _Options) -> 29 | HiForms = lists:map(fun walk_ast/1, Forms), 30 | HiForms. 31 | 32 | walk_ast({function, Line, Name, Args, Clauses}) -> 33 | {function, Line, Name, Args, walk_clauses([], Clauses)}; 34 | 35 | walk_ast(Form) -> 36 | Form. 37 | 38 | walk_clauses(Acc, []) -> 39 | lists:reverse(Acc); 40 | walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|Rest]) -> 41 | reset_gensym(), 42 | walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], Rest). 43 | 44 | walk_body(Acc, []) -> 45 | lists:reverse(Acc); 46 | walk_body(Acc, [H|R]) -> 47 | walk_body([transform_statement(H)|Acc], R). 48 | 49 | transform_statement({call, Line, 50 | {remote, _, {atom, _, oc_stat}, {atom, _, record}}, 51 | [Tags, {cons, _, _, _} = Measurements]}=_Stmt) -> 52 | gen_record_calls(Line, Tags, erl_syntax:list_elements(Measurements)); 53 | transform_statement({call, Line, 54 | {remote, _, {atom, _, oc_stat}, {atom, _, record}}, 55 | [Tags, {MType, _, _}=Measurement, Value]}=_Stmt) 56 | when is_atom(MType) orelse is_binary(MType) orelse is_list(MType) -> 57 | gen_record_calls(Line, Tags, [{tuple, Line, [Measurement, Value]}]); 58 | transform_statement(Stmt) when is_tuple(Stmt) -> 59 | list_to_tuple(transform_statement(tuple_to_list(Stmt))); 60 | transform_statement(Stmt) when is_list(Stmt) -> 61 | [transform_statement(S) || S <- Stmt]; 62 | transform_statement(Stmt) -> 63 | Stmt. 64 | 65 | %% ============================================================================= 66 | %% private 67 | %% ============================================================================= 68 | 69 | gen_record_calls(Line, Tags, Measurements) -> 70 | CTags = {var, Line, gensym("CTags")}, 71 | GTags = {var, Line, gensym("GTags")}, 72 | {block, Line, 73 | [{match, Line, CTags, Tags}, 74 | {match, Line, GTags, gen_prepare_tags(Line, CTags)}] 75 | ++ 76 | [measure_module_record_call(Line, MeasureName, GTags, Value) 77 | || {tuple, _, [{_, _, MeasureName}, Value]} <- Measurements]}. 78 | 79 | measure_module_record_call(Line, MeasureName, GTags, Value) -> 80 | {'try', Line, 81 | [{call, Line, 82 | {remote, Line, {atom, Line, oc_stat_measure:module_name(MeasureName)}, {atom, Line, record}}, 83 | [GTags, Value]}], 84 | [{clause, Line, [{var, Line, '_'}], [], [{atom, 279, 'ok'}]}], 85 | [{clause, Line, 86 | [{tuple, Line, 87 | [{atom, Line, error}, {atom, Line, undef}, {var, Line, '_'}]}], 88 | [], 89 | [{call, Line, 90 | {remote, Line, {atom, Line, erlang}, {atom, Line, error}}, 91 | [{tuple, Line, 92 | [{atom, Line, unknown_measure}, erl_parse:abstract(MeasureName)]}]}]}], 93 | []}. 94 | 95 | gen_prepare_tags(Line, CTags) -> 96 | {'case', Line, CTags, 97 | [{clause, Line, 98 | [{var, Line, '_'}], 99 | [[{call, Line, {atom, Line, is_map}, [CTags]}]], 100 | [CTags]}, 101 | {clause, Line, 102 | [{var, Line, '_'}], 103 | [], 104 | [{call, Line, 105 | {remote, Line, {atom, Line, oc_tags}, {atom, Line, from_ctx}}, 106 | [CTags]}]}]}. 107 | 108 | gensym(Name) -> 109 | put(oc_gensym_counter, get(oc_gensym_counter) + 1), 110 | list_to_atom( 111 | lists:flatten( 112 | io_lib:format("$oc_gen_~s_~B$", [Name, get(oc_gensym_counter)]))). 113 | 114 | reset_gensym() -> 115 | put(oc_gensym_counter, 0). 116 | 117 | -------------------------------------------------------------------------------- /src/oc_stat_unit.erl: -------------------------------------------------------------------------------- 1 | -module(oc_stat_unit). 2 | 3 | -export([is_comparable/2, 4 | must_convert/1, 5 | convert/3]). 6 | 7 | %% =================================================================== 8 | %% API 9 | %% =================================================================== 10 | 11 | is_comparable(VUnit, MUnit) -> 12 | {CVUnit, _} = canonical(VUnit), 13 | {CMUnit, _} = canonical(MUnit), 14 | 15 | case CMUnit of 16 | arbitrary -> 17 | false; 18 | CVUnit -> 19 | true; 20 | _ -> 21 | false 22 | end. 23 | 24 | must_convert(native_time_unit) -> 25 | true; 26 | must_convert(_) -> 27 | false. 28 | 29 | convert(Value, From, To) -> 30 | case is_comparable(From, To) of 31 | false -> {error, {not_comparable_units, From, To}}; 32 | true -> 33 | {_, FMult} = canonical(From), 34 | {_, TMult} = canonical(To), 35 | Value * FMult / TMult 36 | end. 37 | 38 | %% =================================================================== 39 | %% Private functions 40 | %% =================================================================== 41 | 42 | canonical(native_time_unit) -> 43 | {native_time_unit, 1}; 44 | canonical(nanosecond) -> 45 | {native_time_unit, erlang:convert_time_unit(1, nanosecond, native)}; 46 | canonical(microsecond) -> 47 | {native_time_unit, erlang:convert_time_unit(1, microsecond, native)}; 48 | canonical(millisecond) -> 49 | {native_time_unit, erlang:convert_time_unit(1, millisecond, native)}; 50 | canonical(second) -> 51 | {native_time_unit, erlang:convert_time_unit(1, second, native)}; 52 | canonical(minute) -> 53 | {native_time_unit, 60 * erlang:convert_time_unit(1, second, native)}; 54 | canonical(hour) -> 55 | {native_time_unit, 3600 * erlang:convert_time_unit(1, second, native)}; 56 | canonical(day) -> 57 | {native_time_unit, 3600 * 24 * erlang:convert_time_unit(1, second, native)}; 58 | canonical(_) -> 59 | {arbitrary, arbitrary}. 60 | -------------------------------------------------------------------------------- /src/oc_tag_ctx_binary.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc 16 | %% @end 17 | %%%------------------------------------------------------------------------- 18 | -module(oc_tag_ctx_binary). 19 | 20 | -export([encode/1, 21 | decode/1, 22 | 23 | format_error/1]). 24 | 25 | -define(VERSION, 0). 26 | -define(TAG_CONTEXT_FIELD_ID, 0). 27 | -define(SIZE_LIMIT, 8192). %% bytes 28 | 29 | -spec encode(#{}) -> {ok, iolist()} | {error, any()}. 30 | encode(TagContext) -> 31 | try maps:fold(fun(Key0, Value0, {Size, Acc}) when Size < ?SIZE_LIMIT -> 32 | Key = normalize_proto_string(Key0), 33 | KeySize = length(Key), 34 | Value = normalize_proto_string(Value0), 35 | ValueSize = length(Value), 36 | EncodedKeySize = encode_varint(KeySize), 37 | EncodedValueSize = encode_varint(ValueSize), 38 | {Size+KeySize+ValueSize+size(EncodedKeySize)+size(EncodedValueSize), 39 | [Acc | [<>, EncodedKeySize, 40 | Key, EncodedValueSize, Value]]}; 41 | (_, _, _)-> 42 | throw({?MODULE, encoding_too_large}) 43 | end, {0, [<>]}, TagContext) of 44 | {_Size, IOList} -> 45 | {ok, IOList} 46 | catch 47 | throw:Reason -> 48 | {error, Reason} 49 | end. 50 | 51 | normalize_proto_string(PS) when is_atom(PS) -> 52 | atom_to_list(PS); 53 | normalize_proto_string(PS) when is_binary(PS) -> 54 | binary_to_list(PS); 55 | normalize_proto_string(PS) -> 56 | PS. 57 | 58 | -spec decode(binary()) -> {ok, oc_tags:tags()} | {error, any()}. 59 | decode(<<>>) -> 60 | {ok, oc_tags:new()}; 61 | decode(<>) -> 62 | case size(TagContext) of 63 | Size when Size =< ?SIZE_LIMIT -> 64 | decode(TagContext, {ok, oc_tags:new()}); 65 | _ -> 66 | %% no partial failures 67 | {error, {?MODULE, decoding_too_large}} 68 | end; 69 | decode(<>) -> 70 | {error, {?MODULE, {unsupported_version, V}}}. 71 | 72 | -spec decode(binary(), {ok, oc_tags:tags()} | {error, any()}) -> {ok, oc_tags:tags()} | {error, any()}. 73 | decode(<<>>, TagsOrError) -> 74 | TagsOrError; 75 | decode(_, Error={error, _}) -> 76 | Error; 77 | decode(<>, {ok, Tags}) -> 78 | {KeySize, Rest1} = decode_varint(Rest0), 79 | <> = Rest1, 80 | {ValueSize, Rest3} = decode_varint(Rest2), 81 | <> = Rest3, 82 | Key1 = unicode:characters_to_list(Key, latin1), 83 | Value1 = unicode:characters_to_list(Value, latin1), 84 | 85 | %% add tag and continue processing if no failure 86 | decode(Rest4, oc_tags:put(Key1, Value1, Tags)). 87 | 88 | format_error(encoding_too_large) -> 89 | io_lib:format("invalid tag context: size greater than the limit of ~p bytes", [?SIZE_LIMIT]); 90 | format_error(decoding_too_large) -> 91 | io_lib:format("invalid tag context: size greater than the limit of ~p bytes", [?SIZE_LIMIT]); 92 | format_error({unsupported_version, V}) -> 93 | io_lib:format("unsupported version of encoded tag context: ~p is greater than suported version ~p", [V, ?VERSION]). 94 | 95 | 96 | %% encode/decode of varints shamelessly stolen from https://github.com/whatyouhide/small_ints 97 | 98 | -spec encode_varint(integer()) -> binary(). 99 | encode_varint(I) when is_integer(I), I >= 0, I =< 127 -> 100 | <>; 101 | encode_varint(I) when is_integer(I), I > 127 -> 102 | <<1:1, (I band 127):7, (encode_varint(I bsr 7))/binary>>; 103 | encode_varint(I) -> 104 | erlang:error({badarg, I}). 105 | 106 | -spec decode_varint(binary()) -> {non_neg_integer(), binary()}. 107 | decode_varint(Bin) -> 108 | decode_varint(Bin, 0, 0). 109 | 110 | decode_varint(<<1:1, Number:7, Rest/binary>>, Position, Acc) -> 111 | decode_varint(Rest, Position + 7, (Number bsl Position) + Acc); 112 | decode_varint(<<0:1, Number:7, Rest/binary>>, Position, Acc) -> 113 | {(Number bsl Position) + Acc, Rest}. 114 | -------------------------------------------------------------------------------- /src/oc_tag_ctx_header.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------- 2 | %% Copyright 2018, OpenCensus Authors 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 | %% @doc Functions to support sending tags over http as an http header 16 | %% @end 17 | %%%------------------------------------------------------------------------- 18 | -module(oc_tag_ctx_header). 19 | 20 | -export([field_name/0, 21 | encode/1, 22 | decode/1, 23 | format_error/1]). 24 | 25 | -include("opencensus.hrl"). 26 | 27 | field_name() -> 28 | <<"tracestate">>. 29 | 30 | -spec encode(oc_tags:tags()) -> maybe(iodata()). 31 | encode(Tags) when map_size(Tags) =:= 0 -> 32 | <<"0">>; 33 | encode(Tags) -> 34 | {ok, IOList} = oc_tag_ctx_binary:encode(Tags), 35 | base64:encode_to_string(iolist_to_binary(IOList)). 36 | 37 | -spec decode(iodata()) -> {ok, oc_tags:tags()} | {error, any()}. 38 | decode("0") -> 39 | {ok, oc_tags:new()}; 40 | decode(<<"0">>) -> 41 | {ok, oc_tags:new()}; 42 | decode(Thing) -> 43 | try base64:decode(iolist_to_binary(Thing)) of 44 | Data -> 45 | oc_tag_ctx_binary:decode(Data) 46 | catch 47 | _:_ -> 48 | {error, {?MODULE, base64_decode_failed}} 49 | end. 50 | 51 | format_error(base64_decode_failed) -> 52 | "invalid tag context: tag context header value not base64 encoded". 53 | -------------------------------------------------------------------------------- /src/oc_tags.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc 16 | %% @end 17 | %%%------------------------------------------------------------------------- 18 | -module(oc_tags). 19 | 20 | -export([new/0, 21 | new/1, 22 | new/2, 23 | update/2, 24 | new_ctx/2, 25 | from_ctx/1, 26 | to_map/1, 27 | put/3, 28 | verify_key/1, 29 | verify_value/1, 30 | format_error/1]). 31 | 32 | -export_type([key/0, 33 | value/0, 34 | tags/0]). 35 | 36 | -include("opencensus.hrl"). 37 | 38 | -type key() :: atom() | binary() | unicode:latin1_charlist(). 39 | -type value() :: binary() | unicode:latin1_charlist(). 40 | -type tags() :: #{key() => value()}. 41 | 42 | -spec new() -> tags(). 43 | new() -> 44 | #{}. 45 | 46 | -spec new(maps:map()) -> tags(). 47 | new(Map) -> 48 | maps:fold(fun(K, V, Acc) -> 49 | case put(K, V, Acc) of 50 | {ok, Acc1} -> 51 | Acc1; 52 | {error, _} -> 53 | Acc 54 | end 55 | end, #{}, Map). 56 | 57 | -spec new(ctx:t(), maps:map()) -> ctx:t(). 58 | new(Ctx, Map) -> 59 | ctx:with_value(Ctx, ?TAG_CTX, update(from_ctx(Ctx), Map)). 60 | 61 | -spec update(tags(), maps:map()) -> tags(). 62 | update(Tags, Map) -> 63 | maps:fold(fun(K, V, Acc) -> 64 | case put(K, V, Acc) of 65 | {ok, Acc1} -> 66 | Acc1; 67 | {error, _} -> 68 | Acc 69 | end 70 | end, to_map(Tags), Map). 71 | 72 | -spec new_ctx(ctx:t(), tags()) -> ctx:t(). 73 | new_ctx(Ctx, Tags) -> 74 | ctx:with_value(Ctx, ?TAG_CTX, Tags). 75 | 76 | -spec from_ctx(ctx:t()) -> tags(). 77 | from_ctx(Ctx) -> 78 | ctx:get(Ctx, ?TAG_CTX, #{}). 79 | 80 | -spec to_map(tags()) -> maps:map(). 81 | to_map(Tags) -> 82 | Tags. 83 | 84 | -spec put(key(), value(), tags()) -> {ok, tags()} | {error, any()}. 85 | put(Key, Value, Tags) -> 86 | case verify_key(Key) andalso verify_value(Value) of 87 | true -> 88 | {ok, maps:put(Key, Value, Tags)}; 89 | false -> 90 | {error, {?MODULE, invalid_tag}} 91 | end. 92 | 93 | verify_key(Key) when is_atom(Key) -> 94 | verify_key(atom_to_list(Key)); 95 | verify_key(Key) when is_binary(Key) -> 96 | verify_key(binary_to_list(Key)); 97 | verify_key(Key) -> 98 | KeyLength = erlang:length(Key), 99 | KeyLength > 0 andalso KeyLength < 256 andalso 100 | io_lib:printable_latin1_list(Key). 101 | 102 | verify_value(Value) when is_binary(Value) -> 103 | verify_value(binary_to_list(Value)); 104 | verify_value(Value) -> 105 | erlang:length(Value) < 256 andalso io_lib:printable_latin1_list(Value). 106 | 107 | format_error(invalid_tag) -> 108 | "tag key and value value must be ascii strings less than 256 characters". 109 | -------------------------------------------------------------------------------- /src/oc_tracestate.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc `oc_tracestate' implements support for the Tracestate header of the 16 | %% W3C TraceContext propagation format. 17 | %% @end 18 | %%%------------------------------------------------------------------------- 19 | -module(oc_tracestate). 20 | 21 | -export([new/2, 22 | add/2, 23 | is_valid/1, 24 | are_valid/1, 25 | format_error/1]). 26 | 27 | -include("oc_logger.hrl"). 28 | -include("opencensus.hrl"). 29 | 30 | -define(MAX_PAIRS, 32). 31 | -define(KEY_WITHOUT_VENDOR, "[a-z][_0-9a-z\-\*\/]{0,255}"). 32 | -define(KEY_WITH_VENDOR, "[a-z][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}"). 33 | -define(KEY_FORMAT, "^((" ++ ?KEY_WITHOUT_VENDOR ++ ")|(" ++ ?KEY_WITH_VENDOR ++ "))$"). 34 | -define(VALUE_FORMAT, "^([\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e])$"). 35 | 36 | -define(KEY_RE_MP, element(2, re:compile(?KEY_FORMAT))). 37 | -define(VALUE_RE_MP, element(2, re:compile(?VALUE_FORMAT))). 38 | 39 | -type key() :: unicode:latin1_chardata(). 40 | -type value() :: unicode:latin1_chardata(). 41 | -type entry() :: {key(), value()}. 42 | -type entries() :: [entry()]. 43 | 44 | %% create tracestate from parent 45 | -spec new(opencensus:tracestate(), list()) -> maybe(opencensus:tracestate()). 46 | new(undefined, []) -> 47 | undefined; 48 | new(undefined, Entries) -> 49 | case are_valid(Entries) of 50 | true -> 51 | #tracestate{entries=Entries}; 52 | {false, Error} -> 53 | ?LOG_INFO(format_error(Error), []), 54 | undefined 55 | end; 56 | new(#tracestate{entries=ParentEntries}, Entries) -> 57 | case are_valid(Entries) of 58 | true -> 59 | case add(#tracestate{entries=ParentEntries}, Entries) of 60 | {error, Reason} -> 61 | ?LOG_INFO(format_error(Reason), []), 62 | undefined; 63 | Tracestate -> 64 | Tracestate 65 | end; 66 | {false, Error} -> 67 | ?LOG_INFO(format_error(Error), []), 68 | undefined 69 | end. 70 | 71 | -spec remove(key(), entries()) -> entries(). 72 | remove(Key, Entries) -> 73 | lists:keydelete(Key, 1, Entries). 74 | 75 | -spec add(maybe(opencensus:tracestate()), entries()) -> opencensus:tracestate() | {error, term()}. 76 | add(undefined, Entries) -> 77 | add(#tracestate{entries=[]}, Entries); 78 | add(Tracestate=#tracestate{entries=CurrentEntries0}, Entries) -> 79 | case are_valid(Entries) of 80 | true -> 81 | CurrentEntries = lists:foldl(fun({Key, _Value}, Acc) -> 82 | remove(Key, Acc) 83 | end, CurrentEntries0, Entries), 84 | CurrentLen = length(CurrentEntries), 85 | EntriesLen = length(Entries), 86 | case CurrentLen + EntriesLen > ?MAX_PAIRS of 87 | true -> 88 | {error, {exceeds_max, CurrentLen, EntriesLen}}; 89 | false -> 90 | Tracestate#tracestate{entries=CurrentEntries ++ Entries} 91 | end; 92 | {false, _} -> 93 | Tracestate 94 | end. 95 | 96 | -spec are_valid(entries()) -> true | {false, {invalid_entry, entry()}}. 97 | are_valid([]) -> 98 | true; 99 | are_valid([Entry | Rest]) -> 100 | case is_valid(Entry) of 101 | true -> 102 | are_valid(Rest); 103 | false -> 104 | {false, {invalid_entry, Entry}} 105 | end. 106 | 107 | -spec is_valid(entry()) -> boolean(). 108 | is_valid({Key, Value}) -> 109 | re:run(Key, ?KEY_RE_MP, [{capture, none}]) =/= nomatch 110 | andalso re:run(Value, ?VALUE_RE_MP, [{capture, none}]) =/= nomatch. 111 | 112 | -spec format_error(term()) -> string(). 113 | format_error({exceeds_max, CurrentLen, EntriesLen}) -> 114 | io_lib:format("adding ~b key-value pairs to current ~b pairs exceeds the limit of ~b", 115 | [EntriesLen, CurrentLen, ?MAX_PAIRS]); 116 | format_error({invalid_entry, Entry}) -> 117 | io_lib:format("invalid tracestate entry ~p", [Entry]). 118 | -------------------------------------------------------------------------------- /src/opencensus.app.src: -------------------------------------------------------------------------------- 1 | {application, opencensus, 2 | [{description, "Erlang stats collection and distributed tracing framework"}, 3 | {vsn, git}, 4 | {registered, []}, 5 | {mod, {opencensus_app, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib, 9 | compiler, 10 | syntax_tools, 11 | inets, 12 | wts, 13 | ctx, 14 | counters 15 | ]}, 16 | {modules, []}, 17 | {env, [{send_interval_ms, 500}]}, 18 | 19 | {licenses, ["Apache 2.0"]}, 20 | {links, [{"GitHub", "https://github.com/census-instrumentation/opencensus-erlang"}]} 21 | ]}. 22 | -------------------------------------------------------------------------------- /src/opencensus.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc opencensus main module 16 | %% @end 17 | %%%------------------------------------------------------------------------- 18 | -module(opencensus). 19 | 20 | -export([generate_trace_id/0, 21 | generate_span_id/0, 22 | http_status_to_trace_status/1]). 23 | 24 | -include("opencensus.hrl"). 25 | 26 | -export_type([span_ctx/0, 27 | tracestate/0, 28 | trace_id/0, 29 | span_id/0, 30 | span/0, 31 | link/0, 32 | links/0, 33 | link_type/0, 34 | attributes/0, 35 | annotation/0, 36 | time_events/0, 37 | message_event/0, 38 | message_event_type/0, 39 | stack_trace/0, 40 | status/0, 41 | 42 | tags/0]). 43 | 44 | -type span_ctx() :: #span_ctx{}. 45 | -type tracestate() :: #tracestate{}. 46 | -type trace_id() :: non_neg_integer(). 47 | -type span_id() :: non_neg_integer(). 48 | -type span() :: #span{}. 49 | -type stack_trace() :: [erlang:stack_item()]. 50 | -type attribute_value() :: any(). 51 | -type attributes() :: #{unicode:unicode_binary() => attribute_value()}. 52 | -type annotation() :: #annotation{}. 53 | -type span_kind() :: ?SPAN_KIND_UNSPECIFIED | ?SPAN_KIND_SERVER | ?SPAN_KIND_CLIENT. 54 | -type message_event() :: #message_event{}. 55 | -type message_event_type() :: ?MESSAGE_EVENT_TYPE_UNSPECIFIED | ?MESSAGE_EVENT_TYPE_SENT | ?MESSAGE_EVENT_TYPE_RECEIVED. 56 | -type time_events() :: [{wts:timestamp(), annotation() | message_event()}]. 57 | -type link() :: #link{}. 58 | -type links() :: [link()]. 59 | -type link_type() :: ?LINK_TYPE_UNSPECIFIED | ?LINK_TYPE_CHILD_LINKED_SPAN | ?LINK_TYPE_PARENT_LINKED_SPAN. 60 | -type status() :: #status{}. 61 | 62 | -type tags() :: oc_tags:tags(). 63 | 64 | %%-------------------------------------------------------------------- 65 | %% @doc 66 | %% Generates a 128 bit random integer to use as a trace id. 67 | %% @end 68 | %%-------------------------------------------------------------------- 69 | -spec generate_trace_id() -> trace_id(). 70 | generate_trace_id() -> 71 | uniform(2 bsl 127 - 1). %% 2 shifted left by 127 == 2 ^ 128 72 | 73 | %%-------------------------------------------------------------------- 74 | %% @doc 75 | %% Generates a 64 bit random integer to use as a span id. 76 | %% @end 77 | %%-------------------------------------------------------------------- 78 | -spec generate_span_id() -> span_id(). 79 | generate_span_id() -> 80 | uniform(2 bsl 63 - 1). %% 2 shifted left by 63 == 2 ^ 64 81 | 82 | %%-------------------------------------------------------------------- 83 | %% @doc 84 | %% Convert HTTP status code to Trace status code. 85 | %% @end 86 | %%-------------------------------------------------------------------- 87 | -spec http_status_to_trace_status(integer()) -> integer(). 88 | http_status_to_trace_status(S) when S >= 0 andalso S =< 199 -> 89 | 2; 90 | http_status_to_trace_status(S) when S >= 200 andalso S =< 399 -> 91 | 0; 92 | http_status_to_trace_status(400) -> 93 | 3; 94 | http_status_to_trace_status(504) -> 95 | 4; 96 | http_status_to_trace_status(404) -> 97 | 5; 98 | http_status_to_trace_status(403) -> 99 | 7; 100 | http_status_to_trace_status(401) -> 101 | 16; 102 | http_status_to_trace_status(429) -> 103 | 8; 104 | http_status_to_trace_status(501) -> 105 | 12; 106 | http_status_to_trace_status(503) -> 107 | 14; 108 | http_status_to_trace_status(S) -> 109 | S. 110 | 111 | %% 112 | 113 | %% Before OTP-20 rand:uniform could not give precision higher than 2^56. 114 | %% Here we do a compile time check for support of this feature and will 115 | %% combine multiple calls to rand if on an OTP version older than 20.0 116 | -ifdef(OTP_RELEASE). 117 | uniform(X) -> 118 | rand:uniform(X). 119 | -else. 120 | -define(TWO_POW_56, 2 bsl 55). 121 | 122 | uniform(X) when X =< ?TWO_POW_56 -> 123 | rand:uniform(X); 124 | uniform(X) -> 125 | R = rand:uniform(?TWO_POW_56), 126 | (uniform(X bsr 56) bsl 56) + R. 127 | -endif. 128 | -------------------------------------------------------------------------------- /src/opencensus_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc opencensus application 16 | %% @end 17 | %%%------------------------------------------------------------------------ 18 | 19 | -module(opencensus_app). 20 | 21 | -behaviour(application). 22 | 23 | -export([start/2, stop/1]). 24 | 25 | -include("opencensus.hrl"). 26 | 27 | start(_StartType, _StartArgs) -> 28 | maybe_init_ets(), 29 | opencensus_sup:start_link(). 30 | 31 | stop(_State) -> 32 | ok. 33 | 34 | maybe_init_ets() -> 35 | case ets:info(?SPAN_TAB, name) of 36 | undefined -> 37 | ets:new(?SPAN_TAB, [named_table, public, {write_concurrency, true}, {keypos, #span.span_id}]); 38 | _ -> 39 | ok 40 | end, 41 | 42 | ets:new(oc_producer_registry, [bag, named_table, public]), 43 | 44 | oc_producer_registry:add_producer(oc_self_producer). 45 | -------------------------------------------------------------------------------- /src/opencensus_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc opencensus top level supervisor. 16 | %% @end 17 | %%%------------------------------------------------------------------------ 18 | 19 | -module(opencensus_sup). 20 | 21 | -behaviour(supervisor). 22 | 23 | -export([start_link/0]). 24 | 25 | -export([init/1]). 26 | 27 | -define(SERVER, ?MODULE). 28 | 29 | start_link() -> 30 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 31 | 32 | init([]) -> 33 | ok = oc_sampler:init(application:get_env(opencensus, sampler, {oc_sampler_always, []})), 34 | 35 | Reporter = #{id => oc_reporter, 36 | start => {oc_reporter, start_link, []}, 37 | restart => permanent, 38 | shutdown => 1000, 39 | type => worker, 40 | modules => [oc_reporter]}, 41 | 42 | ViewServer = #{id => oc_stat, 43 | start => {oc_stat, start_link, []}, 44 | restart => permanent, 45 | shutdown => 1000, 46 | type => worker, 47 | modules => [oc_stat]}, 48 | 49 | Exporter = #{id => oc_stat_exporter, 50 | start => {oc_stat_exporter, start_link, []}, 51 | restart => permanent, 52 | shutdown => 1000, 53 | type => worker, 54 | modules => [oc_stat_exporter]}, 55 | 56 | TraceServer = #{id => oc_server, 57 | start => {oc_server, start_link, []}, 58 | restart => permanent, 59 | shutdown => 1000, 60 | type => worker, 61 | modules => [oc_server]}, 62 | 63 | Sweeper = #{id => oc_span_sweeper, 64 | start => {oc_span_sweeper, start_link, []}, 65 | restart => permanent, 66 | shutdown => 1000, 67 | type => worker, 68 | modules => [oc_span_sweeper]}, 69 | 70 | {ok, {#{strategy => one_for_one, 71 | intensity => 1, 72 | period => 5}, [Reporter, ViewServer, Exporter, TraceServer, Sweeper]}}. 73 | -------------------------------------------------------------------------------- /test/oc_metrics_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(oc_metrics_SUITE). 2 | 3 | -compile(export_all). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | -include_lib("common_test/include/ct.hrl"). 7 | 8 | -include("oc_metrics.hrl"). 9 | 10 | all() -> 11 | [self_producer_default]. 12 | 13 | init_per_suite(Config) -> 14 | _ = application:load(opencensus), 15 | _ = application:ensure_all_started(opencensus), 16 | Config. 17 | 18 | end_per_suite(_Config) -> 19 | application:stop(opencensus), 20 | application:unload(opencensus), 21 | ok. 22 | 23 | self_producer_default(_Config) -> 24 | 25 | ?assertMatch([], lists:sort(oc_producer_registry:read_to_list(test_registry))), 26 | 27 | oc_producer_registry:add_producer(test_registry, oc_self_producer), 28 | 29 | SpanName1 = <<"span-1">>, 30 | Span1 = oc_trace:start_span(SpanName1, undefined), 31 | 32 | ?assertMatch([#oc_metric{descriptor=#oc_metric_descriptor{ 33 | name="oc_span_buffer_bytes", 34 | description="Size of the spans ETS table", 35 | unit= <<"1">>, 36 | type='GAUGE_INT64', 37 | label_keys=[]}, 38 | timeseries=[#oc_time_series{ 39 | start_timestamp=undefined, 40 | label_values=[], 41 | points=[#oc_point{ 42 | timestamp={_, _}, 43 | value=S1}]}], 44 | resource=undefined}, 45 | #oc_metric{descriptor=#oc_metric_descriptor{ 46 | name="oc_span_buffer_size", 47 | description="Count of spans in the ETS table", 48 | unit= <<"1">>, 49 | type='GAUGE_INT64', 50 | label_keys=[]}, 51 | timeseries=[#oc_time_series{ 52 | start_timestamp=undefined, 53 | label_values=[], 54 | points=[#oc_point{ 55 | timestamp={_, _}, 56 | value=1}]}], 57 | resource=undefined}] when S1 > 5000, 58 | lists:sort(oc_producer_registry:read_to_list(test_registry))), 59 | 60 | ChildSpanName1 = <<"child-span-1">>, 61 | ChildSpan1 = oc_trace:start_span(ChildSpanName1, Span1, #{}), 62 | 63 | ?assertMatch([#oc_metric{descriptor=#oc_metric_descriptor{ 64 | name="oc_span_buffer_bytes", 65 | description="Size of the spans ETS table", 66 | unit= <<"1">>, 67 | type='GAUGE_INT64', 68 | label_keys=[]}, 69 | timeseries=[#oc_time_series{ 70 | start_timestamp=undefined, 71 | label_values=[], 72 | points=[#oc_point{ 73 | timestamp={_, _}, 74 | value=S2}]}], 75 | resource=undefined}, 76 | #oc_metric{descriptor=#oc_metric_descriptor{ 77 | name="oc_span_buffer_size", 78 | description="Count of spans in the ETS table", 79 | unit= <<"1">>, 80 | type='GAUGE_INT64', 81 | label_keys=[]}, 82 | timeseries=[#oc_time_series{ 83 | start_timestamp=undefined, 84 | label_values=[], 85 | points=[#oc_point{ 86 | timestamp={_, _}, 87 | value=2}]}], 88 | resource=undefined}] when S2 > 10000, 89 | lists:sort(oc_producer_registry:read_to_list(test_registry))), 90 | 91 | oc_producer_registry:remove_producer(test_registry, oc_self_producer), 92 | ?assertMatch([], lists:sort(oc_producer_registry:read_to_list(test_registry))), 93 | 94 | oc_trace:finish_span(ChildSpan1), 95 | oc_trace:finish_span(Span1). 96 | -------------------------------------------------------------------------------- /test/oc_sampler_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------------------- 2 | %%% @doc 3 | %%% @end 4 | %%% --------------------------------------------------------------------------- 5 | -module(oc_sampler_SUITE). 6 | 7 | -compile(export_all). 8 | 9 | -include_lib("eunit/include/eunit.hrl"). 10 | -include_lib("common_test/include/ct.hrl"). 11 | 12 | -include("opencensus.hrl"). 13 | 14 | all() -> 15 | [never_sample, always_sample, probability_sample, probability_zero_sample, 16 | probability_hundred_sample, deterministic_probability]. 17 | 18 | init_per_suite(Config) -> 19 | application:load(opencensus), 20 | [{limit, 10} | Config]. 21 | 22 | end_per_suite(_Config) -> 23 | ok. 24 | 25 | init_per_testcase(never_sample, Config) -> 26 | application:set_env(opencensus, sampler, {oc_sampler_never, []}), 27 | {ok, _} = application:ensure_all_started(opencensus), 28 | Config; 29 | init_per_testcase(always_sample, Config) -> 30 | application:set_env(opencensus, sampler, {oc_sampler_always, []}), 31 | {ok, _} = application:ensure_all_started(opencensus), 32 | Config; 33 | init_per_testcase(probability_sample, Config) -> 34 | application:set_env(opencensus, sampler, {oc_sampler_probability, [{probability, 0.5}]}), 35 | {ok, _} = application:ensure_all_started(opencensus), 36 | Config; 37 | init_per_testcase(probability_zero_sample, Config) -> 38 | application:set_env(opencensus, sampler, {oc_sampler_probability, [{probability, 0.0}]}), 39 | {ok, _} = application:ensure_all_started(opencensus), 40 | Config; 41 | init_per_testcase(probability_hundred_sample, Config) -> 42 | application:set_env(opencensus, sampler, {oc_sampler_probability, [{probability, 1.0}]}), 43 | {ok, _} = application:ensure_all_started(opencensus), 44 | Config; 45 | init_per_testcase(deterministic_probability, Config) -> 46 | application:set_env(opencensus, sampler, {oc_sampler_probability, [{probability, 0.5}]}), 47 | {ok, _} = application:ensure_all_started(opencensus), 48 | Config. 49 | 50 | end_per_testcase(_, _Config) -> 51 | ok = application:stop(opencensus), 52 | ok. 53 | 54 | never_sample(Config) -> 55 | Limit = ?config(limit, Config), 56 | L = lists:filter(fun(_) -> 57 | SpanContext = oc_trace:start_span(<<"span">>, undefined), 58 | oc_trace:is_enabled(SpanContext) 59 | end, lists:seq(1, Limit)), 60 | 61 | ?assertEqual(0, length(L)), 62 | %% no garbage in ETS 63 | ?assertEqual(0, ets:info(?SPAN_TAB, size)). 64 | 65 | always_sample(Config) -> 66 | Limit = ?config(limit, Config), 67 | L = lists:filter(fun(_) -> 68 | %% include a test where 'undefined' is passed as the TC 69 | SpanContext = oc_trace:start_span(<<"span">>, undefined), 70 | oc_trace:is_enabled(SpanContext) 71 | end, lists:seq(1, Limit)), 72 | ?assertEqual(Limit, length(L)), 73 | %% all spans are in ETS 74 | ?assertEqual(Limit, ets:info(?SPAN_TAB, size)). 75 | 76 | probability_sample(Config) -> 77 | Limit = ?config(limit, Config), 78 | L = lists:filter(fun(_) -> 79 | SpanContext = oc_trace:start_span(<<"span">>, undefined), 80 | oc_trace:is_enabled(SpanContext) 81 | end, lists:seq(1, Limit)), 82 | Length = length(L), 83 | ?assert(Length < Limit andalso Length > 0). 84 | 85 | probability_zero_sample(Config) -> 86 | Limit = ?config(limit, Config), 87 | L = lists:filter(fun(_) -> 88 | SpanContext = oc_trace:start_span(<<"span">>, undefined), 89 | oc_trace:is_enabled(SpanContext) 90 | end, lists:seq(1, Limit)), 91 | ?assertEqual(0, length(L)). 92 | 93 | probability_hundred_sample(Config) -> 94 | Limit = ?config(limit, Config), 95 | L = lists:filter(fun(_) -> 96 | %% include a test where an already generated id is passed as the TraceId 97 | SpanContext = oc_trace:start_span(<<"span">>, undefined), 98 | oc_trace:is_enabled(SpanContext) 99 | end, lists:seq(1, Limit)), 100 | ?assertEqual(Limit, length(L)). 101 | 102 | deterministic_probability(Config) -> 103 | Limit = ?config(limit, Config), 104 | 105 | TraceIds = [opencensus:generate_trace_id() || _ <- lists:seq(1, Limit)], 106 | 107 | lists:foldl(fun(_, Acc) -> 108 | L = lists:filter(fun(TraceId) -> 109 | %% include a test where a TC record is passed as the TraceId 110 | SpanContext = oc_trace:start_span(<<"span">>, #span_ctx{trace_id=TraceId}), 111 | oc_trace:is_enabled(SpanContext) 112 | end, TraceIds), 113 | %% verify that every list of sampled is the same 114 | ?assert(lists:all(fun(X) -> X =:= L end, Acc)), 115 | [L | Acc] 116 | end, [], lists:seq(1, Limit)). 117 | -------------------------------------------------------------------------------- /test/oc_span_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------------------- 2 | %%% @doc 3 | %%% @end 4 | %%% --------------------------------------------------------------------------- 5 | -module(oc_span_SUITE). 6 | 7 | -compile(export_all). 8 | 9 | -include_lib("eunit/include/eunit.hrl"). 10 | -include_lib("common_test/include/ct.hrl"). 11 | 12 | -include("opencensus.hrl"). 13 | 14 | all() -> 15 | [modifications]. 16 | 17 | init_per_suite(Config) -> 18 | Config. 19 | 20 | end_per_suite(_Config) -> 21 | ok. 22 | 23 | init_per_testcase(_, Config) -> 24 | Config. 25 | 26 | end_per_testcase(_, _Config) -> 27 | ok. 28 | 29 | modifications(_Config) -> 30 | Span = #span{trace_id=opencensus:generate_trace_id(), 31 | span_id=opencensus:generate_span_id(), 32 | start_time=wts:timestamp(), 33 | attributes=#{}}, 34 | Span1 = oc_span:put_attribute(<<"key">>, <<"value">>, Span), 35 | ?assertEqual(undefined, oc_span:put_attribute(<<"key">>, <<"value">>, undefined)), 36 | 37 | Span2 = oc_span:put_attributes(#{<<"key2">> => <<"value2">>}, Span1), 38 | ?assertEqual(undefined, oc_span:put_attributes(#{<<"key">> => <<"value">>}, undefined)), 39 | 40 | Annotation = oc_span:annotation(<<"description">>, #{<<"attr">> => <<"attr-value">>}), 41 | Span3 = oc_span:add_time_event(Annotation, Span2), 42 | ?assertEqual(undefined, oc_span:add_time_event(Annotation, undefined)), 43 | 44 | MessageEvent = oc_span:message_event(?MESSAGE_EVENT_TYPE_SENT, 1, 0, 0), 45 | Span4 = oc_span:add_time_event(MessageEvent, Span3), 46 | ?assertEqual(undefined, oc_span:add_time_event(MessageEvent, undefined)), 47 | 48 | Span5 = oc_span:set_status(1, <<"ok">>, Span4), 49 | ?assertEqual(undefined, oc_span:set_status(1, <<"ok">>, undefined)), 50 | 51 | Link = oc_span:link(?LINK_TYPE_UNSPECIFIED, opencensus:generate_trace_id(), 52 | opencensus:generate_span_id(), #{<<"attr-1">> => <<"value-1">>}), 53 | Span6 = oc_span:add_link(Link, Span5), 54 | ?assertEqual(undefined, oc_span:add_link(Link, undefined)), 55 | 56 | ?assertEqual({error, no_report_buffer}, oc_span:finish_span(#span_ctx{}, Span6)), 57 | 58 | {ok, _} = application:ensure_all_started(opencensus), 59 | ?assertEqual(true, oc_span:finish_span(#span_ctx{}, Span6)), 60 | ?assertEqual(true, oc_span:finish_span(#span_ctx{}, undefined)), 61 | 62 | application:stop(opencensus). 63 | -------------------------------------------------------------------------------- /test/oc_stat_aggregation_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(oc_stat_aggregation_SUITE). 2 | 3 | -compile(export_all). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | -include_lib("common_test/include/ct.hrl"). 7 | 8 | all() -> 9 | [conversion]. 10 | 11 | init_per_suite(Config) -> 12 | application:load(opencensus), 13 | Config. 14 | 15 | end_per_suite(_Config) -> 16 | application:unload(opencensus), 17 | ok. 18 | 19 | init_per_testcase(_, Config) -> 20 | {ok, _} = application:ensure_all_started(opencensus), 21 | Config. 22 | 23 | end_per_testcase(_, _Config) -> 24 | ok = application:stop(opencensus), 25 | ok = application:stop(counters), 26 | ok. 27 | 28 | conversion(_Config) -> 29 | ?assertMatch(#{rows := [#{tags := [], 30 | value := #{count := 2, 31 | mean := 60060, 32 | sum := 120120}}], 33 | type := sum}, 34 | oc_stat_aggregation:convert(#{rows => [#{tags => [], 35 | value => #{count => 2, 36 | mean => 60060, 37 | sum => 120120}}], 38 | type => sum}, 39 | unit, undefined)), 40 | 41 | 42 | ?assertMatch(#{rows := [#{tags := [], 43 | value := 120.120}], 44 | type := latest}, 45 | oc_stat_aggregation:convert(#{rows => [#{tags => [], 46 | value => 120120}], 47 | type => latest}, 48 | millisecond, second)), 49 | 50 | ?assertMatch(#{rows := [#{tags := [], 51 | value := 120120}], 52 | type := count}, 53 | oc_stat_aggregation:convert(#{rows => [#{tags => [], 54 | value => 120120}], 55 | type => count}, 56 | millisecond, second)), 57 | 58 | ?assertMatch(#{rows := [#{tags := [], 59 | value := #{count := 2, 60 | mean := 60.060, 61 | sum := 120.120}}], 62 | type := sum}, 63 | oc_stat_aggregation:convert(#{rows => [#{tags => [], 64 | value => #{count => 2, 65 | mean => 60060, 66 | sum => 120120}}], 67 | type => sum}, 68 | millisecond, second)), 69 | 70 | ?assertMatch(#{rows := [#{tags := [], 71 | value := #{count := 2, 72 | mean := 60.060, 73 | sum := 120.120, 74 | buckets := [{100.000, 0}, {infinity, 2}]}}], 75 | type := distribution}, 76 | oc_stat_aggregation:convert(#{rows => [#{tags => [], 77 | value => #{count => 2, 78 | mean => 60060, 79 | sum => 120120, 80 | buckets => [{100000, 0}, {infinity, 2}]}}], 81 | type => distribution}, 82 | millisecond, second)). 83 | 84 | %% =================================================================== 85 | %% Private functions 86 | %% =================================================================== 87 | -------------------------------------------------------------------------------- /test/oc_stat_aggregation_pid.erl: -------------------------------------------------------------------------------- 1 | -module(oc_stat_aggregation_pid). 2 | 3 | -export([init/3, 4 | type/0, 5 | add_sample/4, 6 | export/2, 7 | clear_rows/2]). 8 | 9 | -behavior(oc_stat_aggregation). 10 | 11 | init(_Name, _Keys, Pid0) -> 12 | Pid = list_to_pid(Pid0), 13 | Pid ! aggregation_init, 14 | Pid0. 15 | 16 | type() -> 17 | pid. 18 | 19 | -spec add_sample(oc_stat_view:name(), oc_tags:tags(), number(), any()) -> ok. 20 | add_sample(_Name, _Tags, _Value, _Options) -> 21 | ok. 22 | 23 | export(_Name, _Options) -> 24 | #{type=>type(), 25 | rows=>[]}. 26 | 27 | clear_rows(_Name, Pid0) -> 28 | Pid = list_to_pid(Pid0), 29 | Pid ! aggregation_clear_rows, 30 | ok. 31 | -------------------------------------------------------------------------------- /test/oc_stat_exporter_pid.erl: -------------------------------------------------------------------------------- 1 | -module(oc_stat_exporter_pid). 2 | 3 | -export([export/2]). 4 | 5 | export(ViewData, Pid) -> 6 | Pid ! {view_data, ViewData}. 7 | -------------------------------------------------------------------------------- /test/oc_stat_pt_user.erl: -------------------------------------------------------------------------------- 1 | -module(oc_stat_pt_user). 2 | 3 | -export([record_non_existing/0, 4 | record_ok/0]). 5 | 6 | -compile({parse_transform, oc_stat_transform}). 7 | 8 | record_non_existing() -> 9 | oc_stat:record(#{}, "qwe", 1). 10 | 11 | record_ok() -> 12 | oc_stat_measure:record(#{}, 'my.org/measures/video_size', 1024). 13 | -------------------------------------------------------------------------------- /test/oc_stat_unit_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(oc_stat_unit_SUITE). 2 | 3 | -compile(export_all). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | -include_lib("common_test/include/ct.hrl"). 7 | 8 | all() -> 9 | [conversion, 10 | no_conversion, 11 | errors]. 12 | 13 | init_per_suite(Config) -> 14 | application:load(opencensus), 15 | Config. 16 | 17 | end_per_suite(_Config) -> 18 | ok. 19 | 20 | init_per_testcase(_, Config) -> 21 | {ok, _} = application:ensure_all_started(opencensus), 22 | Config. 23 | 24 | end_per_testcase(_, _Config) -> 25 | ok = application:stop(opencensus), 26 | ok = application:stop(counters), 27 | ok. 28 | 29 | conversion(_Config) -> 30 | oc_stat_measure:new(http_request_duration, "Http request duration", native_time_unit), 31 | oc_stat_view:subscribe(http_request_duration_seconds, 32 | http_request_duration, second, "desc", [], oc_stat_aggregation_sum), 33 | oc_stat:record(#{}, http_request_duration, 1200000), 34 | oc_stat:record(#{}, http_request_duration, 1200000000), 35 | 36 | ?assertMatch([#{name := http_request_duration_seconds, 37 | description := "desc", 38 | tags := [], 39 | ctags := #{}, 40 | data := #{rows := [#{tags := [], 41 | value := #{count := 2, 42 | mean := 0.6006, 43 | sum := 1.2012}}], 44 | type := sum}}], 45 | oc_stat:export()). 46 | 47 | no_conversion(_Config) -> 48 | oc_stat_measure:new(http_request_duration, "Http request duration", microsecond), 49 | oc_stat_view:subscribe(http_request_duration_microsecond, 50 | http_request_duration, "desc", [], oc_stat_aggregation_sum), 51 | oc_stat:record(#{}, http_request_duration, 1200000), 52 | oc_stat:record(#{}, http_request_duration, 1200000000), 53 | 54 | ?assertMatch([#{name := http_request_duration_microsecond, 55 | description := "desc", 56 | tags := [], 57 | ctags := #{}, 58 | data := #{rows := [#{tags := [], 59 | value := #{count := 2, 60 | mean := 6.006e8, 61 | sum := 1201200000}}], 62 | type := sum}}], 63 | oc_stat:export()). 64 | 65 | errors(_Config) -> 66 | oc_stat_measure:new(http_request_duration, "Http request duration", native_time_unit), 67 | ?assertMatch({error, {invalid_unit, "view must override measure unit", 68 | native_time_unit}}, 69 | oc_stat_view:subscribe(http_request_duration_seconds, 70 | http_request_duration, "desc", [], oc_stat_aggregation_sum)), 71 | 72 | oc_stat_measure:new(http_requests, "Http requests count", "req"), 73 | ?assertMatch({error, {not_comparable_units, "req", "ms"}}, 74 | oc_stat_view:subscribe(http_requests_count, 75 | http_requests, "ms", "desc", [], oc_stat_aggregation_count)). 76 | 77 | %% =================================================================== 78 | %% Private functions 79 | %% =================================================================== 80 | -------------------------------------------------------------------------------- /test/oc_tab_reporter.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------------ 2 | %% Copyright 2017, OpenCensus Authors 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 | %% @doc A test reporter that keeps finished spans in an ETS table. 16 | %% @end 17 | %%%----------------------------------------------------------------------- 18 | -module(oc_tab_reporter). 19 | 20 | -behaviour(oc_reporter). 21 | 22 | -export([init/1, 23 | report/2]). 24 | 25 | init(_) -> 26 | application:get_env(opencensus, tab_reporter, #{}). 27 | 28 | report(Spans, Opts) -> 29 | Tid = maps:get(tid, Opts), 30 | ets:insert(Tid, Spans), 31 | ok. 32 | -------------------------------------------------------------------------------- /test/oc_tags_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------------------- 2 | %%% @doc 3 | %%% @end 4 | %%% --------------------------------------------------------------------------- 5 | -module(oc_tags_SUITE). 6 | 7 | -compile(export_all). 8 | 9 | -include_lib("eunit/include/eunit.hrl"). 10 | -include_lib("common_test/include/ct.hrl"). 11 | 12 | all() -> 13 | [ 14 | basic, 15 | ctx, 16 | encode_decode, 17 | encode_decode_headers, 18 | invalid_tags 19 | ]. 20 | 21 | encode_decode(_Config) -> 22 | Empty = #{}, 23 | {ok, EmptyIOList} = oc_tag_ctx_binary:encode(Empty), 24 | EmptyEncodedT = iolist_to_binary(EmptyIOList), 25 | ?assertMatch({ok, Empty}, oc_tag_ctx_binary:decode(EmptyEncodedT)), 26 | 27 | T = #{"key-1" => "value-1", 28 | "key-2" => "value-2"}, 29 | {ok, IOList} = oc_tag_ctx_binary:encode(T), 30 | EncodedT = iolist_to_binary(IOList), 31 | ?assertMatch({ok, T}, oc_tag_ctx_binary:decode(EncodedT)), 32 | ok. 33 | 34 | encode_decode_headers(_Config) -> 35 | Empty = #{}, 36 | EmptyHeader = oc_tag_ctx_header:encode(Empty), 37 | ?assertMatch(<<"0">>, EmptyHeader), 38 | ?assertMatch({ok, Empty}, oc_tag_ctx_header:decode(EmptyHeader)), 39 | 40 | T = #{'key-1' => "value-1", 41 | "key-2" => "value-2"}, 42 | Header = oc_tag_ctx_header:encode(T), 43 | ?assertMatch("AAAFa2V5LTEHdmFsdWUtMQAFa2V5LTIHdmFsdWUtMg==", Header), 44 | ?assertMatch({ok, #{"key-1" := "value-1", 45 | "key-2" := "value-2"}}, oc_tag_ctx_header:decode(Header)), 46 | ok. 47 | 48 | invalid_tags(_Config) -> 49 | TooLongString = lists:duplicate($a, 256), 50 | ?assertMatch({error, {oc_tags, invalid_tag}}, oc_tags:put(TooLongString, "fine value", #{})), 51 | ?assertMatch({error, {oc_tags, invalid_tag}}, oc_tags:put("fine key", TooLongString, #{})), 52 | ?assertMatch({error, {oc_tags, invalid_tag}}, oc_tags:put(TooLongString, TooLongString, #{})), 53 | 54 | ?assertMatch({error, {oc_tags, invalid_tag}}, oc_tags:put("invalid", "k\x7f", #{})), 55 | 56 | TooManyBytes = maps:from_list([{integer_to_list(X), "some value"} || X <- lists:seq(1, 8192)]), 57 | ?assertMatch({error, {oc_tag_ctx_binary, encoding_too_large}}, oc_tag_ctx_binary:encode(TooManyBytes)), 58 | ok. 59 | 60 | basic(_Config) -> 61 | Tags = oc_tags:new(#{'key-1' => "value-1", 62 | <<"key-2">> => "value-2"}), 63 | {ok, Tags1} = oc_tags:put("key-3", <<"value-3">>, Tags), 64 | {ok, Tags2} = oc_tags:put("key-4", "value-4", Tags1), 65 | 66 | 67 | ?assertMatch(#{'key-1' := "value-1", 68 | <<"key-2">> := "value-2", 69 | "key-3" := <<"value-3">>, 70 | "key-4" := "value-4"}, oc_tags:to_map(Tags2)). 71 | 72 | ctx(_Config) -> 73 | Ctx = oc_tags:new_ctx(ctx:new(), #{"key-1" => "value-1", 74 | "key-2" => "value-2"}), 75 | Tags = oc_tags:from_ctx(Ctx), 76 | 77 | ?assertMatch(#{"key-1" := "value-1", 78 | "key-2" := "value-2"}, oc_tags:to_map(Tags)). 79 | -------------------------------------------------------------------------------- /test/oc_test_utils.hrl: -------------------------------------------------------------------------------- 1 | %% Try for 1 seconds 2 | -define(UNTIL(X), (fun Until(I) when I =:= 10 -> 3 | erlang:error(fail); 4 | Until(I) -> 5 | case X of 6 | true -> 7 | ok; 8 | false -> 9 | timer:sleep(100), 10 | Until(I+1) 11 | end 12 | end)(0)). 13 | 14 | -define(OCP_FINISH(Tab), 15 | (fun() -> 16 | __SpanCtx = ocp:current_span_ctx(), 17 | ocp:finish_span(), 18 | 19 | %% wait for span to be reported 20 | ?UNTIL(ets:member(Tab, __SpanCtx#span_ctx.span_id)) 21 | end)()). 22 | 23 | -define(FINISH(Tab, SpanCtx), 24 | begin 25 | oc_trace:finish_span(SpanCtx), 26 | 27 | %% wait for span to be reported 28 | ?UNTIL(ets:member(Tab, SpanCtx#span_ctx.span_id)) 29 | end). 30 | -------------------------------------------------------------------------------- /test/oc_transform_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------------------- 2 | %%% @doc 3 | %%% @end 4 | %%% --------------------------------------------------------------------------- 5 | -module(oc_transform_SUITE). 6 | 7 | -compile({parse_transform, oc_span_transform}). 8 | 9 | -compile(export_all). 10 | 11 | -include_lib("eunit/include/eunit.hrl"). 12 | -include_lib("common_test/include/ct.hrl"). 13 | 14 | -include("opencensus.hrl"). 15 | 16 | all() -> 17 | [trace_transform]. 18 | 19 | init_per_suite(Config) -> 20 | application:load(opencensus), 21 | application:set_env(opencensus, sampler, {oc_sampler_always, []}), 22 | Config. 23 | 24 | end_per_suite(_Config) -> 25 | ok. 26 | 27 | init_per_testcase(_, Config) -> 28 | application:set_env(opencensus, reporters, [{oc_reporter_pid, self()}]), 29 | application:set_env(opencensus, pid_reporter, #{pid => self()}), 30 | 31 | {ok, _} = application:ensure_all_started(opencensus), 32 | 33 | Config. 34 | 35 | end_per_testcase(_, _Config) -> 36 | ok = application:stop(opencensus). 37 | 38 | trace_transform(_Config) -> 39 | SpanName1 = <<"span-1">>, 40 | 41 | ocp:with_child_span(SpanName1), 42 | traced_function(), 43 | ocp:finish_span(), 44 | 45 | %% verify all spans, including spans for the transform using functions are reported 46 | lists:foreach(fun(Name) -> 47 | receive 48 | {span, S=#span{name=Name}} -> 49 | %% Verify the end time and duration are set when the span was finished 50 | ?assertMatch({ST, O} when is_integer(ST) 51 | andalso is_integer(O), S#span.start_time), 52 | ?assertMatch({ST, O} when is_integer(ST) 53 | andalso is_integer(O), S#span.end_time) 54 | after 1000 -> 55 | ct:fail("Did not receive any message in 1s") 56 | end 57 | end, [SpanName1, <<"oc_transform_SUITE:traced_function/0">>, <<"my_name">>]). 58 | 59 | -span([]). 60 | traced_function() -> 61 | another_traced_function(). 62 | 63 | -span(<<"my_name">>). 64 | another_traced_function() -> 65 | trace_this. 66 | --------------------------------------------------------------------------------