From 04ae4a4ac0f08090fb21ddac5d002f41ff3f3cc3 Mon Sep 17 00:00:00 2001 From: Mark Van de Vyver Date: Wed, 13 Sep 2023 13:23:47 +1000 Subject: [PATCH 1/7] [Evolve][Issue] Resolve issue 113 Close issue #113. Signed-off-by: Mark Van de Vyver --- .github/workflows/ci.yml | 4 +- .github/workflows/coverage.yml | 2 - .gitignore | 1 + CHANGELOG.md | 7 +- Cargo.toml | 86 +- {minitrace/benches => benches}/compare.rs | 0 {minitrace/benches => benches}/object_pool.rs | 0 {minitrace/benches => benches}/spsc.rs | 0 {minitrace/benches => benches}/trace.rs | 0 .../examples => examples}/asynchronous.rs | 0 .../examples => examples}/get_started.rs | 8 +- {minitrace/examples => examples}/log.rs | 0 .../examples => examples}/synchronous.rs | 0 {minitrace/examples => examples}/unit_test.rs | 0 img/benchmark.jpeg | 1 + img/jaeger-asynchronous.png | 1 + img/jaeger-synchronous.png | 1 + minitrace-datadog/Cargo.toml | 2 +- minitrace-jaeger/Cargo.toml | 2 +- minitrace-macro/Cargo.toml | 21 +- minitrace-macro/README.md | 179 +++- minitrace-macro/src/lib.rs | 365 ++----- minitrace-macro/src/trace.rs | 13 + minitrace-macro/src/trace/analyze.rs | 553 +++++++++++ minitrace-macro/src/trace/generate.rs | 87 ++ minitrace-macro/src/trace/lower.rs | 160 ++++ .../src/trace/lower/async_trait.rs | 134 +++ minitrace-macro/src/trace/lower/block.rs | 50 + minitrace-macro/src/trace/lower/lifetime.rs | 60 ++ minitrace-macro/src/trace/lower/quotable.rs | 80 ++ minitrace-macro/src/trace/lower/signature.rs | 200 ++++ minitrace-macro/src/trace/parse.rs | 272 ++++++ minitrace-macro/tests/expand.rs | 58 ++ .../defaults/001-empty-sync.expanded.rs | 16 + .../tests/expand/defaults/001-empty-sync.rs | 14 + .../expand/defaults/001-sync.expanded.rs | 16 + .../tests/expand/defaults/001-sync.rs | 14 + .../tests/expand/defaults/empty-async.rsi | 2 + .../tests/expand/issues/tokio-1613.rs | 26 + .../expand/issues/tokio-1615.expanded.rs | 27 + .../tests/expand/issues/tokio-1615.rsi | 19 + minitrace-macro/tests/spans.rs | 5 + .../spans/black-box-false-local-async.rs | 41 + .../tests/spans/black-box-false-local-sync.rs | 40 + .../spans/black-box-false-threads-async.rs | 42 + .../spans/black-box-false-threads-sync.rs | 41 + .../tests/spans/default-name-local-async.rs | 39 + .../tests/spans/default-name-local-sync.rs | 38 + .../tests/spans/default-name-threads-async.rs | 40 + .../tests/spans/default-name-threads-sync.rs | 39 + minitrace-macro/tests/spans/doctest-async.rs | 47 + minitrace-macro/tests/spans/doctest-sync.rs | 46 + minitrace-macro/tests/spans/example-async.rs | 188 ++++ minitrace-macro/tests/spans/example-sync.rs | 220 +++++ .../no-be-drop-local-async-enter-false.rs | 46 + .../no-be-drop-local-async-enter-true.rs | 46 + .../tests/spans/no-be-drop-local-sync.rs | 44 + .../tests/spans/no-be-drop-threads-async.rs | 45 + .../tests/spans/no-be-drop-threads-sync.rs | 43 + .../spans/no-be-no-drop-async-enter-false.rs | 46 + .../no-be-no-drop-local-async-enter-true.rs | 38 + .../tests/spans/no-be-no-drop-local-sync.rs | 35 + .../spans/no-be-no-drop-threads-async.rs | 36 + .../tests/spans/no-be-no-drop-threads-sync.rs | 35 + minitrace-macro/tests/trace-dev.rs | 13 + minitrace-macro/tests/trace.rs | 158 +++ .../ui/err/001-name-is-ident.rs} | 2 +- .../trace/ui/err/001-name-is-ident.stderr | 5 + .../tests/trace/ui/err/002-name-is-symbol.rs | 6 + .../trace/ui/err/002-name-is-symbol.stderr | 5 + .../tests/trace/ui/err/003-name-is-boolean.rs | 6 + .../trace/ui/err/003-name-is-boolean.stderr | 5 + .../tests/trace/ui/err/004-has-all-sync.rs | 10 + .../trace/ui/err/004-has-all-sync.stderr | 18 + .../ui/err/005-has-enter_on_poll-sync.rs} | 2 +- .../ui/err/005-has-enter_on_poll-sync.stderr | 18 + .../ui/err/006-has-too-many-arguments.rs | 6 + .../ui/err/006-has-too-many-arguments.stderr | 5 + .../ui/err/007-interleaved.rs} | 2 +- .../tests/trace/ui/err/007-interleaved.stderr | 5 + .../ui/err/008-item-is-not-a-function.rs} | 2 +- .../ui/err/008-item-is-not-a-function.stderr | 7 + .../trace/ui/err/009-optional-is-known-str.rs | 8 + .../ui/err/009-optional-is-known-str.stderr | 5 + .../ui/err/010-optional-is-not-assignment.rs | 8 + .../err/010-optional-is-not-assignment.stderr | 7 + .../trace/ui/err/011-optional-is-symbol.rs | 8 + .../ui/err/011-optional-is-symbol.stderr | 7 + .../ui/err/012-optional-is-unknown-ident.rs | 8 + .../err/012-optional-is-unknown-ident.stderr | 5 + .../trace/ui/err/013-optional-is-not-ident.rs | 6 + .../ui/err/013-optional-is-not-ident.stderr | 5 + .../err/014-optional-value-type-is-ident.rs | 8 + .../014-optional-value-type-is-ident.stderr | 5 + .../015-optional-value-type-is-unknown-lit.rs | 8 + ...-optional-value-type-is-unknown-lit.stderr | 5 + .../ui/err/016-name-is-char.rs} | 2 +- .../trace/ui/err/016-name-is-char.stderr | 5 + .../trace/ui/err/017-name-is-byte-str.rs | 6 + .../trace/ui/err/017-name-is-byte-str.stderr | 5 + .../trace/ui/err/018-has-duplicate-name.rs | 10 + .../ui/err/018-has-duplicate-name.stderr | 5 + .../ui/err/019-has-duplicate-enter_on_poll.rs | 10 + .../019-has-duplicate-enter_on_poll.stderr | 5 + .../ui/err/has-duplicated-arguments.rs | 0 .../ui/err/has-duplicated-arguments.stderr | 0 .../{ => trace}/ui/err/has-ident-arguments.rs | 2 +- .../ui/err/has-ident-arguments.stderr | 0 .../tests/trace/ui/ok/001-has-no-arguments.rs | 8 + .../ok/002-has-enter_on_poll-ident-async.rs} | 2 +- .../tests/trace/ui/ok/003-has-name-async.rs | 11 + .../ui/ok/004-has-name-mut-async.rs} | 5 +- .../trace/ui/ok/005-has-name-mut-sync.rs | 11 + .../ui/ok/006-has-name-sync.rs} | 2 +- .../tests/trace/ui/ok/007-interleaved.rs | 10 + .../tests/trace/ui/ok/008-has-all-async.rs | 11 + .../tests/trace/ui/ok/008-has-all-sync.rs | 10 + .../trace/ui/ok/009-consecutive-traces.rs | 10 + .../trace/ui/ok/010-name-out-of-place.rs | 11 + .../tests/{ => trace}/ui/ok/async-in-trait.rs | 0 .../tests/{ => trace}/ui/ok/async-trait.rs | 0 .../ui/err/item-is-not-a-function.stderr | 5 - .../tests/ui/ok/has-no-arguments.rs | 10 - minitrace-old/Cargo.toml | 60 ++ {minitrace => minitrace-old}/LICENSE | 0 minitrace-old/benches/compare.rs | 88 ++ minitrace-old/benches/object_pool.rs | 39 + minitrace-old/benches/spsc.rs | 82 ++ minitrace-old/benches/trace.rs | 142 +++ minitrace-old/examples/asynchronous.rs | 76 ++ minitrace-old/examples/get_started.rs | 35 + minitrace-old/examples/synchronous.rs | 52 + minitrace-old/img/benchmark.jpeg | 1 + minitrace-old/img/jaeger-asynchronous.png | 1 + minitrace-old/img/jaeger-synchronous.png | 1 + .../src/collector/command.rs | 0 .../src/collector/console_reporter.rs | 0 .../src/collector/global_collector.rs | 0 .../src/collector/id.rs | 0 .../src/collector/mod.rs | 0 .../src/collector/test_reporter.rs | 0 {minitrace => minitrace-old}/src/event.rs | 0 {minitrace => minitrace-old}/src/future.rs | 0 minitrace-old/src/lib.rs | 316 ++++++ minitrace-old/src/local/guard.rs | 36 + .../src/local/local_collector.rs | 0 .../src/local/local_span.rs | 0 .../src/local/local_span_line.rs | 0 .../src/local/local_span_stack.rs | 0 {minitrace => minitrace-old}/src/local/mod.rs | 0 .../src/local/raw_span.rs | 0 minitrace-old/src/local/span_id.rs | 72 ++ .../src/local/span_queue.rs | 0 {minitrace => minitrace-old}/src/span.rs | 0 {minitrace => minitrace-old}/src/util/mod.rs | 0 .../src/util/object_pool.rs | 0 {minitrace => minitrace-old}/src/util/spsc.rs | 0 {minitrace => minitrace-old}/src/util/tree.rs | 0 minitrace-old/tests/lib.rs | 517 ++++++++++ minitrace-opentelemetry/Cargo.toml | 2 +- minitrace-tests/Cargo.toml | 48 + minitrace-tests/README.md | 94 ++ minitrace-tests/integration/indev.rs | 28 + minitrace-tests/integration/main.rs | 27 + minitrace-tests/integration/tests/defaults.rs | 75 ++ .../integration/tests/defaults/Cargo.toml | 21 + .../defaults/no-be-no-drop-local.expanded.rs | 36 + .../tests/defaults/no-be-no-drop-local.rs | 48 + .../tests/defaults/no-be-no-drop-threads.rs | 57 ++ minitrace-tests/integration/tests/issues.rs | 46 + .../integration/tests/issues/129.rs | 26 + .../integration/tests/issues/141.rs | 29 + minitrace-tests/integration/tests/mod.rs | 23 + minitrace-tests/integration/tests/options.rs | 8 + .../integration/tests/options/name-threads.rs | 33 + .../options/no-be-local-async-enter-false.rs | 46 + .../options/no-be-local-async-enter-true.rs | 38 + .../tests/options/no-be-local-sync.rs | 35 + .../integration/tests/options/threads-sync.rs | 40 + minitrace-tests/src/lib.rs | 21 + minitrace/Cargo.toml | 70 -- result.txt | 21 + rustfmt.toml | 3 + src/collector/command.rs | 33 + src/collector/console_reporter.rs | 15 + src/collector/global_collector.rs | 519 ++++++++++ src/collector/id.rs | 61 ++ src/collector/mod.rs | 387 ++++++++ src/collector/test_reporter.rs | 30 + src/event.rs | 62 ++ src/future.rs | 149 +++ {minitrace/src => src}/lib.rs | 0 src/local/local_collector.rs | 262 +++++ src/local/local_span.rs | 206 ++++ src/local/local_span_line.rs | 247 +++++ src/local/local_span_stack.rs | 386 ++++++++ src/local/mod.rs | 15 + src/local/raw_span.rs | 46 + src/local/span_queue.rs | 332 +++++++ src/span.rs | 897 ++++++++++++++++++ src/util/mod.rs | 71 ++ src/util/object_pool.rs | 135 +++ src/util/spsc.rs | 75 ++ src/util/tree.rs | 259 +++++ test-no-report/Cargo.toml | 2 +- test-utilities/Cargo.toml | 16 + test-utilities/src/lib.rs | 92 ++ {minitrace/tests => tests}/lib.rs | 0 208 files changed, 10523 insertions(+), 412 deletions(-) rename {minitrace/benches => benches}/compare.rs (100%) rename {minitrace/benches => benches}/object_pool.rs (100%) rename {minitrace/benches => benches}/spsc.rs (100%) rename {minitrace/benches => benches}/trace.rs (100%) rename {minitrace/examples => examples}/asynchronous.rs (100%) rename {minitrace/examples => examples}/get_started.rs (71%) rename {minitrace/examples => examples}/log.rs (100%) rename {minitrace/examples => examples}/synchronous.rs (100%) rename {minitrace/examples => examples}/unit_test.rs (100%) create mode 120000 img/benchmark.jpeg create mode 120000 img/jaeger-asynchronous.png create mode 120000 img/jaeger-synchronous.png create mode 100644 minitrace-macro/src/trace.rs create mode 100644 minitrace-macro/src/trace/analyze.rs create mode 100644 minitrace-macro/src/trace/generate.rs create mode 100644 minitrace-macro/src/trace/lower.rs create mode 100644 minitrace-macro/src/trace/lower/async_trait.rs create mode 100644 minitrace-macro/src/trace/lower/block.rs create mode 100644 minitrace-macro/src/trace/lower/lifetime.rs create mode 100644 minitrace-macro/src/trace/lower/quotable.rs create mode 100644 minitrace-macro/src/trace/lower/signature.rs create mode 100644 minitrace-macro/src/trace/parse.rs create mode 100644 minitrace-macro/tests/expand.rs create mode 100644 minitrace-macro/tests/expand/defaults/001-empty-sync.expanded.rs create mode 100644 minitrace-macro/tests/expand/defaults/001-empty-sync.rs create mode 100644 minitrace-macro/tests/expand/defaults/001-sync.expanded.rs create mode 100644 minitrace-macro/tests/expand/defaults/001-sync.rs create mode 100644 minitrace-macro/tests/expand/defaults/empty-async.rsi create mode 100644 minitrace-macro/tests/expand/issues/tokio-1613.rs create mode 100644 minitrace-macro/tests/expand/issues/tokio-1615.expanded.rs create mode 100644 minitrace-macro/tests/expand/issues/tokio-1615.rsi create mode 100644 minitrace-macro/tests/spans.rs create mode 100644 minitrace-macro/tests/spans/black-box-false-local-async.rs create mode 100644 minitrace-macro/tests/spans/black-box-false-local-sync.rs create mode 100644 minitrace-macro/tests/spans/black-box-false-threads-async.rs create mode 100644 minitrace-macro/tests/spans/black-box-false-threads-sync.rs create mode 100644 minitrace-macro/tests/spans/default-name-local-async.rs create mode 100644 minitrace-macro/tests/spans/default-name-local-sync.rs create mode 100644 minitrace-macro/tests/spans/default-name-threads-async.rs create mode 100644 minitrace-macro/tests/spans/default-name-threads-sync.rs create mode 100644 minitrace-macro/tests/spans/doctest-async.rs create mode 100644 minitrace-macro/tests/spans/doctest-sync.rs create mode 100644 minitrace-macro/tests/spans/example-async.rs create mode 100644 minitrace-macro/tests/spans/example-sync.rs create mode 100644 minitrace-macro/tests/spans/no-be-drop-local-async-enter-false.rs create mode 100644 minitrace-macro/tests/spans/no-be-drop-local-async-enter-true.rs create mode 100644 minitrace-macro/tests/spans/no-be-drop-local-sync.rs create mode 100644 minitrace-macro/tests/spans/no-be-drop-threads-async.rs create mode 100644 minitrace-macro/tests/spans/no-be-drop-threads-sync.rs create mode 100644 minitrace-macro/tests/spans/no-be-no-drop-async-enter-false.rs create mode 100644 minitrace-macro/tests/spans/no-be-no-drop-local-async-enter-true.rs create mode 100644 minitrace-macro/tests/spans/no-be-no-drop-local-sync.rs create mode 100644 minitrace-macro/tests/spans/no-be-no-drop-threads-async.rs create mode 100644 minitrace-macro/tests/spans/no-be-no-drop-threads-sync.rs create mode 100644 minitrace-macro/tests/trace-dev.rs create mode 100644 minitrace-macro/tests/trace.rs rename minitrace-macro/tests/{ui/err/has-expr-argument.rs => trace/ui/err/001-name-is-ident.rs} (71%) create mode 100644 minitrace-macro/tests/trace/ui/err/001-name-is-ident.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/002-name-is-symbol.rs create mode 100644 minitrace-macro/tests/trace/ui/err/002-name-is-symbol.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/003-name-is-boolean.rs create mode 100644 minitrace-macro/tests/trace/ui/err/003-name-is-boolean.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/004-has-all-sync.rs create mode 100644 minitrace-macro/tests/trace/ui/err/004-has-all-sync.stderr rename minitrace-macro/tests/{ui/ok/has-name-mut.rs => trace/ui/err/005-has-enter_on_poll-sync.rs} (72%) create mode 100644 minitrace-macro/tests/trace/ui/err/005-has-enter_on_poll-sync.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/006-has-too-many-arguments.rs create mode 100644 minitrace-macro/tests/trace/ui/err/006-has-too-many-arguments.stderr rename minitrace-macro/tests/{ui/err/trace-interleaved.rs => trace/ui/err/007-interleaved.rs} (79%) create mode 100644 minitrace-macro/tests/trace/ui/err/007-interleaved.stderr rename minitrace-macro/tests/{ui/err/item-is-not-a-function.rs => trace/ui/err/008-item-is-not-a-function.rs} (68%) create mode 100644 minitrace-macro/tests/trace/ui/err/008-item-is-not-a-function.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/009-optional-is-known-str.rs create mode 100644 minitrace-macro/tests/trace/ui/err/009-optional-is-known-str.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/010-optional-is-not-assignment.rs create mode 100644 minitrace-macro/tests/trace/ui/err/010-optional-is-not-assignment.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/011-optional-is-symbol.rs create mode 100644 minitrace-macro/tests/trace/ui/err/011-optional-is-symbol.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/012-optional-is-unknown-ident.rs create mode 100644 minitrace-macro/tests/trace/ui/err/012-optional-is-unknown-ident.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/013-optional-is-not-ident.rs create mode 100644 minitrace-macro/tests/trace/ui/err/013-optional-is-not-ident.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/014-optional-value-type-is-ident.rs create mode 100644 minitrace-macro/tests/trace/ui/err/014-optional-value-type-is-ident.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/015-optional-value-type-is-unknown-lit.rs create mode 100644 minitrace-macro/tests/trace/ui/err/015-optional-value-type-is-unknown-lit.stderr rename minitrace-macro/tests/{ui/err/name-is-not-an-assignment-expression.rs => trace/ui/err/016-name-is-char.rs} (69%) create mode 100644 minitrace-macro/tests/trace/ui/err/016-name-is-char.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/017-name-is-byte-str.rs create mode 100644 minitrace-macro/tests/trace/ui/err/017-name-is-byte-str.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/018-has-duplicate-name.rs create mode 100644 minitrace-macro/tests/trace/ui/err/018-has-duplicate-name.stderr create mode 100644 minitrace-macro/tests/trace/ui/err/019-has-duplicate-enter_on_poll.rs create mode 100644 minitrace-macro/tests/trace/ui/err/019-has-duplicate-enter_on_poll.stderr rename minitrace-macro/tests/{ => trace}/ui/err/has-duplicated-arguments.rs (100%) rename minitrace-macro/tests/{ => trace}/ui/err/has-duplicated-arguments.stderr (100%) rename minitrace-macro/tests/{ => trace}/ui/err/has-ident-arguments.rs (69%) rename minitrace-macro/tests/{ => trace}/ui/err/has-ident-arguments.stderr (100%) create mode 100644 minitrace-macro/tests/trace/ui/ok/001-has-no-arguments.rs rename minitrace-macro/tests/{ui/ok/has-name-async.rs => trace/ui/ok/002-has-enter_on_poll-ident-async.rs} (79%) create mode 100644 minitrace-macro/tests/trace/ui/ok/003-has-name-async.rs rename minitrace-macro/tests/{ui/ok/has-name-async-mut.rs => trace/ui/ok/004-has-name-mut-async.rs} (68%) create mode 100644 minitrace-macro/tests/trace/ui/ok/005-has-name-mut-sync.rs rename minitrace-macro/tests/{ui/ok/has-name.rs => trace/ui/ok/006-has-name-sync.rs} (72%) create mode 100644 minitrace-macro/tests/trace/ui/ok/007-interleaved.rs create mode 100644 minitrace-macro/tests/trace/ui/ok/008-has-all-async.rs create mode 100644 minitrace-macro/tests/trace/ui/ok/008-has-all-sync.rs create mode 100644 minitrace-macro/tests/trace/ui/ok/009-consecutive-traces.rs create mode 100644 minitrace-macro/tests/trace/ui/ok/010-name-out-of-place.rs rename minitrace-macro/tests/{ => trace}/ui/ok/async-in-trait.rs (100%) rename minitrace-macro/tests/{ => trace}/ui/ok/async-trait.rs (100%) delete mode 100644 minitrace-macro/tests/ui/err/item-is-not-a-function.stderr delete mode 100644 minitrace-macro/tests/ui/ok/has-no-arguments.rs create mode 100644 minitrace-old/Cargo.toml rename {minitrace => minitrace-old}/LICENSE (100%) create mode 100644 minitrace-old/benches/compare.rs create mode 100644 minitrace-old/benches/object_pool.rs create mode 100644 minitrace-old/benches/spsc.rs create mode 100644 minitrace-old/benches/trace.rs create mode 100644 minitrace-old/examples/asynchronous.rs create mode 100644 minitrace-old/examples/get_started.rs create mode 100644 minitrace-old/examples/synchronous.rs create mode 120000 minitrace-old/img/benchmark.jpeg create mode 120000 minitrace-old/img/jaeger-asynchronous.png create mode 120000 minitrace-old/img/jaeger-synchronous.png rename {minitrace => minitrace-old}/src/collector/command.rs (100%) rename {minitrace => minitrace-old}/src/collector/console_reporter.rs (100%) rename {minitrace => minitrace-old}/src/collector/global_collector.rs (100%) rename {minitrace => minitrace-old}/src/collector/id.rs (100%) rename {minitrace => minitrace-old}/src/collector/mod.rs (100%) rename {minitrace => minitrace-old}/src/collector/test_reporter.rs (100%) rename {minitrace => minitrace-old}/src/event.rs (100%) rename {minitrace => minitrace-old}/src/future.rs (100%) create mode 100644 minitrace-old/src/lib.rs create mode 100644 minitrace-old/src/local/guard.rs rename {minitrace => minitrace-old}/src/local/local_collector.rs (100%) rename {minitrace => minitrace-old}/src/local/local_span.rs (100%) rename {minitrace => minitrace-old}/src/local/local_span_line.rs (100%) rename {minitrace => minitrace-old}/src/local/local_span_stack.rs (100%) rename {minitrace => minitrace-old}/src/local/mod.rs (100%) rename {minitrace => minitrace-old}/src/local/raw_span.rs (100%) create mode 100644 minitrace-old/src/local/span_id.rs rename {minitrace => minitrace-old}/src/local/span_queue.rs (100%) rename {minitrace => minitrace-old}/src/span.rs (100%) rename {minitrace => minitrace-old}/src/util/mod.rs (100%) rename {minitrace => minitrace-old}/src/util/object_pool.rs (100%) rename {minitrace => minitrace-old}/src/util/spsc.rs (100%) rename {minitrace => minitrace-old}/src/util/tree.rs (100%) create mode 100644 minitrace-old/tests/lib.rs create mode 100644 minitrace-tests/Cargo.toml create mode 100644 minitrace-tests/README.md create mode 100644 minitrace-tests/integration/indev.rs create mode 100644 minitrace-tests/integration/main.rs create mode 100644 minitrace-tests/integration/tests/defaults.rs create mode 100644 minitrace-tests/integration/tests/defaults/Cargo.toml create mode 100644 minitrace-tests/integration/tests/defaults/no-be-no-drop-local.expanded.rs create mode 100644 minitrace-tests/integration/tests/defaults/no-be-no-drop-local.rs create mode 100644 minitrace-tests/integration/tests/defaults/no-be-no-drop-threads.rs create mode 100644 minitrace-tests/integration/tests/issues.rs create mode 100644 minitrace-tests/integration/tests/issues/129.rs create mode 100644 minitrace-tests/integration/tests/issues/141.rs create mode 100644 minitrace-tests/integration/tests/mod.rs create mode 100644 minitrace-tests/integration/tests/options.rs create mode 100644 minitrace-tests/integration/tests/options/name-threads.rs create mode 100644 minitrace-tests/integration/tests/options/no-be-local-async-enter-false.rs create mode 100644 minitrace-tests/integration/tests/options/no-be-local-async-enter-true.rs create mode 100644 minitrace-tests/integration/tests/options/no-be-local-sync.rs create mode 100644 minitrace-tests/integration/tests/options/threads-sync.rs create mode 100644 minitrace-tests/src/lib.rs delete mode 100644 minitrace/Cargo.toml create mode 100644 result.txt create mode 100644 src/collector/command.rs create mode 100644 src/collector/console_reporter.rs create mode 100644 src/collector/global_collector.rs create mode 100644 src/collector/id.rs create mode 100644 src/collector/mod.rs create mode 100644 src/collector/test_reporter.rs create mode 100644 src/event.rs create mode 100644 src/future.rs rename {minitrace/src => src}/lib.rs (100%) create mode 100644 src/local/local_collector.rs create mode 100644 src/local/local_span.rs create mode 100644 src/local/local_span_line.rs create mode 100644 src/local/local_span_stack.rs create mode 100644 src/local/mod.rs create mode 100644 src/local/raw_span.rs create mode 100644 src/local/span_queue.rs create mode 100644 src/span.rs create mode 100644 src/util/mod.rs create mode 100644 src/util/object_pool.rs create mode 100644 src/util/spsc.rs create mode 100644 src/util/tree.rs create mode 100644 test-utilities/Cargo.toml create mode 100644 test-utilities/src/lib.rs rename {minitrace/tests => tests}/lib.rs (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 723b961e..c3ed6955 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d8ade9b8..5edc8ca3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -22,8 +22,6 @@ jobs: RUSTDOCFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off" run: | cargo test --workspace --all-features --no-fail-fast - cargo run --example synchronous - cargo run --example asynchronous cargo run --example get_started - id: coverage uses: actions-rs/grcov@v0.1 diff --git a/.gitignore b/.gitignore index 6d9c405a..15e38b9c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .idea Cargo.lock +*.log \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e50df1..6794c7a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +- Refactor `minitrace-macro` to pipeline model (issue #113). +- Issue #142: Attribute arguments are all keyword/named arguments. Position arguments deprecated. +- Issue #128: Attribute parsing errors should not break IDE completion, etc. +- Issue #112: Fixed + ## v0.5.1 - Fix panics due to destruction of Thread Local Storage value @@ -19,7 +24,7 @@ - Remove `LocalSpanGuard` and merge it into `LocalSpan`. - Remove `LocalSpan::with_property`, `LocalSpan::with_properties`, `Span::with_property` and `Span::with_properties`. - Add `LocalSpan::add_property`, `LocalSpan::add_properties`, `Span::add_property` and `Span::add_properties`. -- Remove `LocalParentGuard`. `Span::set_local_parent` returns a general `Option>` instead. +- Remove `LocalParentGuard`. `Span::set_local_parent` returns a general `Option>` instead. ## v0.3.1 diff --git a/Cargo.toml b/Cargo.toml index 958fe469..7167c6d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,92 @@ +[package] +name = "minitrace" +version = "0.5.1" +authors = ["The TiKV Project Authors"] +license = "Apache-2.0" +rust-version = "1.56.0" +edition = "2021" +description = "A high-performance timeline tracing library for Rust" +homepage = "https://github.com/tikv/minitrace-rust" +repository = "https://github.com/tikv/minitrace-rust" +documentation = "https://docs.rs/minitrace" +readme = "README.md" +keywords = ["tracing", "span", "datadog", "jaeger", "opentracing"] + [workspace] resolver = "2" members = [ - "minitrace", "minitrace-macro", "minitrace-jaeger", "minitrace-datadog", "minitrace-opentelemetry", "test-no-report", + "minitrace-tests", ] +exclude = ["minitrace-old"] + +[dependencies] +futures = "0.3" +minitrace-macro = { version = "0.5.1", path = "minitrace-macro", optional = true} +minstant = "0.1" +parking_lot = "0.12" +pin-project = "1.0" +# TODO: Remove once_cell once #![feature(once_cell)] is stabilized +once_cell = "1" +rand = "0.8" + +[dev-dependencies] +# The procedural macro `trace` only supports async-trait higher than 0.1.52 +async-trait = "0.1.52" +criterion = { version = "0.3", features = ["html_reports"] } +crossbeam = "0.8" +env_logger = "0.10" +futures = "0.3" +futures-timer = "3" +log = "0.4" +logcall = "0.1.4" +minitrace-datadog = { path = "minitrace-datadog" } +minitrace-jaeger = { path = "minitrace-jaeger" } +minitrace-opentelemetry = { version = "0.5.1", path = "minitrace-opentelemetry" } +mockall = "0.11" +once_cell = "1" +opentelemetry = { version = "0.19", default-features = false, features = ["trace"] } +opentelemetry-otlp = { version = "0.12", features = ["trace"] } +rand = "0.8" +rustracing = "0.6" +serial_test = "2" +test-harness = "0.1.1" +tokio = { version = "1", features = ["rt", "time", "macros"] } +tracing = "0.1" +tracing-core = "0.1" +tracing-opentelemetry = "0.15" +tracing-subscriber = "0.2" + +# Cargo does not pass on features to subcrates in a virtual workspace +# https://github.com/rust-lang/cargo/issues/4942 +# +# Workaround: +# +# export MINITRACE_FEATURES="default minitrace-tests/tk" +# cargo test --manifest-path=moniker/Cargo.toml --no-default-features --features="$MINITRACE_FEATURES" test-name +# +[features] +default = ["attributes", "enable"] +attributes = ["minitrace-macro"] +ci = [] +enable = [] + +[[bench]] +name = "trace" +harness = false + +[[bench]] +name = "compare" +harness = false + +[[bench]] +name = "spsc" +harness = false -[profile.bench] -opt-level = 3 -lto = true +[[bench]] +name = "object_pool" +harness = false diff --git a/minitrace/benches/compare.rs b/benches/compare.rs similarity index 100% rename from minitrace/benches/compare.rs rename to benches/compare.rs diff --git a/minitrace/benches/object_pool.rs b/benches/object_pool.rs similarity index 100% rename from minitrace/benches/object_pool.rs rename to benches/object_pool.rs diff --git a/minitrace/benches/spsc.rs b/benches/spsc.rs similarity index 100% rename from minitrace/benches/spsc.rs rename to benches/spsc.rs diff --git a/minitrace/benches/trace.rs b/benches/trace.rs similarity index 100% rename from minitrace/benches/trace.rs rename to benches/trace.rs diff --git a/minitrace/examples/asynchronous.rs b/examples/asynchronous.rs similarity index 100% rename from minitrace/examples/asynchronous.rs rename to examples/asynchronous.rs diff --git a/minitrace/examples/get_started.rs b/examples/get_started.rs similarity index 71% rename from minitrace/examples/get_started.rs rename to examples/get_started.rs index 519fa9c2..29181571 100644 --- a/minitrace/examples/get_started.rs +++ b/examples/get_started.rs @@ -1,5 +1,11 @@ // Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. - +//! # Get started +//! +//! 1. Setup a trace viewer/frontend. Jaeger example: +//! ```ignore +//! podman run -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in-one:latest +//! ``` +//! use minitrace::collector::Config; use minitrace::collector::ConsoleReporter; use minitrace::prelude::*; diff --git a/minitrace/examples/log.rs b/examples/log.rs similarity index 100% rename from minitrace/examples/log.rs rename to examples/log.rs diff --git a/minitrace/examples/synchronous.rs b/examples/synchronous.rs similarity index 100% rename from minitrace/examples/synchronous.rs rename to examples/synchronous.rs diff --git a/minitrace/examples/unit_test.rs b/examples/unit_test.rs similarity index 100% rename from minitrace/examples/unit_test.rs rename to examples/unit_test.rs diff --git a/img/benchmark.jpeg b/img/benchmark.jpeg new file mode 120000 index 00000000..2ad3e507 --- /dev/null +++ b/img/benchmark.jpeg @@ -0,0 +1 @@ +../../img/benchmark.jpeg \ No newline at end of file diff --git a/img/jaeger-asynchronous.png b/img/jaeger-asynchronous.png new file mode 120000 index 00000000..b2f7470e --- /dev/null +++ b/img/jaeger-asynchronous.png @@ -0,0 +1 @@ +../../img/jaeger-asynchronous.png \ No newline at end of file diff --git a/img/jaeger-synchronous.png b/img/jaeger-synchronous.png new file mode 120000 index 00000000..8ca02166 --- /dev/null +++ b/img/jaeger-synchronous.png @@ -0,0 +1 @@ +../../img/jaeger-synchronous.png \ No newline at end of file diff --git a/minitrace-datadog/Cargo.toml b/minitrace-datadog/Cargo.toml index 1a12e454..40639480 100644 --- a/minitrace-datadog/Cargo.toml +++ b/minitrace-datadog/Cargo.toml @@ -13,7 +13,7 @@ categories = ["development-tools::debugging"] keywords = ["tracing", "span", "datadog", "jaeger", "opentelemetry"] [dependencies] -minitrace = { path = "../minitrace" } +minitrace = { path = "../" } reqwest = { version = "0.11", features = ["blocking"] } rmp-serde = "1" serde = { version = "1", features = ["derive"] } diff --git a/minitrace-jaeger/Cargo.toml b/minitrace-jaeger/Cargo.toml index e40628f7..1137120c 100644 --- a/minitrace-jaeger/Cargo.toml +++ b/minitrace-jaeger/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["tracing", "span", "datadog", "jaeger", "opentelemetry"] [dependencies] log = "0.4" -minitrace = { path = "../minitrace" } +minitrace = { path = "../" } thrift_codec = "0.2" [dev-dependencies] diff --git a/minitrace-macro/Cargo.toml b/minitrace-macro/Cargo.toml index 440fea26..9b94d391 100644 --- a/minitrace-macro/Cargo.toml +++ b/minitrace-macro/Cargo.toml @@ -3,6 +3,7 @@ name = "minitrace-macro" version = "0.5.1" authors = ["The TiKV Project Authors"] license = "Apache-2.0" +rust-version = "1.56.0" edition = "2021" description = "Attribute procedural macro for minitrace-rust" homepage = "https://github.com/tikv/minitrace-rust" @@ -16,17 +17,31 @@ keywords = ["tracing", "span", "datadog", "jaeger", "opentelemetry"] proc-macro = true [dependencies] -# The macro `quote_spanned!` is added to syn in 1.0.84 +darling = "0.14" proc-macro-error = "1" proc-macro2 = "1" quote = "1" -syn = { version = "1.0.84", features = ["full", "parsing", "extra-traits", "proc-macro", "visit-mut"] } +# The macro `quote_spanned!` is added to syn in 1.0.84 +syn = { version = "1.0.84", features = ["full", "parsing", "extra-traits", "proc-macro", "visit", "visit-mut"] } +thiserror = "1.0.30" +tree-flat = "0.1.1" [dev-dependencies] +aquamarine = "0.1" +futures = "0.3" +futures-timer = "3.0" logcall = "0.1.4" -minitrace = { path = "../minitrace" } +macrotest = "1" +minitrace = { path = "../" } +minitrace-jaeger = { path = "../minitrace-jaeger" } +rand = "0.8" +test-utilities = { path = "../test-utilities" } tokio = { version = "1", features = ["full"] } trybuild = "1" # The procedural macro `trace` only supports async-trait higher than 0.1.52 async-trait = "0.1.52" log = "0.4" + +[features] +default = [] +ci = [] diff --git a/minitrace-macro/README.md b/minitrace-macro/README.md index afa6cfd8..58f5bc26 100644 --- a/minitrace-macro/README.md +++ b/minitrace-macro/README.md @@ -4,4 +4,181 @@ [![Crates.io](https://img.shields.io/crates/v/minitrace-macro.svg)](https://crates.io/crates/minitrace-macro) [![LICENSE](https://img.shields.io/github/license/tikv/minitrace-rust.svg)](https://github.com/tikv/minitrace-rust/blob/master/LICENSE) -An attribute macro designed to eliminate boilerplate code for [`minitrace`](https://crates.io/crates/minitrace). +Provides an attribute-macro `trace` to help get rid of boilerplate. + +## Usage + +### Parameters + +- `name`: Spans are generally named, by convention, using the string given as + the first argument: `#[trace( name = "my_name")]`. These names ***must*** adhere to + Rust function name conventions. If a name is not given, the name of the + function being traced is used: `#[trace] fn f(){}` is equivalent to + `#[trace( name = "f")] fn f(){}`. +- `enter_on_poll`: Applies only to `async` functions. + +### Dependency + +```toml +[dependencies] +minitrace = "0.4" # minitrace-macro is within minitrace::prelude +``` + +### Synchronous Function + +```rust +use minitrace::prelude::*; + +#[trace( name = "foo")] +fn foo() { + // function body +} + +// ** WILL BE TRANSLATED INTO: ** +// +// fn foo() { +// let __guard = LocalSpan::enter_with_local_parent("foo"); +// { +// // function body +// } +// } +``` + +### Asynchronous Function + +```rust +use minitrace::prelude::*; + +#[trace( name = "bar")] +async fn bar() { + // function body +} + +// ** WILL BE TRANSLATED INTO: ** +// +// fn bar() -> impl core::future::Future { +// async { +// // function body +// } +// .in_span(Span::enter_with_local_parent("bar")) +// } + + +#[trace( name = "qux", enter_on_poll = true)] +async fn qux() { + // function body +} + +// ** WILL BE TRANSLATED INTO: ** +// +// fn qux() -> impl core::future::Future { +// async { +// // function body +// } +// .enter_on_poll("qux") +// } +``` + +### ⚠️ Local Parent Needed + +A function instrumented by `trace` always require a local parent in the context. Make sure that the caller is within the scope of `Span::set_local_parent()`. + +```rust +use minitrace::prelude::*; + +#[trace( name = "foo")] +fn foo() {} + +#[trace( name = "bar")] +async fn bar() {} + +let (root, collector) = Span::root("root"); + +{ + foo(); // This `foo` will __not__ be traced. +} + +{ + let _g = root.set_local_parent(); + foo(); // This `foo` will be traced. +} + +{ + runtime::spawn(bar()); // This `bar` will __not__ be traced. +} + +{ + let _g = root.set_local_parent(); + runtime::spawn(bar()); // This `bar` will be traced. +} +``` + +## Developers + +NOTE: A nightly compiler is required for the macro expansion tests + +This Crate adapts the [Ferrous Systems](https://ferrous-systems.com/blog/testing-proc-macros/) +proc-macro pipeline: + + + +![image info](./img/pipeline.png) + +### Pipeline Stages + +1. **Validate:** Validate the parsed user input, which are Rust tokens + ([`TokenStream`]), into an a `syn::Item`. The [syn crate] does the parsing. + Invalid macro arguments will trigger an error and exit. Hereafter, code + should be able to proceed under the assumption each parameter value is valid + and their combination of values is valid too. +1. **Analyze:** Map the [`TokenStream`] into the proc-macro "domain model", + that is, into types that reflect the Minitrace semantics. +1. **Lower:** Transform the "domain model" into a "intermediate representation" + (IR), ready for the generate stage. +1. **Generate:** Turn the IR into Rust tokens, [`TokenStream`]. These tokens + are actual [`proc_macro2`] tokens which are then converted [`Into`] the + required [`proc_macro`] tokens. + +### Tests + +To see a list of all tests: + +```bash +pushd minitrace-macro + cargo test -- --list +popd +``` + +To run an individual test suite, say the `ui` suite: + +```bash +cargo test ui -- --nocapture +``` + +#### Test Suites + +- `ui`: +- ``: + +#### Generated code tests + +NOTE: A nightly compiler is required for the macro expansion tests + +The cargo expand tool must be present. You can install it via cargo: + +```bash +cargo install cargo-expand +``` + +https://rust-lang.github.io/async-book/02_execution/02_future.html diff --git a/minitrace-macro/src/lib.rs b/minitrace-macro/src/lib.rs index 96c75ba3..d5f73194 100644 --- a/minitrace-macro/src/lib.rs +++ b/minitrace-macro/src/lib.rs @@ -1,70 +1,60 @@ +//! A procedural macro attribute for instrumenting functions with [`minitrace`]. +//! +//! [`minitrace`] is a performance-focused library for instrumenting Rust programs to collect +//! structured, event-based diagnostic information. This crate provides the +//! [`#[trace]`][trace] procedural macro attribute. +//! +//! Note that this macro is also re-exported by the main `minitrace` crate. +//! +//! *Compiler support: [requires `rustc` 1.49+][msrv]* +//! +//! [msrv]: #supported-rust-versions +//! +//! ## Getting Started +//! +//! This crate is included as part of the Minitrace crate. In general, you do +//! ***not** need to add this crate to your project's `Cargo.toml`. +//! However, you may wish to have this crate as a dependency only in +//! development, for this use case, please see [Development Only] in the +//! [Usage] section. +//! +//! ## Usage +//! +//! ### Development Only +//! +//! To have this crate as a dependency only in development: +//! +//! ```toml +//! [package] +//! ... +//! resolver = "2" +//! +//! [dependencies] +//! minitrace = {version = "0.5", default-features = false} +//! +//! [build-dependencies] +//! minitrace = {version = "0.5", features = ["attributes", "enable"]} +//! ``` +//! +//! ## Examples +//! +//! Please review the contents of the `examples` folder, as well as the integration test suite under `tests`. +//! + // Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. //! An attribute macro designed to eliminate boilerplate code for [`minitrace`](https://crates.io/crates/minitrace). -#![recursion_limit = "256"] // Instrumenting the async fn is not as straight forward as expected because `async_trait` rewrites `async fn` // into a normal fn which returns `Box`, and this stops the macro from distinguishing `async fn` from `fn`. // The following code reused the `async_trait` probes from [tokio-tracing](https://github.com/tokio-rs/tracing/blob/6a61897a5e834988ad9ac709e28c93c4dbf29116/tracing-attributes/src/expand.rs). -extern crate proc_macro; - -#[macro_use] -extern crate proc_macro_error; - -use std::collections::HashSet; - -use quote::quote_spanned; -use syn::spanned::Spanned; -use syn::*; - -struct Args { - name: String, - enter_on_poll: bool, -} - -impl Args { - fn parse(default_name: String, input: AttributeArgs) -> Args { - if input.len() > 2 { - abort_call_site!("too many arguments"); - } - - let mut args = HashSet::new(); - let mut name = default_name; - let mut enter_on_poll = false; - - for arg in &input { - match arg { - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(s), - .. - })) if path.is_ident("name") => { - name = s.value(); - args.insert("name"); - } - NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit: Lit::Bool(b), - .. - })) if path.is_ident("enter_on_poll") => { - enter_on_poll = b.value; - args.insert("enter_on_poll"); - } - _ => abort_call_site!("invalid argument"), - } - } +#![recursion_limit = "256"] - if args.len() != input.len() { - abort_call_site!("duplicated arguments"); - } +mod trace; - Args { - name, - enter_on_poll, - } - } -} +extern crate proc_macro; +extern crate proc_macro_error; /// An attribute macro designed to eliminate boilerplate code. /// @@ -122,256 +112,35 @@ impl Args { /// } /// ``` #[proc_macro_attribute] -#[proc_macro_error] pub fn trace( args: proc_macro::TokenStream, - item: proc_macro::TokenStream, + items: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let input = syn::parse_macro_input!(item as ItemFn); - let args = Args::parse( - input.sig.ident.to_string(), - syn::parse_macro_input!(args as AttributeArgs), - ); - - // check for async_trait-like patterns in the block, and instrument - // the future instead of the wrapper - let func_body = if let Some(internal_fun) = - get_async_trait_info(&input.block, input.sig.asyncness.is_some()) - { - // let's rewrite some statements! - match internal_fun.kind { - // async-trait <= 0.1.43 - AsyncTraitKind::Function(_) => { - unimplemented!( - "Please upgrade the crate `async-trait` to a version higher than 0.1.44" - ) - } - // async-trait >= 0.1.44 - AsyncTraitKind::Async(async_expr) => { - // fallback if we couldn't find the '__async_trait' binding, might be - // useful for crates exhibiting the same behaviors as async-trait - let instrumented_block = gen_block(&async_expr.block, true, false, args); - let async_attrs = &async_expr.attrs; - quote! { - Box::pin(#(#async_attrs) * #instrumented_block) - } - } - } - } else { - gen_block( - &input.block, - input.sig.asyncness.is_some(), - input.sig.asyncness.is_some(), - args, - ) - }; - - let ItemFn { - attrs, vis, sig, .. - } = input; - - let Signature { - output: return_type, - inputs: params, - unsafety, - constness, - abi, - ident, - asyncness, - generics: - Generics { - params: gen_params, - where_clause, - .. - }, - .. - } = sig; - - quote::quote!( - #(#attrs) * - #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #return_type - #where_clause - { - #func_body - } - ) - .into() -} - -/// Instrument a block -fn gen_block( - block: &Block, - async_context: bool, - async_keyword: bool, - args: Args, -) -> proc_macro2::TokenStream { - let name = args.name; - - // Generate the instrumented function body. - // If the function is an `async fn`, this will wrap it in an async block. - // Otherwise, this will enter the span and then perform the rest of the body. - if async_context { - let block = if args.enter_on_poll { - quote_spanned!(block.span()=> - minitrace::future::FutureExt::enter_on_poll( - async move { #block }, - #name - ) - ) - } else { - quote_spanned!(block.span()=> - minitrace::future::FutureExt::in_span( - async move { #block }, - minitrace::Span::enter_with_local_parent( #name ) - ) - ) - }; - - if async_keyword { - quote_spanned!(block.span()=> - #block.await - ) - } else { - block - } - } else { - if args.enter_on_poll { - abort_call_site!("`enter_on_poll` can not be applied on non-async function"); + let trace = syn::parse2::(args.into()); + let input: trace::Trace = match trace { + Ok(trace) => trace, + Err(e) => { + return token_stream_with_error(items.into(), e).into(); } - - quote_spanned!(block.span()=> - let __guard = minitrace::local::LocalSpan::enter_with_local_parent( #name ); - #block - ) - } -} - -enum AsyncTraitKind<'a> { - // old construction. Contains the function - Function(&'a ItemFn), - // new construction. Contains a reference to the async block - Async(&'a ExprAsync), -} - -struct AsyncTraitInfo<'a> { - // statement that must be patched - _source_stmt: &'a Stmt, - kind: AsyncTraitKind<'a>, -} - -// Get the AST of the inner function we need to hook, if it was generated -// by async-trait. -// When we are given a function annotated by async-trait, that function -// is only a placeholder that returns a pinned future containing the -// user logic, and it is that pinned future that needs to be instrumented. -// Were we to instrument its parent, we would only collect information -// regarding the allocation of that future, and not its own span of execution. -// Depending on the version of async-trait, we inspect the block of the function -// to find if it matches the pattern -// `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))` (<=0.1.43), or if -// it matches `Box::pin(async move { ... }) (>=0.1.44). We the return the -// statement that must be instrumented, along with some other informations. -// 'gen_body' will then be able to use that information to instrument the -// proper function/future. -// (this follows the approach suggested in -// https://github.com/dtolnay/async-trait/issues/45#issuecomment-571245673) -fn get_async_trait_info(block: &Block, block_is_async: bool) -> Option> { - // are we in an async context? If yes, this isn't a async_trait-like pattern - if block_is_async { - return None; - } - - // list of async functions declared inside the block - let inside_funs = block.stmts.iter().filter_map(|stmt| { - if let Stmt::Item(Item::Fn(fun)) = &stmt { - // If the function is async, this is a candidate - if fun.sig.asyncness.is_some() { - return Some((stmt, fun)); - } - } - None - }); - - // last expression of the block (it determines the return value - // of the block, so that if we are working on a function whose - // `trait` or `impl` declaration is annotated by async_trait, - // this is quite likely the point where the future is pinned) - let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| { - if let Stmt::Expr(expr) = stmt { - Some((stmt, expr)) - } else { - None - } - })?; - - // is the last expression a function call? - let (outside_func, outside_args) = match last_expr { - Expr::Call(ExprCall { func, args, .. }) => (func, args), - _ => return None, }; - // is it a call to `Box::pin()`? - let path = match outside_func.as_ref() { - Expr::Path(path) => &path.path, - _ => return None, - }; - if !path_to_string(path).ends_with("Box::pin") { - return None; - } - - // Does the call take an argument? If it doesn't, - // it's not gonna compile anyway, but that's no reason - // to (try to) perform an out of bounds access - if outside_args.is_empty() { - return None; - } + let models = trace::analyze(input, items.into()); - // Is the argument to Box::pin an async block that - // captures its arguments? - if let Expr::Async(async_expr) = &outside_args[0] { - // check that the move 'keyword' is present - async_expr.capture?; + let quotes = trace::lower(models); - return Some(AsyncTraitInfo { - _source_stmt: last_expr_stmt, - kind: AsyncTraitKind::Async(async_expr), - }); - } + let rust = trace::generate(quotes); - // Is the argument to Box::pin a function call itself? - let func = match &outside_args[0] { - Expr::Call(ExprCall { func, .. }) => func, - _ => return None, - }; - - // "stringify" the path of the function called - let func_name = match **func { - Expr::Path(ref func_path) => path_to_string(&func_path.path), - _ => return None, - }; - - // Was that function defined inside of the current block? - // If so, retrieve the statement where it was declared and the function itself - let (stmt_func_declaration, func) = inside_funs - .into_iter() - .find(|(_, fun)| fun.sig.ident == func_name)?; - - Some(AsyncTraitInfo { - _source_stmt: stmt_func_declaration, - kind: AsyncTraitKind::Function(func), - }) + rust.into() } -// Return a path as a String -fn path_to_string(path: &Path) -> String { - use std::fmt::Write; - // some heuristic to prevent too many allocations - let mut res = String::with_capacity(path.segments.len() * 5); - for i in 0..path.segments.len() { - write!(res, "{}", path.segments[i].ident).expect("writing to a String should never fail"); - if i < path.segments.len() - 1 { - res.push_str("::"); - } - } - res +// If any of the steps for this macro fail, we still want to expand to an item +// that is as close to the expected output as possible. +// This helps out IDEs such that completions and other related features keep +// working. +fn token_stream_with_error( + mut tokens: proc_macro2::TokenStream, + error: syn::Error, +) -> proc_macro2::TokenStream { + tokens.extend(error.into_compile_error()); + tokens } diff --git a/minitrace-macro/src/trace.rs b/minitrace-macro/src/trace.rs new file mode 100644 index 00000000..c329cc48 --- /dev/null +++ b/minitrace-macro/src/trace.rs @@ -0,0 +1,13 @@ +mod analyze; +mod generate; +mod lower; +mod parse; + +// Re-export crate::trace::validate::validate(...) as crate::trace::validate(...) +pub use crate::trace::analyze::analyze; +pub use crate::trace::generate::generate; +pub use crate::trace::lower::lower; +pub use crate::trace::lower::quotable::Quotable; +pub use crate::trace::lower::quotable::Quotables; +pub use crate::trace::lower::quotable::Quote; +pub use crate::trace::parse::Trace; diff --git a/minitrace-macro/src/trace/analyze.rs b/minitrace-macro/src/trace/analyze.rs new file mode 100644 index 00000000..e2457e36 --- /dev/null +++ b/minitrace-macro/src/trace/analyze.rs @@ -0,0 +1,553 @@ +/// Implementation Notes: +/// +/// Check for `async-trait`-like patterns in the block, and instrument the +/// future instead of the wrapper. +/// +/// Instrumenting the `async fn` is not as straight forward as expected because +/// `async_trait` rewrites `async fn` into a normal `fn` which returns +/// `Pin>`, and this stops the macro from +/// distinguishing `async fn` from `fn`. +/// +/// The following logic and code is from the `async-trait` probes from +/// [tokio-tracing][tokio-logic]. +/// The Tokio logic is required for detecting the `async fn` that is already +/// transformed to `fn -> Pin>` by +/// `async-trait`. +/// We have to distinguish this case from `fn -> impl Future` that is written +/// by the user because for the latter, we instrument it like normal `fn` +/// instead of `async fn`. +/// +/// The reason why we elaborate `async fn` into `fn -> impl Future`: +/// For an `async fn foo()`, we have to instrument the +/// `Span::enter_with_local_parent()` in the first call to `foo()`, but not in +/// the `poll()` or `.await`, because it's the only chance that +/// local parent is present in that context. +/// +/// [tokio-logic]: https://github.com/tokio-rs/tracing/blob/6a61897a5e834988ad9ac709e28c93c4dbf29116/tracing-attributes/src/expand.rs + +// Trace Attribute Features +// +// The feature set for the `trace` attribute is evolving, heading to a 1.0 +// release. The following features are under discussion. Implementation +// will be non-trivial until issues #136 and issue #137 are resolved. +// A consequence of this is that implementation will need to be incremental +// rather than big-bang event. +// +// - `.name: syn::LitStr,` +// - See upstream issue #142 +// - `.enter_on_poll: syn::LitBool,` +// - See upstream issue #133 and https://github.com/tikv/minitrace-rust/issues/126#issuecomment-1077326184 +// - `.parent: syn::LitStr,` +// - See upstream issue #117 +// - `.recorder: syn::Ident,` +// - See upstream issue #117 +// - `.recurse: syn::Ident,` +// - See upstream issue #134 +// - `.scope: syn::Ident,` +// - See upstream issue #133 and https://github.com/tikv/minitrace-rust/issues/126#issuecomment-1077326184 +// - `.variables: syn::Ident,` +// - See upstream issue #134 +// - `.conventional: syn::LitBool,` +// - Benefit is to short circuit some of the parsing logic and hopefully +// save on compile time - conjecture. +// - Assume & skip evaluations in analyze when `conventional=true`, +// and follow these defaults/conventions: +// +// - name: `fn` name (item). Including path(?) +// - recorder: `span` +// - recurse: `None` +// - scope: `Local` (sync), `Local` (async). +// - variables: `None` +// - enter_on_poll: +// - `None` (sync) +// - `true` (async) if `false` then convention is that scope: `Threads`. +// +// Note: These conventions change the current defaults. +// See https://github.com/tikv/minitrace-rust/issues/126#issuecomment-1077326184 +// +// Current default: +// +// - `#[trace] async fn` creates thread-safe span (`Span`) +// - `#[trace(enter_on_poll = true)] async fn` creates local context +// span (`LocalSpan`) +// - `#[trace] fn` create local context span (`LocalSpan`) +// +// impl Default for Model { +// +// fn default() -> Self { +// Ok(Model { +// name: todo!(), +// enter_on_poll: todo!(), +// parent: todo!(), +// recorder: todo!(), +// scope: todo!(), +// variables: v, +// }) +// } + +#[derive(Clone, Copy, Debug, PartialEq, darling::FromMeta)] +pub enum Scope { + Local, + Threads, +} + +// `Trace` should be moved into `minitrace-macro::validate`. +// Implement `syn::Parse` there, so that in `lib.rs`: +// +// let attr_args = parse_macro_input!(argsc as crate::trace::validate::TraceAttr); +// let itemfn = parse_macro_input!(itemc as ItemFn); +// let args2: proc_macro2::TokenStream = args.clone().into(); +// trace::validate(args2, item.into()); +// let model = trace::analyze(attr_args, itemfn); +// +// becomes +// +// use crate::trace::validate::Trace; +// let trace = parse_macro_input!(argsc as Trace); +// let item = parse_macro_input!(itemc as Trace); +// let model = trace::analyze(trace, item); +#[derive( + Clone, + std::fmt::Debug, + PartialEq, + // `darling::FromMeta,` adds two functions: + // + // ``` + // fn from_list(items: &[NestedMeta]) -> Result + // ``` + // + // `try_from_attributes(...)` returns: + // - `Ok(None)` if the attribute is missing, + // - `Ok(Some(_))` if its there and is valid, + // - `Err(_)` otherwise. + darling::FromMeta, +)] +pub struct Trace { + // Anything that implements `syn::parse::Parse` is supported. + #[darling(default)] + name: Option, + #[darling(default)] + scope: Option, // Scope::Local, Scope::Thread, etc. + + // Fields wrapped in `Option` are and default to `None` if + // not specified in the attribute. + #[darling(default)] + enter_on_poll: Option, + #[darling(default)] + parent: Option, + #[darling(default)] + recorder: Option, + #[darling(default)] + recurse: Option, + #[darling(default)] + root: Option, + #[darling(default)] + variables: Option, + #[darling(default)] + async_trait: Option, +} + +// Produce `Models` (a Vec-newtype) +// +// The `Models` container is built based on the attribute parameters +// held in the `Trace` type. +// +// The inputs are: +// - `meta`: A `syn::Attribute` encapsulated in `TraceAttr`. +// - `items`: A `proc_macr2::TokenStream`. +use syn::visit::Visit; +pub fn analyze( + //args: std::vec::Vec, + trace: crate::trace::Trace, + items: proc_macro2::TokenStream, +) -> Models { + let mut models = Models::::new(); + + // Prepare and merge each ItemFn with its trace settings + let tree: syn::File = syn::parse2(items).unwrap(); + let mut visitor = FnVisitor { + functions: Vec::new(), + }; + visitor.visit_file(&tree); + for f in visitor.functions { + let item_fn = (*f).clone(); + let default_name = item_fn.sig.ident.to_string(); + let _async_fn = match item_fn.sig.asyncness { + Some(_) => Some(syn::LitBool::new(true, proc_macro2::Span::call_site())), + None => Some(syn::LitBool::new(false, proc_macro2::Span::call_site())), + }; + let traced_item = if let crate::trace::Trace { + default: _, + validated: _, + name, + scope: Some(scope), + enter_on_poll, + parent: Some(parent), + recorder: Some(recorder), + recurse: Some(recurse), + root: Some(root), + variables: Some(variables), + async_trait: Some(async_trait), + async_fn: Some(async_fn), + } = trace.clone() + { + // Use default name when no name is passed in. + // NOTE: + // `#[trace(key = "value")]` maps to + // `#[trace(name = "__default", key = "value")]` + let span_name = if name.value() == "__default" { + syn::LitStr::new(&default_name, proc_macro2::Span::call_site()) + } else { + name + }; + + TracedItem { + name: span_name, + scope, + enter_on_poll, + parent, + recorder, + recurse, + root, + variables, + async_trait, + async_fn, + item_fn, + } + } else { + TracedItem { + ..Default::default() + } + }; + models.push(Model::Item(Box::new(traced_item))); + } + models +} + +// `Models` are a Vec-newtype +// +// A wrapper that allows us to implement *any* trait. As a new-type it rescinds +// the orphan rule so we have headroom. Further we can encapsulate or expose +// Vector functionality as we require. +// +// The [`From`] trait provides these conveniences (`match` branch): +// +// Err(err) => return err.into_compile_error().into(), +// +// Below the following traits are implemented: +// +// - `Debug` (via `#[derive(...)]`) +// - `Default` +// - `Deref` +// - `DerefMut` +// - `Display` +#[derive(Debug, Clone, PartialEq)] +pub struct Models(Vec); + +impl Models { + pub fn new() -> Models { + Models(Vec::::new()) + } + + #[allow(dead_code)] + pub fn with_capacity(capacity: usize) -> Models { + Models(Vec::::with_capacity(capacity)) + } +} + +impl Default for Models { + fn default() -> Models { + Models::new() + } +} + +impl std::fmt::Display for Models { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl std::ops::Deref for Models { + type Target = Vec; + fn deref(&self) -> &Vec { + &self.0 + } +} + +impl std::ops::DerefMut for Models { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TracedItem { + // These are the fields parsed as AttributeArgs into the `Trace` struct + pub name: syn::LitStr, + pub scope: crate::trace::parse::Scope, // Scope::Local, Scope::Thread, etc. + pub enter_on_poll: syn::LitBool, + pub parent: syn::LitStr, + pub recorder: syn::Ident, + pub recurse: syn::LitBool, + pub root: syn::LitBool, + pub variables: syn::ExprArray, + pub async_trait: syn::LitBool, + pub async_fn: syn::LitBool, + + // `item_fn` pairs each function with the `#[trace(...)]` settings. + // This structure admits the `recurse=true` option contemplated in issue #134 + pub item_fn: syn::ItemFn, +} + +#[derive(Clone, Debug, PartialEq, thiserror::Error)] +#[error("Validation logic error")] +pub enum Model { + Attribute(Trace), + // Boxed to satisfy clippy::large-enum-variant which is triggered by CI settings + Item(Box), +} + +// The FnVisitor is used to populate `Models` (a Vec-newtype) when +// `#[trace(recurse=all|public|private)]` on a function or, eventually, +// a module. +struct FnVisitor<'ast> { + functions: Vec<&'ast syn::ItemFn>, +} + +impl<'ast> syn::visit::Visit<'ast> for FnVisitor<'ast> { + fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) { + self.functions.push(node); + // Delegate to the default impl to visit any nested functions. + syn::visit::visit_item_fn(self, node); + } +} + +// Needed when we do convenient things like this (`match` branch): +// +// Err(err) => return err.into_compile_error().into(), +// +impl std::convert::From for Model { + fn from(_inner: proc_macro2::TokenStream) -> Model { + let attribute = Default::default(); + Model::Attribute(attribute) + } +} + +// In the model of the `#[trace]` proc-macro-attribute, the attribute data +// only appears once. We can have multiple `syn::Item` entries. +// For example: +// +impl std::convert::From for Models { + fn from(_inner: proc_macro2::TokenStream) -> Models { + let attribute = Default::default(); + let mut models = Models::::new(); + models.push(Model::Attribute(attribute)); + models + } +} + +impl Default for Trace { + fn default() -> Self { + // let scope = proc_macro2::Ident::new("Local", proc_macro2::Span::call_site()); + // Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + let name = Some(syn::LitStr::new( + "__default", + proc_macro2::Span::call_site(), + )); + let scope = Some(Scope::Local); + let enter_on_poll = Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + let recorder = Some(proc_macro2::Ident::new( + "span", + proc_macro2::Span::call_site(), + )); + let recurse = Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + let root = Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + let variables = Some(syn::parse_quote!([])); + let parent = Some(syn::LitStr::new( + "__default", + proc_macro2::Span::call_site(), + )); + let async_trait = Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + + Self { + name, + async_trait, + enter_on_poll, + parent, + recorder, + recurse, + root, + scope, + variables, + } + } +} + +impl Default for TracedItem { + fn default() -> Self { + // let scope = proc_macro2::Ident::new("Local", proc_macro2::Span::call_site()); + // Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + let name = syn::LitStr::new("__default", proc_macro2::Span::call_site()); + let scope = crate::trace::parse::Scope::Local; + let enter_on_poll = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let item_fn: syn::ItemFn = syn::parse_quote!( + fn __default() {} + ); + let recorder = proc_macro2::Ident::new("span", proc_macro2::Span::call_site()); + let recurse = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let root = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let variables = syn::parse_quote!([]); + let parent = syn::LitStr::new("__default", proc_macro2::Span::call_site()); + let async_trait = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let async_fn = syn::LitBool::new(false, proc_macro2::Span::call_site()); + + Self { + name, + async_trait, + async_fn, + enter_on_poll, + item_fn, + parent, + recorder, + recurse, + root, + scope, + variables, + } + } +} + +#[cfg(test)] +mod tests { + use syn::Attribute; + + use super::*; + + use crate::trace::analyze::Model; + use crate::trace::analyze::Models; + + #[test] + fn models_are_cloneable() { + let models = Models::::new(); + let clones = models.clone(); + assert_eq!(models, clones); + } + #[test] + fn with_traces() { + // `#[trace]` + //let args: Vec = vec![]; + let trace = crate::trace::Trace { + ..Default::default() + }; + + let items: proc_macro2::TokenStream = syn::parse_quote!( + #[trace] + fn f(x: bool) {} + ); + let models = analyze(trace, items.clone()); + + let model = (*models.get(0).unwrap()).clone(); + let traced_item = if let Model::Item(ti) = model { + Ok((*ti).clone()) + } else { + Err(()) + } + .unwrap(); + let expected = TracedItem { + name: syn::LitStr::new("f", proc_macro2::Span::call_site()), + item_fn: syn::parse2::(items).unwrap(), + ..Default::default() + }; + assert_eq!(traced_item, expected); + } + + #[test] + fn with_trace() { + // `#[trace]` + //let args: Vec = vec![]; + let trace = crate::trace::Trace { + ..Default::default() + }; + + let items: proc_macro2::TokenStream = syn::parse_quote!( + fn f(x: bool) {} + ); + let models = analyze(trace, items.clone()); + + let model = (*models.get(0).unwrap()).clone(); + let traced_item = if let Model::Item(ti) = model { + Ok((*ti).clone()) + } else { + Err(()) + } + .unwrap(); + let expected = TracedItem { + name: syn::LitStr::new("f", proc_macro2::Span::call_site()), + item_fn: syn::parse2::(items).unwrap(), + ..Default::default() + }; + assert_eq!(traced_item, expected); + } + + // There is no filtering/validation in the `analyze` function. + // All such checks are done in `validate` function. + #[test] + fn others_with_traces() { + // `#[trace]` + //let args: Vec = vec![]; + let trace = crate::trace::Trace { + ..Default::default() + }; + let models = analyze( + trace, + quote::quote!( + #[a] + #[trace] + #[b] + fn f(x: bool) -> bool { + x + } + ), + ); + let expected: &[Attribute] = &[ + syn::parse_quote!(#[a]), + syn::parse_quote!(#[trace]), + syn::parse_quote!(#[b]), + ]; + let model = (*models.get(0).unwrap()).clone(); + let traced_item = if let Model::Item(item) = model { + *item.clone() + } else { + return; + }; + let TracedItem { item_fn, .. } = traced_item; + assert_eq!(expected, item_fn.attrs); + } + + #[test] + fn others_with_no_trace() { + // `#[trace]` + //let args: Vec = vec![]; + let trace = crate::trace::Trace { + ..Default::default() + }; + + let models = analyze( + trace, + syn::parse_quote!( + #[a] + #[b] + fn f(x: bool) {} + ), + ); + let expected: &[Attribute] = &[syn::parse_quote!(#[a]), syn::parse_quote!(#[b])]; + let model = (*models.get(0).unwrap()).clone(); + let traced_item = if let Model::Item(item) = model { + *item.clone() + } else { + return; + }; + let TracedItem { item_fn, .. } = traced_item; + assert_eq!(expected, item_fn.attrs); + } +} diff --git a/minitrace-macro/src/trace/generate.rs b/minitrace-macro/src/trace/generate.rs new file mode 100644 index 00000000..44f6d76a --- /dev/null +++ b/minitrace-macro/src/trace/generate.rs @@ -0,0 +1,87 @@ +use crate::trace::Quotable; +use crate::trace::Quotables; +use crate::trace::Quote; + +// Generate TokenStream to be returned by the proc-macro +// +// The "Lower" stage is responsible for turning the "Model" data into a +// syn::Item that can be reported here. Currently this is straight forward. +// However, if additional attribute features are implemented there will likely +// be some additional complexity here that will help to cut down compile times. +// See issues: +pub fn generate(quotes: Quotables) -> proc_macro2::TokenStream { + // Have a logic check earlier to error if there is not **at least one** + // `Quotable::Item` + #[allow(clippy::collapsible_match)] + #[allow(unreachable_patterns)] + let ts: proc_macro2::TokenStream = match quotes.get(0).expect("An item") { + Quotable::Item(Quote { + attrs, + vis, + constness, + unsafety, + abi, + ident, + gen_params, + params, + return_type, + where_clause, + func_body, + }) => quote::quote!( + #(#attrs) * + #vis #constness #unsafety #abi fn #ident<#gen_params>(#params) #return_type + #where_clause + { + #func_body + } + ), + _ => { + quote::quote!() + } + }; + ts +} +#[cfg(test)] +mod tests { + use test_utilities::*; + + // Remove #[should_panic] when issue #141 is resolved. Note the integration + // test for issue #141, which will move to the regression suite when + // resolved, is blocked by issue #137. In turn issue #137 is blocked by + // [macrotest issue 74](https://github.com/eupn/macrotest/issues/74). This + // in turn appears to be due to [Cargo issue + // #4942](https://github.com/rust-lang/cargo/issues/4942). Consequently, + // depending on whether Cargo resolve this issue or declare it a 'feature' + // it is possible that the workaround described + // [here](https://github.com/rust-lang/cargo/issues/4942#issuecomment-357729844) + // could be a fix. + // + // If that is not a fix, the next step is to reorganise the workspace from + // 'virtual' to 'real' - which requires moving sources around.... + #[test] + #[should_panic] + fn generate_1() { + let i: syn::ItemFn = syn::parse_quote!( + fn f() {} + ); + let ts = quote::ToTokens::into_token_stream(i); + let trace = crate::trace::Trace { + ..Default::default() + }; + + let models = crate::trace::analyze(trace, ts); + + let quotes = crate::trace::lower(models); + let rust = crate::trace::generate(quotes); + let t: syn::ItemFn = syn::parse_quote!( + fn f() { + let __guard = minitrace::local::LocalSpan::enter_with_local_parent("f"); + {} + } + ); + let ts: proc_macro2::TokenStream = quote::ToTokens::into_token_stream(t); + let expected = format!("{:#?}", ts); + let actual = format!("{:#?}", rust); + assert_eq_text!(&expected, &actual); + } +} diff --git a/minitrace-macro/src/trace/lower.rs b/minitrace-macro/src/trace/lower.rs new file mode 100644 index 00000000..b0c4a659 --- /dev/null +++ b/minitrace-macro/src/trace/lower.rs @@ -0,0 +1,160 @@ +#[allow(unused_imports)] +mod async_trait; +mod block; +mod lifetime; +pub mod quotable; +mod signature; + +use quote::quote; + +use crate::trace::analyze::Model; +use crate::trace::analyze::Models; +use crate::trace::analyze::TracedItem; + +use crate::trace::lower::async_trait::*; +use crate::trace::lower::block::*; +use crate::trace::lower::quotable::*; +use crate::trace::lower::signature::*; + +// The intermediate representation (IR) +// +// The IR is processed by the `quote::quote::quote_spanned!()` macro, hence are +// stored in a `Quotables` collection. +// Quotables is a Vec-newtype, implemented as `Models` was. +// +pub fn lower(models: Models) -> Quotables { + let mut quotes = Quotables::new(); + quotes.extend(models.iter().map(|model| { + let traced_item = if let Model::Item(ti) = model { + Ok(ti) + } else { + Err(()) + } + .unwrap(); + Quotable::Item(quote(*(*traced_item).clone())) + })); + quotes +} + +// This was the legacy attribute `fn trace(..)` +pub fn quote(traced_item: TracedItem) -> Quote { + let input = traced_item.item_fn.clone(); + + // check for async_trait-like patterns in the block, and instrument + // the future instead of the wrapper + let func_body = if let Some(internal_fun) = + get_async_trait_info(&input.block, input.sig.asyncness.is_some()) + { + // let's rewrite some statements! + match internal_fun.kind { + // async-trait <= 0.1.43 + AsyncTraitKind::Function(_) => { + unimplemented!( + "Please upgrade the crate `async-trait` to a version higher than 0.1.44" + ) + } + // async-trait >= 0.1.44 + AsyncTraitKind::Async(async_expr) => { + // fallback if we couldn't find the '__async_trait' binding, might be + // useful for crates exhibiting the same behaviors as async-trait + let instrumented_block = gen_block(&async_expr.block, true, traced_item); + let async_attrs = &async_expr.attrs; + quote! { + Box::pin(#(#async_attrs) * { #instrumented_block }) + } + } + } + } else { + gen_block(&input.block, input.sig.asyncness.is_some(), traced_item) + }; + + let syn::ItemFn { + attrs, + vis, + mut sig, + .. + } = input; + + if sig.asyncness.is_some() { + let has_self = has_self_in_sig(&mut sig); + transform_sig(&mut sig, has_self, true); + } + + let syn::Signature { + output: return_type, + inputs: params, + unsafety, + constness, + abi, + ident, + generics: + syn::Generics { + params: gen_params, + where_clause, + .. + }, + .. + } = sig; + + Quote { + attrs, + vis, + constness, + unsafety, + abi, + ident, + gen_params, + params, + return_type, + where_clause, + func_body, + } +} + +use syn::visit_mut::VisitMut; + +fn has_self_in_sig(sig: &mut syn::Signature) -> bool { + let mut visitor = HasSelf(false); + visitor.visit_signature_mut(sig); + visitor.0 +} + +#[cfg(test)] +mod tests { + use test_utilities::*; + + #[test] + fn sync_quote_1() { + let ts: syn::ItemFn = syn::parse_quote!( + fn f() {} + ); + //let args: Vec = vec![]; + let trace = crate::trace::Trace { + ..Default::default() + }; + + let models = crate::trace::analyze(trace, quote::ToTokens::into_token_stream(ts)); + + let quotes = crate::trace::lower(models); + + let expected = crate::trace::lower::Quotable::Item(crate::trace::lower::Quote { + attrs: Vec::new(), + vis: syn::Visibility::Inherited, + constness: None, + unsafety: None, + abi: None, + ident: syn::Ident::new("f", proc_macro2::Span::call_site()), + gen_params: syn::punctuated::Punctuated::new(), + params: syn::punctuated::Punctuated::new(), + return_type: syn::ReturnType::Default, + where_clause: None, + func_body: quote::quote!( + let __guard = minitrace::local::LocalSpan::enter_with_local_parent("f"); + {} + ), + }); + + let actual = format!("{:#?}", quotes.get(0).unwrap()); + assert_eq_text!(&format!("{:#?}", expected), &actual); + } +} diff --git a/minitrace-macro/src/trace/lower/async_trait.rs b/minitrace-macro/src/trace/lower/async_trait.rs new file mode 100644 index 00000000..cc0f6319 --- /dev/null +++ b/minitrace-macro/src/trace/lower/async_trait.rs @@ -0,0 +1,134 @@ +pub enum AsyncTraitKind<'a> { + // old construction. Contains the function + Function(&'a syn::ItemFn), + // new construction. Contains a reference to the async block + Async(&'a syn::ExprAsync), +} + +pub struct AsyncTraitInfo<'a> { + // statement that must be patched + _source_stmt: &'a syn::Stmt, + pub kind: AsyncTraitKind<'a>, +} + +// Get the AST of the inner function we need to hook, if it was generated +// by async-trait. +// +// When we are given a function annotated by async-trait, that function +// is only a placeholder that returns a pinned future containing the +// user logic, and it is that pinned future that needs to be instrumented. +// Were we to instrument its parent, we would only collect information +// regarding the allocation of that future, and not its own span of execution. +// Depending on the version of async-trait, we inspect the block of the function +// to find if it matches the pattern +// `async fn foo<...>(...) {...}; Box::pin(foo<...>(...))` (<=0.1.43), or if +// it matches `Box::pin(async move { ... }) (>=0.1.44). We the return the +// statement that must be instrumented, along with some other information. +// 'gen_body' will then be able to use that information to instrument the +// proper function/future. +// +// (this follows the approach suggested in +// https://github.com/dtolnay/async-trait/issues/45#issuecomment-571245673) +pub fn get_async_trait_info( + block: &syn::Block, + block_is_async: bool, +) -> Option> { + // are we in an async context? If yes, this isn't a async_trait-like pattern + if block_is_async { + return None; + } + + // list of async functions declared inside the block + let inside_funs = block.stmts.iter().filter_map(|stmt| { + if let syn::Stmt::Item(syn::Item::Fn(fun)) = &stmt { + // If the function is async, this is a candidate + if fun.sig.asyncness.is_some() { + return Some((stmt, fun)); + } + } + None + }); + + // last expression of the block (it determines the return value + // of the block, so that if we are working on a function whose + // `trait` or `impl` declaration is annotated by async_trait, + // this is quite likely the point where the future is pinned) + let (last_expr_stmt, last_expr) = block.stmts.iter().rev().find_map(|stmt| { + if let syn::Stmt::Expr(expr) = stmt { + Some((stmt, expr)) + } else { + None + } + })?; + + // is the last expression a function call? + let (outside_func, outside_args) = match last_expr { + syn::Expr::Call(syn::ExprCall { func, args, .. }) => (func, args), + _ => return None, + }; + + // is it a call to `Box::pin()`? + let path = match outside_func.as_ref() { + syn::Expr::Path(path) => &path.path, + _ => return None, + }; + if !path_to_string(path).ends_with("Box::pin") { + return None; + } + + // Does the call take an argument? If it doesn't, + // it's not gonna compile anyway, but that's no reason + // to (try to) perform an out of bounds access + if outside_args.is_empty() { + return None; + } + + // Is the argument to Box::pin an async block that + // captures its arguments? + if let syn::Expr::Async(async_expr) = &outside_args[0] { + // check that the move 'keyword' is present + async_expr.capture?; + + return Some(AsyncTraitInfo { + _source_stmt: last_expr_stmt, + kind: AsyncTraitKind::Async(async_expr), + }); + } + + // Is the argument to Box::pin a function call itself? + let func = match &outside_args[0] { + syn::Expr::Call(syn::ExprCall { func, .. }) => func, + _ => return None, + }; + + // "stringify" the path of the function called + let func_name = match **func { + syn::Expr::Path(ref func_path) => path_to_string(&func_path.path), + _ => return None, + }; + + // Was that function defined inside of the current block? + // If so, retrieve the statement where it was declared and the function itself + let (stmt_func_declaration, func) = inside_funs + .into_iter() + .find(|(_, fun)| fun.sig.ident == func_name)?; + + Some(AsyncTraitInfo { + _source_stmt: stmt_func_declaration, + kind: AsyncTraitKind::Function(func), + }) +} + +// Return a path as a String +fn path_to_string(path: &syn::Path) -> String { + use std::fmt::Write; + // some heuristic to prevent too many allocations + let mut res = String::with_capacity(path.segments.len() * 5); + for i in 0..path.segments.len() { + write!(res, "{}", path.segments[i].ident).expect("writing to a String should never fail"); + if i < path.segments.len() - 1 { + res.push_str("::"); + } + } + res +} diff --git a/minitrace-macro/src/trace/lower/block.rs b/minitrace-macro/src/trace/lower/block.rs new file mode 100644 index 00000000..e7e92993 --- /dev/null +++ b/minitrace-macro/src/trace/lower/block.rs @@ -0,0 +1,50 @@ +use crate::trace::lower::TracedItem; + +use syn::spanned::Spanned; + +/// Instrument a block +pub fn gen_block( + block: &syn::Block, + async_context: bool, + traced_item: TracedItem, +) -> proc_macro2::TokenStream { + let event = traced_item.name.value(); + + // Generate the instrumented function body. + // If the function is an `async fn`, this will wrap it in an async block. + // Otherwise, this will enter the span and then perform the rest of the body. + if async_context { + if traced_item.enter_on_poll.value { + quote::quote_spanned!(block.span()=> + minitrace::future::FutureExt::enter_on_poll( + async move { #block }, + #event + ) + ) + } else { + quote::quote_spanned!(block.span()=> + minitrace::future::FutureExt::in_span( + async move { #block }, + minitrace::Span::enter_with_local_parent( #event ) + ) + ) + } + } else { + if traced_item.enter_on_poll.value { + let e = syn::Error::new( + syn::spanned::Spanned::span(&async_context), + "`enter_on_poll` can not be applied on non-async function", + ); + let tokens = quote::quote_spanned!(block.span()=> + let __guard = minitrace::local::LocalSpan::enter_with_local_parent( #event ); + #block + ); + return crate::token_stream_with_error(tokens, e); + } + + quote::quote_spanned!(block.span()=> + let __guard = minitrace::local::LocalSpan::enter_with_local_parent( #event ); + #block + ) + } +} diff --git a/minitrace-macro/src/trace/lower/lifetime.rs b/minitrace-macro/src/trace/lower/lifetime.rs new file mode 100644 index 00000000..d1d4f538 --- /dev/null +++ b/minitrace-macro/src/trace/lower/lifetime.rs @@ -0,0 +1,60 @@ +pub struct CollectLifetimes { + pub elided: Vec, + pub explicit: Vec, + pub name: &'static str, + pub default_span: proc_macro2::Span, +} + +impl CollectLifetimes { + pub fn new(name: &'static str, default_span: proc_macro2::Span) -> Self { + CollectLifetimes { + elided: Vec::new(), + explicit: Vec::new(), + name, + default_span, + } + } + + fn visit_opt_lifetime(&mut self, lifetime: &mut Option) { + match lifetime { + None => *lifetime = Some(self.next_lifetime(None)), + Some(lifetime) => self.visit_lifetime(lifetime), + } + } + + fn visit_lifetime(&mut self, lifetime: &mut syn::Lifetime) { + if lifetime.ident == "_" { + *lifetime = self.next_lifetime(lifetime.span()); + } else { + self.explicit.push(lifetime.clone()); + } + } + + fn next_lifetime>>(&mut self, span: S) -> syn::Lifetime { + let name = format!("{}{}", self.name, self.elided.len()); + let span = span.into().unwrap_or(self.default_span); + let life = syn::Lifetime::new(&name, span); + self.elided.push(life.clone()); + life + } +} + +impl syn::visit_mut::VisitMut for CollectLifetimes { + fn visit_receiver_mut(&mut self, arg: &mut syn::Receiver) { + if let Some((_, lifetime)) = &mut arg.reference { + self.visit_opt_lifetime(lifetime); + } + } + + fn visit_type_reference_mut(&mut self, ty: &mut syn::TypeReference) { + self.visit_opt_lifetime(&mut ty.lifetime); + syn::visit_mut::visit_type_reference_mut(self, ty); + } + + fn visit_generic_argument_mut(&mut self, gen: &mut syn::GenericArgument) { + if let syn::GenericArgument::Lifetime(lifetime) = gen { + self.visit_lifetime(lifetime); + } + syn::visit_mut::visit_generic_argument_mut(self, gen); + } +} diff --git a/minitrace-macro/src/trace/lower/quotable.rs b/minitrace-macro/src/trace/lower/quotable.rs new file mode 100644 index 00000000..67fc0bc6 --- /dev/null +++ b/minitrace-macro/src/trace/lower/quotable.rs @@ -0,0 +1,80 @@ +// `Quotables` a Vec-newtype +// +// A wrapper that allows us to implement the [`From`] trait. +// The [`From`] trait provides these conveniences (`match` branch): +// +// Err(err) => return err.into_compile_error().into(), +// +// Below the following traits are implemented: +// +// - Debug (via #[derive(...)]) +// - Default +// - Deref +// - DerefMut +// - Display +#[derive(Debug, Clone)] +pub struct Quotables(Vec); + +impl Quotables { + pub fn new() -> Quotables { + Quotables(Vec::::new()) + } + + #[allow(dead_code)] + pub fn with_capacity(capacity: usize) -> Quotables { + Quotables(Vec::::with_capacity(capacity)) + } +} + +impl Default for Quotables { + fn default() -> Quotables { + Quotables::new() + } +} + +impl std::fmt::Display for Quotables { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl std::ops::Deref for Quotables { + type Target = Vec; + fn deref(&self) -> &Vec { + &self.0 + } +} + +impl std::ops::DerefMut for Quotables { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[allow(dead_code)] +#[derive(Clone, Debug, thiserror::Error)] +#[error("Validation logic error")] +pub enum Quotable { + Item(Quote), +} + +#[derive(Clone, Debug, thiserror::Error)] +pub struct Quote { + pub attrs: Vec, + pub vis: syn::Visibility, + pub constness: Option, + pub unsafety: Option, + pub abi: Option, + pub ident: syn::Ident, + pub gen_params: syn::punctuated::Punctuated, + pub params: syn::punctuated::Punctuated, + pub return_type: syn::ReturnType, + pub where_clause: Option, + pub func_body: proc_macro2::TokenStream, +} + +impl std::fmt::Display for Quote { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/minitrace-macro/src/trace/lower/signature.rs b/minitrace-macro/src/trace/lower/signature.rs new file mode 100644 index 00000000..6bb0143e --- /dev/null +++ b/minitrace-macro/src/trace/lower/signature.rs @@ -0,0 +1,200 @@ +use crate::trace::lower::lifetime::*; + +use syn::visit_mut::VisitMut; + +pub fn transform_sig(sig: &mut syn::Signature, has_self: bool, is_local: bool) { + sig.fn_token.span = sig.asyncness.take().unwrap().span; + + let ret = match &sig.output { + syn::ReturnType::Default => quote::quote!(()), + syn::ReturnType::Type(_, ret) => quote::quote!(#ret), + }; + + let default_span = sig + .ident + .span() + .join(sig.paren_token.span) + .unwrap_or_else(|| sig.ident.span()); + + let mut lifetimes = CollectLifetimes::new("'life", default_span); + for arg in sig.inputs.iter_mut() { + match arg { + syn::FnArg::Receiver(arg) => lifetimes.visit_receiver_mut(arg), + syn::FnArg::Typed(arg) => lifetimes.visit_type_mut(&mut arg.ty), + } + } + + for param in sig.generics.params.iter() { + match param { + syn::GenericParam::Type(param) => { + let param = ¶m.ident; + let span = param.span(); + where_clause_or_default(&mut sig.generics.where_clause) + .predicates + .push(syn::parse_quote_spanned!(span=> #param: 'minitrace)); + } + syn::GenericParam::Lifetime(param) => { + let param = ¶m.lifetime; + let span = param.span(); + where_clause_or_default(&mut sig.generics.where_clause) + .predicates + .push(syn::parse_quote_spanned!(span=> #param: 'minitrace)); + } + syn::GenericParam::Const(_) => {} + } + } + + if sig.generics.lt_token.is_none() { + sig.generics.lt_token = Some(syn::Token![<](sig.ident.span())); + } + if sig.generics.gt_token.is_none() { + sig.generics.gt_token = Some(syn::Token![>](sig.paren_token.span)); + } + + for (idx, elided) in lifetimes.elided.iter().enumerate() { + sig.generics.params.insert(idx, syn::parse_quote!(#elided)); + where_clause_or_default(&mut sig.generics.where_clause) + .predicates + .push(syn::parse_quote_spanned!(elided.span()=> #elided: 'minitrace)); + } + + sig.generics + .params + .insert(0, syn::parse_quote_spanned!(default_span=> 'minitrace)); + + if has_self { + let bound_span = sig.ident.span(); + let bound = match sig.inputs.iter().next() { + Some(syn::FnArg::Receiver(syn::Receiver { + reference: Some(_), + mutability: None, + .. + })) => syn::Ident::new("Sync", bound_span), + Some(syn::FnArg::Typed(arg)) + if match (arg.pat.as_ref(), arg.ty.as_ref()) { + (syn::Pat::Ident(pat), syn::Type::Reference(ty)) => { + pat.ident == "self" && ty.mutability.is_none() + } + _ => false, + } => + { + syn::Ident::new("Sync", bound_span) + } + _ => syn::Ident::new("Send", bound_span), + }; + + let where_clause = where_clause_or_default(&mut sig.generics.where_clause); + where_clause.predicates.push(if is_local { + syn::parse_quote_spanned!(bound_span=> Self: 'minitrace) + } else { + syn::parse_quote_spanned!(bound_span=> Self: ::core::marker::#bound + 'minitrace) + }); + } + + for (i, arg) in sig.inputs.iter_mut().enumerate() { + match arg { + syn::FnArg::Receiver(syn::Receiver { + reference: Some(_), .. + }) => {} + syn::FnArg::Receiver(arg) => arg.mutability = None, + syn::FnArg::Typed(arg) => { + if let syn::Pat::Ident(ident) = &mut *arg.pat { + ident.by_ref = None; + //ident.mutability = None; + } else { + let positional = positional_arg(i, &arg.pat); + let m = mut_pat(&mut arg.pat); + arg.pat = syn::parse_quote!(#m #positional); + } + } + } + } + + let ret_span = sig.ident.span(); + let bounds = if is_local { + quote::quote_spanned!(ret_span=> 'minitrace) + } else { + quote::quote_spanned!(ret_span=> ::core::marker::Send + 'minitrace) + }; + sig.output = syn::parse_quote_spanned! {ret_span=> + -> impl ::core::future::Future + #bounds + }; +} + +fn positional_arg(i: usize, pat: &syn::Pat) -> syn::Ident { + quote::format_ident!("__arg{}", i, span = syn::spanned::Spanned::span(&pat)) +} + +fn mut_pat(pat: &mut syn::Pat) -> Option { + let mut visitor = HasMutPat(None); + visitor.visit_pat_mut(pat); + visitor.0 +} + +fn has_self_in_token_stream(tokens: proc_macro2::TokenStream) -> bool { + tokens.into_iter().any(|tt| match tt { + proc_macro2::TokenTree::Ident(ident) => ident == "Self", + proc_macro2::TokenTree::Group(group) => has_self_in_token_stream(group.stream()), + _ => false, + }) +} + +fn where_clause_or_default(clause: &mut Option) -> &mut syn::WhereClause { + clause.get_or_insert_with(|| syn::WhereClause { + where_token: Default::default(), + predicates: syn::punctuated::Punctuated::new(), + }) +} + +struct HasMutPat(Option); + +impl syn::visit_mut::VisitMut for HasMutPat { + fn visit_pat_ident_mut(&mut self, i: &mut syn::PatIdent) { + if let Some(m) = i.mutability { + self.0 = Some(m); + } else { + syn::visit_mut::visit_pat_ident_mut(self, i); + } + } +} + +pub struct HasSelf(pub bool); + +impl syn::visit_mut::VisitMut for HasSelf { + fn visit_expr_path_mut(&mut self, expr: &mut syn::ExprPath) { + self.0 |= expr.path.segments[0].ident == "Self"; + syn::visit_mut::visit_expr_path_mut(self, expr); + } + + fn visit_pat_path_mut(&mut self, pat: &mut syn::PatPath) { + self.0 |= pat.path.segments[0].ident == "Self"; + syn::visit_mut::visit_pat_path_mut(self, pat); + } + + fn visit_type_path_mut(&mut self, ty: &mut syn::TypePath) { + self.0 |= ty.path.segments[0].ident == "Self"; + syn::visit_mut::visit_type_path_mut(self, ty); + } + + fn visit_receiver_mut(&mut self, _arg: &mut syn::Receiver) { + self.0 = true; + } + + fn visit_item_mut(&mut self, _: &mut syn::Item) { + // Do not recurse into nested items. + } + + fn visit_macro_mut(&mut self, mac: &mut syn::Macro) { + if !contains_fn(mac.tokens.clone()) { + self.0 |= has_self_in_token_stream(mac.tokens.clone()); + } + } +} + +fn contains_fn(tokens: proc_macro2::TokenStream) -> bool { + tokens.into_iter().any(|tt| match tt { + proc_macro2::TokenTree::Ident(ident) => ident == "fn", + proc_macro2::TokenTree::Group(group) => contains_fn(group.stream()), + _ => false, + }) +} diff --git a/minitrace-macro/src/trace/parse.rs b/minitrace-macro/src/trace/parse.rs new file mode 100644 index 00000000..22801367 --- /dev/null +++ b/minitrace-macro/src/trace/parse.rs @@ -0,0 +1,272 @@ +// Parse TokenStream +// +// Parse attribute arguments, which arrive as a `proc_macro::TokenStream`, +// into a `Vector` of `syn::NestedMeta` items. +// +// The input stream comes from the `trace::validate::validate` function. +// The output vector goes to the `trace::analyze::analyze` function. + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Scope { + Local, + Threads, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Trace { + pub default: syn::LitBool, + pub name: syn::LitStr, + pub validated: syn::LitBool, + pub enter_on_poll: syn::LitBool, + + pub scope: Option, // Scope::Local, Scope::Thread, etc. + pub parent: Option, + pub recorder: Option, + pub recurse: Option, + pub root: Option, + pub variables: Option, + pub async_trait: Option, + pub async_fn: Option, +} + +impl syn::parse::Parse for Trace { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut enter_on_poll = None; + let mut name = None; + let mut name_set = false; + + let mut parsed = + syn::punctuated::Punctuated::::parse_terminated( + input, + )?; + let arg_n = parsed.len(); + if arg_n > 3 { + // tests/trace/ui/err/has-too-many-arguments.rs + //abort_call_site!(ERROR; help = HELP) + let e = syn::Error::new( + syn::spanned::Spanned::span(&parsed), + "Too many arguments. This attribute takes up to two (2) arguments", + ); + return Err(e); + } + for kv in parsed.clone() { + if kv.path.is_ident("enter_on_poll") { + if enter_on_poll.is_some() { + let e = syn::Error::new( + syn::spanned::Spanned::span(&kv), + "`enter_on_poll` provided twice", + ); + return Err(e); + } else if let syn::Lit::Bool(v) = kv.lit { + enter_on_poll = Some(v); + } else { + let e = syn::Error::new( + syn::spanned::Spanned::span(&kv), + "`enter_on_poll` value should be an boolean", + ); + return Err(e); + } + } else if kv.path.is_ident("name") { + name_set = true; + if name.is_some() { + let e = + syn::Error::new(syn::spanned::Spanned::span(&kv), "`name` provided twice"); + return Err(e); + } else if let syn::Lit::Str(v) = kv.lit { + name = Some(v); + } else { + let e = syn::Error::new( + syn::spanned::Spanned::span(&kv), + "`name` value should be a string", + ); + return Err(e); + } + } else { + let e = syn::Error::new(syn::spanned::Spanned::span(&kv), "unknown option"); + return Err(e); + } + } + + if !name_set { + let name_pair: syn::MetaNameValue = syn::parse_quote!(name = "__default"); + parsed.push(name_pair); + name = Some(syn::LitStr::new( + "__default", + proc_macro2::Span::call_site(), + )); + } + // Validate supported combinations + match (enter_on_poll, name) { + (Some(enter_on_poll), Some(name)) => { + let default = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let validated = syn::LitBool::new(true, proc_macro2::Span::call_site()); + Ok(Self { + default, + enter_on_poll, + name, + validated, + ..Default::default() + }) + } + (None, None) => Err(syn::Error::new( + syn::spanned::Spanned::span(&parsed), + "missing both `enter_on_poll` and `name`", + )), + (None, Some(name)) => { + let default = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let validated = syn::LitBool::new(true, proc_macro2::Span::call_site()); + Ok(Self { + default, + name, + validated, + ..Default::default() + }) + } + (Some(enter_on_poll), None) => { + let default = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let validated = syn::LitBool::new(true, proc_macro2::Span::call_site()); + let name = syn::LitStr::new("__default", proc_macro2::Span::call_site()); + Ok(Self { + default, + enter_on_poll, + name, + validated, + ..Default::default() + }) + } + } + } +} + +impl Default for Trace { + fn default() -> Self { + // Indicate when these defaults have changed + let default = syn::LitBool::new(true, proc_macro2::Span::call_site()); + // Indicate when these values have been validated + let validated = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let name = syn::LitStr::new("__default", proc_macro2::Span::call_site()); + let scope = Some(Scope::Local); + let enter_on_poll = syn::LitBool::new(false, proc_macro2::Span::call_site()); + let recorder = Some(proc_macro2::Ident::new( + "span", + proc_macro2::Span::call_site(), + )); + let recurse = Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + let root = Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + let variables = Some(syn::parse_quote!([])); + let parent = Some(syn::LitStr::new( + "__default", + proc_macro2::Span::call_site(), + )); + let async_trait = Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + let async_fn = Some(syn::LitBool::new(false, proc_macro2::Span::call_site())); + + Self { + name, + async_trait, + async_fn, + default, + enter_on_poll, + parent, + recorder, + recurse, + root, + scope, + variables, + validated, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_utilities::*; + + #[test] + fn valid_trace_001() { + // let ts = syn::parse::Parser::parse_str(syn::Attribute::parse_outer, "#[trace]").unwrap(); + // let args: proc_macro2::TokenStream = ts + // .iter() + // .map(|attr| attr.parse_args::().unwrap()) + // .collect(); + let args = quote::quote!(name = "a", enter_on_poll = false,); + let actual = syn::parse2::(args).unwrap(); + let expected = Trace { + default: syn::LitBool::new(false, proc_macro2::Span::call_site()), + enter_on_poll: syn::LitBool::new(false, proc_macro2::Span::call_site()), + name: syn::LitStr::new("a", proc_macro2::Span::call_site()), + validated: syn::LitBool::new(true, proc_macro2::Span::call_site()), + ..Default::default() + }; + assert_eq!(expected, actual); + } + + #[test] + fn valid_trace_002() { + let args = quote::quote!(name = "a", enter_on_poll = false,); + let actual = syn::parse2::(args).unwrap(); + let expected = Trace { + default: syn::LitBool::new(false, proc_macro2::Span::call_site()), + enter_on_poll: syn::LitBool::new(false, proc_macro2::Span::call_site()), + name: syn::LitStr::new("a", proc_macro2::Span::call_site()), + validated: syn::LitBool::new(true, proc_macro2::Span::call_site()), + ..Default::default() + }; + assert_eq!(expected, actual); + } + + #[test] + fn valid_trace_003() { + let args = quote::quote!(enter_on_poll = false,); + let actual = syn::parse2::(args).unwrap(); + let expected = Trace { + default: syn::LitBool::new(false, proc_macro2::Span::call_site()), + enter_on_poll: syn::LitBool::new(false, proc_macro2::Span::call_site()), + name: syn::LitStr::new("__default", proc_macro2::Span::call_site()), + validated: syn::LitBool::new(true, proc_macro2::Span::call_site()), + ..Default::default() + }; + assert_eq!(expected, actual); + } + + #[test] + fn valid_trace_004() { + let args = quote::quote!(name = "a",); + let actual = syn::parse2::(args).unwrap(); + let expected = Trace { + default: syn::LitBool::new(false, proc_macro2::Span::call_site()), + name: syn::LitStr::new("a", proc_macro2::Span::call_site()), + validated: syn::LitBool::new(true, proc_macro2::Span::call_site()), + ..Default::default() + }; + assert_eq!(expected, actual); + } + + #[test] + fn invalid_trace_001() { + let args = quote::quote!(name = "a", name = "b", enter_on_poll = false,); + let actual = match syn::parse2::(args.clone()) { + Err(error) => error, + _ => syn::Error::new(syn::spanned::Spanned::span(""), "error"), + }; + let expected: syn::Error = + syn::Error::new(syn::spanned::Spanned::span(&args), "`name` provided twice"); + assert_eq_text!(&format!("{:#?}", expected), &format!("{:#?}", actual)); + } + + #[test] + fn invalid_trace_002() { + let args = quote::quote!(name = "a", enter_on_poll = true, enter_on_poll = false,); + let actual = match syn::parse2::(args.clone()) { + Err(error) => error, + _ => syn::Error::new(syn::spanned::Spanned::span(""), "error"), + }; + let expected: syn::Error = syn::Error::new( + syn::spanned::Spanned::span(&args), + "`enter_on_poll` provided twice", + ); + assert_eq_text!(&format!("{:#?}", expected), &format!("{:#?}", actual)); + } +} diff --git a/minitrace-macro/tests/expand.rs b/minitrace-macro/tests/expand.rs new file mode 100644 index 00000000..cf689cc5 --- /dev/null +++ b/minitrace-macro/tests/expand.rs @@ -0,0 +1,58 @@ +#[test] +pub fn expand_defaults_dev() { + // To generate macro result files + macrotest::expand("tests/expand/defaults/*.rs"); +} + +#[test] +#[cfg(feature = "ci")] +pub fn expand_defaults_ci() { + // To test generated macro result files + macrotest::expand_without_refresh("tests/expand/defaults/*.rs"); +} + +#[test] +#[ignore] +pub fn expand_non_defaults_dev() { + // To generate macro result files + macrotest::expand("tests/expand/non-defaults/*.rs"); +} + +#[test] +#[cfg(feature = "ci")] +pub fn expand_non_defaults_ci() { + // To test generated macro result files + macrotest::expand_without_refresh("tests/expand/non-defaults/*.rs"); +} + +#[test] +#[ignore] +pub fn expand_issue_001_dev() { + // To generate macro result files + macrotest::expand_args( + "tests/expand/issues/tokio-1615.rs", + &["--manifest-path", "./Cargo.toml"], + ); + build_issues_dev(); +} + +#[cfg(not(feature = "ci"))] +fn build_issues_dev() { + let t = trybuild::TestCases::new(); + t.pass("tests/expand/issues/*.expanded.rs"); +} + +#[test] +#[ignore] +#[cfg(feature = "ci")] +pub fn issues_ci() { + // To test generated macro result files + macrotest::expand_without_refresh("tests/expand/issues/*.rs"); + build_issues_ci(); +} + +#[cfg(feature = "ci")] +fn build_issues_ci() { + let t = trybuild::TestCases::new(); + t.pass("tests/expand/issues/*.expanded.rs"); +} diff --git a/minitrace-macro/tests/expand/defaults/001-empty-sync.expanded.rs b/minitrace-macro/tests/expand/defaults/001-empty-sync.expanded.rs new file mode 100644 index 00000000..91ecc9f0 --- /dev/null +++ b/minitrace-macro/tests/expand/defaults/001-empty-sync.expanded.rs @@ -0,0 +1,16 @@ +use minitrace::prelude::*; +use minitrace::trace; +struct test; +#[automatically_derived] +impl ::core::fmt::Debug for test { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str(f, "test") + } +} +#[minitrace::trace(name = "f")] +fn f(a: usize) -> usize { + a * 2 +} +fn main() { + f(); +} \ No newline at end of file diff --git a/minitrace-macro/tests/expand/defaults/001-empty-sync.rs b/minitrace-macro/tests/expand/defaults/001-empty-sync.rs new file mode 100644 index 00000000..31b94907 --- /dev/null +++ b/minitrace-macro/tests/expand/defaults/001-empty-sync.rs @@ -0,0 +1,14 @@ +use minitrace::prelude::*; +use minitrace::trace; + +#[derive(Debug)] +struct test; + +#[minitrace::trace(name = "f")] +fn f(a: usize) -> usize { + a * 2 +} + +fn main() { + f(); +} diff --git a/minitrace-macro/tests/expand/defaults/001-sync.expanded.rs b/minitrace-macro/tests/expand/defaults/001-sync.expanded.rs new file mode 100644 index 00000000..2fd38e4c --- /dev/null +++ b/minitrace-macro/tests/expand/defaults/001-sync.expanded.rs @@ -0,0 +1,16 @@ +use minitrace::prelude::*; +use minitrace::trace; +struct test; +#[automatically_derived] +impl ::core::fmt::Debug for test { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::write_str(f, "test") + } +} +#[minitrace::trace()] +fn f(a: usize) -> usize { + a * 2 +} +fn main() { + f(2); +} \ No newline at end of file diff --git a/minitrace-macro/tests/expand/defaults/001-sync.rs b/minitrace-macro/tests/expand/defaults/001-sync.rs new file mode 100644 index 00000000..7e4f7d4c --- /dev/null +++ b/minitrace-macro/tests/expand/defaults/001-sync.rs @@ -0,0 +1,14 @@ +use minitrace::prelude::*; +use minitrace::trace; + +#[derive(Debug)] +struct test; + +#[minitrace::trace()] +fn f(a: usize) -> usize { + a * 2 +} + +fn main() { + f(2); +} diff --git a/minitrace-macro/tests/expand/defaults/empty-async.rsi b/minitrace-macro/tests/expand/defaults/empty-async.rsi new file mode 100644 index 00000000..259940ef --- /dev/null +++ b/minitrace-macro/tests/expand/defaults/empty-async.rsi @@ -0,0 +1,2 @@ +#[trace] +async fn f() {} diff --git a/minitrace-macro/tests/expand/issues/tokio-1613.rs b/minitrace-macro/tests/expand/issues/tokio-1613.rs new file mode 100644 index 00000000..b5b3f93c --- /dev/null +++ b/minitrace-macro/tests/expand/issues/tokio-1613.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright 2022 Tokio project authors + +use minitrace::trace; + +// Reproduces https://github.com/tokio-rs/tracing/issues/1613 +// and https://github.com/rust-lang/rust-clippy/issues/7760 +#[trace] +#[deny(clippy::suspicious_else_formatting)] +async fn re_a() { + // hello world + // else +} + +// Reproduces https://github.com/tokio-rs/tracing/issues/1613 +#[trace] +// LOAD-BEARING `#[rustfmt::skip]`! This is necessary to reproduce the bug; +// with the rustfmt-generated formatting, the lint will not be triggered! +#[rustfmt::skip] +#[deny(clippy::suspicious_else_formatting)] +async fn re_b(var: bool) { + println!( + "{}", + if var { "true" } else { "false" } + ); +} \ No newline at end of file diff --git a/minitrace-macro/tests/expand/issues/tokio-1615.expanded.rs b/minitrace-macro/tests/expand/issues/tokio-1615.expanded.rs new file mode 100644 index 00000000..7ce2c7a2 --- /dev/null +++ b/minitrace-macro/tests/expand/issues/tokio-1615.expanded.rs @@ -0,0 +1,27 @@ +use minitrace::trace; +fn re_a<'minitrace>( + n: i32, +) -> impl ::core::future::Future, ()>> + 'minitrace { + minitrace::future::FutureExt::in_span( + async move { + { + let n = n; + Ok((0..10).filter(move |x| *x < n)) + } + }, + minitrace::Span::enter_with_local_parent("re_a"), + ) +} +fn re_b<'minitrace>( + n: i32, +) -> impl ::core::future::Future, &'static str>> + 'minitrace +{ + minitrace::future::FutureExt::in_span( + async move { + { + Ok((0..10).filter(move |x| *x < n)) + } + }, + minitrace::Span::enter_with_local_parent("err"), + ) +} diff --git a/minitrace-macro/tests/expand/issues/tokio-1615.rsi b/minitrace-macro/tests/expand/issues/tokio-1615.rsi new file mode 100644 index 00000000..d9ce9d96 --- /dev/null +++ b/minitrace-macro/tests/expand/issues/tokio-1615.rsi @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright 2022 Tokio project authors + +use minitrace::trace; + +// Reproduces a compile error when returning an `impl Trait` from an +// instrumented async fn (see https://github.com/tokio-rs/tracing/issues/1615) +#[trace] +async fn re_a(n: i32) -> Result, ()> { + let n = n; + Ok((0..10).filter(move |x| *x < n)) +} + +// Reproduces a compile error when returning an `impl Trait` from an +// instrumented async fn (see https://github.com/tokio-rs/tracing/issues/1615) +#[trace("err")] +async fn re_b(n: i32) -> Result, &'static str> { + Ok((0..10).filter(move |x| *x < n)) +} diff --git a/minitrace-macro/tests/spans.rs b/minitrace-macro/tests/spans.rs new file mode 100644 index 00000000..daf423c8 --- /dev/null +++ b/minitrace-macro/tests/spans.rs @@ -0,0 +1,5 @@ +#[test] +fn spans() { + let t = trybuild::TestCases::new(); + t.pass("tests/spans/*.rs"); +} diff --git a/minitrace-macro/tests/spans/black-box-false-local-async.rs b/minitrace-macro/tests/spans/black-box-false-local-async.rs new file mode 100644 index 00000000..9e9f87cc --- /dev/null +++ b/minitrace-macro/tests/spans/black-box-false-local-async.rs @@ -0,0 +1,41 @@ +use minitrace::trace; +use test_utilities::*; + +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/122 +#[trace( name = "a-span")] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _child_span = root.set_local_parent(); + f(1).await; + } + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "a-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/black-box-false-local-sync.rs b/minitrace-macro/tests/spans/black-box-false-local-sync.rs new file mode 100644 index 00000000..305ec9ca --- /dev/null +++ b/minitrace-macro/tests/spans/black-box-false-local-sync.rs @@ -0,0 +1,40 @@ +use minitrace::trace; +use test_utilities::*; + +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/122 +#[trace(name = "a-span")] +fn f(a: u32) -> u32 { + a +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _sg1 = root.set_local_parent(); + f(1); + } + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "a-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/black-box-false-threads-async.rs b/minitrace-macro/tests/spans/black-box-false-threads-async.rs new file mode 100644 index 00000000..6bbf003a --- /dev/null +++ b/minitrace-macro/tests/spans/black-box-false-threads-async.rs @@ -0,0 +1,42 @@ +use minitrace::trace; +use test_utilities::*; + +// Span names passed via `enter_with_parent` override default names. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/122 +#[trace( name = "a-span")] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _child_span = minitrace::Span::enter_with_parent("test-span", &root); + f(1).await; + } + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "test-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/black-box-false-threads-sync.rs b/minitrace-macro/tests/spans/black-box-false-threads-sync.rs new file mode 100644 index 00000000..ed66a667 --- /dev/null +++ b/minitrace-macro/tests/spans/black-box-false-threads-sync.rs @@ -0,0 +1,41 @@ +use minitrace::trace; +use test_utilities::*; + +// Span names passed via `enter_with_parent` override default names. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/122 +#[trace( name = "a-span")] +fn f(a: u32) -> u32 { + a +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _sg1 = minitrace::Span::enter_with_parent("test-span", &root); + f(1); + } + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "test-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/default-name-local-async.rs b/minitrace-macro/tests/spans/default-name-local-async.rs new file mode 100644 index 00000000..9c20034d --- /dev/null +++ b/minitrace-macro/tests/spans/default-name-local-async.rs @@ -0,0 +1,39 @@ +use minitrace::trace; +use test_utilities::*; + +#[trace] +async fn fa(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _g = root.set_local_parent(); + fa(1).await; + } + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "fa", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/default-name-local-sync.rs b/minitrace-macro/tests/spans/default-name-local-sync.rs new file mode 100644 index 00000000..12fcf7d2 --- /dev/null +++ b/minitrace-macro/tests/spans/default-name-local-sync.rs @@ -0,0 +1,38 @@ +use minitrace::trace; +use test_utilities::*; + +#[trace] +fn f(a: u64) { + std::thread::sleep(std::time::Duration::from_nanos(a)); +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _g = root.set_local_parent(); + f(1); + } + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "f", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/default-name-threads-async.rs b/minitrace-macro/tests/spans/default-name-threads-async.rs new file mode 100644 index 00000000..d0094ddc --- /dev/null +++ b/minitrace-macro/tests/spans/default-name-threads-async.rs @@ -0,0 +1,40 @@ +use minitrace::trace; +use test_utilities::*; + +// Span names passed via `enter_with_parent` override default names. +#[trace] +async fn fa(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _child_span = minitrace::Span::enter_with_parent("test-span", &root); + fa(1).await; + } + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "test-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/default-name-threads-sync.rs b/minitrace-macro/tests/spans/default-name-threads-sync.rs new file mode 100644 index 00000000..485fddd6 --- /dev/null +++ b/minitrace-macro/tests/spans/default-name-threads-sync.rs @@ -0,0 +1,39 @@ +use minitrace::trace; +use test_utilities::*; + +// Span names passed via `enter_with_parent` override default names. +#[trace] +fn f(a: u64) { + std::thread::sleep(std::time::Duration::from_nanos(a)); +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _g = minitrace::Span::enter_with_parent("test-span", &root); + f(1); + } + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "test-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/doctest-async.rs b/minitrace-macro/tests/spans/doctest-async.rs new file mode 100644 index 00000000..50d7326d --- /dev/null +++ b/minitrace-macro/tests/spans/doctest-async.rs @@ -0,0 +1,47 @@ +use futures::executor::block_on; +use minitrace::prelude::*; +use test_utilities::*; + +// Implement documentation example as an integration test. +// +// Reference: +// - https://github.com/tikv/minitrace-rust/blob/master/minitrace/src/lib.rs#L178-L202 + +#[trace( name = "do_something_async")] +async fn do_something_async(i: u64) { + futures_timer::Delay::new(std::time::Duration::from_millis(i)).await; +} + +fn main() { + let (root, collector) = Span::root("root"); + + { + let _g = root.set_local_parent(); + block_on(do_something_async(100)); + } + + drop(root); + let records: Vec = block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "do_something_async", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); + +} diff --git a/minitrace-macro/tests/spans/doctest-sync.rs b/minitrace-macro/tests/spans/doctest-sync.rs new file mode 100644 index 00000000..3653c41a --- /dev/null +++ b/minitrace-macro/tests/spans/doctest-sync.rs @@ -0,0 +1,46 @@ +use futures::executor::block_on; +use minitrace::prelude::*; +use test_utilities::*; + +// Implement documentation example as an integration test. +// +// Reference: +// - https://github.com/tikv/minitrace-rust/blob/master/minitrace/src/lib.rs#L178-L202 + +#[trace(name = "do_something")] +fn do_something(i: u64) { + std::thread::sleep(std::time::Duration::from_millis(i)); +} + +fn main() { + let (root, collector) = Span::root("root"); + + { + let _g = root.set_local_parent(); + do_something(100); + } + + drop(root); + let records: Vec = block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "do_something", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/example-async.rs b/minitrace-macro/tests/spans/example-async.rs new file mode 100644 index 00000000..972bd8e5 --- /dev/null +++ b/minitrace-macro/tests/spans/example-async.rs @@ -0,0 +1,188 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use futures::executor::block_on; +use minitrace::prelude::*; +use test_utilities::*; + +fn parallel_job() -> Vec> { + let mut v = Vec::with_capacity(4); + for i in 0..4 { + v.push(tokio::spawn( + iter_job(i).in_span(Span::enter_with_local_parent("iter job")), + )); + } + v +} + +async fn iter_job(iter: u64) { + std::thread::sleep(std::time::Duration::from_millis(iter * 10)); + tokio::task::yield_now().await; + other_job().await; +} + +#[trace( name = "other job", enter_on_poll = true)] +async fn other_job() { + for i in 0..20 { + if i == 10 { + tokio::task::yield_now().await; + } + std::thread::sleep(std::time::Duration::from_millis(1)); + } +} + +#[tokio::main] +async fn main() { + let (span, collector) = Span::root("root"); + + let f = async { + let jhs = { + let mut span = LocalSpan::enter_with_local_parent("a span"); + span.add_property(|| ("a property", "a value".to_owned())); + parallel_job() + }; + + other_job().await; + + for jh in jhs { + jh.await.unwrap(); + } + } + .in_span(span); + + tokio::spawn(f).await.unwrap(); + + let records: Vec = block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, + SpanRecord { + id: \d+, + parent_id: \d+, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "...", + properties: [ ... ], + }, +]"#; + let actual = normalize_async_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/example-sync.rs b/minitrace-macro/tests/spans/example-sync.rs new file mode 100644 index 00000000..02f59620 --- /dev/null +++ b/minitrace-macro/tests/spans/example-sync.rs @@ -0,0 +1,220 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use futures::executor::block_on; +use minitrace::prelude::*; +use test_utilities::*; + +fn func1(i: u64) { + let _guard = LocalSpan::enter_with_local_parent("func1"); + std::thread::sleep(std::time::Duration::from_millis(i)); + func2(i); +} + +#[trace( name = "func2")] +fn func2(i: u64) { + std::thread::sleep(std::time::Duration::from_millis(i)); +} + +fn main() { + let collector = { + let (span, collector) = Span::root("root"); + + let _sg1 = span.set_local_parent(); + let mut sg2 = LocalSpan::enter_with_local_parent("a span"); + sg2.add_property(|| ("a property", "a value".to_owned())); + + for i in 1..=10 { + func1(i); + } + + collector + }; + + let records: Vec = block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "a span", + properties: [ + ( + "a property", + "a value", + ), + ], + }, + SpanRecord { + id: 3, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 4, + parent_id: 3, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 5, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 6, + parent_id: 5, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 7, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 8, + parent_id: 7, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 9, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 10, + parent_id: 9, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 11, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 12, + parent_id: 11, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 13, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 14, + parent_id: 13, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 15, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 16, + parent_id: 15, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 17, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 18, + parent_id: 17, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 19, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 20, + parent_id: 19, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, + SpanRecord { + id: 21, + parent_id: 2, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func1", + properties: [], + }, + SpanRecord { + id: 22, + parent_id: 21, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "func2", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-drop-local-async-enter-false.rs b/minitrace-macro/tests/spans/no-be-drop-local-async-enter-false.rs new file mode 100644 index 00000000..ce64ff51 --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-drop-local-async-enter-false.rs @@ -0,0 +1,46 @@ +use minitrace::trace; +use test_utilities::*; +// With `enter_on_poll = false`, `async` functions construct `Span` that is +// thread safe. +// +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span", enter_on_poll=false)] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = root.set_local_parent(); + f(1).await; + //} + drop(child_span); + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "a-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-drop-local-async-enter-true.rs b/minitrace-macro/tests/spans/no-be-drop-local-async-enter-true.rs new file mode 100644 index 00000000..5ba981f5 --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-drop-local-async-enter-true.rs @@ -0,0 +1,46 @@ +use minitrace::trace; +use test_utilities::*; +// With `enter_on_poll = true`, `async` functions construct `LocalSpan`. +// Hence this async test produces the same spans as the sync test. +// +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span", enter_on_poll=true)] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = root.set_local_parent(); + f(1).await; + //} + drop(child_span); + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "a-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-drop-local-sync.rs b/minitrace-macro/tests/spans/no-be-drop-local-sync.rs new file mode 100644 index 00000000..8ca5e0b6 --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-drop-local-sync.rs @@ -0,0 +1,44 @@ +use minitrace::prelude::*; + +use test_utilities::*; + +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace] +fn f(a: u32) -> u32 { + a +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = root.set_local_parent(); + f(1); + //} + drop(child_span); + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "f", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-drop-threads-async.rs b/minitrace-macro/tests/spans/no-be-drop-threads-async.rs new file mode 100644 index 00000000..1b033ec7 --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-drop-threads-async.rs @@ -0,0 +1,45 @@ +use minitrace::trace; +use test_utilities::*; + +// With no block expression the span "test-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = minitrace::Span::enter_with_parent("test-span", &root); + f(1).await; + //} + drop(child_span); //This is required when not using `{ ... }` + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "test-span", + properties: [], + }, +]"#; + // Always green (i.e. fixed order) - on failure use `normalize_async_spans` + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-drop-threads-sync.rs b/minitrace-macro/tests/spans/no-be-drop-threads-sync.rs new file mode 100644 index 00000000..1f323110 --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-drop-threads-sync.rs @@ -0,0 +1,43 @@ +use minitrace::trace; +use test_utilities::*; + +// With no block expression the span "test-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace] +fn f(a: u32) -> u32 { + a +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = minitrace::Span::enter_with_parent("test-span", &root); + f(1); + //} + drop(child_span); + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "test-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-no-drop-async-enter-false.rs b/minitrace-macro/tests/spans/no-be-no-drop-async-enter-false.rs new file mode 100644 index 00000000..e48fac6a --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-no-drop-async-enter-false.rs @@ -0,0 +1,46 @@ +use minitrace::trace; +use test_utilities::*; +// With `enter_on_poll = false`, `async` functions construct `Span` that is +// thread safe. +// +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span", enter_on_poll=false)] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let _child_span = root.set_local_parent(); + f(1).await; + //} + //drop(child_span); //This is required when not using `{ ... }` + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "a-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-no-drop-local-async-enter-true.rs b/minitrace-macro/tests/spans/no-be-no-drop-local-async-enter-true.rs new file mode 100644 index 00000000..4be19093 --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-no-drop-local-async-enter-true.rs @@ -0,0 +1,38 @@ +use minitrace::trace; +use test_utilities::*; +// With `enter_on_poll = true`, `async` functions construct `LocalSpan`. +// Hence this async test produces the same spans as the sync test. +// +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span", enter_on_poll=true)] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let _child_span = root.set_local_parent(); + f(1).await; + //} + //drop(child_span); //This is required when not using `{ ... }` + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-no-drop-local-sync.rs b/minitrace-macro/tests/spans/no-be-no-drop-local-sync.rs new file mode 100644 index 00000000..75020cfd --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-no-drop-local-sync.rs @@ -0,0 +1,35 @@ +use minitrace::trace; +use test_utilities::*; + +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span")] +fn f(a: u32) -> u32 { + a +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let _child_span = root.set_local_parent(); + f(1); + //} + //drop(child_span); //This is required when not using `{ ... }` + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-no-drop-threads-async.rs b/minitrace-macro/tests/spans/no-be-no-drop-threads-async.rs new file mode 100644 index 00000000..59d68895 --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-no-drop-threads-async.rs @@ -0,0 +1,36 @@ +use minitrace::trace; +use test_utilities::*; + +// With no block expression the span "test-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span")] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let _child_span = minitrace::Span::enter_with_parent("test-span", &root); + f(1).await; + //} + //drop(child_span); //This is required when not using `{ ... }` + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/spans/no-be-no-drop-threads-sync.rs b/minitrace-macro/tests/spans/no-be-no-drop-threads-sync.rs new file mode 100644 index 00000000..ecf57c78 --- /dev/null +++ b/minitrace-macro/tests/spans/no-be-no-drop-threads-sync.rs @@ -0,0 +1,35 @@ +use minitrace::trace; +use test_utilities::*; + +// With no block expression the span "test-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace(name = "a-span")] +fn f(a: u32) -> u32 { + a +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let _child_span = minitrace::Span::enter_with_parent("test-span", &root); + f(1); + //} + //drop(child_span); //This is required when not using `{ ... }` + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-macro/tests/trace-dev.rs b/minitrace-macro/tests/trace-dev.rs new file mode 100644 index 00000000..724edeae --- /dev/null +++ b/minitrace-macro/tests/trace-dev.rs @@ -0,0 +1,13 @@ +// Useful while working on specific test cases +#[test] +#[ignore] +fn trace_err_dev() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/006-has-too-many-arguments.rs"); +} +#[test] +#[ignore] +fn trace_ok_dev() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/00-has-no-arguments.rs"); +} diff --git a/minitrace-macro/tests/trace.rs b/minitrace-macro/tests/trace.rs new file mode 100644 index 00000000..99d7edd1 --- /dev/null +++ b/minitrace-macro/tests/trace.rs @@ -0,0 +1,158 @@ +#[test] +fn errors() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/*.rs"); +} + +#[test] +fn error_001() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/001-*.rs"); +} +#[test] +fn error_002() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/002-*.rs"); +} +#[test] +fn error_003() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/003-*.rs"); +} +#[test] +fn error_004() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/004-*.rs"); +} +#[test] +fn error_005() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/005-*.rs"); +} +#[test] +fn error_006() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/006-*.rs"); +} +#[test] +fn error_007() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/007-*.rs"); +} +#[test] +fn error_008() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/008-*.rs"); +} +#[test] +fn error_009() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/009-*.rs"); +} +#[test] +fn error_010() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/010-*.rs"); +} +#[test] +fn error_011() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/011-*.rs"); +} +#[test] +fn error_012() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/012-*.rs"); +} +#[test] +fn error_013() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/013-*.rs"); +} +#[test] +fn error_014() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/014-*.rs"); +} +#[test] +fn error_015() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/015-*.rs"); +} +#[test] +fn error_016() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/016-*.rs"); +} +#[test] +fn error_017() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/017-*.rs"); +} +#[test] +fn error_018() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/018-*.rs"); +} +#[test] +fn error_019() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/trace/ui/err/019-*.rs"); +} + +#[test] +fn oks() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/*.rs"); +} + +#[test] +fn ok_001() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/001-*.rs"); +} +#[test] +fn ok_002() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/002-*.rs"); +} +#[test] +fn ok_003() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/003-*.rs"); +} +#[test] +fn ok_004() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/004-*.rs"); +} +#[test] +fn ok_005() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/005-*.rs"); +} +#[test] +fn ok_006() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/006-*.rs"); +} +#[test] +fn ok_007() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/007-*.rs"); +} +#[test] +fn ok_008() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/008-*.rs"); +} +#[test] +fn ok_009() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/009-*.rs"); +} +#[test] +fn ok_010() { + let t = trybuild::TestCases::new(); + t.pass("tests/trace/ui/ok/010-*.rs"); +} diff --git a/minitrace-macro/tests/ui/err/has-expr-argument.rs b/minitrace-macro/tests/trace/ui/err/001-name-is-ident.rs similarity index 71% rename from minitrace-macro/tests/ui/err/has-expr-argument.rs rename to minitrace-macro/tests/trace/ui/err/001-name-is-ident.rs index 2d51d2c6..0aec1c87 100644 --- a/minitrace-macro/tests/ui/err/has-expr-argument.rs +++ b/minitrace-macro/tests/trace/ui/err/001-name-is-ident.rs @@ -1,6 +1,6 @@ use minitrace::trace; -#[trace(true)] +#[trace(name = b)] fn f() {} fn main() {} diff --git a/minitrace-macro/tests/trace/ui/err/001-name-is-ident.stderr b/minitrace-macro/tests/trace/ui/err/001-name-is-ident.stderr new file mode 100644 index 00000000..89d40948 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/001-name-is-ident.stderr @@ -0,0 +1,5 @@ +error: expected literal + --> tests/trace/ui/err/001-name-is-ident.rs:3:16 + | +3 | #[trace(name = b)] + | ^ diff --git a/minitrace-macro/tests/trace/ui/err/002-name-is-symbol.rs b/minitrace-macro/tests/trace/ui/err/002-name-is-symbol.rs new file mode 100644 index 00000000..f887a53b --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/002-name-is-symbol.rs @@ -0,0 +1,6 @@ +use minitrace::trace; + +#[trace(name = struct)] +fn f() {} + +fn main() {} diff --git a/minitrace-macro/tests/trace/ui/err/002-name-is-symbol.stderr b/minitrace-macro/tests/trace/ui/err/002-name-is-symbol.stderr new file mode 100644 index 00000000..61049195 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/002-name-is-symbol.stderr @@ -0,0 +1,5 @@ +error: expected literal + --> tests/trace/ui/err/002-name-is-symbol.rs:3:16 + | +3 | #[trace(name = struct)] + | ^^^^^^ diff --git a/minitrace-macro/tests/trace/ui/err/003-name-is-boolean.rs b/minitrace-macro/tests/trace/ui/err/003-name-is-boolean.rs new file mode 100644 index 00000000..b9eb7555 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/003-name-is-boolean.rs @@ -0,0 +1,6 @@ +use minitrace::trace; + +#[trace(name = true)] +fn f() {} + +fn main() {} diff --git a/minitrace-macro/tests/trace/ui/err/003-name-is-boolean.stderr b/minitrace-macro/tests/trace/ui/err/003-name-is-boolean.stderr new file mode 100644 index 00000000..9d5cc323 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/003-name-is-boolean.stderr @@ -0,0 +1,5 @@ +error: `name` value should be a string + --> tests/trace/ui/err/003-name-is-boolean.rs:3:9 + | +3 | #[trace(name = true)] + | ^^^^ diff --git a/minitrace-macro/tests/trace/ui/err/004-has-all-sync.rs b/minitrace-macro/tests/trace/ui/err/004-has-all-sync.rs new file mode 100644 index 00000000..19a4069e --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/004-has-all-sync.rs @@ -0,0 +1,10 @@ +use minitrace::trace; + +#[trace(name = "test_span", enter_on_poll = true)] +fn f(a: u32) -> u32 { + a +} + +fn main() { + f(1); +} diff --git a/minitrace-macro/tests/trace/ui/err/004-has-all-sync.stderr b/minitrace-macro/tests/trace/ui/err/004-has-all-sync.stderr new file mode 100644 index 00000000..6365df96 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/004-has-all-sync.stderr @@ -0,0 +1,18 @@ +error: `enter_on_poll` can not be applied on non-async function + --> tests/trace/ui/err/004-has-all-sync.rs:3:1 + | +3 | #[trace(name = "test_span", enter_on_poll = true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `trace` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> tests/trace/ui/err/004-has-all-sync.rs:5:5 + | +5 | a + | ^ expected `()`, found `u32` + | +help: you might have meant to return this value + | +5 | return a; + | ++++++ + diff --git a/minitrace-macro/tests/ui/ok/has-name-mut.rs b/minitrace-macro/tests/trace/ui/err/005-has-enter_on_poll-sync.rs similarity index 72% rename from minitrace-macro/tests/ui/ok/has-name-mut.rs rename to minitrace-macro/tests/trace/ui/err/005-has-enter_on_poll-sync.rs index e8aacbad..a8dbfc6d 100644 --- a/minitrace-macro/tests/ui/ok/has-name-mut.rs +++ b/minitrace-macro/tests/trace/ui/err/005-has-enter_on_poll-sync.rs @@ -1,6 +1,6 @@ use minitrace::trace; -#[trace(name = "test-span")] +#[trace(enter_on_poll=true)] fn f(a: u32) -> u32 { a } diff --git a/minitrace-macro/tests/trace/ui/err/005-has-enter_on_poll-sync.stderr b/minitrace-macro/tests/trace/ui/err/005-has-enter_on_poll-sync.stderr new file mode 100644 index 00000000..048fbd33 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/005-has-enter_on_poll-sync.stderr @@ -0,0 +1,18 @@ +error: `enter_on_poll` can not be applied on non-async function + --> tests/trace/ui/err/005-has-enter_on_poll-sync.rs:3:1 + | +3 | #[trace(enter_on_poll=true)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `trace` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> tests/trace/ui/err/005-has-enter_on_poll-sync.rs:5:5 + | +5 | a + | ^ expected `()`, found `u32` + | +help: you might have meant to return this value + | +5 | return a; + | ++++++ + diff --git a/minitrace-macro/tests/trace/ui/err/006-has-too-many-arguments.rs b/minitrace-macro/tests/trace/ui/err/006-has-too-many-arguments.rs new file mode 100644 index 00000000..ac435c0e --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/006-has-too-many-arguments.rs @@ -0,0 +1,6 @@ +use minitrace::trace; + +#[trace(a=true, b=true, c=true, d=true)] +fn f() {} + +fn main() {} diff --git a/minitrace-macro/tests/trace/ui/err/006-has-too-many-arguments.stderr b/minitrace-macro/tests/trace/ui/err/006-has-too-many-arguments.stderr new file mode 100644 index 00000000..6972344b --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/006-has-too-many-arguments.stderr @@ -0,0 +1,5 @@ +error: Too many arguments. This attribute takes up to two (2) arguments + --> tests/trace/ui/err/006-has-too-many-arguments.rs:3:9 + | +3 | #[trace(a=true, b=true, c=true, d=true)] + | ^ diff --git a/minitrace-macro/tests/ui/err/trace-interleaved.rs b/minitrace-macro/tests/trace/ui/err/007-interleaved.rs similarity index 79% rename from minitrace-macro/tests/ui/err/trace-interleaved.rs rename to minitrace-macro/tests/trace/ui/err/007-interleaved.rs index b26d8985..0ce52640 100644 --- a/minitrace-macro/tests/ui/err/trace-interleaved.rs +++ b/minitrace-macro/tests/trace/ui/err/007-interleaved.rs @@ -1,7 +1,7 @@ use minitrace::trace; #[allow(unused_braces)] -#[trace(struct)] +#[trace(name = struct)] #[warn(unused_braces)] fn f() {} diff --git a/minitrace-macro/tests/trace/ui/err/007-interleaved.stderr b/minitrace-macro/tests/trace/ui/err/007-interleaved.stderr new file mode 100644 index 00000000..f64991b7 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/007-interleaved.stderr @@ -0,0 +1,5 @@ +error: expected literal + --> tests/trace/ui/err/007-interleaved.rs:4:16 + | +4 | #[trace(name = struct)] + | ^^^^^^ diff --git a/minitrace-macro/tests/ui/err/item-is-not-a-function.rs b/minitrace-macro/tests/trace/ui/err/008-item-is-not-a-function.rs similarity index 68% rename from minitrace-macro/tests/ui/err/item-is-not-a-function.rs rename to minitrace-macro/tests/trace/ui/err/008-item-is-not-a-function.rs index 927c16bb..98c3468a 100644 --- a/minitrace-macro/tests/ui/err/item-is-not-a-function.rs +++ b/minitrace-macro/tests/trace/ui/err/008-item-is-not-a-function.rs @@ -1,6 +1,6 @@ use minitrace::trace; -#[trace("test-span")] +#[trace] struct S; fn main() {} diff --git a/minitrace-macro/tests/trace/ui/err/008-item-is-not-a-function.stderr b/minitrace-macro/tests/trace/ui/err/008-item-is-not-a-function.stderr new file mode 100644 index 00000000..641d45f2 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/008-item-is-not-a-function.stderr @@ -0,0 +1,7 @@ +error: custom attribute panicked + --> tests/trace/ui/err/008-item-is-not-a-function.rs:3:1 + | +3 | #[trace] + | ^^^^^^^^ + | + = help: message: An item diff --git a/minitrace-macro/tests/trace/ui/err/009-optional-is-known-str.rs b/minitrace-macro/tests/trace/ui/err/009-optional-is-known-str.rs new file mode 100644 index 00000000..3362e40c --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/009-optional-is-known-str.rs @@ -0,0 +1,8 @@ +use minitrace::trace; + +#[trace(name = "a", "enter_on_poll" = true)] +fn f() {} + +fn main() { + f(); +} diff --git a/minitrace-macro/tests/trace/ui/err/009-optional-is-known-str.stderr b/minitrace-macro/tests/trace/ui/err/009-optional-is-known-str.stderr new file mode 100644 index 00000000..0d1553ae --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/009-optional-is-known-str.stderr @@ -0,0 +1,5 @@ +error: expected path + --> tests/trace/ui/err/009-optional-is-known-str.rs:3:21 + | +3 | #[trace(name = "a", "enter_on_poll" = true)] + | ^^^^^^^^^^^^^^^ diff --git a/minitrace-macro/tests/trace/ui/err/010-optional-is-not-assignment.rs b/minitrace-macro/tests/trace/ui/err/010-optional-is-not-assignment.rs new file mode 100644 index 00000000..403ff81b --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/010-optional-is-not-assignment.rs @@ -0,0 +1,8 @@ +use minitrace::trace; + +#[trace(name = "a", enter_on_poll)] +fn f() {} + +fn main() { + f(); +} diff --git a/minitrace-macro/tests/trace/ui/err/010-optional-is-not-assignment.stderr b/minitrace-macro/tests/trace/ui/err/010-optional-is-not-assignment.stderr new file mode 100644 index 00000000..316c8a8c --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/010-optional-is-not-assignment.stderr @@ -0,0 +1,7 @@ +error: expected `=` + --> tests/trace/ui/err/010-optional-is-not-assignment.rs:3:1 + | +3 | #[trace(name = "a", enter_on_poll)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `trace` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/minitrace-macro/tests/trace/ui/err/011-optional-is-symbol.rs b/minitrace-macro/tests/trace/ui/err/011-optional-is-symbol.rs new file mode 100644 index 00000000..ee2a06cc --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/011-optional-is-symbol.rs @@ -0,0 +1,8 @@ +use minitrace::trace; + +#[trace(name = "a", type)] +fn f() {} + +fn main() { + f(); +} diff --git a/minitrace-macro/tests/trace/ui/err/011-optional-is-symbol.stderr b/minitrace-macro/tests/trace/ui/err/011-optional-is-symbol.stderr new file mode 100644 index 00000000..2c2438fe --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/011-optional-is-symbol.stderr @@ -0,0 +1,7 @@ +error: expected `=` + --> tests/trace/ui/err/011-optional-is-symbol.rs:3:1 + | +3 | #[trace(name = "a", type)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `trace` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/minitrace-macro/tests/trace/ui/err/012-optional-is-unknown-ident.rs b/minitrace-macro/tests/trace/ui/err/012-optional-is-unknown-ident.rs new file mode 100644 index 00000000..5de76f84 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/012-optional-is-unknown-ident.rs @@ -0,0 +1,8 @@ +use minitrace::trace; + +#[trace(name = "a", some_unknown = true)] +fn f() {} + +fn main() { + f(); +} diff --git a/minitrace-macro/tests/trace/ui/err/012-optional-is-unknown-ident.stderr b/minitrace-macro/tests/trace/ui/err/012-optional-is-unknown-ident.stderr new file mode 100644 index 00000000..b04c15f1 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/012-optional-is-unknown-ident.stderr @@ -0,0 +1,5 @@ +error: unknown option + --> tests/trace/ui/err/012-optional-is-unknown-ident.rs:3:21 + | +3 | #[trace(name = "a", some_unknown = true)] + | ^^^^^^^^^^^^ diff --git a/minitrace-macro/tests/trace/ui/err/013-optional-is-not-ident.rs b/minitrace-macro/tests/trace/ui/err/013-optional-is-not-ident.rs new file mode 100644 index 00000000..f3a82ac2 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/013-optional-is-not-ident.rs @@ -0,0 +1,6 @@ +use minitrace::trace; + +#[trace(name = "a", some::unknown=true)] +fn f() {} + +fn main() {} diff --git a/minitrace-macro/tests/trace/ui/err/013-optional-is-not-ident.stderr b/minitrace-macro/tests/trace/ui/err/013-optional-is-not-ident.stderr new file mode 100644 index 00000000..190cd4c6 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/013-optional-is-not-ident.stderr @@ -0,0 +1,5 @@ +error: unknown option + --> tests/trace/ui/err/013-optional-is-not-ident.rs:3:21 + | +3 | #[trace(name = "a", some::unknown=true)] + | ^^^^ diff --git a/minitrace-macro/tests/trace/ui/err/014-optional-value-type-is-ident.rs b/minitrace-macro/tests/trace/ui/err/014-optional-value-type-is-ident.rs new file mode 100644 index 00000000..cbb52ab9 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/014-optional-value-type-is-ident.rs @@ -0,0 +1,8 @@ +use minitrace::trace; + +#[trace(name = "a", enter_on_poll = y)] +fn f() {} + +fn main() { + f(); +} diff --git a/minitrace-macro/tests/trace/ui/err/014-optional-value-type-is-ident.stderr b/minitrace-macro/tests/trace/ui/err/014-optional-value-type-is-ident.stderr new file mode 100644 index 00000000..f6a526a1 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/014-optional-value-type-is-ident.stderr @@ -0,0 +1,5 @@ +error: expected literal + --> tests/trace/ui/err/014-optional-value-type-is-ident.rs:3:37 + | +3 | #[trace(name = "a", enter_on_poll = y)] + | ^ diff --git a/minitrace-macro/tests/trace/ui/err/015-optional-value-type-is-unknown-lit.rs b/minitrace-macro/tests/trace/ui/err/015-optional-value-type-is-unknown-lit.rs new file mode 100644 index 00000000..c345c156 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/015-optional-value-type-is-unknown-lit.rs @@ -0,0 +1,8 @@ +use minitrace::trace; + +#[trace(name = "a", enter_on_poll = 'y')] +fn f() {} + +fn main() { + f(); +} diff --git a/minitrace-macro/tests/trace/ui/err/015-optional-value-type-is-unknown-lit.stderr b/minitrace-macro/tests/trace/ui/err/015-optional-value-type-is-unknown-lit.stderr new file mode 100644 index 00000000..9269eb73 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/015-optional-value-type-is-unknown-lit.stderr @@ -0,0 +1,5 @@ +error: `enter_on_poll` value should be an boolean + --> tests/trace/ui/err/015-optional-value-type-is-unknown-lit.rs:3:21 + | +3 | #[trace(name = "a", enter_on_poll = 'y')] + | ^^^^^^^^^^^^^ diff --git a/minitrace-macro/tests/ui/err/name-is-not-an-assignment-expression.rs b/minitrace-macro/tests/trace/ui/err/016-name-is-char.rs similarity index 69% rename from minitrace-macro/tests/ui/err/name-is-not-an-assignment-expression.rs rename to minitrace-macro/tests/trace/ui/err/016-name-is-char.rs index 2ae68a41..af969b76 100644 --- a/minitrace-macro/tests/ui/err/name-is-not-an-assignment-expression.rs +++ b/minitrace-macro/tests/trace/ui/err/016-name-is-char.rs @@ -1,6 +1,6 @@ use minitrace::trace; -#[trace("b")] +#[trace(name = 'b')] fn f() {} fn main() {} diff --git a/minitrace-macro/tests/trace/ui/err/016-name-is-char.stderr b/minitrace-macro/tests/trace/ui/err/016-name-is-char.stderr new file mode 100644 index 00000000..ef923a48 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/016-name-is-char.stderr @@ -0,0 +1,5 @@ +error: `name` value should be a string + --> tests/trace/ui/err/016-name-is-char.rs:3:9 + | +3 | #[trace(name = 'b')] + | ^^^^ diff --git a/minitrace-macro/tests/trace/ui/err/017-name-is-byte-str.rs b/minitrace-macro/tests/trace/ui/err/017-name-is-byte-str.rs new file mode 100644 index 00000000..324243f5 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/017-name-is-byte-str.rs @@ -0,0 +1,6 @@ +use minitrace::trace; + +#[trace(name = b"name")] +fn f() {} + +fn main() {} diff --git a/minitrace-macro/tests/trace/ui/err/017-name-is-byte-str.stderr b/minitrace-macro/tests/trace/ui/err/017-name-is-byte-str.stderr new file mode 100644 index 00000000..7a9c237f --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/017-name-is-byte-str.stderr @@ -0,0 +1,5 @@ +error: `name` value should be a string + --> tests/trace/ui/err/017-name-is-byte-str.rs:3:9 + | +3 | #[trace(name = b"name")] + | ^^^^ diff --git a/minitrace-macro/tests/trace/ui/err/018-has-duplicate-name.rs b/minitrace-macro/tests/trace/ui/err/018-has-duplicate-name.rs new file mode 100644 index 00000000..4fd63c9a --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/018-has-duplicate-name.rs @@ -0,0 +1,10 @@ +use minitrace::trace; + +#[trace(name = "test_span", enter_on_poll = true, name = "not_this")] +fn f(a: u32) -> u32 { + a +} + +fn main() { + f(1); +} diff --git a/minitrace-macro/tests/trace/ui/err/018-has-duplicate-name.stderr b/minitrace-macro/tests/trace/ui/err/018-has-duplicate-name.stderr new file mode 100644 index 00000000..6d5882f1 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/018-has-duplicate-name.stderr @@ -0,0 +1,5 @@ +error: `name` provided twice + --> tests/trace/ui/err/018-has-duplicate-name.rs:3:51 + | +3 | #[trace(name = "test_span", enter_on_poll = true, name = "not_this")] + | ^^^^ diff --git a/minitrace-macro/tests/trace/ui/err/019-has-duplicate-enter_on_poll.rs b/minitrace-macro/tests/trace/ui/err/019-has-duplicate-enter_on_poll.rs new file mode 100644 index 00000000..ad2da569 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/019-has-duplicate-enter_on_poll.rs @@ -0,0 +1,10 @@ +use minitrace::trace; + +#[trace(enter_on_poll = true, enter_on_poll = false)] +fn f(a: u32) -> u32 { + a +} + +fn main() { + f(1); +} diff --git a/minitrace-macro/tests/trace/ui/err/019-has-duplicate-enter_on_poll.stderr b/minitrace-macro/tests/trace/ui/err/019-has-duplicate-enter_on_poll.stderr new file mode 100644 index 00000000..2897cc92 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/err/019-has-duplicate-enter_on_poll.stderr @@ -0,0 +1,5 @@ +error: `enter_on_poll` provided twice + --> tests/trace/ui/err/019-has-duplicate-enter_on_poll.rs:3:31 + | +3 | #[trace(enter_on_poll = true, enter_on_poll = false)] + | ^^^^^^^^^^^^^ diff --git a/minitrace-macro/tests/ui/err/has-duplicated-arguments.rs b/minitrace-macro/tests/trace/ui/err/has-duplicated-arguments.rs similarity index 100% rename from minitrace-macro/tests/ui/err/has-duplicated-arguments.rs rename to minitrace-macro/tests/trace/ui/err/has-duplicated-arguments.rs diff --git a/minitrace-macro/tests/ui/err/has-duplicated-arguments.stderr b/minitrace-macro/tests/trace/ui/err/has-duplicated-arguments.stderr similarity index 100% rename from minitrace-macro/tests/ui/err/has-duplicated-arguments.stderr rename to minitrace-macro/tests/trace/ui/err/has-duplicated-arguments.stderr diff --git a/minitrace-macro/tests/ui/err/has-ident-arguments.rs b/minitrace-macro/tests/trace/ui/err/has-ident-arguments.rs similarity index 69% rename from minitrace-macro/tests/ui/err/has-ident-arguments.rs rename to minitrace-macro/tests/trace/ui/err/has-ident-arguments.rs index 38f2068a..af969b76 100644 --- a/minitrace-macro/tests/ui/err/has-ident-arguments.rs +++ b/minitrace-macro/tests/trace/ui/err/has-ident-arguments.rs @@ -1,6 +1,6 @@ use minitrace::trace; -#[trace(a, b)] +#[trace(name = 'b')] fn f() {} fn main() {} diff --git a/minitrace-macro/tests/ui/err/has-ident-arguments.stderr b/minitrace-macro/tests/trace/ui/err/has-ident-arguments.stderr similarity index 100% rename from minitrace-macro/tests/ui/err/has-ident-arguments.stderr rename to minitrace-macro/tests/trace/ui/err/has-ident-arguments.stderr diff --git a/minitrace-macro/tests/trace/ui/ok/001-has-no-arguments.rs b/minitrace-macro/tests/trace/ui/ok/001-has-no-arguments.rs new file mode 100644 index 00000000..4f78ffee --- /dev/null +++ b/minitrace-macro/tests/trace/ui/ok/001-has-no-arguments.rs @@ -0,0 +1,8 @@ +use minitrace::trace; + +#[trace] +fn f(a: u64) { + std::thread::sleep(std::time::Duration::from_millis(a)); +} + +fn main() {} diff --git a/minitrace-macro/tests/ui/ok/has-name-async.rs b/minitrace-macro/tests/trace/ui/ok/002-has-enter_on_poll-ident-async.rs similarity index 79% rename from minitrace-macro/tests/ui/ok/has-name-async.rs rename to minitrace-macro/tests/trace/ui/ok/002-has-enter_on_poll-ident-async.rs index 4b737423..b9e721ba 100644 --- a/minitrace-macro/tests/ui/ok/has-name-async.rs +++ b/minitrace-macro/tests/trace/ui/ok/002-has-enter_on_poll-ident-async.rs @@ -1,6 +1,6 @@ use minitrace::trace; -#[trace(name = "test-span")] +#[trace(enter_on_poll=true)] async fn f(a: u32) -> u32 { a } diff --git a/minitrace-macro/tests/trace/ui/ok/003-has-name-async.rs b/minitrace-macro/tests/trace/ui/ok/003-has-name-async.rs new file mode 100644 index 00000000..5e52c1f3 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/ok/003-has-name-async.rs @@ -0,0 +1,11 @@ +use minitrace::trace; + +#[trace(name = "test_span")] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + f(1).await; +} diff --git a/minitrace-macro/tests/ui/ok/has-name-async-mut.rs b/minitrace-macro/tests/trace/ui/ok/004-has-name-mut-async.rs similarity index 68% rename from minitrace-macro/tests/ui/ok/has-name-async-mut.rs rename to minitrace-macro/tests/trace/ui/ok/004-has-name-mut-async.rs index a88fa334..bd76bff3 100644 --- a/minitrace-macro/tests/ui/ok/has-name-async-mut.rs +++ b/minitrace-macro/tests/trace/ui/ok/004-has-name-mut-async.rs @@ -1,9 +1,8 @@ -#![allow(unused_mut)] - use minitrace::trace; -#[trace(name = "test-span")] +#[trace(name = "test_span")] async fn f(mut a: u32) -> u32 { + a = a + 1; a } diff --git a/minitrace-macro/tests/trace/ui/ok/005-has-name-mut-sync.rs b/minitrace-macro/tests/trace/ui/ok/005-has-name-mut-sync.rs new file mode 100644 index 00000000..5902a368 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/ok/005-has-name-mut-sync.rs @@ -0,0 +1,11 @@ +use minitrace::trace; + +#[trace(name = "test_span")] +fn f(mut a: u32) -> u32 { + a = a + 1; + a +} + +fn main() { + f(1); +} diff --git a/minitrace-macro/tests/ui/ok/has-name.rs b/minitrace-macro/tests/trace/ui/ok/006-has-name-sync.rs similarity index 72% rename from minitrace-macro/tests/ui/ok/has-name.rs rename to minitrace-macro/tests/trace/ui/ok/006-has-name-sync.rs index e8aacbad..8592f182 100644 --- a/minitrace-macro/tests/ui/ok/has-name.rs +++ b/minitrace-macro/tests/trace/ui/ok/006-has-name-sync.rs @@ -1,6 +1,6 @@ use minitrace::trace; -#[trace(name = "test-span")] +#[trace(name = "test_span")] fn f(a: u32) -> u32 { a } diff --git a/minitrace-macro/tests/trace/ui/ok/007-interleaved.rs b/minitrace-macro/tests/trace/ui/ok/007-interleaved.rs new file mode 100644 index 00000000..4cacbd75 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/ok/007-interleaved.rs @@ -0,0 +1,10 @@ +use minitrace::trace; + +#[allow(unused_braces)] +#[trace] +#[warn(unused_braces)] +fn f(a: u64) { + std::thread::sleep(std::time::Duration::from_millis(a)); +} + +fn main() {} diff --git a/minitrace-macro/tests/trace/ui/ok/008-has-all-async.rs b/minitrace-macro/tests/trace/ui/ok/008-has-all-async.rs new file mode 100644 index 00000000..292e3a7b --- /dev/null +++ b/minitrace-macro/tests/trace/ui/ok/008-has-all-async.rs @@ -0,0 +1,11 @@ +use minitrace::trace; + +#[trace(name = "test_span", enter_on_poll = true)] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + f(1).await; +} diff --git a/minitrace-macro/tests/trace/ui/ok/008-has-all-sync.rs b/minitrace-macro/tests/trace/ui/ok/008-has-all-sync.rs new file mode 100644 index 00000000..8592f182 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/ok/008-has-all-sync.rs @@ -0,0 +1,10 @@ +use minitrace::trace; + +#[trace(name = "test_span")] +fn f(a: u32) -> u32 { + a +} + +fn main() { + f(1); +} diff --git a/minitrace-macro/tests/trace/ui/ok/009-consecutive-traces.rs b/minitrace-macro/tests/trace/ui/ok/009-consecutive-traces.rs new file mode 100644 index 00000000..7bc023d9 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/ok/009-consecutive-traces.rs @@ -0,0 +1,10 @@ +use minitrace::trace; + +// Revisit this in relation to issue #134 +#[trace] +#[allow(unused_braces)] +#[trace] +#[warn(unused_braces)] +fn f() {} + +fn main() {} diff --git a/minitrace-macro/tests/trace/ui/ok/010-name-out-of-place.rs b/minitrace-macro/tests/trace/ui/ok/010-name-out-of-place.rs new file mode 100644 index 00000000..487c9aa1 --- /dev/null +++ b/minitrace-macro/tests/trace/ui/ok/010-name-out-of-place.rs @@ -0,0 +1,11 @@ +use minitrace::trace; + +#[trace(enter_on_poll = true, name = "b")] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + f(1).await; +} diff --git a/minitrace-macro/tests/ui/ok/async-in-trait.rs b/minitrace-macro/tests/trace/ui/ok/async-in-trait.rs similarity index 100% rename from minitrace-macro/tests/ui/ok/async-in-trait.rs rename to minitrace-macro/tests/trace/ui/ok/async-in-trait.rs diff --git a/minitrace-macro/tests/ui/ok/async-trait.rs b/minitrace-macro/tests/trace/ui/ok/async-trait.rs similarity index 100% rename from minitrace-macro/tests/ui/ok/async-trait.rs rename to minitrace-macro/tests/trace/ui/ok/async-trait.rs diff --git a/minitrace-macro/tests/ui/err/item-is-not-a-function.stderr b/minitrace-macro/tests/ui/err/item-is-not-a-function.stderr deleted file mode 100644 index 226dd70b..00000000 --- a/minitrace-macro/tests/ui/err/item-is-not-a-function.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: expected `fn` - --> tests/ui/err/item-is-not-a-function.rs:4:1 - | -4 | struct S; - | ^^^^^^ diff --git a/minitrace-macro/tests/ui/ok/has-no-arguments.rs b/minitrace-macro/tests/ui/ok/has-no-arguments.rs deleted file mode 100644 index a801bd08..00000000 --- a/minitrace-macro/tests/ui/ok/has-no-arguments.rs +++ /dev/null @@ -1,10 +0,0 @@ -use minitrace::trace; - -// This Tracing crate like-syntax -#[allow(unused_braces)] -#[trace] -fn f(a: u32) -> u32 { - a -} - -fn main() {} diff --git a/minitrace-old/Cargo.toml b/minitrace-old/Cargo.toml new file mode 100644 index 00000000..4d9e1894 --- /dev/null +++ b/minitrace-old/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "minitrace" +version = "0.5.0" +authors = ["The TiKV Project Authors"] +license = "Apache-2.0" +rust-version = "1.56.0" +edition = "2021" +description = "A high-performance timeline tracing library for Rust" +homepage = "https://github.com/tikv/minitrace-rust" +repository = "https://github.com/tikv/minitrace-rust" +documentation = "https://docs.rs/minitrace" +readme = "README.md" +keywords = ["tracing", "span", "datadog", "jaeger", "opentracing"] + +[dependencies] +minstant = "0.1" +minitrace-macro = { path = "../minitrace-macro" } +pin-project = "1.0" +parking_lot = "0.11" +futures = "0.3" +# TODO: Remove once_cell once #![feature(once_cell)] is stabilized +once_cell = "1" +# TODO: Remove retain_mut once #![feature(vec_retain_mut)] is stabilized +retain_mut = "0.1" + +[dev-dependencies] +# The procedural macro `trace` only supports async-trait higher than 0.1.52 +async-trait = "0.1.52" +criterion = { version = "0.3", features = ["html_reports"] } +crossbeam = "0.8" +minitrace-jaeger = { path = "../minitrace-jaeger" } +minitrace-datadog = { path = "../minitrace-datadog" } +tokio = { version = "1", features = ["rt", "time", "macros"] } +rustracing = "0.5" +opentelemetry = { version = "0.16", default-features = false, features = ["trace"] } +opentelemetry-jaeger = "0.15" +tracing-opentelemetry = "0.15" +tracing = "0.1" +tracing-core = "0.1" +tracing-subscriber = "0.2" +rand = "0.8" +futures = "0.3" +futures-timer = "3" +mockall = "0.11" + +[[bench]] +name = "trace" +harness = false + +[[bench]] +name = "compare" +harness = false + +[[bench]] +name = "spsc" +harness = false + +[[bench]] +name = "object_pool" +harness = false diff --git a/minitrace/LICENSE b/minitrace-old/LICENSE similarity index 100% rename from minitrace/LICENSE rename to minitrace-old/LICENSE diff --git a/minitrace-old/benches/compare.rs b/minitrace-old/benches/compare.rs new file mode 100644 index 00000000..22bd5875 --- /dev/null +++ b/minitrace-old/benches/compare.rs @@ -0,0 +1,88 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use criterion::{criterion_group, criterion_main, Criterion}; + +fn init_opentelemetry() { + use tracing_subscriber::prelude::*; + + let opentelemetry = tracing_opentelemetry::layer(); + tracing_subscriber::registry() + .with(opentelemetry) + .try_init() + .unwrap(); +} + +fn opentelemetry_harness(n: usize) { + fn dummy_opentelementry(n: usize) { + for _ in 0..n { + let child = tracing::span!(tracing::Level::TRACE, "child"); + let _enter = child.enter(); + } + } + + let root = tracing::span!(tracing::Level::TRACE, "parent"); + let _enter = root.enter(); + + dummy_opentelementry(n); +} + +fn rustracing_harness(n: usize) { + fn dummy_rustracing(n: usize, span: &rustracing::span::Span<()>) { + for _ in 0..n { + let _child_span = span.child("child", |c| c.start_with_state(())); + } + } + + let (span_tx, span_rx) = crossbeam::channel::bounded(1000); + + { + let tracer = rustracing::Tracer::with_sender(rustracing::sampler::AllSampler, span_tx); + let parent_span = tracer.span("parent").start_with_state(()); + dummy_rustracing(n, &parent_span); + } + + let _r = span_rx.iter().collect::>(); +} + +fn minitrace_harness(n: usize) { + use minitrace::prelude::*; + + fn dummy_minitrace(n: usize) { + for _ in 0..n { + let _guard = LocalSpan::enter_with_local_parent("child"); + } + } + + let _spans = { + let (root_span, collector) = Span::root("parent"); + let _g = root_span.set_local_parent(); + + dummy_minitrace(n); + + collector + } + .collect(); +} + +fn tracing_comparison(c: &mut Criterion) { + init_opentelemetry(); + + let mut bgroup = c.benchmark_group("compare"); + + for n in &[1, 10, 100, 1000] { + bgroup.bench_function(format!("Tokio Tracing/{n}"), |b| { + b.iter(|| opentelemetry_harness(*n)) + }); + bgroup.bench_function(format!("Rustracing/{n}"), |b| { + b.iter(|| rustracing_harness(*n)) + }); + bgroup.bench_function(format!("minitrace/{n}"), |b| { + b.iter(|| minitrace_harness(*n)) + }); + } + + bgroup.finish(); +} + +criterion_group!(benches, tracing_comparison); +criterion_main!(benches); diff --git a/minitrace-old/benches/object_pool.rs b/minitrace-old/benches/object_pool.rs new file mode 100644 index 00000000..96f4e517 --- /dev/null +++ b/minitrace-old/benches/object_pool.rs @@ -0,0 +1,39 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use minitrace::util::object_pool::Pool; + +fn bench_alloc_vec(c: &mut Criterion) { + let mut bgroup = c.benchmark_group("Vec::with_capacity"); + + for cap in &[1, 10, 100, 1000, 10000, 100000] { + let vec_pool: Pool> = Pool::new(Vec::new, Vec::clear); + let mut puller = vec_pool.puller(512); + bgroup.bench_function(format!("object-pool/{}", cap), |b| { + b.iter_batched( + || (), + |_| { + let mut vec = puller.pull(); + if vec.capacity() < *cap { + vec.reserve(*cap); + } + vec + }, + BatchSize::NumIterations(512), + ) + }); + + bgroup.bench_function(format!("alloc/{}", cap), |b| { + b.iter_batched( + || (), + |_| Vec::::with_capacity(*cap), + BatchSize::NumIterations(512), + ) + }); + } + + bgroup.finish(); +} + +criterion_group!(benches, bench_alloc_vec); +criterion_main!(benches); diff --git a/minitrace-old/benches/spsc.rs b/minitrace-old/benches/spsc.rs new file mode 100644 index 00000000..e8c7fc4e --- /dev/null +++ b/minitrace-old/benches/spsc.rs @@ -0,0 +1,82 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +use criterion::{criterion_group, criterion_main, Criterion}; + +fn crossbeam(nmsg: usize) { + let (tx, rx) = crossbeam::channel::bounded(10240); + + crossbeam::scope(|scope| { + scope.spawn(|_| { + for i in 0..nmsg { + tx.send(i).unwrap(); + } + }); + + for _ in 0..nmsg { + while rx.try_recv().is_ok() {} + } + }) + .unwrap(); +} + +fn crossbeam_send_only(nmsg: usize) { + let (tx, _rx) = crossbeam::channel::bounded(10240); + + for i in 0..nmsg { + tx.send(i).unwrap(); + } +} + +fn minitrace(nmsg: usize) { + let (tx, mut rx) = minitrace::util::spsc::bounded(10240); + + crossbeam::scope(|scope| { + scope.spawn(|_| { + for i in 0..nmsg { + tx.send(i).unwrap(); + } + }); + + for _ in 0..nmsg { + while rx.try_recv().unwrap().is_some() {} + } + }) + .unwrap(); +} + +fn minitrace_send_only(nmsg: usize) { + let (tx, _rx) = minitrace::util::spsc::bounded(10240); + + for i in 0..nmsg { + tx.send(i).unwrap(); + } +} + +fn spsc_comparison(c: &mut Criterion) { + let mut bgroup = c.benchmark_group("spsc channel"); + + for len in &[1, 10, 100, 1000, 10000] { + bgroup.bench_function(format!("crossbeam/{}", len), |b| b.iter(|| crossbeam(*len))); + bgroup.bench_function(format!("minitrace/{}", len), |b| b.iter(|| minitrace(*len))); + } + + bgroup.finish(); +} + +fn spsc_send_only_comparison(c: &mut Criterion) { + let mut bgroup = c.benchmark_group("spsc channel send only"); + + for len in &[1, 10, 100, 1000, 10000] { + bgroup.bench_function(format!("crossbeam/{}", len), |b| { + b.iter(|| crossbeam_send_only(*len)) + }); + bgroup.bench_function(format!("minitrace/{}", len), |b| { + b.iter(|| minitrace_send_only(*len)) + }); + } + + bgroup.finish(); +} + +criterion_group!(benches, spsc_comparison, spsc_send_only_comparison); +criterion_main!(benches); diff --git a/minitrace-old/benches/trace.rs b/minitrace-old/benches/trace.rs new file mode 100644 index 00000000..74d99ce8 --- /dev/null +++ b/minitrace-old/benches/trace.rs @@ -0,0 +1,142 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use minitrace::local::LocalCollector; +use minitrace::prelude::*; + +fn dummy_iter(i: usize) { + #[trace( name = "")] + fn dummy() {} + + for _ in 0..i { + dummy(); + } +} + +#[trace( name = "")] +fn dummy_rec(i: usize) { + if i > 1 { + dummy_rec(i - 1); + } +} + +fn bench_trace_wide_raw(c: &mut Criterion) { + let mut group = c.benchmark_group("trace_wide_raw"); + + for len in &[1, 10, 100, 1000, 10000] { + group.bench_function(len.to_string(), |b| { + b.iter(|| { + let local_collector = LocalCollector::start(); + dummy_iter(*len); + local_collector.collect() + }) + }); + } + + group.finish(); +} + +fn bench_trace_wide(c: &mut Criterion) { + let mut group = c.benchmark_group("trace_wide"); + + for len in &[1, 10, 100, 1000, 10000] { + group.bench_function(format!("with-collect-{}", len), |b| { + b.iter(|| { + { + let (root_span, collector) = Span::root("root"); + let _sg = root_span.set_local_parent(); + dummy_iter(*len - 1); + collector + } + .collect() + }) + }); + group.bench_function(format!("without-collect-{}", len), |b| { + b.iter(|| { + let (root_span, _) = Span::root("root"); + let _sg = root_span.set_local_parent(); + dummy_iter(*len - 1); + }) + }); + } + + group.finish(); +} + +fn bench_trace_deep_raw(c: &mut Criterion) { + let mut group = c.benchmark_group("trace_deep_raw"); + + for len in &[1, 10, 100, 1000] { + group.bench_function(len.to_string(), |b| { + b.iter(|| { + let local_collector = LocalCollector::start(); + dummy_rec(*len); + local_collector.collect() + }) + }); + } + + group.finish(); +} + +fn bench_trace_deep(c: &mut Criterion) { + let mut group = c.benchmark_group("trace_deep"); + + for len in &[1, 10, 100, 1000] { + group.bench_function(format!("with-collect-{}", len), |b| { + b.iter(|| { + { + let (root_span, collector) = Span::root("root"); + let _sg = root_span.set_local_parent(); + dummy_rec(*len - 1); + collector + } + .collect() + }) + }); + group.bench_function(format!("without-collect-{}", len), |b| { + b.iter(|| { + let (root_span, _) = Span::root("root"); + let _sg = root_span.set_local_parent(); + dummy_rec(*len - 1); + }) + }); + } + + group.finish(); +} + +fn bench_trace_future(c: &mut Criterion) { + async fn f(i: u32) { + for _ in 0..i - 1 { + async {}.enter_on_poll(black_box("")).await + } + } + + let mut group = c.benchmark_group("trace_future"); + + for len in &[1, 10, 100, 1000, 10000] { + group.bench_function(len.to_string(), |b| { + b.iter(|| { + { + let (root_span, collector) = Span::root("root"); + let _ = futures::executor::block_on(f(*len).in_span(root_span)); + collector + } + .collect() + }) + }); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_trace_wide_raw, + bench_trace_wide, + bench_trace_deep_raw, + bench_trace_deep, + bench_trace_future +); +criterion_main!(benches); diff --git a/minitrace-old/examples/asynchronous.rs b/minitrace-old/examples/asynchronous.rs new file mode 100644 index 00000000..9e9a0173 --- /dev/null +++ b/minitrace-old/examples/asynchronous.rs @@ -0,0 +1,76 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use minitrace::prelude::*; + +fn parallel_job() -> Vec> { + let mut v = Vec::with_capacity(4); + for i in 0..4 { + v.push(tokio::spawn( + iter_job(i).in_span(Span::enter_with_local_parent("iter job")), + )); + } + v +} + +async fn iter_job(iter: u64) { + std::thread::sleep(std::time::Duration::from_millis(iter * 10)); + tokio::task::yield_now().await; + other_job().await; +} + +#[trace(name = "other job", enter_on_poll = true)] +async fn other_job() { + for i in 0..20 { + if i == 10 { + tokio::task::yield_now().await; + } + std::thread::sleep(std::time::Duration::from_millis(1)); + } +} + +#[tokio::main] +async fn main() { + let (span, collector) = Span::root("root"); + + let f = async { + let jhs = { + let mut span = LocalSpan::enter_with_local_parent("a span"); + span.add_property(|| ("a property", "a value".to_owned())); + parallel_job() + }; + + other_job().await; + + for jh in jhs { + jh.await.unwrap(); + } + } + .in_span(span); + + tokio::spawn(f).await.unwrap(); + + let spans = collector.collect().await; + + // Report to Jaeger + let bytes = + minitrace_jaeger::encode("asynchronous".to_owned(), rand::random(), 0, 0, &spans).unwrap(); + minitrace_jaeger::report("127.0.0.1:6831".parse().unwrap(), &bytes) + .await + .ok(); + + // Report to Datadog + let bytes = minitrace_datadog::encode( + "asynchronous", + "db", + "select", + 0, + rand::random(), + 0, + 0, + &spans, + ) + .unwrap(); + minitrace_datadog::report("127.0.0.1:8126".parse().unwrap(), bytes) + .await + .ok(); +} diff --git a/minitrace-old/examples/get_started.rs b/minitrace-old/examples/get_started.rs new file mode 100644 index 00000000..1e702c40 --- /dev/null +++ b/minitrace-old/examples/get_started.rs @@ -0,0 +1,35 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +use std::net::SocketAddr; + +use futures::executor::block_on; +use minitrace::prelude::*; + +fn main() { + let collector = { + let (root_span, collector) = Span::root("root"); + let _span_guard = root_span.set_local_parent(); + + let _local_span_guard = LocalSpan::enter_with_local_parent("child"); + + // do something ... + collector + }; + + let spans = block_on(collector.collect()); + + const TRACE_ID: u64 = 42; + const SPAN_ID_PREFIX: u32 = 42; + const ROOT_PARENT_SPAN_ID: u64 = 0; + let bytes = minitrace_jaeger::encode( + String::from("service name"), + TRACE_ID, + ROOT_PARENT_SPAN_ID, + SPAN_ID_PREFIX, + &spans, + ) + .expect("encode error"); + + let socket = SocketAddr::new("127.0.0.1".parse().unwrap(), 6831); + minitrace_jaeger::report_blocking(socket, &bytes).expect("report error"); +} diff --git a/minitrace-old/examples/synchronous.rs b/minitrace-old/examples/synchronous.rs new file mode 100644 index 00000000..e4bfc5fa --- /dev/null +++ b/minitrace-old/examples/synchronous.rs @@ -0,0 +1,52 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use futures::executor::block_on; +use minitrace::prelude::*; + +fn func1(i: u64) { + let _guard = LocalSpan::enter_with_local_parent("func1"); + std::thread::sleep(std::time::Duration::from_millis(i)); + func2(i); +} + +#[trace(name = "func2")] +fn func2(i: u64) { + std::thread::sleep(std::time::Duration::from_millis(i)); +} + +fn main() { + let collector = { + let (span, collector) = Span::root("root"); + + let _sg1 = span.set_local_parent(); + let mut sg2 = LocalSpan::enter_with_local_parent("a span"); + sg2.add_property(|| ("a property", "a value".to_owned())); + + for i in 1..=10 { + func1(i); + } + + collector + }; + + let spans = block_on(collector.collect()); + + // Report to Jaeger + let bytes = + minitrace_jaeger::encode("synchronous".to_owned(), rand::random(), 0, 0, &spans).unwrap(); + minitrace_jaeger::report_blocking("127.0.0.1:6831".parse().unwrap(), &bytes).ok(); + + // Report to Datadog + let bytes = minitrace_datadog::encode( + "synchronous", + "web", + "/health", + 0, + rand::random(), + 0, + 0, + &spans, + ) + .unwrap(); + minitrace_datadog::report_blocking("127.0.0.1:8126".parse().unwrap(), bytes).ok(); +} diff --git a/minitrace-old/img/benchmark.jpeg b/minitrace-old/img/benchmark.jpeg new file mode 120000 index 00000000..2ad3e507 --- /dev/null +++ b/minitrace-old/img/benchmark.jpeg @@ -0,0 +1 @@ +../../img/benchmark.jpeg \ No newline at end of file diff --git a/minitrace-old/img/jaeger-asynchronous.png b/minitrace-old/img/jaeger-asynchronous.png new file mode 120000 index 00000000..b2f7470e --- /dev/null +++ b/minitrace-old/img/jaeger-asynchronous.png @@ -0,0 +1 @@ +../../img/jaeger-asynchronous.png \ No newline at end of file diff --git a/minitrace-old/img/jaeger-synchronous.png b/minitrace-old/img/jaeger-synchronous.png new file mode 120000 index 00000000..8ca02166 --- /dev/null +++ b/minitrace-old/img/jaeger-synchronous.png @@ -0,0 +1 @@ +../../img/jaeger-synchronous.png \ No newline at end of file diff --git a/minitrace/src/collector/command.rs b/minitrace-old/src/collector/command.rs similarity index 100% rename from minitrace/src/collector/command.rs rename to minitrace-old/src/collector/command.rs diff --git a/minitrace/src/collector/console_reporter.rs b/minitrace-old/src/collector/console_reporter.rs similarity index 100% rename from minitrace/src/collector/console_reporter.rs rename to minitrace-old/src/collector/console_reporter.rs diff --git a/minitrace/src/collector/global_collector.rs b/minitrace-old/src/collector/global_collector.rs similarity index 100% rename from minitrace/src/collector/global_collector.rs rename to minitrace-old/src/collector/global_collector.rs diff --git a/minitrace/src/collector/id.rs b/minitrace-old/src/collector/id.rs similarity index 100% rename from minitrace/src/collector/id.rs rename to minitrace-old/src/collector/id.rs diff --git a/minitrace/src/collector/mod.rs b/minitrace-old/src/collector/mod.rs similarity index 100% rename from minitrace/src/collector/mod.rs rename to minitrace-old/src/collector/mod.rs diff --git a/minitrace/src/collector/test_reporter.rs b/minitrace-old/src/collector/test_reporter.rs similarity index 100% rename from minitrace/src/collector/test_reporter.rs rename to minitrace-old/src/collector/test_reporter.rs diff --git a/minitrace/src/event.rs b/minitrace-old/src/event.rs similarity index 100% rename from minitrace/src/event.rs rename to minitrace-old/src/event.rs diff --git a/minitrace/src/future.rs b/minitrace-old/src/future.rs similarity index 100% rename from minitrace/src/future.rs rename to minitrace-old/src/future.rs diff --git a/minitrace-old/src/lib.rs b/minitrace-old/src/lib.rs new file mode 100644 index 00000000..70d78d51 --- /dev/null +++ b/minitrace-old/src/lib.rs @@ -0,0 +1,316 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +//! A high-performance, ergonomic timeline tracing library for Rust. +//! +//! ## Span +//! +//! A [`SpanRecord`] represents an individual unit of work. It contains: +//! - An operation name +//! - A start timestamp and duration +//! - A set of key-value properties +//! - A reference to a parent `Span` +//! +//! To record such a span record, we create a [`Span`] to start clocking and drop it to stop recording. +//! +//! A new `Span` can be started via [`Span::root()`] and [`Span::enter_with_parent()`]. `Span::enter_with_parent()` +//! will start a child span to a given parent span. +//! +//! `Span` is thread-safe and can be sent across threads. +//! +//! ``` +//! use minitrace::prelude::*; +//! use futures::executor::block_on; +//! +//! let (root, collector) = Span::root("root"); +//! +//! { +//! let _child_span = Span::enter_with_parent("a child span", &root); +//! // some work +//! } +//! +//! drop(root); +//! let records: Vec = block_on(collector.collect()); +//! +//! println!("{records:#?}"); +//! // [ +//! // SpanRecord { +//! // id: 1, +//! // parent_id: 0, +//! // begin_unix_time_ns: 1642166520139678013, +//! // duration_ns: 16008, +//! // event: "root", +//! // properties: [], +//! // }, +//! // SpanRecord { +//! // id: 2, +//! // parent_id: 1, +//! // begin_unix_time_ns: 1642166520139692070, +//! // duration_ns: 634, +//! // event: "a child span", +//! // properties: [], +//! // }, +//! // ] +//! ``` +//! +//! +//! ## Local Span +//! +//! A `Span` can be optimized into [`LocalSpan`], if the span is not supposed to be sent to other threads, +//! which can greatly reduce the overhead. +//! +//! Before starting a `LocalSpan`, a scope of parent span should be set using [`Span::set_local_parent()`]. +//! Use [`LocalSpan::enter_with_local_parent()`] to start a `LocalSpan`, and then, it will become the new local parent. +//! +//! If no local parent is set, the `enter_with_local_parent()` will do nothing. +//! +//! ``` +//! use minitrace::prelude::*; +//! use futures::executor::block_on; +//! +//! let (root, collector) = Span::root("root"); +//! +//! { +//! let _guard = root.set_local_parent(); +//! +//! // The parent of this span is `root`. +//! let _span1 = LocalSpan::enter_with_local_parent("a child span"); +//! +//! foo(); +//! } +//! +//! fn foo() { +//! // The parent of this span is `span1`. +//! let _span2 = LocalSpan::enter_with_local_parent("a child span of child span"); +//! } +//! +//! drop(root); +//! let records: Vec = block_on(collector.collect()); +//! +//! println!("{records:#?}"); +//! // [ +//! // SpanRecord { +//! // id: 1, +//! // parent_id: 0, +//! // begin_unix_time_ns: 1643101008017429580, +//! // duration_ns: 64132, +//! // event: "root", +//! // properties: [], +//! // }, +//! // SpanRecord { +//! // id: 2, +//! // parent_id: 1, +//! // begin_unix_time_ns: 1643101008017486383, +//! // duration_ns: 4150, +//! // event: "a child span", +//! // properties: [], +//! // }, +//! // SpanRecord { +//! // id: 3, +//! // parent_id: 2, +//! // begin_unix_time_ns: 1643101008017488703, +//! // duration_ns: 1318, +//! // event: "a child span of child span", +//! // properties: [], +//! // }, +//! // ] +//! ``` +//! +//! +//! ## Property +//! +//! Property is an arbitrary custom kev-value pair associated to a span. +//! +//! ``` +//! use minitrace::prelude::*; +//! use futures::executor::block_on; +//! +//! let (mut root, collector) = Span::root("root"); +//! root.add_property(|| ("key", "value".to_owned())); +//! +//! { +//! let _guard = root.set_local_parent(); +//! +//! let mut span1 = LocalSpan::enter_with_local_parent("a child span"); +//! span1.add_property(|| ("key", "value".to_owned())); +//! } +//! +//! drop(root); +//! let records: Vec = block_on(collector.collect()); +//! +//! println!("{records:#?}"); +//! // [ +//! // SpanRecord { +//! // id: 1, +//! // parent_id: 0, +//! // begin_unix_time_ns: 1642166791041022255, +//! // duration_ns: 121705, +//! // event: "root", +//! // properties: [ +//! // ( +//! // "key", +//! // "value", +//! // ), +//! // ], +//! // }, +//! // SpanRecord { +//! // id: 2, +//! // parent_id: 1, +//! // begin_unix_time_ns: 1642166791041132550, +//! // duration_ns: 7724, +//! // event: "a child span", +//! // properties: [ +//! // ( +//! // "key", +//! // "value", +//! // ), +//! // ], +//! // }, +//! // ] +//! ``` +//! +//! +//! ## Macro +//! +//! An attribute-macro [`trace`] can help get rid of boilerplate. The macro always requires a local +//! parent in the context, otherwise, no span will be recorded. +//! +//! ``` +//! use minitrace::prelude::*; +//! use futures::executor::block_on; +//! +//! #[trace( name = "do_something")] +//! fn do_something(i: u64) { +//! std::thread::sleep(std::time::Duration::from_millis(i)); +//! } +//! +//! #[trace( name = "do_something_async")] +//! async fn do_something_async(i: u64) { +//! futures_timer::Delay::new(std::time::Duration::from_millis(i)).await; +//! } +//! +//! let (root, collector) = Span::root("root"); +//! +//! { +//! let _g = root.set_local_parent(); +//! do_something(100); +//! block_on(do_something_async(100)); +//! } +//! +//! drop(root); +//! let records: Vec = block_on(collector.collect()); +//! +//! println!("{records:#?}"); +//! // [ +//! // SpanRecord { +//! // id: 1, +//! // parent_id: 0, +//! // begin_unix_time_ns: 1642167988459480418, +//! // duration_ns: 200741472, +//! // event: "root", +//! // properties: [], +//! // }, +//! // SpanRecord { +//! // id: 2, +//! // parent_id: 1, +//! // begin_unix_time_ns: 1642167988459571971, +//! // duration_ns: 100084126, +//! // event: "do_something", +//! // properties: [], +//! // }, +//! // SpanRecord { +//! // id: 3, +//! // parent_id: 1, +//! // begin_unix_time_ns: 1642167988559887219, +//! // duration_ns: 100306947, +//! // event: "do_something_async", +//! // properties: [], +//! // }, +//! // ] +//! ``` +//! +//! [`Span`]: crate::Span +//! [`LocalSpan`]: crate::local::LocalSpan +//! [`SpanRecord`]: crate::collector::SpanRecord +//! [`FutureExt`]: crate::future::FutureExt +//! [`trace`]: crate::trace +//! [`LocalCollector`]: crate::local::LocalCollector +//! [`Span::root()`]: crate::Span::root +//! [`Span::enter_with_parent()`]: crate::Span::enter_with_parent +//! [`Span::set_local_parent()`]: crate::Span::set_local_parent +//! [`LocalSpan::enter_with_local_parent()`]: crate::local::LocalSpan::enter_with_local_parent + +pub mod collector; +pub mod future; +pub mod local; +mod span; +#[doc(hidden)] +pub mod util; + +pub use crate::span::Span; +/// An attribute-macro to help get rid of boilerplate. +/// +/// [`trace`] always require an local parent in the context. Make sure that the caller +/// is within the scope of [`Span::set_local_parent()`]. +/// +/// # Examples +/// +/// ``` +/// use minitrace::prelude::*; +/// +/// #[trace( name = "foo")] +/// fn foo() { +/// // some work +/// } +/// +/// #[trace( name = "bar")] +/// async fn bar() { +/// // some work +/// } +/// +/// #[trace( name = "qux", enter_on_poll = true)] +/// async fn qux() { +/// // some work +/// } +/// ``` +/// +/// The examples above will be translated into: +/// +/// ``` +/// # use minitrace::prelude::*; +/// # use minitrace::local::LocalSpan; +/// fn foo() { +/// let __guard = LocalSpan::enter_with_local_parent("foo"); +/// // some work +/// } +/// +/// fn bar() -> impl core::future::Future { +/// async { +/// // some work +/// } +/// .in_span(Span::enter_with_local_parent("bar")) +/// } +/// +/// fn qux() -> impl core::future::Future { +/// async { +/// // some work +/// } +/// .enter_on_poll("qux") +/// } +/// ``` +/// +/// [`in_span()`]: crate::future::FutureExt::in_span +pub use minitrace_macro::trace; + +pub mod prelude { + //! A "prelude" for crates using the `minitrace` crate. + #[doc(no_inline)] + pub use crate::collector::{CollectArgs, Collector, SpanRecord}; + #[doc(no_inline)] + pub use crate::future::FutureExt as _; + #[doc(no_inline)] + pub use crate::local::LocalSpan; + #[doc(no_inline)] + pub use crate::span::Span; + #[doc(no_inline)] + pub use crate::trace; +} diff --git a/minitrace-old/src/local/guard.rs b/minitrace-old/src/local/guard.rs new file mode 100644 index 00000000..9f3d19dc --- /dev/null +++ b/minitrace-old/src/local/guard.rs @@ -0,0 +1,36 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +#[must_use] +pub struct Guard { + inner: Option, +} + +impl Guard { + pub fn new(f: F) -> Self { + Self { inner: Some(f) } + } +} + +impl Drop for Guard { + fn drop(&mut self) { + if let Some(f) = self.inner.take() { + f() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::cell::Cell; + + #[test] + fn guard_basic() { + let a = Cell::new(0); + { + let _guard = Guard::new(|| a.set(1)); + assert_eq!(a.get(), 0); + } + assert_eq!(a.get(), 1); + } +} diff --git a/minitrace/src/local/local_collector.rs b/minitrace-old/src/local/local_collector.rs similarity index 100% rename from minitrace/src/local/local_collector.rs rename to minitrace-old/src/local/local_collector.rs diff --git a/minitrace/src/local/local_span.rs b/minitrace-old/src/local/local_span.rs similarity index 100% rename from minitrace/src/local/local_span.rs rename to minitrace-old/src/local/local_span.rs diff --git a/minitrace/src/local/local_span_line.rs b/minitrace-old/src/local/local_span_line.rs similarity index 100% rename from minitrace/src/local/local_span_line.rs rename to minitrace-old/src/local/local_span_line.rs diff --git a/minitrace/src/local/local_span_stack.rs b/minitrace-old/src/local/local_span_stack.rs similarity index 100% rename from minitrace/src/local/local_span_stack.rs rename to minitrace-old/src/local/local_span_stack.rs diff --git a/minitrace/src/local/mod.rs b/minitrace-old/src/local/mod.rs similarity index 100% rename from minitrace/src/local/mod.rs rename to minitrace-old/src/local/mod.rs diff --git a/minitrace/src/local/raw_span.rs b/minitrace-old/src/local/raw_span.rs similarity index 100% rename from minitrace/src/local/raw_span.rs rename to minitrace-old/src/local/raw_span.rs diff --git a/minitrace-old/src/local/span_id.rs b/minitrace-old/src/local/span_id.rs new file mode 100644 index 00000000..ede23bfb --- /dev/null +++ b/minitrace-old/src/local/span_id.rs @@ -0,0 +1,72 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use std::cell::Cell; +use std::sync::atomic::{AtomicU16, Ordering}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] +pub struct SpanId(pub u32); + +impl SpanId { + pub fn new(id: u32) -> Self { + SpanId(id) + } +} + +pub struct DefaultIdGenerator; + +static NEXT_ID_PREFIX: AtomicU16 = AtomicU16::new(0); +fn next_id_prefix() -> u16 { + NEXT_ID_PREFIX.fetch_add(1, Ordering::Relaxed) +} + +thread_local! { + static LOCAL_ID_GENERATOR: Cell<(u16, u16)> = Cell::new((next_id_prefix(), 0)) +} + +impl DefaultIdGenerator { + #[inline] + /// Create a non-zero `SpanId` + pub fn next_id() -> SpanId { + LOCAL_ID_GENERATOR.with(|g| { + let (mut prefix, mut suffix) = g.get(); + + if suffix == std::u16::MAX { + suffix = 0; + prefix = next_id_prefix(); + } + // `suffix` can not be `0`, so `SpanId` won't be `0`. + suffix += 1; + + g.set((prefix, suffix)); + + SpanId::new(((prefix as u32) << 16) | (suffix as u32)) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + + #[test] + #[allow(clippy::needless_collect)] + fn unique_id() { + let handles = std::iter::repeat_with(|| { + std::thread::spawn(|| { + std::iter::repeat_with(DefaultIdGenerator::next_id) + .take(1000) + .collect::>() + }) + }) + .take(32) + .collect::>(); + + let k = handles + .into_iter() + .flat_map(|h| h.join().unwrap()) + .collect::>(); + + assert_eq!(k.len(), 32 * 1000); + } +} diff --git a/minitrace/src/local/span_queue.rs b/minitrace-old/src/local/span_queue.rs similarity index 100% rename from minitrace/src/local/span_queue.rs rename to minitrace-old/src/local/span_queue.rs diff --git a/minitrace/src/span.rs b/minitrace-old/src/span.rs similarity index 100% rename from minitrace/src/span.rs rename to minitrace-old/src/span.rs diff --git a/minitrace/src/util/mod.rs b/minitrace-old/src/util/mod.rs similarity index 100% rename from minitrace/src/util/mod.rs rename to minitrace-old/src/util/mod.rs diff --git a/minitrace/src/util/object_pool.rs b/minitrace-old/src/util/object_pool.rs similarity index 100% rename from minitrace/src/util/object_pool.rs rename to minitrace-old/src/util/object_pool.rs diff --git a/minitrace/src/util/spsc.rs b/minitrace-old/src/util/spsc.rs similarity index 100% rename from minitrace/src/util/spsc.rs rename to minitrace-old/src/util/spsc.rs diff --git a/minitrace/src/util/tree.rs b/minitrace-old/src/util/tree.rs similarity index 100% rename from minitrace/src/util/tree.rs rename to minitrace-old/src/util/tree.rs diff --git a/minitrace-old/tests/lib.rs b/minitrace-old/tests/lib.rs new file mode 100644 index 00000000..4f40673b --- /dev/null +++ b/minitrace-old/tests/lib.rs @@ -0,0 +1,517 @@ +// Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. + +use std::sync::Arc; + +use futures::executor::block_on; + +use minitrace::local::LocalCollector; +use minitrace::prelude::*; +use minitrace::util::tree::tree_str_from_span_records; +use tokio::runtime::Builder; + +fn four_spans() { + { + // wide + for _ in 0..2 { + let mut span = LocalSpan::enter_with_local_parent("iter-span"); + span.add_property(|| ("tmp_property", "tmp_value".into())); + } + } + + { + #[trace(name = "rec-span")] + fn rec(mut i: u32) { + i -= 1; + + if i > 0 { + rec(i); + } + } + + // deep + rec(2); + } +} + +#[test] +fn single_thread_single_span() { + let collector = { + let (root_span, collector) = Span::root("root"); + let _g = root_span.set_local_parent(); + + four_spans(); + + collector + }; + + let spans = block_on(collector.collect()); + + let expected_graph = r#" +root [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] +"#; + assert_eq!(tree_str_from_span_records(spans), expected_graph); +} + +#[test] +fn single_thread_multiple_spans() { + let (spans1, spans2, spans3) = { + let (c1, c2, c3) = { + let (root_span1, collector1) = Span::root("root1"); + let (root_span2, collector2) = Span::root("root2"); + let (root_span3, collector3) = Span::root("root3"); + + let local_collector = LocalCollector::start(); + + four_spans(); + + let local_spans = Arc::new(local_collector.collect()); + + root_span1.push_child_spans(local_spans.clone()); + root_span2.push_child_spans(local_spans.clone()); + root_span3.push_child_spans(local_spans); + + (collector1, collector2, collector3) + }; + + ( + block_on(c1.collect()), + block_on(c2.collect()), + block_on(c3.collect()), + ) + }; + + let expected_graph1 = r#" +root1 [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] +"#; + let expected_graph2 = r#" +root2 [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] +"#; + let expected_graph3 = r#" +root3 [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] +"#; + assert_eq!(tree_str_from_span_records(spans1), expected_graph1); + assert_eq!(tree_str_from_span_records(spans2), expected_graph2); + assert_eq!(tree_str_from_span_records(spans3), expected_graph3); +} + +#[test] +fn multiple_threads_single_span() { + let collector = crossbeam::scope(|scope| { + let (span, collector) = Span::root("root"); + let _g = span.set_local_parent(); + + for _ in 0..4 { + let child_span = Span::enter_with_local_parent("cross-thread"); + scope.spawn(move |_| { + let _g = child_span.set_local_parent(); + four_spans(); + }); + } + + four_spans(); + + collector + }) + .unwrap(); + + let spans = block_on(collector.collect()); + + let expected_graph = r#" +root [] + cross-thread [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] + cross-thread [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] + cross-thread [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] + cross-thread [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + rec-span [] + rec-span [] +"#; + assert_eq!(tree_str_from_span_records(spans), expected_graph); +} + +#[test] +fn multiple_threads_multiple_spans() { + let (spans1, spans2) = { + let (c1, c2) = crossbeam::scope(|scope| { + let (root_span1, collector1) = Span::root("root1"); + let (root_span2, collector2) = Span::root("root2"); + let local_collector = LocalCollector::start(); + + for _ in 0..4 { + let merged = + Span::enter_with_parents("merged", vec![&root_span1, &root_span2].into_iter()); + let _g = merged.set_local_parent(); + let _local = LocalSpan::enter_with_local_parent("local"); + scope.spawn(move |_| { + let local_collector = LocalCollector::start(); + + four_spans(); + + let local_spans = Arc::new(local_collector.collect()); + merged.push_child_spans(local_spans); + }); + } + + four_spans(); + + let local_spans = Arc::new(local_collector.collect()); + root_span1.push_child_spans(local_spans.clone()); + root_span2.push_child_spans(local_spans); + (collector1, collector2) + }) + .unwrap(); + + (block_on(c1.collect()), block_on(c2.collect())) + }; + + let expected_graph1 = r#" +root1 [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + merged [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + local [] + rec-span [] + rec-span [] + merged [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + local [] + rec-span [] + rec-span [] + merged [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + local [] + rec-span [] + rec-span [] + merged [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + local [] + rec-span [] + rec-span [] + rec-span [] + rec-span [] +"#; + let expected_graph2 = r#" +root2 [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + merged [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + local [] + rec-span [] + rec-span [] + merged [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + local [] + rec-span [] + rec-span [] + merged [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + local [] + rec-span [] + rec-span [] + merged [] + iter-span [("tmp_property", "tmp_value")] + iter-span [("tmp_property", "tmp_value")] + local [] + rec-span [] + rec-span [] + rec-span [] + rec-span [] +"#; + assert_eq!(tree_str_from_span_records(spans1), expected_graph1); + assert_eq!(tree_str_from_span_records(spans2), expected_graph2); +} + +#[test] +fn multiple_spans_without_local_spans() { + let (spans1, spans2) = { + let (c1, c2, c3) = { + let (root_span1, collector1) = Span::root("root1"); + let (root_span2, collector2) = Span::root("root2"); + let (root_span3, collector3) = Span::root("root3"); + + let local_collector = LocalCollector::start(); + + let local_spans = Arc::new(local_collector.collect()); + root_span1.push_child_spans(local_spans.clone()); + root_span2.push_child_spans(local_spans.clone()); + root_span3.push_child_spans(local_spans); + + (collector1, collector2, collector3) + }; + + drop(c3); + (block_on(c1.collect()), block_on(c2.collect())) + }; + + assert_eq!(spans1.len(), 1); + assert_eq!(spans2.len(), 1); +} + +#[test] +fn test_macro() { + use async_trait::async_trait; + + #[async_trait] + trait Foo { + async fn run(&self, millis: &u64); + } + + struct Bar; + + #[async_trait] + impl Foo for Bar { + #[trace(name = "run")] + async fn run(&self, millis: &u64) { + let _g = Span::enter_with_local_parent("run-inner"); + work(millis).await; + let _g = LocalSpan::enter_with_local_parent("local-span"); + } + } + + #[trace(name = "work", enter_on_poll = true)] + async fn work(millis: &u64) { + let _g = Span::enter_with_local_parent("work-inner"); + tokio::time::sleep(std::time::Duration::from_millis(*millis)) + .enter_on_poll("sleep") + .await; + } + + impl Bar { + #[trace(name = "work2")] + async fn work2(&self, millis: &u64) { + let _g = Span::enter_with_local_parent("work-inner"); + tokio::time::sleep(std::time::Duration::from_millis(*millis)) + .enter_on_poll("sleep") + .await; + } + } + + #[trace(name = "work3")] + async fn work3<'a>(millis1: &'a u64, millis2: &u64) { + let _g = Span::enter_with_local_parent("work-inner"); + tokio::time::sleep(std::time::Duration::from_millis(*millis1)) + .enter_on_poll("sleep") + .await; + tokio::time::sleep(std::time::Duration::from_millis(*millis2)) + .enter_on_poll("sleep") + .await; + } + + let collector = { + let (root, collector) = Span::root("root"); + let _g = root.set_local_parent(); + + let runtime = Builder::new_multi_thread() + .worker_threads(4) + .enable_all() + .build() + .unwrap(); + block_on(runtime.spawn(Bar.run(&100))).unwrap(); + block_on(runtime.spawn(Bar.work2(&100))).unwrap(); + block_on(runtime.spawn(work3(&100, &100))).unwrap(); + + collector + }; + + let spans = block_on(collector.collect()); + + let expected_graph = r#" +root [] + run [] + local-span [] + run-inner [] + work [] + sleep [] + work [] + sleep [] + work-inner [] + work2 [] + sleep [] + sleep [] + work-inner [] + work3 [] + sleep [] + sleep [] + sleep [] + sleep [] + work-inner [] +"#; + assert_eq!(tree_str_from_span_records(spans), expected_graph); +} + +#[test] +fn macro_example() { + #[trace(name = "do_something")] + fn do_something(i: u64) { + std::thread::sleep(std::time::Duration::from_millis(i)); + } + + #[trace(name = "do_something_async")] + async fn do_something_async(i: u64) { + futures_timer::Delay::new(std::time::Duration::from_millis(i)).await; + } + + let (root, collector) = Span::root("root"); + + { + let _g = root.set_local_parent(); + do_something(100); + block_on(do_something_async(100)); + } + + drop(root); + let spans = block_on(collector.collect()); + + let expected_graph = r#" +root [] + do_something [] + do_something_async [] +"#; + assert_eq!(tree_str_from_span_records(spans), expected_graph); +} + +#[test] +fn multiple_local_parent() { + let collector = { + let (root, collector) = Span::root("root"); + let _g = root.set_local_parent(); + let _g = LocalSpan::enter_with_local_parent("span1"); + let span2 = Span::enter_with_local_parent("span2"); + { + let _g = span2.set_local_parent(); + let _g = LocalSpan::enter_with_local_parent("span3"); + } + let _g = LocalSpan::enter_with_local_parent("span4"); + + collector + }; + + let spans = block_on(collector.collect()); + + let expected_graph = r#" +root [] + span1 [] + span2 [] + span3 [] + span4 [] +"#; + assert_eq!(tree_str_from_span_records(spans), expected_graph); +} + +#[test] +fn early_local_collect() { + let local_collector = LocalCollector::start(); + let _g1 = LocalSpan::enter_with_local_parent("span1"); + let _g2 = LocalSpan::enter_with_local_parent("span2"); + drop(_g2); + let local_spans = Arc::new(local_collector.collect()); + + let (root, collector) = Span::root("root"); + root.push_child_spans(local_spans); + drop(root); + + let spans = block_on(collector.collect()); + + let expected_graph = r#" +root [] + span1 [] + span2 [] +"#; + assert_eq!(tree_str_from_span_records(spans), expected_graph); +} + +#[test] +fn max_span_count() { + fn block_until_next_collect_loop() { + let (_, collector) = Span::root("dummy"); + block_on(collector.collect()); + } + + #[trace(name = "recursive")] + fn recursive(n: usize) { + if n > 1 { + recursive(n - 1); + } + } + + let collector = { + let (root, collector) = + Span::root_with_args("root", CollectArgs::default().max_span_count(Some(5))); + + { + let _g = root.set_local_parent(); + recursive(3); + } + block_until_next_collect_loop(); + { + let _g = root.set_local_parent(); + recursive(3); + } + { + let _g = root.set_local_parent(); + recursive(3); + } + block_until_next_collect_loop(); + { + let _g = root.set_local_parent(); + recursive(3); + } + + collector + }; + + let spans = block_on(collector.collect()); + + let expected_graph = r#" +root [] + recursive [] + recursive [] + recursive [] + recursive [] + recursive [] + recursive [] +"#; + assert_eq!(tree_str_from_span_records(spans), expected_graph); +} diff --git a/minitrace-opentelemetry/Cargo.toml b/minitrace-opentelemetry/Cargo.toml index 1359ce39..d169b1f4 100644 --- a/minitrace-opentelemetry/Cargo.toml +++ b/minitrace-opentelemetry/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["tracing", "span", "datadog", "jaeger", "opentelemetry"] [dependencies] futures = { version = "0.3", features = ["executor"] } log = "0.4" -minitrace = { path = "../minitrace" } +minitrace = { path = "../" } opentelemetry = { version = "0.19", features = ["trace"] } [dev-dependencies] diff --git a/minitrace-tests/Cargo.toml b/minitrace-tests/Cargo.toml new file mode 100644 index 00000000..fca2ee8d --- /dev/null +++ b/minitrace-tests/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "minitrace-tests" +version = "0.5.1" +authors = ["Mark Van de Vyver "] +description = "Integration tests for Minitrace development" +license = "MIT OR Apache-2.0" +rust-version = "1.56.0" +edition = "2021" + +[features] +default = ["minitrace/enable"] +ci = [] +as = ["async-std"] +tk = ["tokio"] + +[lib] + +# Avoid adding crates, this is widely used in tests. It should compile fast! +[dependencies] +async-std = { version = "1.11.0", features = ["attributes"], optional = true } +futures = "0.3.21" +futures-timer = "3.0.2" +macrotest = "1" +minitrace = { version = "0.5.1", path = "../", features = ["enable"]} +test-utilities = { path = "../test-utilities" } +tokio = { version = "1.17.0", features = ["full"], optional = true } +trybuild = "1" + +[dev-dependencies] +tokio = { version = "1.17.0", features = ["full"] } +inventory = "0.2.2" + +# NOTE: +# Features are only recognized via the command line in the following tests. +# +# cargo test --features minitrace-tests/tk --manifest-path minitrace-tests/Cargo.toml indev-tokio -- --nocapture +# +[[test]] +name = "indev-tokio" +path = "integration/indev.rs" +harness = false +required-features = ["default", "tk"] + +# [[test]] +# name = "dev-tokio" +# path = "integration/main.rs" +# harness = false +# required-features = ["tk"] diff --git a/minitrace-tests/README.md b/minitrace-tests/README.md new file mode 100644 index 00000000..5e9bed90 --- /dev/null +++ b/minitrace-tests/README.md @@ -0,0 +1,94 @@ +# minitrace-tests + +Minitrace attribute `#[trace]` integration tests for developers and +CI environments. + +NOTE: +Cargo virtual manifests do not support the `[features]` stanza (without also +having the `[package stanza]`). See [Cargo issue 4942](https://github.com/rust-lang/cargo/issues/4942). +There are several Cargo issues around features that mean it is a +fraught exercise to change this (working) test harness - here be dragons. + +We aren't the first to bang our heads on this: + +*** +Thanks to the following for [how to do this](https://www.infinyon.com/blog/2021/04/rust-custom-test-harness/) + +- [Infinyon](https://www.infinyon.com) . +- [Fluvio project](https://github.com/infinyon/fluvio) + +*** + +## Developing against a test + +1. Add the test file, say `tests/issues/nnn.rs`. +2. Point the in-development test runner to this file: + + ```rust + // minitrace-tests/src/tests/issues.rs + pub fn indev() { + // To generate macro result files + macrotest::expand("src/tests/issues/nnn.rs"); + build_indev(); + } + ``` + +3. Implement the required logic. +4. Run the single `indev` test case: + + ```bash + cargo test --features "default minitrace-tests/tk" --manifest-path minitrace-tests/Cargo.toml \ + indev-tokio \ + -- --nocapture + ``` + + or + + ```bash + cd minitrace-tests + cargo test build::issues::indev -- --nocapture + ``` + +5. Iterate 3) and 4) until green. + +## Adding a test + +Scenario: +Add several test cases (developed as above) in the issues category +`minitrace-tests/src/tests/issues` + +1. Add the test file, say `build/issues/nnn.rs`. +2. Generate the expanded Rust code: + + ```bash + cargo test issues-dev-tokio --no-fail-fast &>~/tmp/log.txt + ``` + +3. Check the expanded code is as expected: `build/issues/nnn.expanded.rs` +4. Check the log results show the build was successful. +5. commit and push. + +## Test suit execution by environment + +Scenario: +Run test suite for the issues category `minitrace-tests/src/tests/issues` + +To run developer, but not CI-scoped integration tests: + +```bash +cargo test issues-dev-tokio --no-fail-fast +``` + +To run the developer and the CI-scoped tests: + +```bash +cargo test issues-ci-tokio \ + --features ci \ + --no-fail-fast +``` + +## CI/remote test execution + +```bash +cargo test --workspace --all-features --no-fail-fast +``` diff --git a/minitrace-tests/integration/indev.rs b/minitrace-tests/integration/indev.rs new file mode 100644 index 00000000..9ca21414 --- /dev/null +++ b/minitrace-tests/integration/indev.rs @@ -0,0 +1,28 @@ +pub mod tests; + +use tests::IntegrationTest; + +fn setup() { + println!("Setup") +} + +fn teardown() { + println!("Teardown") +} +// NOTE: This function is executed by `cargo test -- --list`. +// Hence we guard it: +#[cfg(any(feature = "as", feature = "tk"))] +fn main() { + // Setup test environment + setup(); + + // Run the tests + for t in inventory::iter:: { + if let Some(category) = t.indev { + (t.test_fn)() + } + } + + // Teardown test environment + teardown(); +} diff --git a/minitrace-tests/integration/main.rs b/minitrace-tests/integration/main.rs new file mode 100644 index 00000000..11f9cec9 --- /dev/null +++ b/minitrace-tests/integration/main.rs @@ -0,0 +1,27 @@ +pub mod tests; + +use tests::IntegrationTest; + +fn setup() { + println!("Setup") +} + +fn teardown() { + println!("Teardown") +} +// NOTE: +// When this code is in src/main.rs, it is executed by `cargo test -- --list`. +// In such cases you can guard it: +// #[cfg(any(feature = "as", feature = "tk"))] +fn main() { + // Setup test environment + setup(); + + // Run the tests + for t in inventory::iter:: { + (t.test_fn)() + } + + // Teardown test environment + teardown(); +} diff --git a/minitrace-tests/integration/tests/defaults.rs b/minitrace-tests/integration/tests/defaults.rs new file mode 100644 index 00000000..4beb0d95 --- /dev/null +++ b/minitrace-tests/integration/tests/defaults.rs @@ -0,0 +1,75 @@ +// Function naming convention: `()` +// Where: +// - `environment`: +// - `indev`: When iterating on a single test case; edit the function to +// point to the test case in development. +// - `dev`: Generate all missing `*.expanded.rs` files, and flag changes. +// - `ci`: Generate nothing, and fail on mismatches. +// +use crate::tests::*; //IntegrationTest; +use super::IntegrationTest; + +inventory::submit!(IntegrationTest { + name: "indev", + test_fn: indev, + indev: Some(true), +}); + +#[cfg(not(feature = "ci"))] +pub fn indev() { + // To generate macro result files + let src = "integration/tests/defaults/no-be-no-drop-local.rs"; + let srcx = "integration/tests/defaults/no-be-no-drop-local.expanded.rs"; + + #[cfg(feature = "as")] + macrotest::expand_args( + src, + &[ + "--features", "minitrace-tests/default minitrace-tests/as", + "--manifest-path", + "./Cargo.toml", + ], + ); + #[cfg(feature = "tk")] + macrotest::expand_args( + src, + &[ + "--features", + "minitrace-tests/default minitrace-tests/tk", + "--manifest-path", + "./Cargo.toml", + ], + ); + + build_indev(srcx); +} + +fn build_indev(src: &str) { + let t = trybuild::TestCases::new(); + t.pass(src); +} + +#[cfg(not(feature = "ci"))] +pub fn dev() { + // To generate macro result files + macrotest::expand("integration/tests/defaults/*.rs"); + build_dev(); +} + +fn build_dev() { + let t = trybuild::TestCases::new(); + t.pass("integration/tests/defaults/*.expanded.rs"); +} + +// #[test] +#[cfg(feature = "ci")] +pub fn ci() { + // To test generated macro result files + macrotest::expand_without_refresh("src/tests/expand/defaults/*.rs"); + build_ci(); +} + +fn build_ci() { + let t = trybuild::TestCases::new(); + t.pass("scr/tests/expand/defaults/*.expanded.rs"); +} diff --git a/minitrace-tests/integration/tests/defaults/Cargo.toml b/minitrace-tests/integration/tests/defaults/Cargo.toml new file mode 100644 index 00000000..a4ef7a76 --- /dev/null +++ b/minitrace-tests/integration/tests/defaults/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "random-thingy" +version = "0.0.0" +authors = ["Mark Van de Vyver "] +description = "Integration tests for Minitrace development" +license = "MIT OR Apache-2.0" +rust-version = "1.56.0" +edition = "2021" + +[features] +ci = [] +as = ["minintrace-tests/as"] +ea = ["minintrace-tests/ea"] +tk = ["minintrace-tests/tk"] + +# [lib] +# doctest = false + +# Avoid adding crates, this is widely used in tests. It should compile fast! +[dependencies] +minitrace = { path = "../../" } diff --git a/minitrace-tests/integration/tests/defaults/no-be-no-drop-local.expanded.rs b/minitrace-tests/integration/tests/defaults/no-be-no-drop-local.expanded.rs new file mode 100644 index 00000000..b8f5835a --- /dev/null +++ b/minitrace-tests/integration/tests/defaults/no-be-no-drop-local.expanded.rs @@ -0,0 +1,36 @@ +extern crate alloc; +use minitrace::trace; +use minitrace::prelude::*; +use test_utilities::*; +#[trace] +async fn test_async(a: u32) -> u32 { + a +} +#[trace] +fn test_sync(a: u32) -> u32 { + a +} +#[tokio::main] +async fn main() { + let (reporter, records) = minitrace::collector::TestReporter::new(); + minitrace::set_reporter(reporter, minitrace::collector::Config::default()); + let root = Span::root("root", SpanContext::random()); + let _child_span = root.set_local_parent(); + let mut handles = ::alloc::vec::Vec::new(); + handles.push(tokio::spawn(test_async(1))); + test_sync(2); + futures::future::join_all(handles).await; + drop(root); + let _expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let _actual = normalize_spans(records.lock().clone()); + (); +} diff --git a/minitrace-tests/integration/tests/defaults/no-be-no-drop-local.rs b/minitrace-tests/integration/tests/defaults/no-be-no-drop-local.rs new file mode 100644 index 00000000..d7cc1162 --- /dev/null +++ b/minitrace-tests/integration/tests/defaults/no-be-no-drop-local.rs @@ -0,0 +1,48 @@ +extern crate alloc; +use minitrace::trace; +use minitrace::prelude::*; +use test_utilities::*; + +// With default (`enter_on_poll = false`), `async` functions construct +// `Span` that is thread safe. +// +// With no block expression the child span is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace] +async fn test_async(a: u32) -> u32 { + a +} + +#[trace] +fn test_sync(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (reporter, records) = minitrace::collector::TestReporter::new(); + minitrace::set_reporter(reporter, minitrace::collector::Config::default()); + let root = Span::root("root", SpanContext::random()); + let _child_span = root.set_local_parent(); + let mut handles = vec![]; + + handles.push(tokio::spawn(test_async(1))); + test_sync(2); + + futures::future::join_all(handles).await; + drop(root); + let _expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let _actual = normalize_spans(records.lock().clone()); + assert_eq_text!(_expected, &_actual); +} diff --git a/minitrace-tests/integration/tests/defaults/no-be-no-drop-threads.rs b/minitrace-tests/integration/tests/defaults/no-be-no-drop-threads.rs new file mode 100644 index 00000000..59c8f6e2 --- /dev/null +++ b/minitrace-tests/integration/tests/defaults/no-be-no-drop-threads.rs @@ -0,0 +1,57 @@ +use minitrace::trace; +use test_utilities::*; + +// With default (`enter_on_poll = false`), `async` functions construct ` +// Span` that is thread safe. + +// With no block expression the span "test-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace] +async fn test_async(a: u32) -> u32 { + a +} + +#[trace] +fn test_sync(a: u32) -> u32 { + a +} + +#[tokio::main] +//#[trace( name = "start", root=true, reporter=None)] +// reporter: Datadog, Jaeger, None (default) +fn main() { + //let minitrace = minitrace::Trace::new("name", Local ) + let (root, collector) = minitrace::Span::root("start"); + //let child_span = minitrace.new("test-span", &root); + let child_span = minitrace::Span::enter_with_parent("test-span", &root); + + let mut handles = vec![]; + handles.push(tokio::spawn(test_async(1).await)); + test_sync(2); + //minitrace.record("key", "Value"); + + futures::future::join_all(handles).await; + + //drop(minitrace.spans); + //} + //drop(child_span); + drop(root); + // let records: minitrace.collect() + let records: Vec = + futures::executor::block_on(collector.collect()); + // let minitrace.report() // when reporter is not None (default) + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-tests/integration/tests/issues.rs b/minitrace-tests/integration/tests/issues.rs new file mode 100644 index 00000000..db7c45cd --- /dev/null +++ b/minitrace-tests/integration/tests/issues.rs @@ -0,0 +1,46 @@ +// Function naming convention: `()` +// Where: +// - `environment`: +// - `indev`(in-development): When iterating on a single test case, +// edit the function to point to the test case in-development. +// - `dev`: Generate all missing `*.expanded.rs` files, and flag changes. +// - `ci`: Generate nothing, and fail on mismatches. +// +#[test] +#[cfg(not(feature = "ci"))] +pub fn indev() { + // To generate macro result files + macrotest::expand("integration/tests/issues/non-drop-local.rs"); + build_indev(); +} + +fn build_indev() { + let t = trybuild::TestCases::new(); + t.pass("integration/tests/issues/*.expanded.rs"); +} + +#[test] +#[cfg(not(feature = "ci"))] +pub fn dev() { + // To generate macro result files + macrotest::expand("integration/tests/issues/*.rs"); + build_dev(); +} + +fn build_dev() { + let t = trybuild::TestCases::new(); + t.pass("src/build/issues/*.expanded.rs"); +} + +#[test] +#[cfg(feature = "ci")] +pub fn ci() { + // To test generated macro result files + macrotest::expand_without_refresh("tests/expand/issues/*.rs"); + build_ci(); +} + +fn build_ci() { + let t = trybuild::TestCases::new(); + t.pass("tests/expand/issues/*.expanded.rs"); +} diff --git a/minitrace-tests/integration/tests/issues/129.rs b/minitrace-tests/integration/tests/issues/129.rs new file mode 100644 index 00000000..68933076 --- /dev/null +++ b/minitrace-tests/integration/tests/issues/129.rs @@ -0,0 +1,26 @@ +use minitrace::trace; + +#[trace] +async fn test_async() {} + +#[trace] +fn test_sync() {} + +#[cfg_attr(feature = "tk", tokio::main)] +#[cfg_attr(not(feature = "tk"), async_std::main)] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _g = root.set_local_parent(); + test_async(1).await; + test_sync(); + } + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + // Enforce future send + // tokio::Runtime::spawn(test_async().await); + test_async().await; + + test_sync(); +} \ No newline at end of file diff --git a/minitrace-tests/integration/tests/issues/141.rs b/minitrace-tests/integration/tests/issues/141.rs new file mode 100644 index 00000000..22de4856 --- /dev/null +++ b/minitrace-tests/integration/tests/issues/141.rs @@ -0,0 +1,29 @@ +// Move this test to the regression suite when issue #141 is resolved. +// +// Note this integration test for issue #141, which will move to the regression +// suite when resolved, is blocked by issue #137. In turn issue #137 is blocked +// by [macrotest issue 74](https://github.com/eupn/macrotest/issues/74). This in +// turn appears to be due to [Cargo issue +// #4942](https://github.com/rust-lang/cargo/issues/4942). Consequently, +// depending on whether Cargo resolve this issue or declare it a 'feature' it is +// possible that the workaround described +// [here](https://github.com/rust-lang/cargo/issues/4942#issuecomment-357729844) +// could be a fix. +// +// If that is not a fix, the next step is to reorganise the workspace from +// 'virtual' to 'real' - which requires moving sources around.... +use minitrace::trace; + +#[trace] +fn f() {} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + { + let _g = root.set_local_parent(); + f(); + } + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); +} diff --git a/minitrace-tests/integration/tests/mod.rs b/minitrace-tests/integration/tests/mod.rs new file mode 100644 index 00000000..144ba0e7 --- /dev/null +++ b/minitrace-tests/integration/tests/mod.rs @@ -0,0 +1,23 @@ +#[macro_export] +pub mod defaults; + +#[derive(Debug)] +pub struct IntegrationTest { + pub name: &'static str, + pub test_fn: fn(), + pub indev: Option, +} + +inventory::collect!(IntegrationTest); + +// #[cfg(feature = "tk")] +// #[cfg_attr(feature = "tk", macro_export)] +// macro_rules! main_runtime2 { +// () => { +// // tokio runtime 2 here +// }; +// ( $( $x:expr ),+ ) => {{ +// $x +// // tokio runtime 2 again +// }}; +// } diff --git a/minitrace-tests/integration/tests/options.rs b/minitrace-tests/integration/tests/options.rs new file mode 100644 index 00000000..5cd02fb0 --- /dev/null +++ b/minitrace-tests/integration/tests/options.rs @@ -0,0 +1,8 @@ +// Function naming convention: `_()` +// Where: +// - `environment`: +// - `indev`: When iterating on a single test case, you are expected to +// edit the funrtion to point to the test case in development. +// - `dev`: Generate all missing `*.expanded.rs` files, and flag changes. +// - `ci`: Generate nothing, and fail on mismatches. +// \ No newline at end of file diff --git a/minitrace-tests/integration/tests/options/name-threads.rs b/minitrace-tests/integration/tests/options/name-threads.rs new file mode 100644 index 00000000..9ba0ab40 --- /dev/null +++ b/minitrace-tests/integration/tests/options/name-threads.rs @@ -0,0 +1,33 @@ +use minitrace::trace; +use test_utilities::*; + +// With no block expression the span "test-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 + + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = minitrace::Span::enter_with_parent("test-span", &root); + f(1).await; + //} + drop(child_span); //This is required when not using `{ ... }` + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-tests/integration/tests/options/no-be-local-async-enter-false.rs b/minitrace-tests/integration/tests/options/no-be-local-async-enter-false.rs new file mode 100644 index 00000000..ce64ff51 --- /dev/null +++ b/minitrace-tests/integration/tests/options/no-be-local-async-enter-false.rs @@ -0,0 +1,46 @@ +use minitrace::trace; +use test_utilities::*; +// With `enter_on_poll = false`, `async` functions construct `Span` that is +// thread safe. +// +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span", enter_on_poll=false)] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = root.set_local_parent(); + f(1).await; + //} + drop(child_span); + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, + SpanRecord { + id: 2, + parent_id: 1, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "a-span", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-tests/integration/tests/options/no-be-local-async-enter-true.rs b/minitrace-tests/integration/tests/options/no-be-local-async-enter-true.rs new file mode 100644 index 00000000..53432ece --- /dev/null +++ b/minitrace-tests/integration/tests/options/no-be-local-async-enter-true.rs @@ -0,0 +1,38 @@ +use minitrace::trace; +use test_utilities::*; +// With `enter_on_poll = true`, `async` functions construct `LocalSpan`. +// Hence this async test produces the same spans as the sync test. +// +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span", enter_on_poll=true)] +async fn f(a: u32) -> u32 { + a +} + +#[tokio::main] +async fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = root.set_local_parent(); + f(1).await; + //} + drop(child_span); + drop(root); + let records: Vec = futures::executor::block_on(collector.collect()); + + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-tests/integration/tests/options/no-be-local-sync.rs b/minitrace-tests/integration/tests/options/no-be-local-sync.rs new file mode 100644 index 00000000..f51d89be --- /dev/null +++ b/minitrace-tests/integration/tests/options/no-be-local-sync.rs @@ -0,0 +1,35 @@ +use minitrace::trace; +use test_utilities::*; + +// With no block expression the span "a-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace(name = "a-span")] +fn f(a: u32) -> u32 { + a +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + //{ + let child_span = root.set_local_parent(); + f(1); + //} + drop(child_span); + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-tests/integration/tests/options/threads-sync.rs b/minitrace-tests/integration/tests/options/threads-sync.rs new file mode 100644 index 00000000..e60d3faa --- /dev/null +++ b/minitrace-tests/integration/tests/options/threads-sync.rs @@ -0,0 +1,40 @@ +use minitrace::trace; +use test_utilities::*; + +// With no block expression the span "test-span" is silently omitted. +// Reference: +// - https://github.com/tikv/minitrace-rust/issues/125 +// - https://github.com/tikv/minitrace-rust/issues/126 +#[trace( name = "a-span")] +async fn test_async(a: u32) -> u32 { + a +} + +#[trace( name = "s-span")] +fn test_sync(a: u32) -> u32 { + a +} + +fn main() { + let (root, collector) = minitrace::Span::root("root"); + + let child_span = minitrace::Span::enter_with_parent("test-span", &root); + f(1); + + drop(child_span); + drop(root); + let records: Vec = + futures::executor::block_on(collector.collect()); + let expected = r#"[ + SpanRecord { + id: 1, + parent_id: 0, + begin_unix_time_ns: \d+, + duration_ns: \d+, + event: "root", + properties: [], + }, +]"#; + let actual = normalize_spans(records); + assert_eq_text!(expected, &actual); +} diff --git a/minitrace-tests/src/lib.rs b/minitrace-tests/src/lib.rs new file mode 100644 index 00000000..0019329d --- /dev/null +++ b/minitrace-tests/src/lib.rs @@ -0,0 +1,21 @@ +#[macro_export] +macro_rules! main_tokio { + () => { + // tokio main here + }; + ( $( $x:expr ),+ ) => {{ + $x + // tokio main again + }}; +} +#[cfg(feature = "tk")] +#[cfg_attr(feature = "tk", macro_export)] +macro_rules! main_runtime { + () => { + // tokio runtime here + }; + ( $( $x:expr ),+ ) => {{ + $x + // tokio runtime again + }}; +} diff --git a/minitrace/Cargo.toml b/minitrace/Cargo.toml deleted file mode 100644 index 7bfb9c27..00000000 --- a/minitrace/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -name = "minitrace" -version = "0.5.1" -authors = ["The TiKV Project Authors"] -license = "Apache-2.0" -edition = "2021" -description = "A high-performance timeline tracing library for Rust" -homepage = "https://github.com/tikv/minitrace-rust" -repository = "https://github.com/tikv/minitrace-rust" -documentation = "https://docs.rs/minitrace" -readme = "../README.md" -categories = ["development-tools::debugging"] -keywords = ["tracing", "span", "datadog", "jaeger", "opentelemetry"] - -[features] -enable = [] - -[dependencies] -futures = "0.3" -minitrace-macro = { version = "0.5.1", path = "../minitrace-macro" } -minstant = "0.1" -parking_lot = "0.12" -pin-project = "1" -# TODO: Remove once_cell once #![feature(once_cell)] is stabilized -once_cell = "1" -rand = "0.8" - -[dev-dependencies] -# The procedural macro `trace` only supports async-trait higher than 0.1.52 -async-trait = "0.1.52" -criterion = { version = "0.4", features = ["html_reports"] } -crossbeam = "0.8" -env_logger = "0.10" -futures = "0.3" -futures-timer = "3" -log = "0.4" -logcall = "0.1.4" -minitrace = { path = ".", features = ["enable"] } -minitrace-datadog = { version = "0.5.1", path = "../minitrace-datadog" } -minitrace-jaeger = { version = "0.5.1", path = "../minitrace-jaeger" } -minitrace-opentelemetry = { version = "0.5.1", path = "../minitrace-opentelemetry" } -mockall = "0.11" -once_cell = "1" -opentelemetry = { version = "0.19", features = ["trace"] } -opentelemetry-otlp = { version = "0.12", features = ["trace"] } -rand = "0.8" -rustracing = "0.6" -serial_test = "2" -test-harness = "0.1.1" -tokio = { version = "1", features = ["rt", "time", "macros"] } -tracing = "0.1" -tracing-core = "0.1" -tracing-opentelemetry = "0.18" -tracing-subscriber = "0.3" - -[[bench]] -name = "trace" -harness = false - -[[bench]] -name = "compare" -harness = false - -[[bench]] -name = "spsc" -harness = false - -[[bench]] -name = "object_pool" -harness = false diff --git a/result.txt b/result.txt new file mode 100644 index 00000000..5cb81afc --- /dev/null +++ b/result.txt @@ -0,0 +1,21 @@ +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.55s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.17s +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.60s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s +test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 11.28s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 179.30s +test result: ok. 31 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 367.24s +test result: ok. 0 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.49s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 3 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 3.49s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s +test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 27.14s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.50s +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.17s +test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.18s +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s diff --git a/rustfmt.toml b/rustfmt.toml index 757638be..60f47031 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -8,3 +8,6 @@ trailing_comma = "Vertical" overflow_delimited_expr = true format_code_in_doc_comments = true normalize_comments = true +ignore = [ + "minitrace-tests/embassy/*", +] diff --git a/src/collector/command.rs b/src/collector/command.rs new file mode 100644 index 00000000..864bf657 --- /dev/null +++ b/src/collector/command.rs @@ -0,0 +1,33 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +use crate::collector::SpanSet; +use crate::util::CollectToken; + +#[derive(Debug)] +pub enum CollectCommand { + StartCollect(StartCollect), + DropCollect(DropCollect), + CommitCollect(CommitCollect), + SubmitSpans(SubmitSpans), +} + +#[derive(Debug)] +pub struct StartCollect { + pub collect_id: usize, +} + +#[derive(Debug)] +pub struct DropCollect { + pub collect_id: usize, +} + +#[derive(Debug)] +pub struct CommitCollect { + pub collect_id: usize, +} + +#[derive(Debug)] +pub struct SubmitSpans { + pub spans: SpanSet, + pub collect_token: CollectToken, +} diff --git a/src/collector/console_reporter.rs b/src/collector/console_reporter.rs new file mode 100644 index 00000000..71623b4c --- /dev/null +++ b/src/collector/console_reporter.rs @@ -0,0 +1,15 @@ +// Copyright 2023 TiKV Project Authors. Licensed under Apache-2.0. + +use super::global_collector::Reporter; +use super::SpanRecord; + +/// A console reporter that prints span records to the stderr. +pub struct ConsoleReporter; + +impl Reporter for ConsoleReporter { + fn report(&mut self, spans: &[SpanRecord]) { + for span in spans { + eprintln!("{:#?}", span); + } + } +} diff --git a/src/collector/global_collector.rs b/src/collector/global_collector.rs new file mode 100644 index 00000000..fdb34fb1 --- /dev/null +++ b/src/collector/global_collector.rs @@ -0,0 +1,519 @@ +// Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. + +use std::collections::HashMap; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::time::Duration; + +use minstant::Anchor; +use once_cell::sync::Lazy; +use parking_lot::Mutex; + +use super::EventRecord; +use crate::collector::command::CollectCommand; +use crate::collector::command::CommitCollect; +use crate::collector::command::DropCollect; +use crate::collector::command::StartCollect; +use crate::collector::command::SubmitSpans; +use crate::collector::Config; +use crate::collector::SpanId; +use crate::collector::SpanRecord; +use crate::collector::SpanSet; +use crate::collector::TraceId; +use crate::local::local_collector::LocalSpansInner; +use crate::local::raw_span::RawSpan; +use crate::util::spsc::Receiver; +use crate::util::spsc::Sender; +use crate::util::spsc::{self}; +use crate::util::CollectToken; + +const COLLECT_LOOP_INTERVAL: Duration = Duration::from_millis(50); + +static NEXT_COLLECT_ID: AtomicUsize = AtomicUsize::new(0); +static GLOBAL_COLLECTOR: Lazy> = + Lazy::new(|| Mutex::new(GlobalCollector::start())); +static SPSC_RXS: Lazy>>> = Lazy::new(|| Mutex::new(Vec::new())); +static REPORTER_READY: AtomicBool = AtomicBool::new(false); + +thread_local! { + static COMMAND_SENDER: Sender = { + let (tx, rx) = spsc::bounded(10240); + register_receiver(rx); + tx + }; +} + +fn register_receiver(rx: Receiver) { + SPSC_RXS.lock().push(rx); +} + +fn send_command(cmd: CollectCommand) { + COMMAND_SENDER.try_with(|sender| sender.send(cmd).ok()).ok(); +} + +fn force_send_command(cmd: CollectCommand) { + COMMAND_SENDER + .try_with(|sender| sender.force_send(cmd)) + .ok(); +} + +/// Sets the reporter and its configuration for the current application. +/// +/// # Examples +/// +/// ``` +/// use minitrace::collector::Config; +/// use minitrace::collector::ConsoleReporter; +/// +/// minitrace::set_reporter(ConsoleReporter, Config::default()); +/// ``` +pub fn set_reporter(reporter: impl Reporter, config: Config) { + #[cfg(feature = "enable")] + { + let mut global_collector = GLOBAL_COLLECTOR.lock(); + global_collector.config = config; + global_collector.reporter = Some(Box::new(reporter)); + REPORTER_READY.store(true, Ordering::Relaxed); + } +} + +pub(crate) fn reporter_ready() -> bool { + REPORTER_READY.load(Ordering::Relaxed) +} + +/// Flushes all pending span records to the reporter immediately. +pub fn flush() { + #[cfg(feature = "enable")] + { + // Spawns a new thread to ensure the reporter operates outside the tokio runtime to prevent panic. + std::thread::Builder::new() + .name("minitrace-flush".to_string()) + .spawn(move || { + let mut global_collector = GLOBAL_COLLECTOR.lock(); + global_collector.handle_commands(true); + }) + .unwrap() + .join() + .unwrap(); + } +} + +/// A trait defining the behavior of a reporter. A reporter is responsible for +/// handling span records, typically by sending them to a remote service for +/// further processing and analysis. +pub trait Reporter: Send + 'static { + /// Reports a batch of spans to a remote service. + fn report(&mut self, spans: &[SpanRecord]); +} + +#[derive(Default, Clone)] +pub(crate) struct GlobalCollect; + +#[cfg_attr(test, mockall::automock)] +impl GlobalCollect { + pub fn start_collect(&self) -> usize { + let collect_id = NEXT_COLLECT_ID.fetch_add(1, Ordering::Relaxed); + send_command(CollectCommand::StartCollect(StartCollect { collect_id })); + collect_id + } + + pub fn commit_collect(&self, collect_id: usize) { + force_send_command(CollectCommand::CommitCollect(CommitCollect { collect_id })); + } + + pub fn drop_collect(&self, collect_id: usize) { + force_send_command(CollectCommand::DropCollect(DropCollect { collect_id })); + } + + // Note that: relationships are not built completely for now so a further job is needed. + // + // Every `SpanSet` has its own root spans whose `raw_span.parent_id`s are equal to `SpanId::default()`. + // + // Every root span can have multiple parents where mainly comes from `Span::enter_with_parents`. + // Those parents are recorded into `CollectToken` which has several `CollectTokenItem`s. Look into + // a `CollectTokenItem`, `parent_ids` can be found. + // + // For example, we have a `SpanSet::LocalSpansInner` and a `CollectToken` as follow: + // + // SpanSet::LocalSpansInner::spans CollectToken::parent_ids + // +------+-----------+-----+ +------------+------------+ + // | id | parent_id | ... | | collect_id | parent_ids | + // +------+-----------+-----+ +------------+------------+ + // | 43 | 545 | ... | | 1212 | 7 | + // | 15 | default | ... | <- root span | 874 | 321 | + // | 545 | 15 | ... | | 915 | 413 | + // | 70 | default | ... | <- root span +------------+------------+ + // +------+-----------+-----+ + // + // There is a many-to-many mapping. Span#15 has parents Span#7, Span#321 and Span#413, so does Span#70. + // + // So the expected further job mentioned above is: + // * Copy `SpanSet` to the same number of copies as `CollectTokenItem`s, one `SpanSet` to one + // `CollectTokenItem` + // * Amend `raw_span.parent_id` of root spans in `SpanSet` to `parent_ids` of `CollectTokenItem` + pub fn submit_spans(&self, spans: SpanSet, collect_token: CollectToken) { + send_command(CollectCommand::SubmitSpans(SubmitSpans { + spans, + collect_token, + })); + } +} + +enum SpanCollection { + Owned { + spans: SpanSet, + trace_id: TraceId, + parent_id: SpanId, + }, + Shared { + spans: Arc, + trace_id: TraceId, + parent_id: SpanId, + }, +} + +pub(crate) struct GlobalCollector { + config: Config, + reporter: Option>, + + active_collectors: HashMap, usize)>, + committed_records: Vec, + last_report: std::time::Instant, + + // Vectors to be reused by collection loops. They must be empty outside of the `handle_commands` loop. + start_collects: Vec, + drop_collects: Vec, + commit_collects: Vec, + submit_spans: Vec, + dangling_events: HashMap>, +} + +impl GlobalCollector { + #[allow(unreachable_code)] + fn start() -> Self { + #[cfg(not(feature = "enable"))] + { + unreachable!( + "Global collector should not be invoked because feature \"enable\" is not set." + ) + } + + std::thread::Builder::new() + .name("minitrace-global-collector".to_string()) + .spawn(move || { + loop { + let begin_instant = std::time::Instant::now(); + GLOBAL_COLLECTOR.lock().handle_commands(false); + std::thread::sleep( + COLLECT_LOOP_INTERVAL.saturating_sub(begin_instant.elapsed()), + ); + } + }) + .unwrap(); + + GlobalCollector { + config: Config::default().max_spans_per_trace(Some(0)), + reporter: None, + + active_collectors: HashMap::new(), + committed_records: Vec::new(), + last_report: std::time::Instant::now(), + + start_collects: Vec::new(), + drop_collects: Vec::new(), + commit_collects: Vec::new(), + submit_spans: Vec::new(), + dangling_events: HashMap::new(), + } + } + + fn handle_commands(&mut self, flush: bool) { + debug_assert!(self.start_collects.is_empty()); + debug_assert!(self.drop_collects.is_empty()); + debug_assert!(self.commit_collects.is_empty()); + debug_assert!(self.submit_spans.is_empty()); + debug_assert!(self.dangling_events.is_empty()); + + let start_collects = &mut self.start_collects; + let drop_collects = &mut self.drop_collects; + let commit_collects = &mut self.commit_collects; + let submit_spans = &mut self.submit_spans; + let committed_records = &mut self.committed_records; + + { + SPSC_RXS.lock().retain_mut(|rx| { + loop { + match rx.try_recv() { + Ok(Some(CollectCommand::StartCollect(cmd))) => start_collects.push(cmd), + Ok(Some(CollectCommand::DropCollect(cmd))) => drop_collects.push(cmd), + Ok(Some(CollectCommand::CommitCollect(cmd))) => commit_collects.push(cmd), + Ok(Some(CollectCommand::SubmitSpans(cmd))) => submit_spans.push(cmd), + Ok(None) => { + return true; + } + Err(_) => { + // Channel disconnected. It must be because the sender thread has stopped. + return false; + } + } + } + }); + } + + // If the reporter is not set, global collectior only clears the channel and then dismiss all messages. + if self.reporter.is_none() { + start_collects.clear(); + drop_collects.clear(); + commit_collects.clear(); + submit_spans.clear(); + return; + } + + for StartCollect { collect_id } in self.start_collects.drain(..) { + self.active_collectors.insert(collect_id, (Vec::new(), 0)); + } + + for DropCollect { collect_id } in self.drop_collects.drain(..) { + self.active_collectors.remove(&collect_id); + } + + for SubmitSpans { + spans, + collect_token, + } in self.submit_spans.drain(..) + { + debug_assert!(!collect_token.is_empty()); + + if collect_token.len() == 1 { + let item = collect_token[0]; + if let Some((buf, span_count)) = self.active_collectors.get_mut(&item.collect_id) { + if *span_count < self.config.max_spans_per_trace.unwrap_or(usize::MAX) + || item.is_root + { + *span_count += spans.len(); + buf.push(SpanCollection::Owned { + spans, + trace_id: item.trace_id, + parent_id: item.parent_id, + }); + } + } + } else { + let spans = Arc::new(spans); + for item in collect_token.iter() { + if let Some((buf, span_count)) = + self.active_collectors.get_mut(&item.collect_id) + { + // Multiple items in a collect token are built from `Span::enter_from_parents`, + // so relative span cannot be a root span. + if *span_count < self.config.max_spans_per_trace.unwrap_or(usize::MAX) { + *span_count += spans.len(); + buf.push(SpanCollection::Shared { + spans: spans.clone(), + trace_id: item.trace_id, + parent_id: item.parent_id, + }); + } + } + } + } + } + + for CommitCollect { collect_id } in commit_collects.drain(..) { + if let Some((span_collections, _)) = self.active_collectors.remove(&collect_id) { + debug_assert!(self.dangling_events.is_empty()); + let dangling_events = &mut self.dangling_events; + + let anchor: Anchor = Anchor::new(); + let committed_len = committed_records.len(); + + for span_collection in span_collections { + match span_collection { + SpanCollection::Owned { + spans, + trace_id, + parent_id, + } => match spans { + SpanSet::Span(raw_span) => amend_span( + &raw_span, + trace_id, + parent_id, + committed_records, + dangling_events, + &anchor, + ), + SpanSet::LocalSpansInner(local_spans) => amend_local_span( + &local_spans, + trace_id, + parent_id, + committed_records, + dangling_events, + &anchor, + ), + SpanSet::SharedLocalSpans(local_spans) => amend_local_span( + &local_spans, + trace_id, + parent_id, + committed_records, + dangling_events, + &anchor, + ), + }, + SpanCollection::Shared { + spans, + trace_id, + parent_id, + } => match &*spans { + SpanSet::Span(raw_span) => amend_span( + raw_span, + trace_id, + parent_id, + committed_records, + dangling_events, + &anchor, + ), + SpanSet::LocalSpansInner(local_spans) => amend_local_span( + local_spans, + trace_id, + parent_id, + committed_records, + dangling_events, + &anchor, + ), + SpanSet::SharedLocalSpans(local_spans) => amend_local_span( + local_spans, + trace_id, + parent_id, + committed_records, + dangling_events, + &anchor, + ), + }, + } + } + + mount_events(&mut committed_records[committed_len..], dangling_events); + dangling_events.clear(); + } + } + + if self.last_report.elapsed() > self.config.batch_report_interval + || committed_records.len() > self.config.batch_report_max_spans.unwrap_or(usize::MAX) + || flush + { + self.reporter + .as_mut() + .unwrap() + .report(committed_records.drain(..).as_slice()); + self.last_report = std::time::Instant::now(); + } + } +} + +fn amend_local_span( + local_spans: &LocalSpansInner, + trace_id: TraceId, + parent_id: SpanId, + spans: &mut Vec, + events: &mut HashMap>, + anchor: &Anchor, +) { + for span in local_spans.spans.iter() { + let begin_time_unix_ns = span.begin_instant.as_unix_nanos(anchor); + let parent_id = if span.parent_id == SpanId::default() { + parent_id + } else { + span.parent_id + }; + + if span.is_event { + let event = EventRecord { + name: span.name, + timestamp_unix_ns: begin_time_unix_ns, + properties: span.properties.clone(), + }; + events.entry(parent_id).or_default().push(event); + continue; + } + + let end_time_unix_ns = if span.end_instant == span.begin_instant { + local_spans.end_time.as_unix_nanos(anchor) + } else { + span.end_instant.as_unix_nanos(anchor) + }; + spans.push(SpanRecord { + trace_id, + span_id: span.id, + parent_id, + begin_time_unix_ns, + duration_ns: end_time_unix_ns.saturating_sub(begin_time_unix_ns), + name: span.name, + properties: span.properties.clone(), + events: vec![], + }); + } +} + +fn amend_span( + raw_span: &RawSpan, + trace_id: TraceId, + parent_id: SpanId, + spans: &mut Vec, + events: &mut HashMap>, + anchor: &Anchor, +) { + let begin_time_unix_ns = raw_span.begin_instant.as_unix_nanos(anchor); + + if raw_span.is_event { + let event = EventRecord { + name: raw_span.name, + timestamp_unix_ns: begin_time_unix_ns, + properties: raw_span.properties.clone(), + }; + events.entry(parent_id).or_default().push(event); + return; + } + + let end_time_unix_ns = raw_span.end_instant.as_unix_nanos(anchor); + spans.push(SpanRecord { + trace_id, + span_id: raw_span.id, + parent_id, + begin_time_unix_ns, + duration_ns: end_time_unix_ns.saturating_sub(begin_time_unix_ns), + name: raw_span.name, + properties: raw_span.properties.clone(), + events: vec![], + }); +} + +fn mount_events( + records: &mut [SpanRecord], + dangling_events: &mut HashMap>, +) { + for record in records.iter_mut() { + if dangling_events.is_empty() { + return; + } + + if let Some(event) = dangling_events.remove(&record.span_id) { + if record.events.is_empty() { + record.events = event; + } else { + record.events.extend(event); + } + } + } +} + +impl SpanSet { + fn len(&self) -> usize { + match self { + SpanSet::LocalSpansInner(local_spans) => local_spans.spans.len(), + SpanSet::SharedLocalSpans(local_spans) => local_spans.spans.len(), + SpanSet::Span(_) => 1, + } + } +} diff --git a/src/collector/id.rs b/src/collector/id.rs new file mode 100644 index 00000000..7bab8e85 --- /dev/null +++ b/src/collector/id.rs @@ -0,0 +1,61 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use std::cell::Cell; + +/// An identifier for a trace, which groups a set of related spans together. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] +pub struct TraceId(pub u128); + +/// An identifier for a span within a trace. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] +pub struct SpanId(pub u64); + +impl SpanId { + #[inline] + /// Create a non-zero `SpanId` + pub(crate) fn next_id() -> SpanId { + LOCAL_ID_GENERATOR + .try_with(|g| { + let (prefix, mut suffix) = g.get(); + + suffix = suffix.wrapping_add(1); + + g.set((prefix, suffix)); + + SpanId(((prefix as u64) << 32) | (suffix as u64)) + }) + .unwrap_or_else(|_| SpanId(rand::random())) + } +} + +thread_local! { + static LOCAL_ID_GENERATOR: Cell<(u32, u32)> = Cell::new((rand::random(), 0)) +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use super::*; + + #[test] + #[allow(clippy::needless_collect)] + fn unique_id() { + let handles = std::iter::repeat_with(|| { + std::thread::spawn(|| { + std::iter::repeat_with(SpanId::next_id) + .take(1000) + .collect::>() + }) + }) + .take(32) + .collect::>(); + + let k = handles + .into_iter() + .flat_map(|h| h.join().unwrap()) + .collect::>(); + + assert_eq!(k.len(), 32 * 1000); + } +} diff --git a/src/collector/mod.rs b/src/collector/mod.rs new file mode 100644 index 00000000..b5865fda --- /dev/null +++ b/src/collector/mod.rs @@ -0,0 +1,387 @@ +// Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. + +//! Collector and the collected spans. + +#![cfg_attr(test, allow(dead_code))] + +pub(crate) mod command; +mod console_reporter; +pub(crate) mod global_collector; +pub(crate) mod id; +mod test_reporter; + +use std::borrow::Cow; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Duration; + +pub use console_reporter::ConsoleReporter; +#[cfg(not(test))] +pub(crate) use global_collector::GlobalCollect; +#[cfg(test)] +pub(crate) use global_collector::MockGlobalCollect; +pub use global_collector::Reporter; +pub use id::SpanId; +pub use id::TraceId; +#[doc(hidden)] +pub use test_reporter::TestReporter; + +use crate::local::local_collector::LocalSpansInner; +use crate::local::local_span_stack::LOCAL_SPAN_STACK; +use crate::local::raw_span::RawSpan; +use crate::Span; +#[cfg(test)] +pub(crate) type GlobalCollect = Arc; + +#[doc(hidden)] +#[derive(Debug)] +pub enum SpanSet { + Span(RawSpan), + LocalSpansInner(LocalSpansInner), + SharedLocalSpans(Arc), +} + +/// A record of a span that includes all the information about the span, +/// such as its identifiers, timing information, name, and associated properties. +#[derive(Clone, Debug, Default)] +pub struct SpanRecord { + pub trace_id: TraceId, + pub span_id: SpanId, + pub parent_id: SpanId, + pub begin_time_unix_ns: u64, + pub duration_ns: u64, + pub name: &'static str, + pub properties: Vec<(Cow<'static, str>, Cow<'static, str>)>, + pub events: Vec, +} + +/// A record of an event that occurred during the execution of a span. +#[derive(Clone, Debug, Default)] +pub struct EventRecord { + pub name: &'static str, + pub timestamp_unix_ns: u64, + pub properties: Vec<(Cow<'static, str>, Cow<'static, str>)>, +} + +#[doc(hidden)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct CollectTokenItem { + pub trace_id: TraceId, + pub parent_id: SpanId, + pub collect_id: usize, + pub is_root: bool, +} + +/// A struct representing the context of a span, including its [`TraceId`] and [`SpanId`]. +/// +/// [`TraceId`]: crate::collector::TraceId +/// [`SpanId`]: crate::collector::SpanId +#[derive(Clone, Copy, Debug, Default)] +pub struct SpanContext { + pub trace_id: TraceId, + pub span_id: SpanId, +} + +impl SpanContext { + /// Creates a new `SpanContext` with the given [`TraceId`] and [`SpanId`]. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let context = SpanContext::new(TraceId(12), SpanId::default()); + /// ``` + /// + /// [`TraceId`]: crate::collector::TraceId + /// [`SpanId`]: crate::collector::SpanId + pub fn new(trace_id: TraceId, span_id: SpanId) -> Self { + Self { trace_id, span_id } + } + + /// Create a new `SpanContext` with a random trace id. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// ``` + pub fn random() -> Self { + Self { + trace_id: TraceId(rand::random()), + span_id: SpanId::default(), + } + } + + /// Creates a `SpanContext` from the given [`Span`]. If the `Span` is a noop span, + /// this function will return `None`. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let span = Span::root("root", SpanContext::random()); + /// let context = SpanContext::from_span(&span); + /// ``` + /// + /// [`Span`]: crate::Span + pub fn from_span(span: &Span) -> Option { + #[cfg(not(feature = "enable"))] + { + None + } + + #[cfg(feature = "enable")] + { + let inner = span.inner.as_ref()?; + let collect_token = inner.issue_collect_token().next()?; + + Some(Self { + trace_id: collect_token.trace_id, + span_id: collect_token.parent_id, + }) + } + } + + /// Creates a `SpanContext` from the current local parent span. If there is no + /// local parent span, this function will return `None`. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let span = Span::root("root", SpanContext::random()); + /// let _guard = span.set_local_parent(); + /// + /// let context = SpanContext::current_local_parent(); + /// ``` + pub fn current_local_parent() -> Option { + #[cfg(not(feature = "enable"))] + { + None + } + + #[cfg(feature = "enable")] + { + let stack = LOCAL_SPAN_STACK.try_with(Rc::clone).ok()?; + + let mut stack = stack.borrow_mut(); + let collect_token = stack.current_collect_token()?[0]; + + Some(Self { + trace_id: collect_token.trace_id, + span_id: collect_token.parent_id, + }) + } + } + + /// Decodes the `SpanContext` from a [W3C Trace Context](https://www.w3.org/TR/trace-context/) + /// `traceparent` header string. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let span_context = SpanContext::decode_w3c_traceparent( + /// "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + /// ) + /// .unwrap(); + /// + /// assert_eq!( + /// span_context.trace_id, + /// TraceId(0x0af7651916cd43dd8448eb211c80319c) + /// ); + /// assert_eq!(span_context.span_id, SpanId(0xb7ad6b7169203331)); + /// ``` + pub fn decode_w3c_traceparent(traceparent: &str) -> Option { + let mut parts = traceparent.split('-'); + + match ( + parts.next(), + parts.next(), + parts.next(), + parts.next(), + parts.next(), + ) { + (Some("00"), Some(trace_id), Some(span_id), Some(_), None) => { + let trace_id = u128::from_str_radix(trace_id, 16).ok()?; + let span_id = u64::from_str_radix(span_id, 16).ok()?; + Some(Self::new(TraceId(trace_id), SpanId(span_id))) + } + _ => None, + } + } + + /// Encodes the `SpanContext` into a [W3C Trace Context](https://www.w3.org/TR/trace-context/) + /// `traceparent` header string. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let span_context = SpanContext::new(TraceId(12), SpanId(34)); + /// let traceparent = span_context.encode_w3c_traceparent(); + /// + /// assert_eq!( + /// traceparent, + /// "00-0000000000000000000000000000000c-0000000000000022-01" + /// ); + /// ``` + pub fn encode_w3c_traceparent(&self) -> String { + Self::encode_w3c_traceparent_with_sampled(self, true) + } + + /// Encodes the `SpanContext` as a [W3C Trace Context](https://www.w3.org/TR/trace-context/) + /// `traceparent` header string with the sampled flag set to false. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let span_context = SpanContext::new(TraceId(12), SpanId(34)); + /// let traceparent = span_context.encode_w3c_traceparent_with_sampled(false); + /// + /// assert_eq!( + /// traceparent, + /// "00-0000000000000000000000000000000c-0000000000000022-00" + /// ); + /// ``` + pub fn encode_w3c_traceparent_with_sampled(&self, sampled: bool) -> String { + format!( + "00-{:032x}-{:016x}-{:02x}", + self.trace_id.0, self.span_id.0, sampled as u8, + ) + } +} + +/// Configuration of the behavior of the global collector. +#[must_use] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct Config { + pub(crate) max_spans_per_trace: Option, + pub(crate) batch_report_interval: Duration, + pub(crate) batch_report_max_spans: Option, +} + +impl Config { + /// A soft limit for the total number of spans and events for a trace, usually used + /// to avoid out-of-memory. + /// + /// # Note + /// + /// Root span will always be collected. The eventually collected spans may exceed the limit. + /// + /// # Examples + /// + /// ``` + /// use minitrace::collector::Config; + /// + /// let config = Config::default().max_spans_per_trace(Some(100)); + /// minitrace::set_reporter(minitrace::collector::ConsoleReporter, config); + /// ``` + pub fn max_spans_per_trace(self, max_spans_per_trace: Option) -> Self { + Self { + max_spans_per_trace, + ..self + } + } + + /// The time duration between two batch reports. + /// + /// The default value is 500 milliseconds. + /// + /// A batch report will be initiated by the earliest of these events: + /// + /// - When the specified time duration between two batch reports is met. + /// - When the number of spans in a batch hits its limit. + /// + /// # Examples + /// + /// ``` + /// use std::time::Duration; + /// + /// use minitrace::collector::Config; + /// + /// let config = Config::default().batch_report_interval(Duration::from_secs(1)); + /// minitrace::set_reporter(minitrace::collector::ConsoleReporter, config); + /// ``` + pub fn batch_report_interval(self, batch_report_interval: Duration) -> Self { + Self { + batch_report_interval, + ..self + } + } + + /// The soft limit for the maximum number of spans in a batch report. + /// + /// A batch report will be initiated by the earliest of these events: + /// + /// - When the specified time duration between two batch reports is met. + /// - When the number of spans in a batch hits its limit. + /// + /// # Note + /// + /// The eventually spans being reported may exceed the limit. + /// + /// # Examples + /// + /// ``` + /// use std::time::Duration; + /// + /// use minitrace::collector::Config; + /// + /// let config = Config::default().batch_report_max_spans(Some(200)); + /// minitrace::set_reporter(minitrace::collector::ConsoleReporter, config); + /// ``` + pub fn batch_report_max_spans(self, batch_report_max_spans: Option) -> Self { + Self { + batch_report_max_spans, + ..self + } + } +} + +impl Default for Config { + fn default() -> Self { + Self { + max_spans_per_trace: None, + batch_report_interval: Duration::from_millis(500), + batch_report_max_spans: None, + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn w3c_traceparent() { + let span_context = SpanContext::decode_w3c_traceparent( + "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + ) + .unwrap(); + assert_eq!( + span_context.trace_id, + TraceId(0x0af7651916cd43dd8448eb211c80319c) + ); + assert_eq!(span_context.span_id, SpanId(0xb7ad6b7169203331)); + + assert_eq!( + span_context.encode_w3c_traceparent(), + "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" + ); + assert_eq!( + span_context.encode_w3c_traceparent_with_sampled(false), + "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00" + ); + } +} diff --git a/src/collector/test_reporter.rs b/src/collector/test_reporter.rs new file mode 100644 index 00000000..adb6d619 --- /dev/null +++ b/src/collector/test_reporter.rs @@ -0,0 +1,30 @@ +// Copyright 2023 TiKV Project Authors. Licensed under Apache-2.0. + +use std::sync::Arc; + +use parking_lot::Mutex; + +use super::global_collector::Reporter; +use super::SpanRecord; + +pub struct TestReporter { + pub spans: Arc>>, +} + +impl TestReporter { + pub fn new() -> (Self, Arc>>) { + let spans = Arc::new(Mutex::new(Vec::new())); + ( + Self { + spans: spans.clone(), + }, + spans, + ) + } +} + +impl Reporter for TestReporter { + fn report(&mut self, spans: &[SpanRecord]) { + self.spans.lock().extend_from_slice(spans); + } +} diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 00000000..c61c1bea --- /dev/null +++ b/src/event.rs @@ -0,0 +1,62 @@ +// Copyright 2023 TiKV Project Authors. Licensed under Apache-2.0. + +use std::borrow::Cow; + +use crate::local::local_span_stack::LOCAL_SPAN_STACK; +use crate::Span; + +/// An event that represents a single point in time during the execution of a span. +pub struct Event; + +impl Event { + /// Adds an event to the parent span with the given name and properties. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// + /// Event::add_to_parent("event in root", &root, || [("key".into(), "value".into())]); + /// ``` + pub fn add_to_parent(name: &'static str, parent: &Span, properties: F) + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + #[cfg(feature = "enable")] + { + let mut span = Span::enter_with_parent(name, parent).with_properties(properties); + if let Some(mut inner) = span.inner.take() { + inner.raw_span.is_event = true; + inner.submit_spans(); + } + } + } + + /// Adds an event to the current local parent span with the given name and properties. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// let _guard = root.set_local_parent(); + /// + /// Event::add_to_local_parent("event in root", || [("key".into(), "value".into())]); + /// ``` + pub fn add_to_local_parent(name: &'static str, properties: F) + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + #[cfg(feature = "enable")] + { + LOCAL_SPAN_STACK + .try_with(|stack| stack.borrow_mut().add_event(name, properties)) + .ok(); + } + } +} diff --git a/src/future.rs b/src/future.rs new file mode 100644 index 00000000..a7f22dcb --- /dev/null +++ b/src/future.rs @@ -0,0 +1,149 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +//! This module provides tools to trace a `Future`. +//! +//! The [`FutureExt`] trait extends `Future` with two methods: [`in_span()`] and +//! [`enter_on_poll()`]. It is crucial that the outermost future uses `in_span()`, +//! otherwise, the traces inside the `Future` will be lost. +//! +//! # Example +//! +//! ``` +//! use minitrace::prelude::*; +//! +//! let root = Span::root("root", SpanContext::random()); +//! +//! // Instrument the a task +//! let task = async { +//! async { +//! // ... +//! } +//! .enter_on_poll("future is polled") +//! .await; +//! } +//! .in_span(Span::enter_with_parent("task", &root)); +//! +//! # let runtime = tokio::runtime::Runtime::new().unwrap(); +//! runtime.spawn(task); +//! ``` +//! +//! [`in_span()`]:(FutureExt::in_span) +//! [`enter_on_poll()`]:(FutureExt::enter_on_poll) + +use std::task::Poll; + +use crate::local::LocalSpan; +use crate::Span; + +impl FutureExt for T {} + +/// An extension trait for `Futures` that provides tracing instrument adapters. +pub trait FutureExt: std::future::Future + Sized { + /// Binds a [`Span`] to the [`Future`] that continues to record until the future is dropped. + /// + /// In addition, it sets the span as the local parent at every poll so that `LocalSpan` + /// becomes available within the future. Internally, it calls [`Span::set_local_parent`] when + /// the executor [`poll`](std::future::Future::poll) it. + /// + /// # Examples + /// + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use minitrace::prelude::*; + /// + /// let root = Span::root("Root", SpanContext::random()); + /// let task = async { + /// // ... + /// } + /// .in_span(Span::enter_with_parent("Task", &root)); + /// + /// tokio::spawn(task); + /// # } + /// ``` + /// + /// [`Future`]:(std::future::Future) + /// [`Span::set_local_parent`](Span::set_local_parent) + #[inline] + fn in_span(self, span: Span) -> InSpan { + InSpan { + inner: self, + span: Some(span), + } + } + + /// Starts a [`LocalSpan`] at every [`Future::poll()`]. If the future gets polled multiple + /// times, it will create multiple _short_ spans. + /// + /// # Examples + /// + /// ``` + /// # #[tokio::main] + /// # async fn main() { + /// use minitrace::prelude::*; + /// + /// let root = Span::root("Root", SpanContext::random()); + /// let task = async { + /// async { + /// // ... + /// } + /// .enter_on_poll("Sub Task") + /// .await + /// } + /// .in_span(Span::enter_with_parent("Task", &root)); + /// + /// tokio::spawn(task); + /// # } + /// ``` + /// + /// [`Future::poll()`]:(std::future::Future::poll) + #[inline] + fn enter_on_poll(self, name: &'static str) -> EnterOnPoll { + EnterOnPoll { inner: self, name } + } +} + +/// Adapter for [`FutureExt::in_span()`](FutureExt::in_span). +#[pin_project::pin_project] +pub struct InSpan { + #[pin] + inner: T, + span: Option, +} + +impl std::future::Future for InSpan { + type Output = T::Output; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let this = self.project(); + + let _guard = this.span.as_ref().map(|s| s.set_local_parent()); + let res = this.inner.poll(cx); + + match res { + r @ Poll::Pending => r, + other => { + this.span.take(); + other + } + } + } +} + +/// Adapter for [`FutureExt::enter_on_poll()`](FutureExt::enter_on_poll). +#[pin_project::pin_project] +pub struct EnterOnPoll { + #[pin] + inner: T, + name: &'static str, +} + +impl std::future::Future for EnterOnPoll { + type Output = T::Output; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let this = self.project(); + let _guard = LocalSpan::enter_with_local_parent(this.name); + this.inner.poll(cx) + } +} diff --git a/minitrace/src/lib.rs b/src/lib.rs similarity index 100% rename from minitrace/src/lib.rs rename to src/lib.rs diff --git a/src/local/local_collector.rs b/src/local/local_collector.rs new file mode 100644 index 00000000..2c9a3d31 --- /dev/null +++ b/src/local/local_collector.rs @@ -0,0 +1,262 @@ +// Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. + +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::Arc; + +use minstant::Instant; + +use crate::local::local_span_stack::LocalSpanStack; +use crate::local::local_span_stack::SpanLineHandle; +use crate::local::local_span_stack::LOCAL_SPAN_STACK; +use crate::util::CollectToken; +use crate::util::RawSpans; + +/// A collector to collect [`LocalSpan`]. +/// +/// `LocalCollector` allows to collect `LocalSpan` manually without a local parent. The collected `LocalSpan` can later be +/// attached to a parent. +/// +/// Generally, [`Span`] and `LocalSpan` are sufficient. However, use `LocalCollector` when the span might initiate before its +/// parent span. This is particularly useful for tracing prior tasks that may be obstructing the current request. +/// +/// # Examples +/// +/// ``` +/// use minitrace::local::LocalCollector; +/// use minitrace::prelude::*; +/// +/// // Collect local spans manually without a parent +/// let collector = LocalCollector::start(); +/// let span = LocalSpan::enter_with_local_parent("a child span"); +/// drop(span); +/// let local_spans = collector.collect(); +/// +/// // Attach the local spans to a parent +/// let root = Span::root("root", SpanContext::random()); +/// root.push_child_spans(local_spans); +/// ``` +/// +/// [`Span`]: crate::Span +/// [`LocalSpan`]: crate::local::LocalSpan +#[must_use] +#[derive(Default)] +pub struct LocalCollector { + #[cfg(feature = "enable")] + inner: Option, +} + +struct LocalCollectorInner { + stack: Rc>, + span_line_handle: SpanLineHandle, +} + +/// A collection of [`LocalSpan`] instances. +/// +/// This struct is typically used to group together multiple `LocalSpan` instances that have been +/// collected from a [`LocalCollector`]. These spans can then be associated with a parent span using +/// the [`Span::push_child_spans()`] method on the parent span. +/// +/// Internally, it is implemented as an `Arc<[LocalSpan]>`, which allows it to be cloned and shared +/// across threads at a low cost. +/// +/// # Examples +/// +/// ``` +/// use minitrace::local::LocalCollector; +/// use minitrace::local::LocalSpans; +/// use minitrace::prelude::*; +/// +/// // Collect local spans manually without a parent +/// let collector = LocalCollector::start(); +/// let span = LocalSpan::enter_with_local_parent("a child span"); +/// drop(span); +/// +/// // Collect local spans into a LocalSpans instance +/// let local_spans: LocalSpans = collector.collect(); +/// +/// // Attach the local spans to a parent +/// let root = Span::root("root", SpanContext::random()); +/// root.push_child_spans(local_spans); +/// ``` +/// +/// [`Span::push_child_spans()`]: crate::Span::push_child_spans() +/// [`LocalSpan`]: crate::local::LocalSpan +/// [`LocalCollector`]: crate::local::LocalCollector +#[derive(Debug, Clone)] +pub struct LocalSpans { + #[cfg(feature = "enable")] + pub(crate) inner: Arc, +} + +#[derive(Debug)] +pub struct LocalSpansInner { + pub spans: RawSpans, + pub end_time: Instant, +} + +impl LocalCollector { + pub fn start() -> Self { + #[cfg(not(feature = "enable"))] + { + LocalCollector::default() + } + + #[cfg(feature = "enable")] + { + LOCAL_SPAN_STACK + .try_with(|stack| Self::new(None, stack.clone())) + .unwrap_or_default() + } + } + + pub fn collect(self) -> LocalSpans { + #[cfg(not(feature = "enable"))] + { + LocalSpans {} + } + + #[cfg(feature = "enable")] + { + LocalSpans { + inner: Arc::new(self.collect_spans_and_token().0), + } + } + } +} + +#[cfg(feature = "enable")] +impl LocalCollector { + pub(crate) fn new( + collect_token: Option, + stack: Rc>, + ) -> Self { + let span_line_epoch = { + let stack = &mut (*stack).borrow_mut(); + stack.register_span_line(collect_token) + }; + + Self { + inner: span_line_epoch.map(move |span_line_handle| LocalCollectorInner { + stack, + span_line_handle, + }), + } + } + + pub(crate) fn collect_spans_and_token(mut self) -> (LocalSpansInner, Option) { + let (spans, collect_token) = self + .inner + .take() + .and_then( + |LocalCollectorInner { + stack, + span_line_handle, + }| { + let s = &mut (*stack).borrow_mut(); + s.unregister_and_collect(span_line_handle) + }, + ) + .unwrap_or_default(); + + ( + LocalSpansInner { + spans, + end_time: Instant::now(), + }, + collect_token, + ) + } +} + +impl Drop for LocalCollector { + fn drop(&mut self) { + #[cfg(feature = "enable")] + if let Some(LocalCollectorInner { + stack, + span_line_handle, + }) = self.inner.take() + { + let s = &mut (*stack).borrow_mut(); + let _ = s.unregister_and_collect(span_line_handle); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::collector::CollectTokenItem; + use crate::collector::SpanId; + use crate::prelude::TraceId; + use crate::util::tree::tree_str_from_raw_spans; + + #[test] + fn local_collector_basic() { + let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); + let collector1 = LocalCollector::new(None, stack.clone()); + + let span1 = stack.borrow_mut().enter_span("span1").unwrap(); + { + let token2 = CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + }; + let collector2 = LocalCollector::new(Some(token2.into()), stack.clone()); + let span2 = stack.borrow_mut().enter_span("span2").unwrap(); + let span3 = stack.borrow_mut().enter_span("span3").unwrap(); + stack.borrow_mut().exit_span(span3); + stack.borrow_mut().exit_span(span2); + + let (spans, token) = collector2.collect_spans_and_token(); + assert_eq!(token.unwrap().as_slice(), &[token2]); + assert_eq!( + tree_str_from_raw_spans(spans.spans), + r" +span2 [] + span3 [] +" + ); + } + stack.borrow_mut().exit_span(span1); + let spans = collector1.collect(); + assert_eq!( + tree_str_from_raw_spans(spans.inner.spans.iter().cloned().collect()), + r" +span1 [] +" + ); + } + + #[test] + fn drop_without_collect() { + let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); + let collector1 = LocalCollector::new(None, stack.clone()); + + let span1 = stack.borrow_mut().enter_span("span1").unwrap(); + { + let token2 = CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + }; + let collector2 = LocalCollector::new(Some(token2.into()), stack.clone()); + let span2 = stack.borrow_mut().enter_span("span2").unwrap(); + let span3 = stack.borrow_mut().enter_span("span3").unwrap(); + stack.borrow_mut().exit_span(span3); + stack.borrow_mut().exit_span(span2); + drop(collector2); + } + stack.borrow_mut().exit_span(span1); + let spans = collector1.collect(); + assert_eq!( + tree_str_from_raw_spans(spans.inner.spans.iter().cloned().collect()), + r" +span1 [] +" + ); + } +} diff --git a/src/local/local_span.rs b/src/local/local_span.rs new file mode 100644 index 00000000..9c14ff78 --- /dev/null +++ b/src/local/local_span.rs @@ -0,0 +1,206 @@ +// Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. + +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; + +use crate::local::local_span_line::LocalSpanHandle; +use crate::local::local_span_stack::LocalSpanStack; +use crate::local::local_span_stack::LOCAL_SPAN_STACK; + +/// An optimized [`Span`] for tracing operations within a single thread. +/// +/// [`Span`]: crate::Span +#[must_use] +#[derive(Default)] +pub struct LocalSpan { + #[cfg(feature = "enable")] + inner: Option, +} + +struct LocalSpanInner { + stack: Rc>, + span_handle: LocalSpanHandle, +} + +impl LocalSpan { + /// Create a new child span associated with the current local span in the current thread, and then + /// it will become the new local parent. + /// + /// If no local span is active, this function is no-op. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// let _g = root.set_local_parent(); + /// + /// let child = Span::enter_with_local_parent("child"); + /// ``` + #[inline] + pub fn enter_with_local_parent(name: &'static str) -> Self { + #[cfg(not(feature = "enable"))] + { + LocalSpan::default() + } + + #[cfg(feature = "enable")] + { + LOCAL_SPAN_STACK + .try_with(|stack| Self::enter_with_stack(name, stack.clone())) + .unwrap_or_default() + } + } + + /// Add a single property to the `LocalSpan` and return the modified `LocalSpan`. + /// + /// A property is an arbitrary key-value pair associated with a span. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let span = LocalSpan::enter_with_local_parent("a child span") + /// .with_property(|| ("key".into(), "value".into())); + /// ``` + #[inline] + pub fn with_property(self, property: F) -> Self + where F: FnOnce() -> (Cow<'static, str>, Cow<'static, str>) { + self.with_properties(|| [property()]) + } + + /// Add multiple properties to the `LocalSpan` and return the modified `LocalSpan`. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let span = LocalSpan::enter_with_local_parent("a child span").with_properties(|| { + /// vec![ + /// ("key1".into(), "value1".into()), + /// ("key2".into(), "value2".into()), + /// ] + /// }); + /// ``` + #[inline] + pub fn with_properties(self, properties: F) -> Self + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + #[cfg(feature = "enable")] + if let Some(LocalSpanInner { stack, span_handle }) = &self.inner { + let span_stack = &mut *stack.borrow_mut(); + span_stack.add_properties(span_handle, properties); + } + + self + } +} + +#[cfg(feature = "enable")] +impl LocalSpan { + #[inline] + pub(crate) fn enter_with_stack(name: &'static str, stack: Rc>) -> Self { + let span_handle = { + let mut stack = stack.borrow_mut(); + stack.enter_span(name) + }; + + let inner = span_handle.map(|span_handle| LocalSpanInner { stack, span_handle }); + + Self { inner } + } +} + +impl Drop for LocalSpan { + #[inline] + fn drop(&mut self) { + #[cfg(feature = "enable")] + if let Some(LocalSpanInner { stack, span_handle }) = self.inner.take() { + let mut span_stack = stack.borrow_mut(); + span_stack.exit_span(span_handle); + } + } +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + use std::rc::Rc; + + use super::*; + use crate::collector::CollectTokenItem; + use crate::collector::SpanId; + use crate::local::local_span_stack::LocalSpanStack; + use crate::local::LocalCollector; + use crate::prelude::TraceId; + use crate::util::tree::tree_str_from_raw_spans; + + #[test] + fn local_span_basic() { + let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); + + let token = CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + }; + let collector = LocalCollector::new(Some(token.into()), stack.clone()); + + { + let _g = LocalSpan::enter_with_stack("span1", stack.clone()); + { + let _span = LocalSpan::enter_with_stack("span2", stack) + .with_property(|| ("k1".into(), "v1".into())); + } + } + + let (spans, collect_token) = collector.collect_spans_and_token(); + assert_eq!(collect_token.unwrap().as_slice(), &[token]); + assert_eq!( + tree_str_from_raw_spans(spans.spans), + r#" +span1 [] + span2 [("k1", "v1")] +"# + ); + } + + #[test] + fn local_span_noop() { + let _span1 = LocalSpan::enter_with_local_parent("span1") + .with_property(|| ("k1".into(), "v1".into())); + } + + #[test] + #[should_panic] + fn drop_out_of_order() { + let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); + + let token = CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + }; + let collector = LocalCollector::new(Some(token.into()), stack.clone()); + + { + let span1 = LocalSpan::enter_with_stack("span1", stack.clone()); + { + let _span2 = LocalSpan::enter_with_stack("span2", stack) + .with_property(|| ("k1".into(), "v1".into())); + + drop(span1); + } + } + + let _ = collector.collect_spans_and_token(); + } +} diff --git a/src/local/local_span_line.rs b/src/local/local_span_line.rs new file mode 100644 index 00000000..2f12a860 --- /dev/null +++ b/src/local/local_span_line.rs @@ -0,0 +1,247 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use std::borrow::Cow; + +use crate::collector::CollectTokenItem; +use crate::local::span_queue::SpanHandle; +use crate::local::span_queue::SpanQueue; +use crate::util::CollectToken; +use crate::util::RawSpans; + +pub struct SpanLine { + span_queue: SpanQueue, + epoch: usize, + collect_token: Option, +} + +impl SpanLine { + pub fn new( + capacity: usize, + span_line_epoch: usize, + collect_token: Option, + ) -> Self { + Self { + span_queue: SpanQueue::with_capacity(capacity), + epoch: span_line_epoch, + collect_token, + } + } + + #[inline] + pub fn span_line_epoch(&self) -> usize { + self.epoch + } + + #[inline] + pub fn start_span(&mut self, name: &'static str) -> Option { + Some(LocalSpanHandle { + span_handle: self.span_queue.start_span(name)?, + span_line_epoch: self.epoch, + }) + } + + #[inline] + pub fn finish_span(&mut self, handle: LocalSpanHandle) { + if self.epoch == handle.span_line_epoch { + self.span_queue.finish_span(handle.span_handle); + } + } + + #[inline] + pub fn add_event(&mut self, name: &'static str, properties: F) + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + self.span_queue.add_event(name, properties); + } + + #[inline] + pub fn add_properties(&mut self, handle: &LocalSpanHandle, properties: F) + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + if self.epoch == handle.span_line_epoch { + self.span_queue + .add_properties(&handle.span_handle, properties()); + } + } + + #[inline] + pub fn current_collect_token(&self) -> Option { + self.collect_token.as_ref().map(|collect_token| { + collect_token + .iter() + .map(|item| CollectTokenItem { + trace_id: item.trace_id, + parent_id: self.span_queue.current_span_id().unwrap_or(item.parent_id), + collect_id: item.collect_id, + is_root: false, + }) + .collect() + }) + } + + #[inline] + pub fn collect(self, span_line_epoch: usize) -> Option<(RawSpans, Option)> { + (self.epoch == span_line_epoch) + .then(move || (self.span_queue.take_queue(), self.collect_token)) + } +} + +pub struct LocalSpanHandle { + pub span_line_epoch: usize, + span_handle: SpanHandle, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::collector::SpanId; + use crate::prelude::TraceId; + use crate::util::tree::tree_str_from_raw_spans; + + #[test] + fn span_line_basic() { + let mut span_line = SpanLine::new(16, 1, None); + { + let span1 = span_line.start_span("span1").unwrap(); + { + let span2 = span_line.start_span("span2").unwrap(); + { + let span3 = span_line.start_span("span3").unwrap(); + span_line.add_properties(&span3, || [("k1".into(), "v1".into())]); + span_line.finish_span(span3); + } + span_line.finish_span(span2); + } + span_line.finish_span(span1); + } + let (spans, collect_token) = span_line.collect(1).unwrap(); + assert!(collect_token.is_none()); + assert_eq!( + tree_str_from_raw_spans(spans), + r#" +span1 [] + span2 [] + span3 [("k1", "v1")] +"# + ); + } + + #[test] + fn current_collect_token() { + let token1 = CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + }; + let token2 = CollectTokenItem { + trace_id: TraceId(1235), + parent_id: SpanId::default(), + collect_id: 43, + is_root: false, + }; + let token = [token1, token2].iter().collect(); + let mut span_line = SpanLine::new(16, 1, Some(token)); + + let current_token = span_line.current_collect_token().unwrap(); + assert_eq!(current_token.as_slice(), &[token1, token2]); + + let span = span_line.start_span("span").unwrap(); + let current_token = span_line.current_collect_token().unwrap(); + assert_eq!(current_token.len(), 2); + assert_eq!(current_token.as_slice(), &[ + CollectTokenItem { + trace_id: TraceId(1234), + parent_id: span_line.span_queue.current_span_id().unwrap(), + collect_id: 42, + is_root: false, + }, + CollectTokenItem { + trace_id: TraceId(1235), + parent_id: span_line.span_queue.current_span_id().unwrap(), + collect_id: 43, + is_root: false, + } + ]); + span_line.finish_span(span); + + let current_token = span_line.current_collect_token().unwrap(); + assert_eq!(current_token.as_slice(), &[token1, token2]); + + let (spans, collect_token) = span_line.collect(1).unwrap(); + assert_eq!(collect_token.unwrap().as_slice(), &[token1, token2]); + assert_eq!( + tree_str_from_raw_spans(spans), + r#" +span [] +"# + ); + } + + #[test] + fn unmatched_epoch_add_properties() { + let mut span_line1 = SpanLine::new(16, 1, None); + let mut span_line2 = SpanLine::new(16, 2, None); + assert_eq!(span_line1.span_line_epoch(), 1); + assert_eq!(span_line2.span_line_epoch(), 2); + + let span = span_line1.start_span("span").unwrap(); + span_line2.add_properties(&span, || [("k1".into(), "v1".into())]); + span_line1.finish_span(span); + + let raw_spans = span_line1.collect(1).unwrap().0.into_inner(); + assert_eq!(raw_spans.len(), 1); + assert_eq!(raw_spans[0].properties.len(), 0); + + let raw_spans = span_line2.collect(2).unwrap().0.into_inner(); + assert!(raw_spans.is_empty()); + } + + #[test] + fn unmatched_epoch_finish_span() { + let item = CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + }; + let mut span_line1 = SpanLine::new(16, 1, Some(item.into())); + let mut span_line2 = SpanLine::new(16, 2, None); + assert_eq!(span_line1.span_line_epoch(), 1); + assert_eq!(span_line2.span_line_epoch(), 2); + + let span = span_line1.start_span("span").unwrap(); + let token_before_finish = span_line1.current_collect_token().unwrap(); + span_line2.finish_span(span); + + let token_after_finish = span_line1.current_collect_token().unwrap(); + // the span failed to finish + assert_eq!( + token_before_finish.as_slice(), + token_after_finish.as_slice() + ); + + let (spans, collect_token) = span_line1.collect(1).unwrap(); + let collect_token = collect_token.unwrap(); + assert_eq!(collect_token.as_slice(), &[item]); + assert_eq!(spans.into_inner().len(), 1); + + let (spans, collect_token) = span_line2.collect(2).unwrap(); + assert!(collect_token.is_none()); + assert!(spans.into_inner().is_empty()); + } + + #[test] + fn unmatched_epoch_collect() { + let span_line1 = SpanLine::new(16, 1, None); + let span_line2 = SpanLine::new(16, 2, None); + assert_eq!(span_line1.span_line_epoch(), 1); + assert_eq!(span_line2.span_line_epoch(), 2); + assert!(span_line1.collect(2).is_none()); + assert!(span_line2.collect(1).is_none()); + } +} diff --git a/src/local/local_span_stack.rs b/src/local/local_span_stack.rs new file mode 100644 index 00000000..5dc7952b --- /dev/null +++ b/src/local/local_span_stack.rs @@ -0,0 +1,386 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; + +use crate::local::local_span_line::LocalSpanHandle; +use crate::local::local_span_line::SpanLine; +use crate::util::CollectToken; +use crate::util::RawSpans; + +const DEFAULT_SPAN_STACK_SIZE: usize = 4096; +const DEFAULT_SPAN_QUEUE_SIZE: usize = 10240; + +thread_local! { + pub static LOCAL_SPAN_STACK: Rc> = Rc::new(RefCell::new(LocalSpanStack::with_capacity(DEFAULT_SPAN_STACK_SIZE))); +} + +pub struct LocalSpanStack { + span_lines: Vec, + capacity: usize, + next_span_line_epoch: usize, +} + +impl LocalSpanStack { + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self { + span_lines: Vec::with_capacity(capacity / 8), + capacity, + next_span_line_epoch: 0, + } + } + + #[inline] + pub fn enter_span(&mut self, name: &'static str) -> Option { + let span_line = self.current_span_line()?; + span_line.start_span(name) + } + + #[inline] + pub fn exit_span(&mut self, local_span_handle: LocalSpanHandle) { + if let Some(span_line) = self.current_span_line() { + debug_assert_eq!( + span_line.span_line_epoch(), + local_span_handle.span_line_epoch + ); + span_line.finish_span(local_span_handle); + } + } + + #[inline] + pub fn add_event(&mut self, name: &'static str, properties: F) + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + if let Some(span_line) = self.current_span_line() { + span_line.add_event(name, properties); + } + } + + /// Register a new span line to the span stack. If succeed, return a span line epoch which can + /// be used to unregister the span line via [`LocalSpanStack::unregister_and_collect`]. If + /// the size of the span stack is greater than the `capacity`, registration will fail + /// and a `None` will be returned. + /// + /// [`LocalSpanStack::unregister_and_collect`](LocalSpanStack::unregister_and_collect) + #[inline] + pub fn register_span_line( + &mut self, + collect_token: Option, + ) -> Option { + if self.span_lines.len() >= self.capacity { + return None; + } + + let epoch = self.next_span_line_epoch; + self.next_span_line_epoch = self.next_span_line_epoch.wrapping_add(1); + + let span_line = SpanLine::new(DEFAULT_SPAN_QUEUE_SIZE, epoch, collect_token); + self.span_lines.push(span_line); + Some(SpanLineHandle { + span_line_epoch: epoch, + }) + } + + pub fn unregister_and_collect( + &mut self, + span_line_handle: SpanLineHandle, + ) -> Option<(RawSpans, Option)> { + debug_assert_eq!( + self.current_span_line().unwrap().span_line_epoch(), + span_line_handle.span_line_epoch, + ); + let span_line = self.span_lines.pop()?; + span_line.collect(span_line_handle.span_line_epoch) + } + + #[inline] + pub fn add_properties(&mut self, local_span_handle: &LocalSpanHandle, properties: F) + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + debug_assert!(self.current_span_line().is_some()); + if let Some(span_line) = self.current_span_line() { + debug_assert_eq!( + span_line.span_line_epoch(), + local_span_handle.span_line_epoch + ); + span_line.add_properties(local_span_handle, properties); + } + } + + pub fn current_collect_token(&mut self) -> Option { + let span_line = self.current_span_line()?; + span_line.current_collect_token() + } + + #[inline] + fn current_span_line(&mut self) -> Option<&mut SpanLine> { + self.span_lines.last_mut() + } +} + +pub struct SpanLineHandle { + span_line_epoch: usize, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::collector::CollectTokenItem; + use crate::collector::SpanId; + use crate::prelude::TraceId; + use crate::util::tree::tree_str_from_raw_spans; + + #[test] + fn span_stack_basic() { + let mut span_stack = LocalSpanStack::with_capacity(16); + + let token1 = CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + }; + let span_line1 = span_stack.register_span_line(Some(token1.into())).unwrap(); + { + { + let span1 = span_stack.enter_span("span1").unwrap(); + { + let span2 = span_stack.enter_span("span2").unwrap(); + span_stack.exit_span(span2); + } + span_stack.exit_span(span1); + } + + let token2 = CollectTokenItem { + trace_id: TraceId(1235), + parent_id: SpanId::default(), + collect_id: 48, + is_root: false, + }; + let span_line2 = span_stack.register_span_line(Some(token2.into())).unwrap(); + { + let span3 = span_stack.enter_span("span3").unwrap(); + { + let span4 = span_stack.enter_span("span4").unwrap(); + span_stack.exit_span(span4); + } + span_stack.exit_span(span3); + } + + let (spans, collect_token) = span_stack.unregister_and_collect(span_line2).unwrap(); + assert_eq!(collect_token.unwrap().as_slice(), &[token2]); + assert_eq!( + tree_str_from_raw_spans(spans), + r" +span3 [] + span4 [] +" + ); + } + + let (spans, collect_token) = span_stack.unregister_and_collect(span_line1).unwrap(); + assert_eq!(collect_token.unwrap().as_slice(), &[token1]); + assert_eq!( + tree_str_from_raw_spans(spans), + r" +span1 [] + span2 [] +" + ); + } + + #[test] + fn span_stack_is_full() { + let mut span_stack = LocalSpanStack::with_capacity(4); + + let span_line1 = span_stack.register_span_line(None).unwrap(); + { + let span_line2 = span_stack.register_span_line(None).unwrap(); + { + let span_line3 = span_stack + .register_span_line(Some( + CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + } + .into(), + )) + .unwrap(); + { + let span_line4 = span_stack.register_span_line(None).unwrap(); + { + assert!( + span_stack + .register_span_line(Some( + CollectTokenItem { + trace_id: TraceId(1235), + parent_id: SpanId::default(), + collect_id: 43, + is_root: false, + } + .into() + )) + .is_none() + ); + assert!(span_stack.register_span_line(None).is_none()); + } + let _ = span_stack.unregister_and_collect(span_line4).unwrap(); + } + { + let span_line5 = span_stack.register_span_line(None).unwrap(); + { + assert!( + span_stack + .register_span_line(Some( + CollectTokenItem { + trace_id: TraceId(1236), + parent_id: SpanId::default(), + collect_id: 44, + is_root: false, + } + .into() + )) + .is_none() + ); + assert!(span_stack.register_span_line(None).is_none()); + } + let _ = span_stack.unregister_and_collect(span_line5).unwrap(); + } + let _ = span_stack.unregister_and_collect(span_line3).unwrap(); + } + let _ = span_stack.unregister_and_collect(span_line2).unwrap(); + } + let _ = span_stack.unregister_and_collect(span_line1).unwrap(); + } + + #[test] + fn current_collect_token() { + let mut span_stack = LocalSpanStack::with_capacity(16); + assert!(span_stack.current_collect_token().is_none()); + let token1 = CollectTokenItem { + trace_id: TraceId(1), + parent_id: SpanId(1), + collect_id: 1, + is_root: false, + }; + let span_line1 = span_stack.register_span_line(Some(token1.into())).unwrap(); + assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ + token1 + ]); + { + let span_line2 = span_stack.register_span_line(None).unwrap(); + assert!(span_stack.current_collect_token().is_none()); + { + let token3 = CollectTokenItem { + trace_id: TraceId(3), + parent_id: SpanId(3), + collect_id: 3, + is_root: false, + }; + let span_line3 = span_stack.register_span_line(Some(token3.into())).unwrap(); + assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ + token3 + ]); + let _ = span_stack.unregister_and_collect(span_line3).unwrap(); + } + assert!(span_stack.current_collect_token().is_none()); + let _ = span_stack.unregister_and_collect(span_line2).unwrap(); + + let token4 = CollectTokenItem { + trace_id: TraceId(4), + parent_id: SpanId(4), + collect_id: 4, + is_root: false, + }; + let span_line4 = span_stack.register_span_line(Some(token4.into())).unwrap(); + assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ + token4 + ]); + let _ = span_stack.unregister_and_collect(span_line4).unwrap(); + } + assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ + token1 + ]); + let _ = span_stack.unregister_and_collect(span_line1).unwrap(); + assert!(span_stack.current_collect_token().is_none()); + } + + #[test] + #[should_panic] + fn unmatched_span_line_exit_span() { + let mut span_stack = LocalSpanStack::with_capacity(16); + let span_line1 = span_stack.register_span_line(None).unwrap(); + let span1 = span_stack.enter_span("span1").unwrap(); + { + let span_line2 = span_stack + .register_span_line(Some( + CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + } + .into(), + )) + .unwrap(); + span_stack.exit_span(span1); + let _ = span_stack.unregister_and_collect(span_line2).unwrap(); + } + let _ = span_stack.unregister_and_collect(span_line1).unwrap(); + } + + #[test] + #[should_panic] + fn unmatched_span_line_add_properties() { + let mut span_stack = LocalSpanStack::with_capacity(16); + let span_line1 = span_stack.register_span_line(None).unwrap(); + let span1 = span_stack.enter_span("span1").unwrap(); + { + let span_line2 = span_stack + .register_span_line(Some( + CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + } + .into(), + )) + .unwrap(); + span_stack.add_properties(&span1, || [("k1".into(), "v1".into())]); + let _ = span_stack.unregister_and_collect(span_line2).unwrap(); + } + span_stack.exit_span(span1); + let _ = span_stack.unregister_and_collect(span_line1).unwrap(); + } + + #[test] + #[should_panic] + fn unmatched_span_line_collect() { + let mut span_stack = LocalSpanStack::with_capacity(16); + let span_line1 = span_stack.register_span_line(None).unwrap(); + { + let span_line2 = span_stack + .register_span_line(Some( + CollectTokenItem { + trace_id: TraceId(1234), + parent_id: SpanId::default(), + collect_id: 42, + is_root: false, + } + .into(), + )) + .unwrap(); + let _ = span_stack.unregister_and_collect(span_line1).unwrap(); + let _ = span_stack.unregister_and_collect(span_line2).unwrap(); + } + } +} diff --git a/src/local/mod.rs b/src/local/mod.rs new file mode 100644 index 00000000..2c0615fc --- /dev/null +++ b/src/local/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +//! Non thread-safe span with low overhead. + +pub(crate) mod local_collector; +pub(crate) mod local_span; +pub(crate) mod local_span_line; +pub(crate) mod local_span_stack; +pub(crate) mod raw_span; +pub(crate) mod span_queue; + +pub use self::local_collector::LocalCollector; +pub use self::local_collector::LocalSpans; +pub use self::local_span::LocalSpan; +pub use crate::span::LocalParentGuard; diff --git a/src/local/raw_span.rs b/src/local/raw_span.rs new file mode 100644 index 00000000..69038b37 --- /dev/null +++ b/src/local/raw_span.rs @@ -0,0 +1,46 @@ +// Copyright 2021 TiKV Project Authors. Licensed under Apache-2.0. + +use std::borrow::Cow; + +use minstant::Instant; + +use crate::collector::SpanId; + +#[derive(Clone, Debug)] +pub struct RawSpan { + pub id: SpanId, + pub parent_id: SpanId, + pub begin_instant: Instant, + pub name: &'static str, + pub properties: Vec<(Cow<'static, str>, Cow<'static, str>)>, + pub is_event: bool, + + // Will write this field at post processing + pub end_instant: Instant, +} + +impl RawSpan { + #[inline] + pub(crate) fn begin_with( + id: SpanId, + parent_id: SpanId, + begin_instant: Instant, + name: &'static str, + is_event: bool, + ) -> Self { + RawSpan { + id, + parent_id, + begin_instant, + name, + properties: vec![], + is_event, + end_instant: begin_instant, + } + } + + #[inline] + pub(crate) fn end_with(&mut self, end_instant: Instant) { + self.end_instant = end_instant; + } +} diff --git a/src/local/span_queue.rs b/src/local/span_queue.rs new file mode 100644 index 00000000..eff1dfd2 --- /dev/null +++ b/src/local/span_queue.rs @@ -0,0 +1,332 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use std::borrow::Cow; + +use minstant::Instant; + +use crate::collector::SpanId; +use crate::local::raw_span::RawSpan; +use crate::util::RawSpans; + +pub struct SpanQueue { + span_queue: RawSpans, + capacity: usize, + next_parent_id: Option, +} + +pub struct SpanHandle { + index: usize, +} + +impl SpanQueue { + pub fn with_capacity(capacity: usize) -> Self { + Self { + span_queue: RawSpans::default(), + capacity, + next_parent_id: None, + } + } + + #[inline] + pub fn start_span(&mut self, name: &'static str) -> Option { + if self.span_queue.len() >= self.capacity { + return None; + } + + let span = RawSpan::begin_with( + SpanId::next_id(), + self.next_parent_id.unwrap_or_default(), + Instant::now(), + name, + false, + ); + self.next_parent_id = Some(span.id); + + let index = self.span_queue.len(); + self.span_queue.push(span); + + Some(SpanHandle { index }) + } + + #[inline] + pub fn finish_span(&mut self, span_handle: SpanHandle) { + debug_assert!(span_handle.index < self.span_queue.len()); + debug_assert_eq!( + self.next_parent_id, + Some(self.span_queue[span_handle.index].id) + ); + + let span = &mut self.span_queue[span_handle.index]; + span.end_with(Instant::now()); + + self.next_parent_id = Some(span.parent_id).filter(|id| *id != SpanId::default()); + } + + #[inline] + pub fn add_event(&mut self, name: &'static str, properties: F) + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + if self.span_queue.len() >= self.capacity { + return; + } + + let mut span = RawSpan::begin_with( + SpanId::next_id(), + self.next_parent_id.unwrap_or_default(), + Instant::now(), + name, + true, + ); + span.properties.extend(properties()); + + self.span_queue.push(span); + } + + #[inline] + pub fn add_properties, Cow<'static, str>)>>( + &mut self, + span_handle: &SpanHandle, + properties: I, + ) { + debug_assert!(span_handle.index < self.span_queue.len()); + + let span = &mut self.span_queue[span_handle.index]; + span.properties.extend(properties); + } + + #[inline] + pub fn take_queue(self) -> RawSpans { + self.span_queue + } + + #[inline] + pub fn current_span_id(&self) -> Option { + self.next_parent_id + } + + #[cfg(test)] + pub fn get_raw_span(&self, handle: &SpanHandle) -> &RawSpan { + &self.span_queue[handle.index] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::tree::tree_str_from_raw_spans; + + #[test] + fn span_queue_basic() { + let mut queue = SpanQueue::with_capacity(16); + { + let span1 = queue.start_span("span1").unwrap(); + { + let span2 = queue.start_span("span2").unwrap(); + { + let span3 = queue.start_span("span3").unwrap(); + queue.finish_span(span3); + } + queue.finish_span(span2); + } + queue.finish_span(span1); + } + assert_eq!( + tree_str_from_raw_spans(queue.take_queue()), + r" +span1 [] + span2 [] + span3 [] +" + ); + } + + #[test] + fn span_add_properties() { + let mut queue = SpanQueue::with_capacity(16); + { + let span1 = queue.start_span("span1").unwrap(); + queue.add_properties(&span1, [ + ("k1".into(), "v1".into()), + ("k2".into(), "v2".into()), + ]); + { + let span2 = queue.start_span("span2").unwrap(); + queue.add_properties(&span2, [("k1".into(), "v1".into())]); + queue.finish_span(span2); + } + queue.finish_span(span1); + } + assert_eq!( + tree_str_from_raw_spans(queue.take_queue()), + r#" +span1 [("k1", "v1"), ("k2", "v2")] + span2 [("k1", "v1")] +"# + ); + } + + #[test] + fn span_not_finished() { + let mut queue = SpanQueue::with_capacity(16); + { + let _span1 = queue.start_span("span1").unwrap(); + { + let _span2 = queue.start_span("span2").unwrap(); + { + let _span3 = queue.start_span("span3").unwrap(); + } + } + } + assert_eq!( + tree_str_from_raw_spans(queue.take_queue()), + r" +span1 [] + span2 [] + span3 [] +" + ); + } + + #[test] + #[should_panic] + fn finish_span_out_of_order() { + let mut queue = SpanQueue::with_capacity(16); + let span1 = queue.start_span("span1").unwrap(); + let span2 = queue.start_span("span2").unwrap(); + queue.finish_span(span1); + queue.finish_span(span2); + } + + #[test] + fn span_queue_out_of_size() { + let mut queue = SpanQueue::with_capacity(4); + { + let span1 = queue.start_span("span1").unwrap(); + { + let span2 = queue.start_span("span2").unwrap(); + { + let span3 = queue.start_span("span3").unwrap(); + { + let span4 = queue.start_span("span4").unwrap(); + assert!(queue.start_span("span5").is_none()); + queue.finish_span(span4); + } + assert!(queue.start_span("span5").is_none()); + queue.finish_span(span3); + } + assert!(queue.start_span("span5").is_none()); + queue.finish_span(span2); + } + assert!(queue.start_span("span5").is_none()); + queue.finish_span(span1); + } + assert!(queue.start_span("span5").is_none()); + assert_eq!( + tree_str_from_raw_spans(queue.take_queue()), + r" +span1 [] + span2 [] + span3 [] + span4 [] +" + ); + } + + #[test] + fn last_span_id() { + let mut queue = SpanQueue::with_capacity(16); + + assert_eq!(queue.current_span_id(), None); + { + let span1 = queue.start_span("span1").unwrap(); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span1).id + ); + queue.finish_span(span1); + assert_eq!(queue.current_span_id(), None); + } + { + let span2 = queue.start_span("span2").unwrap(); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span2).id + ); + { + let span3 = queue.start_span("span3").unwrap(); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span3).id + ); + queue.finish_span(span3); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span2).id + ); + } + { + let span4 = queue.start_span("span4").unwrap(); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span4).id + ); + { + let span5 = queue.start_span("span5").unwrap(); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span5).id + ); + { + let span6 = queue.start_span("span6").unwrap(); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span6).id + ); + queue.finish_span(span6); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span5).id + ); + } + queue.finish_span(span5); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span4).id + ); + } + queue.finish_span(span4); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span2).id + ); + } + queue.finish_span(span2); + assert_eq!(queue.current_span_id(), None); + } + { + let span7 = queue.start_span("span7").unwrap(); + assert_eq!( + queue.current_span_id().unwrap(), + queue.get_raw_span(&span7).id + ); + queue.finish_span(span7); + assert_eq!(queue.current_span_id(), None); + } + assert_eq!( + tree_str_from_raw_spans(queue.take_queue()), + r" +span1 [] + +span2 [] + span3 [] + span4 [] + span5 [] + span6 [] + +span7 [] +" + ); + } +} diff --git a/src/span.rs b/src/span.rs new file mode 100644 index 00000000..df949a6d --- /dev/null +++ b/src/span.rs @@ -0,0 +1,897 @@ +// Copyright 2020 TiKV Project Authors. Licensed under Apache-2.0. + +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::Arc; + +use minstant::Instant; + +use crate::collector::global_collector::reporter_ready; +use crate::collector::CollectTokenItem; +use crate::collector::GlobalCollect; +use crate::collector::SpanContext; +use crate::collector::SpanId; +use crate::collector::SpanSet; +use crate::local::local_collector::LocalSpansInner; +use crate::local::local_span_stack::LocalSpanStack; +use crate::local::local_span_stack::LOCAL_SPAN_STACK; +use crate::local::raw_span::RawSpan; +use crate::local::LocalCollector; +use crate::local::LocalSpans; +use crate::util::CollectToken; + +/// A thread-safe span. +#[must_use] +#[derive(Default)] +pub struct Span { + #[cfg(feature = "enable")] + pub(crate) inner: Option, +} + +pub(crate) struct SpanInner { + pub(crate) raw_span: RawSpan, + collect_token: CollectToken, + // If the span is not a root span, this field is `None`. + collect_id: Option, + collect: GlobalCollect, +} + +impl Span { + /// Create a place-holder span that never starts recording. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let mut root = Span::noop(); + /// ``` + #[inline] + pub fn noop() -> Self { + Self { + #[cfg(feature = "enable")] + inner: None, + } + } + + /// Create a new trace and return its root span. + /// + /// Once dropped, the root span automatically submits all associated child spans to the reporter. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let mut root = Span::root("root", SpanContext::random()); + /// ``` + #[inline] + pub fn root( + name: &'static str, + parent: SpanContext, + #[cfg(test)] collect: GlobalCollect, + ) -> Self { + #[cfg(not(feature = "enable"))] + { + Self::noop() + } + + #[cfg(feature = "enable")] + { + if !reporter_ready() { + return Self::noop(); + } + + #[cfg(not(test))] + let collect = GlobalCollect; + let collect_id = collect.start_collect(); + let token = CollectTokenItem { + trace_id: parent.trace_id, + parent_id: parent.span_id, + collect_id, + is_root: true, + } + .into(); + Self::new(token, name, Some(collect_id), collect) + } + } + + /// Create a new child span associated with the specified parent span. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// + /// let child = Span::enter_with_parent("child", &root); + #[inline] + pub fn enter_with_parent(name: &'static str, parent: &Span) -> Self { + #[cfg(not(feature = "enable"))] + { + Self::noop() + } + + #[cfg(feature = "enable")] + { + match &parent.inner { + Some(_inner) => Self::enter_with_parents( + name, + [parent], + #[cfg(test)] + _inner.collect.clone(), + ), + None => Span::noop(), + } + } + } + + /// Create a new child span associated with multiple parent spans. + /// + /// This function is particularly useful when a single operation amalgamates multiple requests. + /// It enables the creation of a unique child span that is interconnected with all the parent spans + /// related to the requests, thereby obviating the need to generate individual child spans for each parent span. + /// + /// The newly created child span, and its children, will have a replica for each trace of parent spans. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let parent1 = Span::root("parent1", SpanContext::random()); + /// let parent2 = Span::root("parent2", SpanContext::random()); + /// + /// let child = Span::enter_with_parents("child", [&parent1, &parent2]); + #[inline] + pub fn enter_with_parents<'a>( + name: &'static str, + parents: impl IntoIterator, + #[cfg(test)] collect: GlobalCollect, + ) -> Self { + #[cfg(not(feature = "enable"))] + { + Self::noop() + } + + #[cfg(feature = "enable")] + { + #[cfg(not(test))] + let collect = GlobalCollect; + let token = parents + .into_iter() + .filter_map(|span| span.inner.as_ref()) + .flat_map(|inner| inner.issue_collect_token()) + .collect(); + Self::new(token, name, None, collect) + } + } + + /// Create a new child span associated with the current local span in the current thread. + /// + /// If no local span is active, this function returns a no-op span. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// let _g = root.set_local_parent(); + /// + /// let child = Span::enter_with_local_parent("child"); + /// ``` + #[inline] + pub fn enter_with_local_parent( + name: &'static str, + #[cfg(test)] collect: GlobalCollect, + ) -> Self { + #[cfg(not(feature = "enable"))] + { + Self::noop() + } + + #[cfg(feature = "enable")] + { + #[cfg(not(test))] + let collect = GlobalCollect; + LOCAL_SPAN_STACK + .try_with(move |stack| { + Self::enter_with_stack(name, &mut (*stack).borrow_mut(), collect) + }) + .unwrap_or_default() + } + } + + /// Sets the current `Span` as the local parent for the current thread. + /// + /// This method is used to establish a `Span` as the local parent within the current scope. + /// + /// A local parent is necessary for creating a [`LocalSpan`] using [`LocalSpan::enter_with_local_parent()`]. + /// If no local parent is set, `enter_with_local_parent()` will not perform any action. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()); + /// let _guard = root.set_local_parent(); // root is now the local parent + /// + /// // Now we can create a LocalSpan with root as the local parent. + /// let _span = LocalSpan::enter_with_local_parent("a child span"); + /// ``` + /// + /// [`LocalSpan`]: crate::local::LocalSpan + /// [`LocalSpan::enter_with_local_parent()`]: crate::local::LocalSpan::enter_with_local_parent + pub fn set_local_parent(&self) -> LocalParentGuard { + #[cfg(not(feature = "enable"))] + { + LocalParentGuard::noop() + } + + #[cfg(feature = "enable")] + { + LOCAL_SPAN_STACK + .try_with(|s| self.attach_into_stack(s)) + .unwrap_or_default() + } + } + + /// Add a single property to the `Span` and return the modified `Span`. + /// + /// A property is an arbitrary key-value pair associated with a span. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = + /// Span::root("root", SpanContext::random()).with_property(|| ("key".into(), "value".into())); + /// ``` + #[inline] + pub fn with_property(self, property: F) -> Self + where F: FnOnce() -> (Cow<'static, str>, Cow<'static, str>) { + self.with_properties(move || [property()]) + } + + /// Add multiple properties to the `Span` and return the modified `Span`. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let root = Span::root("root", SpanContext::random()).with_properties(|| { + /// vec![ + /// ("key1".into(), "value1".into()), + /// ("key2".into(), "value2".into()), + /// ] + /// }); + /// ``` + #[inline] + pub fn with_properties(mut self, properties: F) -> Self + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + #[cfg(feature = "enable")] + if let Some(inner) = self.inner.as_mut() { + inner.add_properties(properties); + } + + self + } + + /// Attach a collection of [`LocalSpan`] instances as child spans to the current span. + /// + /// This method allows you to associate previously collected `LocalSpan` instances with the current span. + /// This is particularly useful when the `LocalSpan` instances were initiated before their parent span, + /// and were collected manually using a [`LocalCollector`]. + /// + /// # Examples + /// + /// ``` + /// use minitrace::local::LocalCollector; + /// use minitrace::prelude::*; + /// + /// // Collect local spans manually without a parent + /// let collector = LocalCollector::start(); + /// let span = LocalSpan::enter_with_local_parent("a child span"); + /// drop(span); + /// let local_spans = collector.collect(); + /// + /// // Attach the local spans to a parent + /// let root = Span::root("root", SpanContext::random()); + /// root.push_child_spans(local_spans); + /// ``` + /// + /// [`LocalSpan`]: crate::local::LocalSpan + /// [`LocalSpans`]: crate::local::LocalSpans + /// [`LocalCollector`]: crate::local::LocalCollector + #[inline] + pub fn push_child_spans(&self, local_spans: LocalSpans) { + #[cfg(feature = "enable")] + { + if let Some(inner) = self.inner.as_ref() { + inner.push_child_spans(local_spans.inner) + } + } + } + + /// Dismisses the trace, preventing the reporting of any span records associated with it. + /// + /// This is particularly useful when focusing on the tail latency of a program. For instant, + /// you can dismiss all traces finishes within the 99th percentile. + /// + /// # Note + /// + /// This method only dismisses the entire trace when called on the root span. + /// If called on a non-root span, it will only cancel the reporting of that specific span. + /// + /// # Examples + /// + /// ``` + /// use minitrace::prelude::*; + /// + /// let mut root = Span::root("root", SpanContext::random()); + /// + /// root.cancel(); + /// ``` + #[inline] + pub fn cancel(&mut self) { + #[cfg(feature = "enable")] + if let Some(inner) = self.inner.take() { + if let Some(collect_id) = inner.collect_id { + inner.collect.drop_collect(collect_id); + } + } + } +} + +#[cfg(feature = "enable")] +impl Span { + #[inline] + fn new( + collect_token: CollectToken, + name: &'static str, + collect_id: Option, + collect: GlobalCollect, + ) -> Self { + let span_id = SpanId::next_id(); + let begin_instant = Instant::now(); + let raw_span = RawSpan::begin_with(span_id, SpanId::default(), begin_instant, name, false); + + Self { + inner: Some(SpanInner { + raw_span, + collect_token, + collect_id, + collect, + }), + } + } + + pub(crate) fn enter_with_stack( + name: &'static str, + stack: &mut LocalSpanStack, + collect: GlobalCollect, + ) -> Self { + match stack.current_collect_token() { + Some(token) => Span::new(token, name, None, collect), + None => Self::noop(), + } + } + + pub(crate) fn attach_into_stack( + &self, + stack: &Rc>, + ) -> LocalParentGuard { + self.inner + .as_ref() + .map(move |inner| inner.capture_local_spans(stack.clone())) + .unwrap_or_else(LocalParentGuard::noop) + } +} + +#[cfg(feature = "enable")] +impl SpanInner { + #[inline] + fn add_properties(&mut self, properties: F) + where + I: IntoIterator, Cow<'static, str>)>, + F: FnOnce() -> I, + { + for prop in properties() { + self.raw_span.properties.push(prop); + } + } + + #[inline] + fn capture_local_spans(&self, stack: Rc>) -> LocalParentGuard { + let token = self.issue_collect_token().collect(); + let collector = LocalCollector::new(Some(token), stack); + + LocalParentGuard::new(collector, self.collect.clone()) + } + + #[inline] + fn push_child_spans(&self, local_spans: Arc) { + if local_spans.spans.is_empty() { + return; + } + + self.collect.submit_spans( + SpanSet::SharedLocalSpans(local_spans), + self.issue_collect_token().collect(), + ); + } + + #[inline] + pub(crate) fn issue_collect_token(&self) -> impl Iterator + '_ { + self.collect_token + .iter() + .map(move |collect_item| CollectTokenItem { + trace_id: collect_item.trace_id, + parent_id: self.raw_span.id, + collect_id: collect_item.collect_id, + is_root: false, + }) + } + + #[inline] + pub(crate) fn submit_spans(self) { + self.collect + .submit_spans(SpanSet::Span(self.raw_span), self.collect_token); + } +} + +impl Drop for Span { + fn drop(&mut self) { + #[cfg(feature = "enable")] + if let Some(mut inner) = self.inner.take() { + let collect_id = inner.collect_id.take(); + let collect = inner.collect.clone(); + + let end_instant = Instant::now(); + inner.raw_span.end_with(end_instant); + inner.submit_spans(); + + if let Some(collect_id) = collect_id { + collect.commit_collect(collect_id); + } + } + } +} + +/// A guard created by [`Span::set_local_parent()`]. +#[derive(Default)] +pub struct LocalParentGuard { + #[cfg(feature = "enable")] + inner: Option, +} + +struct LocalParentGuardInner { + collector: LocalCollector, + collect: GlobalCollect, +} + +impl LocalParentGuard { + pub(crate) fn noop() -> LocalParentGuard { + LocalParentGuard { + #[cfg(feature = "enable")] + inner: None, + } + } + + pub(crate) fn new(collector: LocalCollector, collect: GlobalCollect) -> LocalParentGuard { + LocalParentGuard { + #[cfg(feature = "enable")] + inner: Some(LocalParentGuardInner { collector, collect }), + } + } +} + +impl Drop for LocalParentGuard { + fn drop(&mut self) { + #[cfg(feature = "enable")] + if let Some(inner) = self.inner.take() { + let (spans, token) = inner.collector.collect_spans_and_token(); + debug_assert!(token.is_some()); + if let Some(token) = token { + if !spans.spans.is_empty() { + inner + .collect + .submit_spans(SpanSet::LocalSpansInner(spans), token); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use std::sync::atomic::AtomicUsize; + use std::sync::atomic::Ordering; + use std::sync::Mutex; + + use mockall::predicate; + use mockall::Sequence; + use rand::seq::SliceRandom; + use rand::thread_rng; + + use super::*; + use crate::collector::ConsoleReporter; + use crate::collector::MockGlobalCollect; + use crate::local::LocalSpan; + use crate::prelude::TraceId; + use crate::util::tree::tree_str_from_span_sets; + + #[test] + fn noop_basic() { + let span = Span::noop(); + let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); + assert!(span.attach_into_stack(&stack).inner.is_none()); + assert!(stack.borrow_mut().enter_span("span1").is_none()); + } + + #[test] + fn root_collect() { + crate::set_reporter(ConsoleReporter, crate::collector::Config::default()); + + let mut mock = MockGlobalCollect::new(); + let mut seq = Sequence::new(); + mock.expect_start_collect() + .times(1) + .in_sequence(&mut seq) + .return_const(42_usize); + mock.expect_submit_spans() + .times(1) + .in_sequence(&mut seq) + .with( + predicate::always(), + predicate::eq::( + CollectTokenItem { + trace_id: TraceId(12), + parent_id: SpanId::default(), + collect_id: 42, + is_root: true, + } + .into(), + ), + ) + .return_const(()); + mock.expect_commit_collect() + .times(1) + .in_sequence(&mut seq) + .with(predicate::eq(42_usize)) + .return_const(()); + mock.expect_drop_collect().times(0); + + let mock: Arc = Arc::new(mock); + let _root = Span::root( + "root", + SpanContext::new(TraceId(12), SpanId::default()), + mock, + ); + } + + #[test] + fn root_cancel() { + crate::set_reporter(ConsoleReporter, crate::collector::Config::default()); + + let mut mock = MockGlobalCollect::new(); + let mut seq = Sequence::new(); + mock.expect_start_collect() + .times(1) + .in_sequence(&mut seq) + .return_const(42_usize); + mock.expect_drop_collect() + .times(1) + .in_sequence(&mut seq) + .with(predicate::eq(42_usize)) + .return_const(()); + mock.expect_commit_collect().times(0); + mock.expect_submit_spans().times(0); + + let mock = Arc::new(mock); + let mut root = Span::root("root", SpanContext::random(), mock); + root.cancel(); + } + + #[test] + fn span_with_parent() { + crate::set_reporter(ConsoleReporter, crate::collector::Config::default()); + + let routine = |collect| { + let parent_ctx = SpanContext::random(); + let root = Span::root("root", parent_ctx, collect); + let child1 = Span::enter_with_parent("child1", &root) + .with_properties(|| [("k1".into(), "v1".into())]); + let grandchild = Span::enter_with_parent("grandchild", &child1); + let child2 = Span::enter_with_parent("child2", &root); + + crossbeam::scope(move |scope| { + let mut rng = thread_rng(); + let mut spans = [child1, grandchild, child2]; + spans.shuffle(&mut rng); + for span in spans { + scope.spawn(|_| drop(span)); + } + }) + .unwrap(); + }; + + let mut mock = MockGlobalCollect::new(); + let mut seq = Sequence::new(); + let span_sets = Arc::new(Mutex::new(Vec::new())); + mock.expect_start_collect() + .times(1) + .in_sequence(&mut seq) + .return_const(42_usize); + mock.expect_submit_spans() + .times(4) + .in_sequence(&mut seq) + .withf(|_, collect_token| collect_token.len() == 1 && collect_token[0].collect_id == 42) + .returning({ + let span_sets = span_sets.clone(); + move |span_set, token| span_sets.lock().unwrap().push((span_set, token)) + }); + mock.expect_commit_collect() + .times(1) + .in_sequence(&mut seq) + .with(predicate::eq(42_usize)) + .return_const(()); + mock.expect_drop_collect().times(0); + + routine(Arc::new(mock)); + let span_sets = std::mem::take(&mut *span_sets.lock().unwrap()); + assert_eq!( + tree_str_from_span_sets(span_sets.as_slice()), + r#" +#42 +root [] + child1 [("k1", "v1")] + grandchild [] + child2 [] +"# + ); + } + + #[test] + fn span_with_parents() { + crate::set_reporter(ConsoleReporter, crate::collector::Config::default()); + + let routine = |collect: GlobalCollect| { + let parent_ctx = SpanContext::random(); + let parent1 = Span::root("parent1", parent_ctx, collect.clone()); + let parent2 = Span::root("parent2", parent_ctx, collect.clone()); + let parent3 = Span::root("parent3", parent_ctx, collect.clone()); + let parent4 = Span::root("parent4", parent_ctx, collect.clone()); + let parent5 = Span::root("parent5", parent_ctx, collect.clone()); + let child1 = Span::enter_with_parent("child1", &parent5); + let child2 = Span::enter_with_parents( + "child2", + [&parent1, &parent2, &parent3, &parent4, &parent5, &child1], + collect, + ) + .with_property(|| ("k1".into(), "v1".into())); + + crossbeam::scope(move |scope| { + let mut rng = thread_rng(); + let mut spans = [child1, child2]; + spans.shuffle(&mut rng); + for span in spans { + scope.spawn(|_| drop(span)); + } + }) + .unwrap(); + crossbeam::scope(move |scope| { + let mut rng = thread_rng(); + let mut spans = [parent1, parent2, parent3, parent4, parent5]; + spans.shuffle(&mut rng); + for span in spans { + scope.spawn(|_| drop(span)); + } + }) + .unwrap(); + }; + + let mut mock = MockGlobalCollect::new(); + let mut seq = Sequence::new(); + let span_sets = Arc::new(Mutex::new(Vec::new())); + mock.expect_start_collect() + .times(5) + .in_sequence(&mut seq) + .returning({ + let id = Arc::new(AtomicUsize::new(1)); + move || id.fetch_add(1, Ordering::SeqCst) + }); + mock.expect_submit_spans() + .times(7) + .in_sequence(&mut seq) + .returning({ + let span_sets = span_sets.clone(); + move |span_set, token| span_sets.lock().unwrap().push((span_set, token)) + }); + mock.expect_commit_collect() + .times(5) + .with(predicate::in_iter([1_usize, 2, 3, 4, 5])) + .return_const(()); + mock.expect_drop_collect().times(0); + + routine(Arc::new(mock)); + let span_sets = std::mem::take(&mut *span_sets.lock().unwrap()); + assert_eq!( + tree_str_from_span_sets(span_sets.as_slice()), + r#" +#1 +parent1 [] + child2 [("k1", "v1")] + +#2 +parent2 [] + child2 [("k1", "v1")] + +#3 +parent3 [] + child2 [("k1", "v1")] + +#4 +parent4 [] + child2 [("k1", "v1")] + +#5 +parent5 [] + child1 [] + child2 [("k1", "v1")] + child2 [("k1", "v1")] +"# + ); + } + + #[test] + fn span_push_child_spans() { + crate::set_reporter(ConsoleReporter, crate::collector::Config::default()); + + let routine = |collect: GlobalCollect| { + let parent_ctx = SpanContext::random(); + let parent1 = Span::root("parent1", parent_ctx, collect.clone()); + let parent2 = Span::root("parent2", parent_ctx, collect.clone()); + let parent3 = Span::root("parent3", parent_ctx, collect.clone()); + let parent4 = Span::root("parent4", parent_ctx, collect.clone()); + let parent5 = Span::root("parent5", parent_ctx, collect); + + let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); + let collector = LocalCollector::new(None, stack.clone()); + { + let _s = LocalSpan::enter_with_stack("child", stack); + } + let spans = collector.collect(); + + for parent in [&parent1, &parent2, &parent3, &parent4, &parent5] { + parent.push_child_spans(spans.clone()); + } + + crossbeam::scope(move |scope| { + let mut rng = thread_rng(); + let mut spans = [parent1, parent2, parent3, parent4, parent5]; + spans.shuffle(&mut rng); + for span in spans { + scope.spawn(|_| drop(span)); + } + }) + .unwrap(); + }; + + let mut mock = MockGlobalCollect::new(); + let mut seq = Sequence::new(); + let span_sets = Arc::new(Mutex::new(Vec::new())); + mock.expect_start_collect() + .times(5) + .in_sequence(&mut seq) + .returning({ + let id = Arc::new(AtomicUsize::new(1)); + move || id.fetch_add(1, Ordering::SeqCst) + }); + mock.expect_submit_spans() + .times(10) + .in_sequence(&mut seq) + .returning({ + let span_sets = span_sets.clone(); + move |span_set, token| span_sets.lock().unwrap().push((span_set, token)) + }); + mock.expect_commit_collect() + .times(5) + .with(predicate::in_iter([1_usize, 2, 3, 4, 5])) + .return_const(()); + mock.expect_drop_collect().times(0); + + routine(Arc::new(mock)); + let span_sets = std::mem::take(&mut *span_sets.lock().unwrap()); + assert_eq!( + tree_str_from_span_sets(span_sets.as_slice()), + r" +#1 +parent1 [] + child [] + +#2 +parent2 [] + child [] + +#3 +parent3 [] + child [] + +#4 +parent4 [] + child [] + +#5 +parent5 [] + child [] +" + ); + } + + #[test] + fn span_communicate_via_stack() { + crate::set_reporter(ConsoleReporter, crate::collector::Config::default()); + + let routine = |collect: GlobalCollect| { + let stack = Rc::new(RefCell::new(LocalSpanStack::with_capacity(16))); + + { + let parent_ctx = SpanContext::random(); + let root = Span::root("root", parent_ctx, collect.clone()); + let _g = root.attach_into_stack(&stack); + let child = + Span::enter_with_stack("child", &mut stack.borrow_mut(), collect.clone()); + { + let _g = child.attach_into_stack(&stack); + let _s = Span::enter_with_stack("grandchild", &mut stack.borrow_mut(), collect); + } + let _s = LocalSpan::enter_with_stack("local", stack); + } + }; + + let mut mock = MockGlobalCollect::new(); + let mut seq = Sequence::new(); + let span_sets = Arc::new(Mutex::new(Vec::new())); + mock.expect_start_collect() + .times(1) + .in_sequence(&mut seq) + .return_const(42_usize); + mock.expect_submit_spans() + .times(4) + .in_sequence(&mut seq) + .withf(|_, collect_token| collect_token.len() == 1 && collect_token[0].collect_id == 42) + .returning({ + let span_sets = span_sets.clone(); + move |span_set, token| span_sets.lock().unwrap().push((span_set, token)) + }); + mock.expect_commit_collect() + .times(1) + .in_sequence(&mut seq) + .with(predicate::eq(42_usize)) + .return_const(()); + mock.expect_drop_collect().times(0); + + routine(Arc::new(mock)); + let span_sets = std::mem::take(&mut *span_sets.lock().unwrap()); + assert_eq!( + tree_str_from_span_sets(span_sets.as_slice()), + r#" +#42 +root [] + child [] + grandchild [] + local [] +"# + ); + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 00000000..fd31a152 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,71 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +pub mod object_pool; +pub mod spsc; +#[doc(hidden)] +pub mod tree; + +use std::cell::RefCell; +use std::iter::FromIterator; + +use once_cell::sync::Lazy; + +use crate::collector::CollectTokenItem; +use crate::local::raw_span::RawSpan; +use crate::util::object_pool::Pool; +use crate::util::object_pool::Puller; +use crate::util::object_pool::Reusable; + +static RAW_SPANS_POOL: Lazy>> = Lazy::new(|| Pool::new(Vec::new, Vec::clear)); +static COLLECT_TOKEN_ITEMS_POOL: Lazy>> = + Lazy::new(|| Pool::new(Vec::new, Vec::clear)); + +thread_local! { + static RAW_SPANS_PULLER: RefCell>> = RefCell::new(RAW_SPANS_POOL.puller(512)); + static COLLECT_TOKEN_ITEMS_PULLER: RefCell>> = RefCell::new(COLLECT_TOKEN_ITEMS_POOL.puller(512)); +} + +pub type RawSpans = Reusable<'static, Vec>; +pub type CollectToken = Reusable<'static, Vec>; + +impl Default for RawSpans { + fn default() -> Self { + RAW_SPANS_PULLER + .try_with(|puller| puller.borrow_mut().pull()) + .unwrap_or_else(|_| Reusable::new(&*RAW_SPANS_POOL, vec![])) + } +} + +fn new_collect_token(items: impl IntoIterator) -> CollectToken { + let mut token = COLLECT_TOKEN_ITEMS_PULLER + .try_with(|puller| puller.borrow_mut().pull()) + .unwrap_or_else(|_| Reusable::new(&*COLLECT_TOKEN_ITEMS_POOL, vec![])); + token.extend(items); + token +} + +impl FromIterator for RawSpans { + fn from_iter>(iter: T) -> Self { + let mut raw_spans = RawSpans::default(); + raw_spans.extend(iter); + raw_spans + } +} + +impl FromIterator for CollectToken { + fn from_iter>(iter: T) -> Self { + new_collect_token(iter) + } +} + +impl<'a> FromIterator<&'a CollectTokenItem> for CollectToken { + fn from_iter>(iter: T) -> Self { + new_collect_token(iter.into_iter().copied()) + } +} + +impl From for CollectToken { + fn from(item: CollectTokenItem) -> Self { + new_collect_token([item]) + } +} diff --git a/src/util/object_pool.rs b/src/util/object_pool.rs new file mode 100644 index 00000000..8cf58750 --- /dev/null +++ b/src/util/object_pool.rs @@ -0,0 +1,135 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +use std::mem::ManuallyDrop; +use std::ops::Deref; +use std::ops::DerefMut; + +use parking_lot::Mutex; + +pub struct Pool { + objects: Mutex>, + init: fn() -> T, + reset: fn(&mut T), +} + +impl Pool { + #[inline] + pub fn new(init: fn() -> T, reset: fn(&mut T)) -> Pool { + Pool { + objects: Mutex::new(Vec::new()), + init, + reset, + } + } + + #[inline] + fn batch_pull<'a>(&'a self, n: usize, buffer: &mut Vec>) { + let mut objects = self.objects.lock(); + let len = objects.len(); + buffer.extend( + objects + .drain(len.saturating_sub(n)..) + .map(|obj| Reusable::new(self, obj)), + ); + drop(objects); + buffer.resize_with(n, || Reusable::new(self, (self.init)())); + } + + pub fn puller(&self, buffer_size: usize) -> Puller { + assert!(buffer_size > 0); + Puller { + pool: self, + buffer: Vec::with_capacity(buffer_size), + buffer_size, + } + } + + #[inline] + pub fn recycle(&self, mut obj: T) { + (self.reset)(&mut obj); + self.objects.lock().push(obj) + } +} + +pub struct Puller<'a, T> { + pool: &'a Pool, + buffer: Vec>, + buffer_size: usize, +} + +impl<'a, T> Puller<'a, T> { + #[inline] + pub fn pull(&mut self) -> Reusable<'a, T> { + self.buffer.pop().unwrap_or_else(|| { + self.pool.batch_pull(self.buffer_size, &mut self.buffer); + self.buffer.pop().unwrap() + }) + } +} + +pub struct Reusable<'a, T> { + pool: &'a Pool, + obj: ManuallyDrop, +} + +impl<'a, T> Reusable<'a, T> { + #[inline] + pub fn new(pool: &'a Pool, obj: T) -> Self { + Self { + pool, + obj: ManuallyDrop::new(obj), + } + } + + #[inline] + pub fn into_inner(mut self) -> T { + unsafe { + let obj = ManuallyDrop::take(&mut self.obj); + std::mem::forget(self); + obj + } + } +} + +impl<'a, T> std::fmt::Debug for Reusable<'a, T> +where T: std::fmt::Debug +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.obj.fmt(f) + } +} + +impl<'a, T> std::cmp::PartialEq for Reusable<'a, T> +where T: std::cmp::PartialEq +{ + fn eq(&self, other: &Self) -> bool { + T::eq(self, other) + } +} + +impl<'a, T> std::cmp::Eq for Reusable<'a, T> where T: std::cmp::Eq {} + +impl<'a, T> Deref for Reusable<'a, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.obj + } +} + +impl<'a, T> DerefMut for Reusable<'a, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.obj + } +} + +impl<'a, T> Drop for Reusable<'a, T> { + #[inline] + fn drop(&mut self) { + unsafe { + self.pool.recycle(ManuallyDrop::take(&mut self.obj)); + } + } +} diff --git a/src/util/spsc.rs b/src/util/spsc.rs new file mode 100644 index 00000000..d7b57c66 --- /dev/null +++ b/src/util/spsc.rs @@ -0,0 +1,75 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +use std::sync::Arc; + +use parking_lot::Mutex; + +pub fn bounded(capacity: usize) -> (Sender, Receiver) { + let page = Arc::new(Mutex::new(Vec::with_capacity(capacity))); + ( + Sender { + page: page.clone(), + capacity, + }, + Receiver { + page, + received: Vec::with_capacity(capacity), + }, + ) +} + +pub struct Sender { + page: Arc>>, + capacity: usize, +} + +pub struct Receiver { + page: Arc>>, + received: Vec, +} + +#[derive(Debug)] +pub struct ChannelFull; + +#[derive(Debug)] +pub struct ChannelClosed; + +impl Sender { + pub fn send(&self, value: T) -> Result<(), ChannelFull> { + let mut page = self.page.lock(); + if page.len() < self.capacity { + page.push(value); + Ok(()) + } else { + Err(ChannelFull) + } + } + + pub fn force_send(&self, value: T) { + let mut page = self.page.lock(); + page.push(value); + } +} + +impl Receiver { + pub fn try_recv(&mut self) -> Result, ChannelClosed> { + match self.received.pop() { + Some(val) => Ok(Some(val)), + None => { + let mut page = self.page.lock(); + std::mem::swap(&mut *page, &mut self.received); + match self.received.pop() { + Some(val) => Ok(Some(val)), + None => { + let is_disconnected = Arc::strong_count(&self.page) < 2; + if is_disconnected { + Err(ChannelClosed) + } else { + Ok(None) + } + } + } + } + } + } +} diff --git a/src/util/tree.rs b/src/util/tree.rs new file mode 100644 index 00000000..ffbe54c2 --- /dev/null +++ b/src/util/tree.rs @@ -0,0 +1,259 @@ +// Copyright 2022 TiKV Project Authors. Licensed under Apache-2.0. + +//! A module for relationship checking in test + +use std::borrow::Cow; +use std::collections::HashMap; +use std::fmt::Display; +use std::fmt::Formatter; + +use crate::collector::SpanId; +use crate::collector::SpanRecord; +use crate::collector::SpanSet; +use crate::util::CollectToken; +use crate::util::RawSpans; + +#[derive(Debug, PartialOrd, PartialEq, Ord, Eq)] +pub struct Tree { + name: &'static str, + children: Vec, + properties: Vec<(Cow<'static, str>, Cow<'static, str>)>, +} + +impl Display for Tree { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.fmt_with_depth(f, 0) + } +} + +impl Tree { + fn fmt_with_depth(&self, f: &mut Formatter<'_>, depth: usize) -> std::fmt::Result { + writeln!( + f, + "{:indent$}{} {:?}", + "", + self.name, + self.properties, + indent = depth * 4 + )?; + for child in &self.children { + child.fmt_with_depth(f, depth + 1)?; + } + Ok(()) + } +} + +impl Tree { + pub fn sort(&mut self) { + for child in &mut self.children { + child.sort(); + } + self.children.as_mut_slice().sort_unstable(); + self.properties.as_mut_slice().sort_unstable(); + } + + pub fn from_raw_spans(raw_spans: RawSpans) -> Vec { + let mut children = HashMap::new(); + + let spans = raw_spans.into_inner(); + children.insert(SpanId::default(), ("", vec![], vec![])); + for span in &spans { + children.insert(span.id, (span.name, vec![], span.properties.clone())); + } + for span in &spans { + children + .get_mut(&span.parent_id) + .as_mut() + .unwrap() + .1 + .push(span.id); + } + + let mut t = Self::build_tree(SpanId::default(), &mut children); + t.sort(); + t.children + } + + /// Return a vector of collect id -> Tree + pub fn from_span_sets(span_sets: &[(SpanSet, CollectToken)]) -> Vec<(usize, Tree)> { + let mut collect = HashMap::< + usize, + HashMap< + SpanId, + ( + &'static str, + Vec, + Vec<(Cow<'static, str>, Cow<'static, str>)>, + ), + >, + >::new(); + for (span_set, token) in span_sets { + for item in token.iter() { + collect + .entry(item.collect_id) + .or_default() + .insert(SpanId::default(), ("", vec![], vec![])); + match span_set { + SpanSet::Span(span) => { + collect + .entry(item.collect_id) + .or_default() + .insert(span.id, (span.name, vec![], span.properties.clone())); + } + SpanSet::LocalSpansInner(spans) => { + for span in spans.spans.iter() { + collect + .entry(item.collect_id) + .or_default() + .insert(span.id, (span.name, vec![], span.properties.clone())); + } + } + SpanSet::SharedLocalSpans(spans) => { + for span in spans.spans.iter() { + collect + .entry(item.collect_id) + .or_default() + .insert(span.id, (span.name, vec![], span.properties.clone())); + } + } + } + } + } + + for (span_set, token) in span_sets { + for item in token.iter() { + match span_set { + SpanSet::Span(span) => { + let parent_id = if span.parent_id == SpanId::default() { + item.parent_id + } else { + span.parent_id + }; + collect + .get_mut(&item.collect_id) + .as_mut() + .unwrap() + .get_mut(&parent_id) + .as_mut() + .unwrap() + .1 + .push(span.id); + } + SpanSet::LocalSpansInner(spans) => { + for span in spans.spans.iter() { + let parent_id = if span.parent_id == SpanId::default() { + item.parent_id + } else { + span.parent_id + }; + collect + .get_mut(&item.collect_id) + .as_mut() + .unwrap() + .get_mut(&parent_id) + .as_mut() + .unwrap() + .1 + .push(span.id); + } + } + SpanSet::SharedLocalSpans(spans) => { + for span in spans.spans.iter() { + let parent_id = if span.parent_id == SpanId::default() { + item.parent_id + } else { + span.parent_id + }; + collect + .get_mut(&item.collect_id) + .as_mut() + .unwrap() + .get_mut(&parent_id) + .as_mut() + .unwrap() + .1 + .push(span.id); + } + } + } + } + } + + let mut res = collect + .into_iter() + .map(|(id, mut children)| { + let mut tree = Self::build_tree(SpanId::default(), &mut children); + tree.sort(); + assert_eq!(tree.children.len(), 1); + (id, tree.children.pop().unwrap()) + }) + .collect::>(); + res.sort_unstable(); + res + } + + pub fn from_span_records(span_records: Vec) -> Tree { + let mut children = HashMap::new(); + + children.insert(SpanId::default(), ("", vec![], vec![])); + for span in &span_records { + children.insert(span.span_id, (span.name, vec![], span.properties.clone())); + } + for span in &span_records { + children + .get_mut(&span.parent_id) + .as_mut() + .unwrap() + .1 + .push(span.span_id); + } + + let mut t = Self::build_tree(SpanId::default(), &mut children); + t.sort(); + assert_eq!(t.children.len(), 1); + t.children.remove(0) + } + + #[allow(clippy::type_complexity)] + fn build_tree( + id: SpanId, + raw: &mut HashMap< + SpanId, + ( + &'static str, + Vec, + Vec<(Cow<'static, str>, Cow<'static, str>)>, + ), + >, + ) -> Tree { + let (name, children, properties) = raw.get(&id).cloned().unwrap(); + Tree { + name, + children: children + .into_iter() + .map(|id| Self::build_tree(id, raw)) + .collect(), + properties, + } + } +} + +pub fn tree_str_from_raw_spans(raw_spans: RawSpans) -> String { + Tree::from_raw_spans(raw_spans) + .iter() + .map(|t| format!("\n{}", t)) + .collect::>() + .join("") +} + +pub fn tree_str_from_span_sets(span_sets: &[(SpanSet, CollectToken)]) -> String { + Tree::from_span_sets(span_sets) + .iter() + .map(|(id, t)| format!("\n#{}\n{}", id, t)) + .collect::>() + .join("") +} + +pub fn tree_str_from_span_records(span_records: Vec) -> String { + format!("\n{}", Tree::from_span_records(span_records)) +} diff --git a/test-no-report/Cargo.toml b/test-no-report/Cargo.toml index 04ca22ff..06fd71f5 100644 --- a/test-no-report/Cargo.toml +++ b/test-no-report/Cargo.toml @@ -8,4 +8,4 @@ readme = "README.md" publish = false [dependencies] -minitrace = { path = "../minitrace" } +minitrace = { path = "../" } diff --git a/test-utilities/Cargo.toml b/test-utilities/Cargo.toml new file mode 100644 index 00000000..182aadf8 --- /dev/null +++ b/test-utilities/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "test-utilities" +version = "0.0.0" +description = "Utilities for Minitrace development" +license = "MIT OR Apache-2.0" +rust-version = "1.56.0" +edition = "2021" + +[lib] +doctest = false + +# Avoid adding crates, this is widely used in tests. It should compile fast! +[dependencies] +dissimilar = "1.0.2" +minitrace = { path = "../" } +regex = "1.5.5" diff --git a/test-utilities/src/lib.rs b/test-utilities/src/lib.rs new file mode 100644 index 00000000..92423b8a --- /dev/null +++ b/test-utilities/src/lib.rs @@ -0,0 +1,92 @@ +//! Assorted testing utilities. +//! +//! Most notable: +//! +//! * `assert_eq_text!`: Rich text comparison, which outputs a diff then panics. + +pub use dissimilar::diff as __diff; + +/// Asserts two strings are equal, otherwise sends a diff to stderr then panics. +/// +/// The rich diff shows changes from the "original" left string to the "actual" +/// right string. +/// +/// All arguments starting from and including the 3rd one are passed to +/// `eprintln!()` macro in case of text inequality. +/// +/// # Panics +/// +/// The macro will panic in case of text inequality. +/// +/// # License +/// +/// SPDX-License-Identifier: Apache-2.0 OR MIT +/// Copyright 2022 rust-analyzer project authors +/// +#[macro_export] +macro_rules! assert_eq_text { + ($left:expr, $right:expr) => { + assert_eq_text!($left, $right,) + }; + ($left:expr, $right:expr, $($tt:tt)*) => {{ + let left = $left; + let right = $right; + if left != right { + if left.trim() == right.trim() { + std::eprintln!("Left:\n{:?}\n\nRight:\n{:?}\n\nWhitespace difference\n", left, right); + } else { + let diff = $crate::__diff(left, right); + std::eprintln!("Left:\n{}\n\nRight:\n{}\n\nDiff:\n{}\n", left, right, $crate::format_diff(diff)); + } + std::eprintln!($($tt)*); + panic!("text differs"); + } + }}; +} + +pub fn format_diff(chunks: Vec) -> String { + let mut buf = String::new(); + for chunk in chunks { + let formatted = match chunk { + dissimilar::Chunk::Equal(text) => text.into(), + dissimilar::Chunk::Delete(text) => format!("\x1b[41m{}\x1b[0m", text), + dissimilar::Chunk::Insert(text) => format!("\x1b[42m{}\x1b[0m", text), + }; + buf.push_str(&formatted); + } + buf +} + +pub fn normalize_spans(records: R) -> std::string::String +where + S: Sized, + R: AsRef<[S]> + std::fmt::Debug, +{ + let pre = format!("{records:#?}"); + let re1 = regex::Regex::new(r"begin_unix_time_ns: \d+,").unwrap(); + let re2 = regex::Regex::new(r"duration_ns: \d+,").unwrap(); + let int: std::string::String = re1.replace_all(&pre, r"begin_unix_time_ns: \d+,").into(); + let norm: std::string::String = re2.replace_all(&int, r"duration_ns: \d+,").into(); + norm +} + +pub fn normalize_async_spans(records: R) -> std::string::String +where + S: Sized, + R: AsRef<[S]> + std::fmt::Debug, +{ + let pre = format!("{records:#?}"); + let re1 = regex::Regex::new(r"begin_unix_time_ns: \d+,").unwrap(); + let re2 = regex::Regex::new(r"duration_ns: \d+,").unwrap(); + let re3 = regex::Regex::new(r"id: \d+,").unwrap(); + let re4 = regex::Regex::new(r"parent_id: \d+,").unwrap(); + let re5 = regex::Regex::new(r#"event: ".*","#).unwrap(); + let re6 = regex::Regex::new(r"properties: \[?(.|\n)*?\],").unwrap(); + let time: std::string::String = re1.replace_all(&pre, r"begin_unix_time_ns: \d+,").into(); + let dur: std::string::String = re2.replace_all(&time, r"duration_ns: \d+,").into(); + let id: std::string::String = re3.replace_all(&dur, r"id: \d+,").into(); + let event: std::string::String = re4.replace_all(&id, r"parent_id: \d+,").into(); + let props: std::string::String = re5.replace_all(&event, r#"event: "...","#).into(); + let norm: std::string::String = re6.replace_all(&props, r"properties: [ ... ],").into(); + norm +} diff --git a/minitrace/tests/lib.rs b/tests/lib.rs similarity index 100% rename from minitrace/tests/lib.rs rename to tests/lib.rs From 01bdf8a2a0d409710cab1960a2233ca57fe09fe3 Mon Sep 17 00:00:00 2001 From: Mark Van de Vyver Date: Wed, 13 Sep 2023 13:59:22 +1000 Subject: [PATCH 2/7] [Evolve][Issue] Resolve issue 113 Close issue #113. Signed-off-by: Mark Van de Vyver --- rustfmt.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 60f47031..757638be 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -8,6 +8,3 @@ trailing_comma = "Vertical" overflow_delimited_expr = true format_code_in_doc_comments = true normalize_comments = true -ignore = [ - "minitrace-tests/embassy/*", -] From 23422fbb3aa1e2e41e6943dc441fdeabb271b461 Mon Sep 17 00:00:00 2001 From: Mark Van de Vyver Date: Wed, 13 Sep 2023 14:07:45 +1000 Subject: [PATCH 3/7] [Add][Test] Linting Signed-off-by: Mark Van de Vyver --- examples/unit_test.rs | 4 +- minitrace-tests/integration/tests/defaults.rs | 9 ++- src/collector/global_collector.rs | 12 +-- src/local/local_span.rs | 4 +- src/local/local_span_line.rs | 31 ++++---- src/local/local_span_stack.rs | 76 +++++++++---------- src/local/span_queue.rs | 8 +- src/span.rs | 4 +- src/util/object_pool.rs | 6 +- 9 files changed, 81 insertions(+), 73 deletions(-) diff --git a/examples/unit_test.rs b/examples/unit_test.rs index 795ff3ce..7850a667 100644 --- a/examples/unit_test.rs +++ b/examples/unit_test.rs @@ -29,7 +29,9 @@ mod test_util { use super::*; pub fn setup_minitrace(test: F) - where F: FnOnce() -> Result<()> + 'static { + where + F: FnOnce() -> Result<()> + 'static, + { minitrace::set_reporter(ConsoleReporter, Config::default()); { let root = Span::root(closure_name::(), SpanContext::random()); diff --git a/minitrace-tests/integration/tests/defaults.rs b/minitrace-tests/integration/tests/defaults.rs index 4beb0d95..e202fb7c 100644 --- a/minitrace-tests/integration/tests/defaults.rs +++ b/minitrace-tests/integration/tests/defaults.rs @@ -6,8 +6,8 @@ // - `dev`: Generate all missing `*.expanded.rs` files, and flag changes. // - `ci`: Generate nothing, and fail on mismatches. // -use crate::tests::*; //IntegrationTest; use super::IntegrationTest; +use crate::tests::*; //IntegrationTest; inventory::submit!(IntegrationTest { name: "indev", @@ -25,11 +25,12 @@ pub fn indev() { macrotest::expand_args( src, &[ - "--features", "minitrace-tests/default minitrace-tests/as", + "--features", + "minitrace-tests/default minitrace-tests/as", "--manifest-path", "./Cargo.toml", - ], - ); + ], + ); #[cfg(feature = "tk")] macrotest::expand_args( src, diff --git a/src/collector/global_collector.rs b/src/collector/global_collector.rs index fdb34fb1..9a19caf1 100644 --- a/src/collector/global_collector.rs +++ b/src/collector/global_collector.rs @@ -202,14 +202,10 @@ impl GlobalCollector { std::thread::Builder::new() .name("minitrace-global-collector".to_string()) - .spawn(move || { - loop { - let begin_instant = std::time::Instant::now(); - GLOBAL_COLLECTOR.lock().handle_commands(false); - std::thread::sleep( - COLLECT_LOOP_INTERVAL.saturating_sub(begin_instant.elapsed()), - ); - } + .spawn(move || loop { + let begin_instant = std::time::Instant::now(); + GLOBAL_COLLECTOR.lock().handle_commands(false); + std::thread::sleep(COLLECT_LOOP_INTERVAL.saturating_sub(begin_instant.elapsed())); }) .unwrap(); diff --git a/src/local/local_span.rs b/src/local/local_span.rs index 9c14ff78..9917a0ce 100644 --- a/src/local/local_span.rs +++ b/src/local/local_span.rs @@ -68,7 +68,9 @@ impl LocalSpan { /// ``` #[inline] pub fn with_property(self, property: F) -> Self - where F: FnOnce() -> (Cow<'static, str>, Cow<'static, str>) { + where + F: FnOnce() -> (Cow<'static, str>, Cow<'static, str>), + { self.with_properties(|| [property()]) } diff --git a/src/local/local_span_line.rs b/src/local/local_span_line.rs index 2f12a860..c593b986 100644 --- a/src/local/local_span_line.rs +++ b/src/local/local_span_line.rs @@ -153,20 +153,23 @@ span1 [] let span = span_line.start_span("span").unwrap(); let current_token = span_line.current_collect_token().unwrap(); assert_eq!(current_token.len(), 2); - assert_eq!(current_token.as_slice(), &[ - CollectTokenItem { - trace_id: TraceId(1234), - parent_id: span_line.span_queue.current_span_id().unwrap(), - collect_id: 42, - is_root: false, - }, - CollectTokenItem { - trace_id: TraceId(1235), - parent_id: span_line.span_queue.current_span_id().unwrap(), - collect_id: 43, - is_root: false, - } - ]); + assert_eq!( + current_token.as_slice(), + &[ + CollectTokenItem { + trace_id: TraceId(1234), + parent_id: span_line.span_queue.current_span_id().unwrap(), + collect_id: 42, + is_root: false, + }, + CollectTokenItem { + trace_id: TraceId(1235), + parent_id: span_line.span_queue.current_span_id().unwrap(), + collect_id: 43, + is_root: false, + } + ] + ); span_line.finish_span(span); let current_token = span_line.current_collect_token().unwrap(); diff --git a/src/local/local_span_stack.rs b/src/local/local_span_stack.rs index 5dc7952b..975618e7 100644 --- a/src/local/local_span_stack.rs +++ b/src/local/local_span_stack.rs @@ -217,19 +217,17 @@ span1 [] { let span_line4 = span_stack.register_span_line(None).unwrap(); { - assert!( - span_stack - .register_span_line(Some( - CollectTokenItem { - trace_id: TraceId(1235), - parent_id: SpanId::default(), - collect_id: 43, - is_root: false, - } - .into() - )) - .is_none() - ); + assert!(span_stack + .register_span_line(Some( + CollectTokenItem { + trace_id: TraceId(1235), + parent_id: SpanId::default(), + collect_id: 43, + is_root: false, + } + .into() + )) + .is_none()); assert!(span_stack.register_span_line(None).is_none()); } let _ = span_stack.unregister_and_collect(span_line4).unwrap(); @@ -237,19 +235,17 @@ span1 [] { let span_line5 = span_stack.register_span_line(None).unwrap(); { - assert!( - span_stack - .register_span_line(Some( - CollectTokenItem { - trace_id: TraceId(1236), - parent_id: SpanId::default(), - collect_id: 44, - is_root: false, - } - .into() - )) - .is_none() - ); + assert!(span_stack + .register_span_line(Some( + CollectTokenItem { + trace_id: TraceId(1236), + parent_id: SpanId::default(), + collect_id: 44, + is_root: false, + } + .into() + )) + .is_none()); assert!(span_stack.register_span_line(None).is_none()); } let _ = span_stack.unregister_and_collect(span_line5).unwrap(); @@ -272,9 +268,10 @@ span1 [] is_root: false, }; let span_line1 = span_stack.register_span_line(Some(token1.into())).unwrap(); - assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ - token1 - ]); + assert_eq!( + span_stack.current_collect_token().unwrap().as_slice(), + &[token1] + ); { let span_line2 = span_stack.register_span_line(None).unwrap(); assert!(span_stack.current_collect_token().is_none()); @@ -286,9 +283,10 @@ span1 [] is_root: false, }; let span_line3 = span_stack.register_span_line(Some(token3.into())).unwrap(); - assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ - token3 - ]); + assert_eq!( + span_stack.current_collect_token().unwrap().as_slice(), + &[token3] + ); let _ = span_stack.unregister_and_collect(span_line3).unwrap(); } assert!(span_stack.current_collect_token().is_none()); @@ -301,14 +299,16 @@ span1 [] is_root: false, }; let span_line4 = span_stack.register_span_line(Some(token4.into())).unwrap(); - assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ - token4 - ]); + assert_eq!( + span_stack.current_collect_token().unwrap().as_slice(), + &[token4] + ); let _ = span_stack.unregister_and_collect(span_line4).unwrap(); } - assert_eq!(span_stack.current_collect_token().unwrap().as_slice(), &[ - token1 - ]); + assert_eq!( + span_stack.current_collect_token().unwrap().as_slice(), + &[token1] + ); let _ = span_stack.unregister_and_collect(span_line1).unwrap(); assert!(span_stack.current_collect_token().is_none()); } diff --git a/src/local/span_queue.rs b/src/local/span_queue.rs index eff1dfd2..ebf387de 100644 --- a/src/local/span_queue.rs +++ b/src/local/span_queue.rs @@ -147,10 +147,10 @@ span1 [] let mut queue = SpanQueue::with_capacity(16); { let span1 = queue.start_span("span1").unwrap(); - queue.add_properties(&span1, [ - ("k1".into(), "v1".into()), - ("k2".into(), "v2".into()), - ]); + queue.add_properties( + &span1, + [("k1".into(), "v1".into()), ("k2".into(), "v2".into())], + ); { let span2 = queue.start_span("span2").unwrap(); queue.add_properties(&span2, [("k1".into(), "v1".into())]); diff --git a/src/span.rs b/src/span.rs index df949a6d..ceb9ab7e 100644 --- a/src/span.rs +++ b/src/span.rs @@ -254,7 +254,9 @@ impl Span { /// ``` #[inline] pub fn with_property(self, property: F) -> Self - where F: FnOnce() -> (Cow<'static, str>, Cow<'static, str>) { + where + F: FnOnce() -> (Cow<'static, str>, Cow<'static, str>), + { self.with_properties(move || [property()]) } diff --git a/src/util/object_pool.rs b/src/util/object_pool.rs index 8cf58750..a206edfb 100644 --- a/src/util/object_pool.rs +++ b/src/util/object_pool.rs @@ -92,7 +92,8 @@ impl<'a, T> Reusable<'a, T> { } impl<'a, T> std::fmt::Debug for Reusable<'a, T> -where T: std::fmt::Debug +where + T: std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.obj.fmt(f) @@ -100,7 +101,8 @@ where T: std::fmt::Debug } impl<'a, T> std::cmp::PartialEq for Reusable<'a, T> -where T: std::cmp::PartialEq +where + T: std::cmp::PartialEq, { fn eq(&self, other: &Self) -> bool { T::eq(self, other) From 628f1708e02ea55eff8485283916644b4e2af3dd Mon Sep 17 00:00:00 2001 From: Mark Van de Vyver Date: Wed, 13 Sep 2023 15:23:49 +1000 Subject: [PATCH 4/7] [Add][Test] Suspend Signed-off-by: Mark Van de Vyver --- minitrace-macro/tests/expand.rs | 7 ++++ minitrace-macro/tests/spans.rs | 2 + minitrace-macro/tests/trace-dev.rs | 2 + minitrace-macro/tests/trace.rs | 62 ++++++++++++++++++++++++++++++ minitrace-tests/Cargo.toml | 14 ++++--- minitrace-tests/README.md | 2 +- 6 files changed, 82 insertions(+), 7 deletions(-) diff --git a/minitrace-macro/tests/expand.rs b/minitrace-macro/tests/expand.rs index cf689cc5..0f2a4fc6 100644 --- a/minitrace-macro/tests/expand.rs +++ b/minitrace-macro/tests/expand.rs @@ -1,4 +1,6 @@ #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 pub fn expand_defaults_dev() { // To generate macro result files macrotest::expand("tests/expand/defaults/*.rs"); @@ -6,6 +8,8 @@ pub fn expand_defaults_dev() { #[test] #[cfg(feature = "ci")] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 pub fn expand_defaults_ci() { // To test generated macro result files macrotest::expand_without_refresh("tests/expand/defaults/*.rs"); @@ -13,6 +17,7 @@ pub fn expand_defaults_ci() { #[test] #[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 pub fn expand_non_defaults_dev() { // To generate macro result files macrotest::expand("tests/expand/non-defaults/*.rs"); @@ -20,6 +25,8 @@ pub fn expand_non_defaults_dev() { #[test] #[cfg(feature = "ci")] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 pub fn expand_non_defaults_ci() { // To test generated macro result files macrotest::expand_without_refresh("tests/expand/non-defaults/*.rs"); diff --git a/minitrace-macro/tests/spans.rs b/minitrace-macro/tests/spans.rs index daf423c8..0fb20681 100644 --- a/minitrace-macro/tests/spans.rs +++ b/minitrace-macro/tests/spans.rs @@ -1,4 +1,6 @@ #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn spans() { let t = trybuild::TestCases::new(); t.pass("tests/spans/*.rs"); diff --git a/minitrace-macro/tests/trace-dev.rs b/minitrace-macro/tests/trace-dev.rs index 724edeae..3f0584a5 100644 --- a/minitrace-macro/tests/trace-dev.rs +++ b/minitrace-macro/tests/trace-dev.rs @@ -1,12 +1,14 @@ // Useful while working on specific test cases #[test] #[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn trace_err_dev() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/006-has-too-many-arguments.rs"); } #[test] #[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn trace_ok_dev() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/00-has-no-arguments.rs"); diff --git a/minitrace-macro/tests/trace.rs b/minitrace-macro/tests/trace.rs index 99d7edd1..c949bb80 100644 --- a/minitrace-macro/tests/trace.rs +++ b/minitrace-macro/tests/trace.rs @@ -1,157 +1,219 @@ #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn errors() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_001() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/001-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_002() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/002-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_003() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/003-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_004() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/004-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_005() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/005-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_006() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/006-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_007() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/007-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_008() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/008-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_009() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/009-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_010() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/010-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_011() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/011-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_012() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/012-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_013() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/013-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_014() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/014-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_015() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/015-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_016() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/016-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_017() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/017-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_018() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/018-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn error_019() { let t = trybuild::TestCases::new(); t.compile_fail("tests/trace/ui/err/019-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn oks() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_001() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/001-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_002() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/002-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_003() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/003-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_004() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/004-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_005() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/005-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_006() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/006-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_007() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/007-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_008() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/008-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_009() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/009-*.rs"); } #[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn ok_010() { let t = trybuild::TestCases::new(); t.pass("tests/trace/ui/ok/010-*.rs"); diff --git a/minitrace-tests/Cargo.toml b/minitrace-tests/Cargo.toml index fca2ee8d..beb21000 100644 --- a/minitrace-tests/Cargo.toml +++ b/minitrace-tests/Cargo.toml @@ -33,13 +33,15 @@ inventory = "0.2.2" # NOTE: # Features are only recognized via the command line in the following tests. # -# cargo test --features minitrace-tests/tk --manifest-path minitrace-tests/Cargo.toml indev-tokio -- --nocapture +# cargo test --features "minitrace-tests/default minitrace-tests/tk" \ +# --manifest-path minitrace-tests/Cargo.toml \ +# indev-tokio -- --nocapture # -[[test]] -name = "indev-tokio" -path = "integration/indev.rs" -harness = false -required-features = ["default", "tk"] +# [[test]] +# name = "indev-tokio" +# path = "integration/indev.rs" +# harness = false +# required-features = ["default", "tk"] # [[test]] # name = "dev-tokio" diff --git a/minitrace-tests/README.md b/minitrace-tests/README.md index 5e9bed90..7f18e6a3 100644 --- a/minitrace-tests/README.md +++ b/minitrace-tests/README.md @@ -37,7 +37,7 @@ Thanks to the following for [how to do this](https://www.infinyon.com/blog/2021/ 4. Run the single `indev` test case: ```bash - cargo test --features "default minitrace-tests/tk" --manifest-path minitrace-tests/Cargo.toml \ + cargo test --features "minitrace-tests/default minitrace-tests/tk" --manifest-path minitrace-tests/Cargo.toml \ indev-tokio \ -- --nocapture ``` From 21622cd71d055fbdc51a5f83a6fb88ce9b833579 Mon Sep 17 00:00:00 2001 From: Mark Van de Vyver Date: Wed, 13 Sep 2023 15:33:54 +1000 Subject: [PATCH 5/7] [Add][Test] Suspend Signed-off-by: Mark Van de Vyver --- minitrace-macro/tests/expand.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/minitrace-macro/tests/expand.rs b/minitrace-macro/tests/expand.rs index 0f2a4fc6..fc7425bd 100644 --- a/minitrace-macro/tests/expand.rs +++ b/minitrace-macro/tests/expand.rs @@ -43,7 +43,9 @@ pub fn expand_issue_001_dev() { build_issues_dev(); } -#[cfg(not(feature = "ci"))] +// #[cfg(not(feature = "ci"))] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 fn build_issues_dev() { let t = trybuild::TestCases::new(); t.pass("tests/expand/issues/*.expanded.rs"); @@ -51,6 +53,7 @@ fn build_issues_dev() { #[test] #[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 #[cfg(feature = "ci")] pub fn issues_ci() { // To test generated macro result files @@ -58,6 +61,9 @@ pub fn issues_ci() { build_issues_ci(); } +#[test] +#[ignore] +// Integration tests to be migrated once PR #127 lands and closes #113 #[cfg(feature = "ci")] fn build_issues_ci() { let t = trybuild::TestCases::new(); From 72484c11e5c7915ebe39fec78c73968dfa9991df Mon Sep 17 00:00:00 2001 From: Mark Van de Vyver Date: Wed, 13 Sep 2023 15:47:54 +1000 Subject: [PATCH 6/7] [Add][Test] Coverage Signed-off-by: Mark Van de Vyver --- .github/workflows/coverage.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5edc8ca3..e8c0c868 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -22,7 +22,11 @@ jobs: RUSTDOCFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off" run: | cargo test --workspace --all-features --no-fail-fast + cargo run --example asynchronous cargo run --example get_started + cargo run --example log + cargo run --example synchronous + cargo run --example unit_test - id: coverage uses: actions-rs/grcov@v0.1 - name: Coveralls upload From 526b4192b212bad056b676aa829124a1b2828fb5 Mon Sep 17 00:00:00 2001 From: Mark Van de Vyver Date: Wed, 13 Sep 2023 16:06:06 +1000 Subject: [PATCH 7/7] [Evolve][Core] Dependencies Signed-off-by: Mark Van de Vyver --- minitrace-macro/Cargo.toml | 3 +-- minitrace-macro/src/lib.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/minitrace-macro/Cargo.toml b/minitrace-macro/Cargo.toml index 9b94d391..4bbeea57 100644 --- a/minitrace-macro/Cargo.toml +++ b/minitrace-macro/Cargo.toml @@ -17,8 +17,7 @@ keywords = ["tracing", "span", "datadog", "jaeger", "opentelemetry"] proc-macro = true [dependencies] -darling = "0.14" -proc-macro-error = "1" +darling = "0.20" proc-macro2 = "1" quote = "1" # The macro `quote_spanned!` is added to syn in 1.0.84 diff --git a/minitrace-macro/src/lib.rs b/minitrace-macro/src/lib.rs index d5f73194..9f4a0227 100644 --- a/minitrace-macro/src/lib.rs +++ b/minitrace-macro/src/lib.rs @@ -54,7 +54,6 @@ mod trace; extern crate proc_macro; -extern crate proc_macro_error; /// An attribute macro designed to eliminate boilerplate code. ///