diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..4ed9141 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,13 @@ +name: Security Audit + +on: [push, pull_request] + +jobs: + audit: + name: Audit + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c3a746d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,122 @@ +name: CI + +on: [push, pull_request] + +jobs: + lint: + runs-on: ${{ matrix.os }} + strategy: + matrix: + rust: [stable] + os: [ubuntu-latest] + + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Cache target + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ matrix.os }}-cargo--${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + components: clippy, rustfmt + toolchain: ${{ matrix.rust }} + override: true + + - name: Clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + + - name: Format + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Doc Generation + uses: actions-rs/cargo@v1 + with: + command: doc + args: --all-features + + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + rust: [stable] + os: [ubuntu-latest] + + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Cache target + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ matrix.os }}-cargo--${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + components: clippy + toolchain: ${{ matrix.rust }} + override: true + + - name: Build debug binary + uses: actions-rs/cargo@v1 + with: + command: build + + - name: Build release binary + uses: actions-rs/cargo@v1 + with: + command: build + args: --release + + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + rust: [stable] + os: [ubuntu-latest] + + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Cache target + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ matrix.os }}-cargo--${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + components: clippy + toolchain: ${{ matrix.rust }} + override: true + + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features -- --test-threads=1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..dcf8f55 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,91 @@ +name: Release + +# Push events to matching v*, i.e. v1.0, v20.15.10 +on: + push: + tags: + - 'v*' + +jobs: + check: + timeout-minutes: 30 + name: Check Signed Tag + runs-on: ubuntu-20.04 + outputs: + stringver: ${{ steps.contentrel.outputs.stringver }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + path: src/github.com/auxon/modality-ctf-plugins + + - name: Check signature + run: | + RELEASE_TAG=${{ github.ref }} + RELEASE_TAG="${RELEASE_TAG#refs/tags/}" + TAGCHECK=$(git tag -v ${RELEASE_TAG} 2>&1 >/dev/null) || + echo "${TAGCHECK}" | grep -q "error" && { + echo "::error::tag ${RELEASE_TAG} is not a signed tag. Failing release process." + exit 1 + } || { + echo "Tag ${RELEASE_TAG} is signed." + exit 0 + } + working-directory: src/github.com/auxon/modality-ctf-plugins + + package: + name: Build Release Package + timeout-minutes: 60 + runs-on: ubuntu-20.04 + needs: [check] + steps: + - name: Print version + run: | + RELEASE_TAG=${{ github.ref }} + RELEASE_TAG="${RELEASE_TAG#refs/tags/}" + RELEASE_VERSION="${RELEASE_TAG#v}" + echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV + echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV + echo "Release tag: $RELEASE_TAG" + echo "Release version: $RELEASE_VERSION" + + - name: Install system packages + run: | + sudo apt update + sudo apt-get install -y flex bison m4 gettext autotools-dev build-essential libglib2.0-dev + + - name: Checkout + uses: actions/checkout@v2 + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Fetch dependencies + uses: actions-rs/cargo@v1 + with: + command: fetch + + - name: Build release binaries + uses: actions-rs/cargo@v1 + with: + command: build + args: --release + + - name: Create github release + id: create_release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + draft: true + prerelease: false + name: Release ${{ env.RELEASE_VERSION }} + files: | + target/release/modality-ctf-import + target/release/modality-lttng-live-collector diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4d2c4f8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1190 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "autotools" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8138adefca3e5d2e73bfba83bd6eeaf904b26a7ac1b4a19892cfe16cc7e1701" +dependencies = [ + "cc", +] + +[[package]] +name = "babeltrace2-sys" +version = "0.2.8" +source = "git+https://github.com/auxoncorp/babeltrace2-sys?branch=master#af1094a4dd7d51e8153fcdae40450581b1a575d9" +dependencies = [ + "autotools", + "libc", + "log", + "ordered-float 3.2.0", + "pkg-config", + "thiserror", + "uuid", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "ctor" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ctrlc" +version = "3.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "exitcode" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minicbor" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "124d887cb82f0b1469bdac3d1b65764a381eed1a54fdab0070e5772b13114521" +dependencies = [ + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f79d5d3fb4f93c77ef7b97065fb65efe6abe670795ad8bc5be9c0e12005290" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "modality-api" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f487013c34bba9a7f9d8cef8c8e67fd9323b4811bea2b35840883a6dc54d56db" +dependencies = [ + "minicbor", + "ordered-float 2.10.0", + "uuid", +] + +[[package]] +name = "modality-ctf-plugins" +version = "0.1.0" +dependencies = [ + "async-trait", + "babeltrace2-sys", + "clap", + "ctrlc", + "derive_more", + "dirs", + "exitcode", + "hex", + "modality-api", + "modality-ingest-client", + "modality-ingest-protocol", + "modality-reflector-config", + "pretty_assertions", + "serde", + "socket2", + "tempfile", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + +[[package]] +name = "modality-ingest-client" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22ca3568632fe67f8c7f961fbf83572dffba3aca4ec37288887cc34d44bd89f" +dependencies = [ + "minicbor", + "modality-api", + "modality-ingest-protocol", + "native-tls", + "ordered-float 2.10.0", + "thiserror", + "tokio", + "tokio-native-tls", + "url", + "uuid", +] + +[[package]] +name = "modality-ingest-protocol" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef0a6db0c7ba12821207caa8ca42771458248129d14974258c8c932ef948fad2" +dependencies = [ + "minicbor", + "modality-api", +] + +[[package]] +name = "modality-reflector-config" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff4249f6790ca03ecf0090e29fa173cbf41378a7f6244359b964600597b235" +dependencies = [ + "lazy_static", + "modality-api", + "regex", + "serde", + "thiserror", + "toml", + "url", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "openssl" +version = "0.10.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-float" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129d36517b53c461acc6e1580aeb919c8ae6708a4b1eae61c4463a615d4f0411" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "pretty_assertions" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +dependencies = [ + "ctor", + "diff", + "output_vt100", + "yansi", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + +[[package]] +name = "serde" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "matchers", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom", + "serde", + "sha1_smol", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..90f4574 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "modality-ctf-plugins" +version = "0.1.0" +edition = "2021" +authors = ["Jon Lamb "] +description = "A Modality reflector plugin suite for CTF data" +license = "Apache-2.0" +repository = "https://github.com/auxoncorp/modality-ctf-plugins" + +[lib] +name = "modality_ctf" +path = "src/lib.rs" + +[[bin]] +name = "modality-ctf-import" +path = "src/bin/importer.rs" +test = false + +[[bin]] +name = "modality-lttng-live-collector" +path = "src/bin/lttng_live_collector.rs" +test = false + +[dependencies] +modality-api = "0.1" +modality-ingest-client = "0.2" +modality-ingest-protocol = "0.1" +modality-reflector-config = "0.2" +serde = { version = "1.0", features=["derive"] } +derive_more = "0.99" +hex = "0.4" +dirs = "4" +socket2 = "0.4" +exitcode = "1" +clap = { version = "3.2", features = ["env", "color", "derive"] } +thiserror = "1.0" +tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "io-util", "net", "signal", "tracing"] } +async-trait = "0.1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +url = { version = "2.2", features = ["serde"] } +uuid = { version = "1.1.2", features = ["v5", "v4", "serde"] } +babeltrace2-sys = { git = "https://github.com/auxoncorp/babeltrace2-sys", branch = "master" } +ctrlc = { version = "3.2", features=["termination"] } + +[dev-dependencies] +pretty_assertions = "1.2" +tempfile = "3.1" + +[profile.release] +strip="debuginfo" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9ad2d86 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Auxon Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3bd807d --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# modality-ctf-plugins   ![ci] + +A [Modality][modality] reflector plugin suite and ingest adapter library for [CTF][ctf] data. + +## Getting Started + +1. Configure a modality reflector to run either the CTF importer or the LTTng collector (see Configuration below) +2. Use the importer to import a CTF trace from disk, or use the LTTng streaming collector to collect data from an LTTng relay daemon + +## Adapter Concept Mapping + +The following describes the default mapping between [CTF][ctf] concepts +and [Modality's][modality] concepts. See the configuration section for ways to change the +default behavior. + +* CTF streams are represented as separate Modality timelines +* CTF trace and stream properties are represented as Modality timeline attributes under the `timeline.internal.ctf` prefix +* CTF event common, specific, and packet context fields are represented as Modality event attributes under the `timeline.internal.ctf` prefix +* CTF event field attributes are at the root level + +See the [Modality documentation](https://docs.auxon.io/modality/) for more information on the Modality concepts. + +## Configuration + +All of the plugins can be configured through a TOML configuration file (from either the `--config` option or the `MODALITY_REFLECTOR_CONFIG` environment variable). +All of the configuration fields can optionally be overridden at the CLI, see `--help` for more details. + +See the [`modality-reflector` Configuration File documentation](https://docs.auxon.io/modality/ingest/modality-reflector-configuration-file.html) for more information +about the reflector configuration. + +### Common Sections + +These sections are the same for each of the plugins. + +* `[ingest]` — Top-level ingest configuration. + - `additional-timeline-attributes` — Array of key-value attribute pairs to add to every timeline seen by the plugin. + - `override-timeline-attributes` — Array of key-value attribute pairs to override on every timeline seen by this plugin. + - `allow-insecure-tls` — Whether to allow insecure connections. Defaults to `false`. + - `protocol-parent-url` — URL to which this reflector will send its collected data. + +* `[plugins.ingest.importers.ctf.metadata]` or `[plugins.ingest.collectors.lttng-live.metadata]` — Plugin configuration table. (just `metadata` if running standalone) + - `run-id` — Use the provided UUID as the run ID instead of generating a random one. + - `trace-uuid` — Use the provided UUID as the trace UUID to override any present (or not) UUID contained in the CTF metadata. + - `log-level` — Logging level for libbabeltrace. Defaults to `none`. + +### Importer Section + +These `metadata` fields are specific to the CTF importer plugin. +See [babeltrace2-source.ctf.fs][ctf-fs-docs] for more information. + +* `[metadata]` — Plugin configuration table. +* `[plugins.ingest.importers.ctf.metadata]` — Plugin configuration table. (just `metadata` if running standalone) + - `trace-name` — Set the name of the trace object. + - `clock-class-offset-ns` — Add nanoseconds to the offset of all the clock classes. + - `clock-class-offset-s` — Add seconds to the offset of all the clock classes. + - `force-clock-class-origin-unix-epoch` — Force the origin of all clock classes that the component creates to have a Unix epoch origin. + - `inputs` — The metadata file paths of the CTF traces to import. + +### LTTng Collector Section + +These `metadata` fields are specific to the LTTng collector plugin. +See [babeltrace2-source.ctf.lttng-live][lttng-live-docs] for more information. + +* `[metadata]` — Plugin configuration table. +* `[plugins.ingest.collectors.lttng-live.metadata]` — Plugin configuration table. (just `metadata` if running standalone) + - `retry-duration-us` — The libbabeltrace graph run retry interval. + - `session-not-found-action` — The action to take when the remote tracing session is not found. Defaults to `continue`. + - `url` — The URL of the LTTng relay daemon to connect to. + +## LICENSE + +See [LICENSE](./LICENSE) for more details. + +Copyright 2022 [Auxon Corporation](https://auxon.io) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +[ci]: https://github.com/auxoncorp/modality-ctf-plugins/workflows/CI/badge.svg +[ctf]: https://diamon.org/ctf/ +[modality]: https://auxon.io/products/modality +[lttng-live-docs]: https://babeltrace.org/docs/v2.0/man7/babeltrace2-source.ctf.lttng-live.7/ +[ctf-fs-docs]: https://babeltrace.org/docs/v2.0/man7/babeltrace2-source.ctf.fs.7/ diff --git a/src/attrs.rs b/src/attrs.rs new file mode 100644 index 0000000..ce2ace6 --- /dev/null +++ b/src/attrs.rs @@ -0,0 +1,90 @@ +use crate::error::Error; +use async_trait::async_trait; +use derive_more::Display; +use modality_ingest_protocol::InternedAttrKey; + +// N.B. maybe we'll expand on this to separate out the various types +// of ctf-plugins producers (lttng/ctf-plugins/barectf/python-logger-backend/etc). +// Probably relevant for inferring a communications/interactions synthesis pattern. +pub(crate) const TIMELINE_INGEST_SOURCE_VAL: &str = "ctf-plugins"; + +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display)] +pub enum TimelineAttrKey { + #[display(fmt = "timeline.name")] + Name, + #[display(fmt = "timeline.description")] + Description, + #[display(fmt = "timeline.run_id")] + RunId, + #[display(fmt = "timeline.time_domain")] + TimeDomain, + #[display(fmt = "timeline.ingest_source")] + IngestSource, + + #[display(fmt = "timeline.internal.ctf.trace.name")] + TraceName, + #[display(fmt = "timeline.internal.ctf.trace.uuid")] + TraceUuid, + #[display(fmt = "timeline.internal.ctf.trace.stream_count")] + TraceStreamCount, + #[display(fmt = "timeline.internal.ctf.trace.env.{_0}")] + TraceEnv(String), + + #[display(fmt = "timeline.internal.ctf.stream.id")] + StreamId, + #[display(fmt = "timeline.internal.ctf.stream.name")] + StreamName, + #[display(fmt = "timeline.internal.ctf.stream.clock.frequency")] + StreamClockFreq, + #[display(fmt = "timeline.internal.ctf.stream.clock.offset_seconds")] + StreamClockOffsetSeconds, + #[display(fmt = "timeline.internal.ctf.stream.clock.offset_cycles")] + StreamClockOffsetCycles, + #[display(fmt = "timeline.internal.ctf.stream.clock.precision")] + StreamClockPrecision, + #[display(fmt = "timeline.internal.ctf.stream.clock.unix_epoch_origin")] + StreamClockUnixEpoch, + #[display(fmt = "timeline.internal.ctf.stream.clock.name")] + StreamClockName, + #[display(fmt = "timeline.internal.ctf.stream.clock.description")] + StreamClockDesc, + #[display(fmt = "timeline.internal.ctf.stream.clock.uuid")] + StreamClockUuid, +} + +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display)] +pub enum EventAttrKey { + #[display(fmt = "event.name")] + Name, + #[display(fmt = "event.timestamp")] + Timestamp, + + #[display(fmt = "event.internal.ctf.stream_id")] + StreamId, + #[display(fmt = "event.internal.ctf.id")] + Id, + #[display(fmt = "event.internal.ctf.log_level")] + LogLevel, + #[display(fmt = "event.internal.ctf.clock_snapshot")] + ClockSnapshot, + + #[display(fmt = "event.internal.ctf.common_context.{_0}")] + CommonContext(String), + #[display(fmt = "event.internal.ctf.specific_context.{_0}")] + SpecificContext(String), + #[display(fmt = "event.internal.ctf.packet_context.{_0}")] + PacketContext(String), + + #[display(fmt = "event.{_0}")] + Field(String), +} + +#[async_trait] +pub trait TimelineAttrKeyExt { + async fn interned_key(&mut self, key: TimelineAttrKey) -> Result; +} + +#[async_trait] +pub trait EventAttrKeyExt { + async fn interned_key(&mut self, key: EventAttrKey) -> Result; +} diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..10a4e2e --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,33 @@ +use derive_more::{Deref, Into}; +use thiserror::Error; +use tracing::debug; + +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deref, Into)] +pub struct AuthTokenBytes(Vec); + +#[derive(Copy, Clone, Debug, Error)] +pub enum AuthTokenError { + #[error("An auth token is required. Provide one at the command line or via the MODALITY_AUTH_TOKEN environment variable.")] + AuthRequired, + + #[error("Encountered an error decoding the auth token. {0}")] + Hex(#[from] hex::FromHexError), +} + +impl AuthTokenBytes { + pub fn resolve(maybe_provided_hex: Option<&str>) -> Result { + let hex = if let Some(provided_hex) = maybe_provided_hex { + provided_hex.to_string() + } else { + dirs::config_dir() + .and_then(|config| { + let file_path = config.join("modality_cli").join(".user_auth_token"); + debug!("Resolving auth token from default Modality auth token file"); + std::fs::read_to_string(file_path).ok() + }) + .ok_or(AuthTokenError::AuthRequired)? + }; + + Ok(Self(hex::decode(hex.trim())?)) + } +} diff --git a/src/bin/importer.rs b/src/bin/importer.rs new file mode 100644 index 0000000..12c56ae --- /dev/null +++ b/src/bin/importer.rs @@ -0,0 +1,242 @@ +#![deny(warnings, clippy::all)] + +use babeltrace2_sys::{CtfIterator, CtfPluginSourceFsInitParams}; +use clap::Parser; +use modality_api::types::TimelineId; +use modality_ctf::{prelude::*, tracing::try_init_tracing_subscriber}; +use modality_ingest_client::IngestClient; +use std::collections::HashMap; +use std::path::PathBuf; +use thiserror::Error; +use tracing::warn; + +/// Import CTF trace data from files +#[derive(Parser, Debug, Clone)] +#[clap(version)] +pub struct Opts { + #[clap(flatten)] + pub rf_opts: ReflectorOpts, + + #[clap(flatten)] + pub bt_opts: BabeltraceOpts, + + /// Set the name of the trace object that the component creates, overriding the data's trace + /// name if present + #[clap(long, name = "trace-name", help_heading = "IMPORT CONFIGURATION")] + pub trace_name: Option, + + /// Add offset-ns nanoseconds to the offset of all the clock classes that the component creates + #[clap(long, name = "offset-ns", help_heading = "IMPORT CONFIGURATION")] + pub clock_class_offset_ns: Option, + + /// Add offset-s seconds to the offset of all the clock classes that the component creates + #[clap(long, name = "offset-s", help_heading = "IMPORT CONFIGURATION")] + pub clock_class_offset_s: Option, + + /// Force the origin of all clock classes that the component creates to have a Unix epoch origin + #[clap(long, name = "unix-epoch", help_heading = "IMPORT CONFIGURATION")] + pub force_clock_class_origin_unix_epoch: Option, + + /// Path to trace directories + #[clap(name = "input", help_heading = "IMPORT CONFIGURATION")] + pub inputs: Vec, +} + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + Ctf(#[from] modality_ctf::error::Error), + + #[error("At least one CTF containing input path is required.")] + MissingInputs, +} + +#[tokio::main] +async fn main() { + match do_main().await { + Ok(()) => (), + Err(e) => { + eprintln!("{}", e); + let mut cause = e.source(); + while let Some(err) = cause { + eprintln!("Caused by: {err}"); + cause = err.source(); + } + std::process::exit(exitcode::SOFTWARE); + } + } +} + +async fn do_main() -> Result<(), Box> { + let opts = Opts::parse(); + + try_init_tracing_subscriber()?; + + let intr = Interruptor::new(); + let interruptor = intr.clone(); + ctrlc::set_handler(move || { + if intr.is_set() { + // 128 (fatal error signal "n") + 2 (control-c is fatal error signal 2) + std::process::exit(130); + } else { + intr.set(); + } + })?; + + let mut cfg = CtfConfig::load_merge_with_opts(opts.rf_opts, opts.bt_opts)?; + if let Some(tn) = opts.trace_name { + cfg.plugin.import.trace_name = tn.into(); + } + if let Some(ns) = opts.clock_class_offset_ns { + cfg.plugin.import.clock_class_offset_ns = ns.into(); + } + if let Some(s) = opts.clock_class_offset_s { + cfg.plugin.import.clock_class_offset_s = s.into(); + } + if let Some(ue) = opts.force_clock_class_origin_unix_epoch { + cfg.plugin.import.force_clock_class_origin_unix_epoch = ue.into(); + } + if !opts.inputs.is_empty() { + cfg.plugin.import.inputs = opts.inputs; + } + + if cfg.plugin.import.inputs.is_empty() { + return Err(Error::MissingInputs.into()); + } + for p in cfg.plugin.import.inputs.iter() { + if !p.join("metadata").exists() { + warn!( + "Input path '{}' does not contain a metadata file", + p.display() + ); + } + } + + let c = + IngestClient::connect(&cfg.protocol_parent_url()?, cfg.ingest.allow_insecure_tls).await?; + let c_authed = c.authenticate(cfg.resolve_auth()?.into()).await?; + let mut client = Client::new(c_authed); + + let ctf_params = CtfPluginSourceFsInitParams::try_from(&cfg.plugin.import)?; + let trace_iter = CtfIterator::new(cfg.plugin.log_level.into(), &ctf_params)?; + let props = CtfProperties::new( + cfg.plugin.run_id, + cfg.plugin.trace_uuid, + trace_iter.trace_properties(), + trace_iter.stream_properties(), + &mut client, + ) + .await?; + + let mut last_timeline_ordering_val: HashMap = Default::default(); + + if props.streams.is_empty() { + warn!("The CTF containing input path(s) don't contain any trace data"); + } + + for (tid, attr_kvs) in props.timelines() { + client.c.open_timeline(tid).await?; + client.c.timeline_metadata(attr_kvs).await?; + last_timeline_ordering_val.insert(tid, 0); + } + + for maybe_event in trace_iter { + if interruptor.is_set() { + break; + } + let event = maybe_event?; + + let timeline_id = match props.streams.get(&event.stream_id).map(|s| s.timeline_id()) { + Some(tid) => tid, + None => { + warn!( + "Dropping event ID {} because it's stream ID was not reported in the metadata", + event.class_properties.id + ); + continue; + } + }; + + let ordering = match last_timeline_ordering_val.get_mut(&timeline_id) { + Some(ord) => ord, + None => { + warn!( + "Dropping event ID {} because it's timeline ID was not registered", + event.class_properties.id + ); + continue; + } + }; + + let event = CtfEvent::new(&event, &mut client).await?; + client.c.open_timeline(timeline_id).await?; + client.c.event(*ordering, event.attr_kvs()).await?; + *ordering += 1; + client.c.close_timeline(); + } + + Ok(()) +} + +/// Plugin descriptor related data, pointers to this data +/// will end up in special linker sections in the binary +/// so libbabeltrace2 can discover it +/// +/// TODO: figure out how to work around +/// For now, this has to be defined in the binary crate for it to work +pub mod proxy_plugin_descriptors { + use babeltrace2_sys::ffi::*; + use babeltrace2_sys::proxy_plugin_descriptors::*; + + #[used] + #[link_section = "__bt_plugin_descriptors"] + pub static PLUGIN_DESC_PTR: __bt_plugin_descriptor_ptr = + __bt_plugin_descriptor_ptr(&PLUGIN_DESC); + + #[used] + #[link_section = "__bt_plugin_component_class_descriptors"] + pub static SINK_COMP_DESC_PTR: __bt_plugin_component_class_descriptor_ptr = + __bt_plugin_component_class_descriptor_ptr(&SINK_COMP_DESC); + + #[used] + #[link_section = "__bt_plugin_component_class_descriptor_attributes"] + pub static SINK_COMP_CLASS_INIT_ATTR_PTR: __bt_plugin_component_class_descriptor_attribute_ptr = + __bt_plugin_component_class_descriptor_attribute_ptr(&SINK_COMP_CLASS_INIT_ATTR); + + #[used] + #[link_section = "__bt_plugin_component_class_descriptor_attributes"] + pub static SINK_COMP_CLASS_FINI_ATTR_PTR: __bt_plugin_component_class_descriptor_attribute_ptr = + __bt_plugin_component_class_descriptor_attribute_ptr(&SINK_COMP_CLASS_FINI_ATTR); + + #[used] + #[link_section = "__bt_plugin_component_class_descriptor_attributes"] + pub static SINK_COMP_CLASS_GRAPH_CONF_ATTR_PTR: + __bt_plugin_component_class_descriptor_attribute_ptr = + __bt_plugin_component_class_descriptor_attribute_ptr(&SINK_COMP_CLASS_GRAPH_CONF_ATTR); +} + +pub mod utils_plugin_descriptors { + use babeltrace2_sys::ffi::*; + + #[link( + name = "babeltrace-plugin-utils", + kind = "static", + modifiers = "+whole-archive" + )] + extern "C" { + pub static __bt_plugin_descriptor_auto_ptr: *const __bt_plugin_descriptor; + } +} + +pub mod ctf_plugin_descriptors { + use babeltrace2_sys::ffi::*; + + #[link( + name = "babeltrace-plugin-ctf", + kind = "static", + modifiers = "+whole-archive" + )] + extern "C" { + pub static __bt_plugin_descriptor_auto_ptr: *const __bt_plugin_descriptor; + } +} diff --git a/src/bin/lttng_live_collector.rs b/src/bin/lttng_live_collector.rs new file mode 100644 index 0000000..f90567b --- /dev/null +++ b/src/bin/lttng_live_collector.rs @@ -0,0 +1,320 @@ +#![deny(warnings, clippy::all)] + +use babeltrace2_sys::{CtfPluginSourceLttnLiveInitParams, CtfStream, RunStatus}; +use clap::Parser; +use modality_api::types::TimelineId; +use modality_ctf::{ + prelude::*, + tracing::try_init_tracing_subscriber, + types::{RetryDurationUs, SessionNotFoundAction}, +}; +use modality_ingest_client::IngestClient; +use socket2::{Domain, Socket, Type}; +use std::collections::HashMap; +use std::ffi::CString; +use std::time::Duration; +use std::{net, thread}; +use thiserror::Error; +use tracing::{debug, warn}; +use url::Url; + +/// Import CTF trace data from files +#[derive(Parser, Debug, Clone)] +#[clap(version)] +pub struct Opts { + #[clap(flatten)] + pub rf_opts: ReflectorOpts, + + #[clap(flatten)] + pub bt_opts: BabeltraceOpts, + + /// When babeltrace2 needs to retry to run + /// the graph later, retry in retry-duration-us µs + /// (default: 100000) + #[clap(long, name = "duration µs")] + pub retry_duration_us: Option, + + /// When the message iterator does not find the specified remote tracing + /// session (SESSION part of the inputs parameter), do one of the following actions. + /// * continue (default) + /// * fail + /// * end + #[clap(long, verbatim_doc_comment, name = "action")] + pub session_not_found_action: Option, + + /// The URL to connect to the LTTng relay daemon. + /// + /// Format: net\[4\]://RDHOST\[:RDPORT\]/host/TGTHOST/SESSION + /// * RDHOST + /// LTTng relay daemon’s host name or IP address. + /// * RDPORT + /// LTTng relay daemon’s listening port. + /// If not specified, the component uses the default port (5344). + /// * TGTHOST + /// Target’s host name or IP address. + /// * SESSION + /// Name of the LTTng tracing session from which to receive data. + /// + /// Example: net://localhost/host/ubuntu-focal/my-kernel-session + #[clap(verbatim_doc_comment, name = "url")] + pub url: Option, +} + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + Ctf(#[from] modality_ctf::error::Error), + + #[error("The URL to connect to the LTTng relay daemon is required.")] + MissingUrl, + + #[error("The CTF connection was established but the trace doesn't contain any stream data.")] + EmptyCtfTrace, +} + +const LTTNG_RELAYD_DEFAULT_PORT: u16 = 5344; +const RELAYD_QUICK_PING_CONNECT_TIMEOUT: Duration = Duration::from_millis(100); + +#[tokio::main] +async fn main() { + match do_main().await { + Ok(()) => (), + Err(e) => { + eprintln!("{}", e); + let mut cause = e.source(); + while let Some(err) = cause { + eprintln!("Caused by: {err}"); + cause = err.source(); + } + std::process::exit(exitcode::SOFTWARE); + } + } +} + +async fn do_main() -> Result<(), Box> { + let opts = Opts::parse(); + + try_init_tracing_subscriber()?; + + let intr = Interruptor::new(); + let interruptor = intr.clone(); + ctrlc::set_handler(move || { + if intr.is_set() { + // 128 (fatal error signal "n") + 2 (control-c is fatal error signal 2) + std::process::exit(130); + } else { + intr.set(); + } + })?; + + let mut cfg = CtfConfig::load_merge_with_opts(opts.rf_opts, opts.bt_opts)?; + if let Some(retry) = opts.retry_duration_us { + cfg.plugin.lttng_live.retry_duration_us = retry; + } + if let Some(action) = opts.session_not_found_action { + cfg.plugin.lttng_live.session_not_found_action = action; + } + if let Some(url) = opts.url { + cfg.plugin.lttng_live.url = url.into(); + } + + let url = match cfg.plugin.lttng_live.url.as_ref() { + Some(url) => url.clone(), + None => return Err(Error::MissingUrl.into()), + }; + + // Attempt to inform user if we can't connect to remote to provide + // some help when babeltrace2 can't connect, since its error is just -1 + // and you'd have to turn on logging to really know + if let Ok(relayd_addrs) = url.socket_addrs(|| Some(LTTNG_RELAYD_DEFAULT_PORT)) { + if !relayd_addrs.is_empty() { + let addr = relayd_addrs[0]; + let domain = if addr.is_ipv4() { + Domain::IPV4 + } else { + Domain::IPV6 + }; + let sock = Socket::new(domain, Type::STREAM, None)?; + + if sock + .connect_timeout(&addr.into(), RELAYD_QUICK_PING_CONNECT_TIMEOUT) + .is_err() + { + warn!( + "Failed to connect to '{}', the remote host may not be reachable", + url + ); + } + let _ = sock.shutdown(net::Shutdown::Both).ok(); + } + } + + let url_cstring = CString::new(url.to_string().as_bytes())?; + let params = CtfPluginSourceLttnLiveInitParams::new( + &url_cstring, + Some(cfg.plugin.lttng_live.session_not_found_action.into()), + )?; + let mut ctf_stream = CtfStream::new(cfg.plugin.log_level.into(), ¶ms)?; + let retry_duration = Duration::from_micros(cfg.plugin.lttng_live.retry_duration_us.into()); + + debug!("Waiting for CTF metadata"); + + // Loop until we get some metadata from the relayd + while !ctf_stream.has_metadata() { + if interruptor.is_set() { + return Ok(()); + } + + match ctf_stream.update()? { + RunStatus::Ok => (), + RunStatus::TryAgain => { + thread::sleep(retry_duration); + continue; + } + RunStatus::End => break, + } + } + + debug!("Found CTF metadata"); + + if ctf_stream.stream_properties().is_empty() { + return Err(Error::EmptyCtfTrace.into()); + } + + let c = + IngestClient::connect(&cfg.protocol_parent_url()?, cfg.ingest.allow_insecure_tls).await?; + let c_authed = c.authenticate(cfg.resolve_auth()?.into()).await?; + let mut client = Client::new(c_authed); + + let props = CtfProperties::new( + cfg.plugin.run_id, + cfg.plugin.trace_uuid, + ctf_stream.trace_properties(), + ctf_stream.stream_properties(), + &mut client, + ) + .await?; + + let mut last_timeline_ordering_val: HashMap = Default::default(); + + for (tid, attr_kvs) in props.timelines() { + client.c.open_timeline(tid).await?; + client.c.timeline_metadata(attr_kvs).await?; + last_timeline_ordering_val.insert(tid, 0); + } + + // Loop until user-signaled-exit or server-side-signaled-done + loop { + if interruptor.is_set() { + break; + } + + match ctf_stream.update()? { + RunStatus::Ok => (), + RunStatus::TryAgain => { + thread::sleep(retry_duration); + continue; + } + RunStatus::End => break, + } + + for event in ctf_stream.events_chunk() { + if interruptor.is_set() { + break; + } + + let timeline_id = match props.streams.get(&event.stream_id).map(|s| s.timeline_id()) { + Some(tid) => tid, + None => { + warn!( + "Dropping event ID {} because it's stream ID was not reported in the metadata", + event.class_properties.id + ); + continue; + } + }; + + let ordering = match last_timeline_ordering_val.get_mut(&timeline_id) { + Some(ord) => ord, + None => { + warn!( + "Dropping event ID {} because it's timeline ID was not registered", + event.class_properties.id + ); + continue; + } + }; + + let event = CtfEvent::new(&event, &mut client).await?; + client.c.open_timeline(timeline_id).await?; + client.c.event(*ordering, event.attr_kvs()).await?; + *ordering += 1; + client.c.close_timeline(); + } + } + + Ok(()) +} + +/// Plugin descriptor related data, pointers to this data +/// will end up in special linker sections in the binary +/// so libbabeltrace2 can discover it +/// +/// TODO: figure out how to work around +/// For now, this has to be defined in the binary crate for it to work +pub mod proxy_plugin_descriptors { + use babeltrace2_sys::ffi::*; + use babeltrace2_sys::proxy_plugin_descriptors::*; + + #[used] + #[link_section = "__bt_plugin_descriptors"] + pub static PLUGIN_DESC_PTR: __bt_plugin_descriptor_ptr = + __bt_plugin_descriptor_ptr(&PLUGIN_DESC); + + #[used] + #[link_section = "__bt_plugin_component_class_descriptors"] + pub static SINK_COMP_DESC_PTR: __bt_plugin_component_class_descriptor_ptr = + __bt_plugin_component_class_descriptor_ptr(&SINK_COMP_DESC); + + #[used] + #[link_section = "__bt_plugin_component_class_descriptor_attributes"] + pub static SINK_COMP_CLASS_INIT_ATTR_PTR: __bt_plugin_component_class_descriptor_attribute_ptr = + __bt_plugin_component_class_descriptor_attribute_ptr(&SINK_COMP_CLASS_INIT_ATTR); + + #[used] + #[link_section = "__bt_plugin_component_class_descriptor_attributes"] + pub static SINK_COMP_CLASS_FINI_ATTR_PTR: __bt_plugin_component_class_descriptor_attribute_ptr = + __bt_plugin_component_class_descriptor_attribute_ptr(&SINK_COMP_CLASS_FINI_ATTR); + + #[used] + #[link_section = "__bt_plugin_component_class_descriptor_attributes"] + pub static SINK_COMP_CLASS_GRAPH_CONF_ATTR_PTR: + __bt_plugin_component_class_descriptor_attribute_ptr = + __bt_plugin_component_class_descriptor_attribute_ptr(&SINK_COMP_CLASS_GRAPH_CONF_ATTR); +} + +pub mod utils_plugin_descriptors { + use babeltrace2_sys::ffi::*; + + #[link( + name = "babeltrace-plugin-utils", + kind = "static", + modifiers = "+whole-archive" + )] + extern "C" { + pub static __bt_plugin_descriptor_auto_ptr: *const __bt_plugin_descriptor; + } +} + +pub mod ctf_plugin_descriptors { + use babeltrace2_sys::ffi::*; + + #[link( + name = "babeltrace-plugin-ctf", + kind = "static", + modifiers = "+whole-archive" + )] + extern "C" { + pub static __bt_plugin_descriptor_auto_ptr: *const __bt_plugin_descriptor; + } +} diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..32f178a --- /dev/null +++ b/src/client.rs @@ -0,0 +1,51 @@ +use crate::attrs::{EventAttrKey, EventAttrKeyExt, TimelineAttrKey, TimelineAttrKeyExt}; +use crate::error::Error; +use async_trait::async_trait; +use modality_ingest_client::dynamic::DynamicIngestClient; +use modality_ingest_client::{IngestClient, ReadyState}; +use modality_ingest_protocol::InternedAttrKey; +use std::collections::BTreeMap; + +pub struct Client { + pub c: DynamicIngestClient, + pub timeline_keys: BTreeMap, + pub event_keys: BTreeMap, +} + +impl Client { + pub fn new(c: IngestClient) -> Self { + Self { + c: c.into(), + timeline_keys: Default::default(), + event_keys: Default::default(), + } + } +} + +#[async_trait] +impl TimelineAttrKeyExt for Client { + async fn interned_key(&mut self, key: TimelineAttrKey) -> Result { + let int_key = if let Some(k) = self.timeline_keys.get(&key) { + *k + } else { + let k = self.c.declare_attr_key(key.to_string()).await?; + self.timeline_keys.insert(key, k); + k + }; + Ok(int_key) + } +} + +#[async_trait] +impl EventAttrKeyExt for Client { + async fn interned_key(&mut self, key: EventAttrKey) -> Result { + let int_key = if let Some(k) = self.event_keys.get(&key) { + *k + } else { + let k = self.c.declare_attr_key(key.to_string()).await?; + self.event_keys.insert(key, k); + k + }; + Ok(int_key) + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5b788bd --- /dev/null +++ b/src/config.rs @@ -0,0 +1,334 @@ +use crate::auth::{AuthTokenBytes, AuthTokenError}; +use crate::opts::{BabeltraceOpts, ReflectorOpts}; +use crate::types::{LoggingLevel, RetryDurationUs, SessionNotFoundAction}; +use babeltrace2_sys::CtfPluginSourceFsInitParams; +use modality_reflector_config::{Config, TomlValue, TopLevelIngest, CONFIG_ENV_VAR}; +use serde::Deserialize; +use std::convert::TryFrom; +use std::env; +use std::ffi::{CString, NulError}; +use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; +use url::Url; +use uuid::Uuid; + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct CtfConfig { + pub auth_token: Option, + pub ingest: TopLevelIngest, + pub plugin: PluginConfig, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Deserialize)] +#[serde(rename_all = "kebab-case", default)] +pub struct PluginConfig { + pub run_id: Option, + + /// Optionally provide a trace UUID to override any present (or not) UUID contained + /// in the CTF metadata. + /// + /// This is useful for constructing deterministic trace UUIDis which form the timeline IDs. + pub trace_uuid: Option, + + /// Logging level for libbabeltrace + pub log_level: LoggingLevel, + + #[serde(flatten)] + pub import: ImportConfig, + #[serde(flatten)] + pub lttng_live: LttngLiveConfig, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Deserialize)] +#[serde(rename_all = "kebab-case", default)] +pub struct ImportConfig { + /// See + pub trace_name: Option, + + /// See + pub clock_class_offset_ns: Option, + + /// See + pub clock_class_offset_s: Option, + + /// See + pub force_clock_class_origin_unix_epoch: Option, + + /// See + pub inputs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Deserialize)] +#[serde(rename_all = "kebab-case", default)] +pub struct LttngLiveConfig { + /// When libbabeltrace needs to retry to run + /// the graph later, retry in retry-duration-us µs + pub retry_duration_us: RetryDurationUs, + + /// See + /// + pub session_not_found_action: SessionNotFoundAction, + + /// See + /// + pub url: Option, +} + +impl CtfConfig { + pub fn load_merge_with_opts( + rf_opts: ReflectorOpts, + bt_opts: BabeltraceOpts, + ) -> Result> { + let cfg = if let Some(cfg_path) = &rf_opts.config_file { + modality_reflector_config::try_from_file(cfg_path)? + } else if let Ok(env_path) = env::var(CONFIG_ENV_VAR) { + modality_reflector_config::try_from_file(Path::new(&env_path))? + } else { + Config::default() + }; + + let mut ingest = cfg.ingest.clone().unwrap_or_default(); + if let Some(url) = &rf_opts.protocol_parent_url { + ingest.protocol_parent_url = Some(url.clone()); + } + if rf_opts.allow_insecure_tls { + ingest.allow_insecure_tls = true; + } + + let plugin_cfg: PluginConfig = + TomlValue::Table(cfg.metadata.into_iter().collect()).try_into()?; + let plugin = PluginConfig { + run_id: rf_opts.run_id.or(plugin_cfg.run_id), + trace_uuid: bt_opts.trace_uuid.or(plugin_cfg.trace_uuid), + log_level: bt_opts.log_level.unwrap_or(plugin_cfg.log_level), + import: plugin_cfg.import, + lttng_live: plugin_cfg.lttng_live, + }; + + Ok(Self { + auth_token: rf_opts.auth_token, + ingest, + plugin, + }) + } + + pub fn protocol_parent_url(&self) -> Result { + if let Some(url) = &self.ingest.protocol_parent_url { + Ok(url.clone()) + } else { + let url = Url::parse("modality-ingest://127.0.0.1:14188")?; + Ok(url) + } + } + + pub fn resolve_auth(&self) -> Result { + AuthTokenBytes::resolve(self.auth_token.as_deref()) + } +} + +impl TryFrom<&ImportConfig> for CtfPluginSourceFsInitParams { + type Error = babeltrace2_sys::Error; + + fn try_from(config: &ImportConfig) -> Result { + let trace_name: Option = config + .trace_name + .as_ref() + .map(|n| CString::new(n.as_bytes())) + .transpose()?; + + let input_cstrings: Vec = config + .inputs + .iter() + .map(|p| CString::new(p.as_os_str().as_bytes())) + .collect::, NulError>>()?; + let inputs = input_cstrings + .iter() + .map(|i| i.as_c_str()) + .collect::>(); + + CtfPluginSourceFsInitParams::new( + trace_name.as_deref(), + config.clock_class_offset_ns, + config.clock_class_offset_s, + config.force_clock_class_origin_unix_epoch, + &inputs, + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + use modality_reflector_config::{AttrKeyEqValuePair, TimelineAttributes}; + use pretty_assertions::assert_eq; + use std::str::FromStr; + use std::{env, fs::File, io::Write}; + + const IMPORT_CONFIG: &str = r#"[ingest] +protocol-parent-url = 'modality-ingest://127.0.0.1:14182' +additional-timeline-attributes = [ + "ci_run=1", + "module='linux-import'", +] + +[metadata] +run-id = 'a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d1' +trace-uuid = 'a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d2' +log-level = 'info' +trace-name = 'my-trace' +clock-class-offset-ns = -1 +clock-class-offset-s = 2 +force-clock-class-origin-unix-epoch = true +inputs = ['path/traces-a', 'path/traces-b'] +"#; + + const LTTNG_LIVE_CONFIG: &str = r#"[ingest] +protocol-parent-url = 'modality-ingest://127.0.0.1:14182' +additional-timeline-attributes = [ + "ci_run=1", + "module='linux-import'", +] + +[metadata] +run-id = 'a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d1' +trace-uuid = 'a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d2' +log-level = 'debug' +retry-duration-us = 100 +session-not-found-action = 'end' +url = 'net://localhost/host/ubuntu-focal/my-kernel-session' +"#; + + #[test] + fn import_cfg() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("my_config.toml"); + { + let mut f = File::create(&path).unwrap(); + f.write_all(IMPORT_CONFIG.as_bytes()).unwrap(); + f.flush().unwrap(); + } + + let cfg = CtfConfig::load_merge_with_opts( + ReflectorOpts { + config_file: Some(path.to_path_buf()), + ..Default::default() + }, + Default::default(), + ) + .unwrap(); + + env::set_var(CONFIG_ENV_VAR, path); + let env_cfg = + CtfConfig::load_merge_with_opts(Default::default(), Default::default()).unwrap(); + env::remove_var(CONFIG_ENV_VAR); + assert_eq!(cfg, env_cfg); + + assert_eq!( + cfg, + CtfConfig { + auth_token: None, + ingest: TopLevelIngest { + protocol_parent_url: Url::parse("modality-ingest://127.0.0.1:14182") + .unwrap() + .into(), + allow_insecure_tls: false, + protocol_child_port: None, + timeline_attributes: TimelineAttributes { + additional_timeline_attributes: vec![ + AttrKeyEqValuePair::from_str("ci_run=1").unwrap(), + AttrKeyEqValuePair::from_str("module='linux-import'").unwrap(), + ], + override_timeline_attributes: Default::default(), + }, + max_write_batch_staleness: None, + }, + plugin: PluginConfig { + run_id: Uuid::from_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d1") + .unwrap() + .into(), + trace_uuid: Uuid::from_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d2") + .unwrap() + .into(), + log_level: babeltrace2_sys::LoggingLevel::Info.into(), + import: ImportConfig { + trace_name: "my-trace".to_owned().into(), + clock_class_offset_ns: Some(-1_i64), + clock_class_offset_s: 2_i64.into(), + force_clock_class_origin_unix_epoch: true.into(), + inputs: vec![ + PathBuf::from("path/traces-a"), + PathBuf::from("path/traces-b") + ], + }, + lttng_live: Default::default(), + } + } + ); + } + + #[test] + fn lttng_live_cfg() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("my_config.toml"); + { + let mut f = File::create(&path).unwrap(); + f.write_all(LTTNG_LIVE_CONFIG.as_bytes()).unwrap(); + f.flush().unwrap(); + } + + let cfg = CtfConfig::load_merge_with_opts( + ReflectorOpts { + config_file: Some(path.to_path_buf()), + ..Default::default() + }, + Default::default(), + ) + .unwrap(); + + env::set_var(CONFIG_ENV_VAR, path); + let env_cfg = + CtfConfig::load_merge_with_opts(Default::default(), Default::default()).unwrap(); + env::remove_var(CONFIG_ENV_VAR); + assert_eq!(cfg, env_cfg); + + assert_eq!( + cfg, + CtfConfig { + auth_token: None, + ingest: TopLevelIngest { + protocol_parent_url: Url::parse("modality-ingest://127.0.0.1:14182") + .unwrap() + .into(), + allow_insecure_tls: false, + protocol_child_port: None, + timeline_attributes: TimelineAttributes { + additional_timeline_attributes: vec![ + AttrKeyEqValuePair::from_str("ci_run=1").unwrap(), + AttrKeyEqValuePair::from_str("module='linux-import'").unwrap(), + ], + override_timeline_attributes: Default::default(), + }, + max_write_batch_staleness: None, + }, + plugin: PluginConfig { + run_id: Uuid::from_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d1") + .unwrap() + .into(), + trace_uuid: Uuid::from_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d2") + .unwrap() + .into(), + log_level: babeltrace2_sys::LoggingLevel::Debug.into(), + import: Default::default(), + lttng_live: LttngLiveConfig { + retry_duration_us: 100.into(), + session_not_found_action: babeltrace2_sys::SessionNotFoundAction::End + .into(), + url: Url::parse("net://localhost/host/ubuntu-focal/my-kernel-session") + .unwrap() + .into(), + } + } + } + ); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..e70132a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,24 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error(transparent)] + Babeltrace(#[from] babeltrace2_sys::Error), + + #[error("Encountered an ingest client initialization error. {0}")] + IngestClientInitialization(#[from] modality_ingest_client::IngestClientInitializationError), + + #[error("Encountered an ingest client error. {0}")] + Ingest(#[from] modality_ingest_client::IngestError), + + #[error("Encountered an ingest client error. {0}")] + DynamicIngest(#[from] modality_ingest_client::dynamic::DynamicIngestError), + + #[error(transparent)] + Auth(#[from] crate::auth::AuthTokenError), + + #[error( + "Event attribute key prefix cannot start or end with the reserved delimeter '.' character" + )] + InvalidAttrKeyPrefix, +} diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..79d8f8f --- /dev/null +++ b/src/event.rs @@ -0,0 +1,445 @@ +use crate::attrs::{EventAttrKey, EventAttrKeyExt}; +use crate::error::Error; +use babeltrace2_sys::{OwnedEvent, OwnedField, ScalarField}; +use modality_api::{AttrKey, AttrVal, BigInt, Nanoseconds}; +use modality_ingest_protocol::InternedAttrKey; +use std::collections::{BTreeSet, HashMap}; +use tracing::warn; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct CtfEvent { + attrs: HashMap, +} + +impl CtfEvent { + pub async fn new( + event: &OwnedEvent, + client: &mut T, + ) -> Result { + let mut attrs = HashMap::new(); + + if let Some(n) = event.class_properties.name.as_ref() { + attrs.insert( + client.interned_key(EventAttrKey::Name).await?, + n.to_owned().into(), + ); + } + + let timestamp_ns: Option = event.clock_snapshot.and_then(|c: i64| { + if c < 0 { + warn!("Dropping Event ID {} clock snapshot because it's negative, consider adjusting the origin epoch offset input parameter", event.class_properties.id); + None + } else { + Some(c as u64) + } + }); + if let Some(ts) = timestamp_ns { + attrs.insert( + client.interned_key(EventAttrKey::Timestamp).await?, + Nanoseconds::from(ts).into(), + ); + attrs.insert( + client.interned_key(EventAttrKey::ClockSnapshot).await?, + Nanoseconds::from(ts).into(), + ); + } + + attrs.insert( + client.interned_key(EventAttrKey::StreamId).await?, + BigInt::new_attr_val(event.stream_id.into()), + ); + attrs.insert( + client.interned_key(EventAttrKey::Id).await?, + BigInt::new_attr_val(event.class_properties.id.into()), + ); + if let Some(ll) = event.class_properties.log_level { + attrs.insert( + client.interned_key(EventAttrKey::LogLevel).await?, + format!("{:?}", ll).to_lowercase().into(), + ); + } + + const EMPTY_PREFIX: &str = ""; + let common_context = event + .properties + .common_context + .as_ref() + .map(|f| field_to_attr(EMPTY_PREFIX, f)) + .transpose()? + .unwrap_or_default(); + for (k, v) in common_context.into_iter() { + attrs.insert( + client + .interned_key(EventAttrKey::CommonContext(k.into())) + .await?, + v, + ); + } + + let specific_context = event + .properties + .specific_context + .as_ref() + .map(|f| field_to_attr(EMPTY_PREFIX, f)) + .transpose()? + .unwrap_or_default(); + for (k, v) in specific_context.into_iter() { + attrs.insert( + client + .interned_key(EventAttrKey::SpecificContext(k.into())) + .await?, + v, + ); + } + + let packet_context = event + .properties + .packet_context + .as_ref() + .map(|f| field_to_attr(EMPTY_PREFIX, f)) + .transpose()? + .unwrap_or_default(); + for (k, v) in packet_context.into_iter() { + attrs.insert( + client + .interned_key(EventAttrKey::PacketContext(k.into())) + .await?, + v, + ); + } + + let event_fields = event + .properties + .payload + .as_ref() + .map(|f| field_to_attr(EMPTY_PREFIX, f)) + .transpose()? + .unwrap_or_default(); + for (k, v) in event_fields.into_iter() { + attrs.insert(client.interned_key(EventAttrKey::Field(k.into())).await?, v); + } + + Ok(Self { attrs }) + } + + pub fn attr_kvs(&self) -> Vec<(InternedAttrKey, AttrVal)> { + self.attrs.clone().into_iter().collect() + } +} + +/// Yields a map of <'.', AttrVal> +fn field_to_attr(prefix: &str, f: &OwnedField) -> Result, Error> { + let gen = FieldToAttrKeysGen::new(prefix)?; + Ok(gen.generate(f)) +} + +#[derive(Debug)] +struct FieldToAttrKeysGen { + // A stack of indices for each nested structure. + // We use this to name fields that did not come with a name + // since it's allowed in the spec, although unlikely in the wild. + // Invariant: len is always >= 1 for the root structure + anonymous_field_idices_per_nesting_depth: Vec, + + // A stack of attr key components built from the field names. + // A stack so we can push/pop as we encounter nested structures + // mixed inbetween parent container fields. + // Invariant: len is always >= 1 for the root structure's key_prefix + // Invariant: none of the entries should contain a '.' character + // We're certain ctf-plugins/babeltrace won't produce field names with that character because + // it's not allowed by the spec (must be valid C identifiers) + attr_key_stack: Vec, + + root_struct_observed: bool, + + attrs: HashMap, +} + +impl FieldToAttrKeysGen { + /// Invariant: key_prefix must not end in a '.', this util will handle that based + /// on compound or singular scalar types + fn new(key_prefix: &str) -> std::result::Result { + if key_prefix.starts_with('.') || key_prefix.ends_with('.') { + Err(Error::InvalidAttrKeyPrefix) + } else { + Ok(Self { + anonymous_field_idices_per_nesting_depth: vec![0], + attr_key_stack: vec![key_prefix.to_string()], + root_struct_observed: false, + attrs: Default::default(), + }) + } + } + + /// Destructure the contents of `root_field` + /// into its representative set of attr keys and values + fn generate(mut self, root_field: &OwnedField) -> HashMap { + self.generate_inner(root_field); + self.attrs + } + + fn generate_inner(&mut self, root_field: &OwnedField) { + match root_field { + OwnedField::Scalar(name, scalar) => match self.handle_scalar_field(name, scalar) { + ScalarFieldAttrKeyVal::Single(kv) => { + self.attrs.insert(kv.0, kv.1); + } + ScalarFieldAttrKeyVal::Double(kv, extra_kv) => { + self.attrs.insert(kv.0, kv.1); + self.attrs.insert(extra_kv.0, extra_kv.1); + } + }, + OwnedField::Structure(name, fields) => { + self.begin_nested_struture(name); + + // Recurse on down each field + for f in fields.iter() { + self.generate_inner(f); + } + + self.end_nested_structure(); + } + } + } + + fn handle_scalar_field( + &mut self, + field_name: &Option, + s: &ScalarField, + ) -> ScalarFieldAttrKeyVal { + let k = self.attr_key_for_field_name(field_name); + // Enums get an extra `.label` attr + match s { + ScalarField::UnsignedEnumeration(_, labels) + | ScalarField::SignedEnumeration(_, labels) => enum_label_attr(&k, labels) + .map(|extra_kv| { + ScalarFieldAttrKeyVal::Double( + (AttrKey::new(k.clone()), scalar_field_to_val(s)), + extra_kv, + ) + }) + .unwrap_or_else(|| { + ScalarFieldAttrKeyVal::Single((AttrKey::new(k.clone()), scalar_field_to_val(s))) + }), + _ => ScalarFieldAttrKeyVal::Single((AttrKey::new(k), scalar_field_to_val(s))), + } + } + + /// Get the fully qualified attr key for the given field name. + /// + /// The key is returned as a string so the caller may do additional things + /// like join with `.label` in the case of enum fields. + fn attr_key_for_field_name(&mut self, field_name: &Option) -> String { + // TODO - make this lessy stringy/allocation-heavy + let key_suffix = self.resolve_field_name(field_name); + self.attr_key_stack + .iter() + .filter(|k| !k.is_empty()) + .cloned() + .chain(std::iter::once(key_suffix)) + .collect::>() + .join(".") + } + + /// If the field name is none, generate the next anonymous field name + /// at the current nesting depth, otherwise return the provided name. + fn resolve_field_name(&mut self, field_name: &Option) -> String { + if let Some(n) = field_name { + n.to_string() + } else { + // Safety: this impl ensures self.anonymous_field_idices_per_nesting_depth.len() >= 1 + let nesting_depth = self.anonymous_field_idices_per_nesting_depth.len() - 1; + let n = format!( + "anonymous_{}", + self.anonymous_field_idices_per_nesting_depth[nesting_depth] + ); + self.anonymous_field_idices_per_nesting_depth[nesting_depth] += 1; + n + } + } + + /// Push down a new level of structure nesting. + fn begin_nested_struture(&mut self, field_name: &Option) { + // We intentionally don't generate anonymous component for the + // root-level structure, normally it's called 'fields' (implied by babeltrace/ctf-plugins) + // but we flatten that one out. + if !self.root_struct_observed { + self.root_struct_observed = true; + return; + } + + // Push on the next attr key component, either provided, or + // anonymous + // + let name = self.resolve_field_name(field_name); + self.attr_key_stack.push(name); + + // Make a new anonymous field index for the fields contained + // within this new structure. + // + // Safety: do this after we possibly updated the current nesting_depth's + // anonymous index + self.anonymous_field_idices_per_nesting_depth.push(0); + } + + /// Mark the end of the current level of structure nesting. + fn end_nested_structure(&mut self) { + let _ = self.anonymous_field_idices_per_nesting_depth.pop(); + let _ = self.attr_key_stack.pop(); + } +} + +enum ScalarFieldAttrKeyVal { + // Most ScalarFields will be in this variant + Single((AttrKey, AttrVal)), + // Enum ScalarFields get an extre '.label' attr + Double((AttrKey, AttrVal), (AttrKey, AttrVal)), +} + +// NOTE: We don't have a good strategy for arrays/sequences yet, so for now enumeration classes +// with mutliple label mappings will omit the '.label' Attr. +fn enum_label_attr(key_prefix: &str, labels: &BTreeSet) -> Option<(AttrKey, AttrVal)> { + if labels.len() == 1 { + labels.iter().next().map(|l| { + ( + AttrKey::new(format!("{}.label", key_prefix)), + l.to_owned().into(), + ) + }) + } else { + None + } +} + +fn scalar_field_to_val(s: &ScalarField) -> AttrVal { + match s { + ScalarField::Bool(v) => (*v).into(), + ScalarField::UnsignedInteger(v) => BigInt::new_attr_val(i128::from(*v)), + ScalarField::SignedInteger(v) => (*v).into(), + ScalarField::SinglePrecisionReal(v) => f64::from(v.0).into(), + ScalarField::DoublePrecisionReal(v) => v.0.into(), + ScalarField::String(v) => v.clone().into(), + ScalarField::UnsignedEnumeration(v, _) => BigInt::new_attr_val(i128::from(*v)), + ScalarField::SignedEnumeration(v, _) => (*v).into(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + // { + // l0_f0: bool, == .l0_f0 = true + // anonymous_0: u64, == .anonymous_0 = 0 + // l0_f1: String, == .l0_f1 = "blah" + // l0_s0: struct { + // anonymous_0: bool, == .l0_s0.anonymous_0 = false + // l1_f0: i64, == .l0_s0.l1_f0 = -1 + // anonymous_1: struct { + // l2_f0: String, == .l0_s0.anonymous_1.l2_f0 = "blah" + // anonymous_0: bool, == .l0_s0.anonymous_1.anonymous_0 = true + // anonymous_1: u64, == .l0_s0.anonymous_1.anonymous_1 = 2 + // } + // anonymous_2: i64, == .l0_s0.anonymous_2 = 3 + // l1_f1: String, == .l0_s0.l1_f1 = "foo" + // }, + // l0_f2: i64, == .l0_f2 = -2 + // anonymous_1: bool, == .anonymous_1 = false + // } + fn messy_event_structure() -> OwnedField { + use OwnedField::*; + use ScalarField::*; + Structure( + None, // Root structure never has a name + vec![ + Scalar("l0_f0".to_string().into(), Bool(true)), + Scalar(None, UnsignedInteger(0)), + Scalar("l0_f1".to_string().into(), String("blah".to_string())), + Structure( + "l0_s0".to_string().into(), + vec![ + Scalar(None, Bool(false)), + Scalar("l1_f0".to_string().into(), SignedInteger(-1)), + Structure( + None, + vec![ + Scalar("l2_f0".to_string().into(), String("blah".to_string())), + Scalar(None, Bool(true)), + Scalar(None, UnsignedInteger(2)), + ], + ), + Scalar(None, SignedInteger(3)), + Scalar("l1_f1".to_string().into(), String("foo".to_string())), + ], + ), + Scalar("l0_f2".to_string().into(), SignedInteger(-2)), + Scalar(None, Bool(false)), + ], + ) + } + + #[test] + fn attr_key_gen_mixed_nested_structs() { + let root = messy_event_structure(); + let gen = FieldToAttrKeysGen::new("some.prefix").unwrap(); + let mut attrs = gen.generate(&root).into_iter().collect::>(); + attrs.sort_by(|a, b| a.0.as_ref().cmp(b.0.as_ref())); + assert_eq!( + attrs, + vec![ + ( + AttrKey::new("some.prefix.anonymous_0".to_owned()), + BigInt::new_attr_val(0) + ), + ( + AttrKey::new("some.prefix.anonymous_1".to_owned()), + false.into() + ), + (AttrKey::new("some.prefix.l0_f0".to_owned()), true.into()), + ( + AttrKey::new("some.prefix.l0_f1".to_owned()), + "blah".to_string().into() + ), + ( + AttrKey::new("some.prefix.l0_f2".to_owned()), + AttrVal::from(-2_i64) + ), + ( + AttrKey::new("some.prefix.l0_s0.anonymous_0".to_owned()), + false.into() + ), + ( + AttrKey::new("some.prefix.l0_s0.anonymous_1.anonymous_0".to_owned()), + true.into() + ), + ( + AttrKey::new("some.prefix.l0_s0.anonymous_1.anonymous_1".to_owned()), + BigInt::new_attr_val(2) + ), + ( + AttrKey::new("some.prefix.l0_s0.anonymous_1.l2_f0".to_owned()), + "blah".to_string().into() + ), + ( + AttrKey::new("some.prefix.l0_s0.anonymous_2".to_owned()), + 3_i64.into() + ), + ( + AttrKey::new("some.prefix.l0_s0.l1_f0".to_owned()), + AttrVal::from(-1_i64) + ), + ( + AttrKey::new("some.prefix.l0_s0.l1_f1".to_owned()), + "foo".to_string().into() + ), + ] + ); + } + + #[test] + fn attr_key_gen_smoke() { + assert!(FieldToAttrKeysGen::new(".asdf").is_err()); + assert!(FieldToAttrKeysGen::new("asdf.").is_err()); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9ddea90 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,75 @@ +//! # Overview +//! +//! Conceptually CTF data is organized as followed (from babeltrace2 docs): +//! * Trace (all the specified physical CTF traces must belong to the same logical CTF trace) +//! - One or more streams (timelines) +//! * Series of events +//! +//! ![trace-structure](https://babeltrace.org/docs/v2.0/libbabeltrace2/trace-structure.png) +//! +//! # Attrs Mappings +//! +//! Trace Attrs +//! * timeline.internal.ctf.trace.name +//! * timeline.internal.ctf.trace.uuid +//! * timeline.internal.ctf.trace.stream_count +//! * timeline.internal.ctf.trace.env. +//! +//! Stream Attrs +//! * timeline.internal.ctf.stream.id +//! * timeline.internal.ctf.stream.name +//! - timeline.name +//! * timeline.internal.ctf.stream.clock.frequency +//! * timeline.internal.ctf.stream.clock.offset_seconds +//! * timeline.internal.ctf.stream.clock.offset_cycles +//! * timeline.internal.ctf.stream.clock.precision +//! * timeline.internal.ctf.stream.clock.unix_epoch_origin +//! * timeline.internal.ctf.stream.clock.name +//! * timeline.internal.ctf.stream.clock.description +//! * timeline.internal.ctf.stream.clock.uuid +//! - timeline.time_domain +//! * timeline.ingest_source +//! +//! Event Attrs +//! * event.internal.ctf.stream_id +//! * event.internal.ctf.id +//! * event.name +//! * event.internal.ctf.log_level +//! * event.internal.ctf.clock_snapshot +//! - event.timestamp +//! * event.internal.ctf.common_context. +//! * event.internal.ctf.specific_context. +//! * event.internal.ctf.packet_context. +//! * event. +//! +//! # Mapping Conventions +//! +//! ## Enumeration classes +//! +//! CTF signed/unsigned enumeration classes will be given at Attr for the discriminant value +//! and possibly one or more Attrs for the label mappings. +//! Values are allowed to have no label mapping, or have many label +//! mappings (values are allowed to overlap). +//! +//! NOTE: We don't have a good strategy for arrays/sequences yet, so for now enumeration classes +//! with mutliple label mappings will omit the `.label` Attr. +//! +//! Example: `my_enum` is an enumeration class with value 5 and single label mapping "RUNNING" +//! * event.my_enum = 5 +//! * event.my_enum.label = "RUNNING" +//! +//! Example: `my_enum` is an enumeration class with value 1 and no label mapping +//! * event.my_enum = 5 +#![deny(warnings, clippy::all)] + +pub mod attrs; +pub mod auth; +pub mod client; +pub mod config; +pub mod error; +pub mod event; +pub mod opts; +pub mod prelude; +pub mod properties; +pub mod tracing; +pub mod types; diff --git a/src/opts.rs b/src/opts.rs new file mode 100644 index 0000000..d3faab1 --- /dev/null +++ b/src/opts.rs @@ -0,0 +1,66 @@ +use crate::types::LoggingLevel; +use clap::Parser; +use std::path::PathBuf; +use url::Url; +use uuid::Uuid; + +#[derive(Parser, Debug, Clone, Default)] +pub struct ReflectorOpts { + /// Use configuration from file + #[clap( + long = "config", + name = "config file", + env = "MODALITY_REFLECTOR_CONFIG", + help_heading = "REFLECTOR CONFIGURATION" + )] + pub config_file: Option, + + /// Modality auth token hex string used to authenticate with. + /// Can also be provide via the MODALITY_AUTH_TOKEN environment variable. + #[clap( + long, + name = "auth-token-hex-string", + env = "MODALITY_AUTH_TOKEN", + help_heading = "REFLECTOR CONFIGURATION" + )] + pub auth_token: Option, + + /// The modalityd or modality-reflector ingest protocol parent service address + /// + /// The default value is `modality-ingest://127.0.0.1:14188`. + /// + /// You can talk directly to the default ingest server port with + /// `--ingest-protocol-parent-url modality-ingest://127.0.0.1:14182` + #[clap( + long = "ingest-protocol-parent-url", + name = "URL", + help_heading = "REFLECTOR CONFIGURATION" + )] + pub protocol_parent_url: Option, + + /// Allow insecure TLS + #[clap( + short = 'k', + long = "insecure", + help_heading = "REFLECTOR CONFIGURATION" + )] + pub allow_insecure_tls: bool, + + /// Use the provided UUID as the run ID instead of generating a random one + #[clap(long, name = "run-uuid", help_heading = "REFLECTOR CONFIGURATION")] + pub run_id: Option, +} + +#[derive(Parser, Debug, Clone, Default)] +pub struct BabeltraceOpts { + /// Optionally provide a trace UUID to override any present (or not) UUID contained + /// in the CTF metadata. + /// + /// This is useful for constructing deterministic trace UUIDis which form the timeline IDs. + #[clap(long, name = "trace-uuid", help_heading = "BABELTRACE CONFIGURATION")] + pub trace_uuid: Option, + + /// Logging level for libbabeltrace + #[clap(long, name = "log-level", help_heading = "BABELTRACE CONFIGURATION")] + pub log_level: Option, +} diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..b3c1ed2 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,7 @@ +pub use crate::attrs::{EventAttrKey, EventAttrKeyExt, TimelineAttrKey, TimelineAttrKeyExt}; +pub use crate::client::Client; +pub use crate::config::{CtfConfig, ImportConfig, LttngLiveConfig, PluginConfig}; +pub use crate::event::CtfEvent; +pub use crate::opts::{BabeltraceOpts, ReflectorOpts}; +pub use crate::properties::{CtfProperties, CtfStreamProperties, CtfTraceProperties}; +pub use crate::types::Interruptor; diff --git a/src/properties/mod.rs b/src/properties/mod.rs new file mode 100644 index 0000000..12da7e6 --- /dev/null +++ b/src/properties/mod.rs @@ -0,0 +1,58 @@ +use crate::attrs::TimelineAttrKeyExt; +use crate::error::Error; +use babeltrace2_sys::{StreamId, StreamProperties, TraceProperties}; +use modality_api::{AttrVal, TimelineId}; +use modality_ingest_protocol::InternedAttrKey; +use std::collections::{BTreeMap, BTreeSet}; +use uuid::Uuid; + +pub use stream::CtfStreamProperties; +pub use trace::CtfTraceProperties; + +pub(crate) mod stream; +pub(crate) mod trace; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct CtfProperties { + pub trace: CtfTraceProperties, + pub streams: BTreeMap, +} + +impl CtfProperties { + pub async fn new( + run_id: Option, + trace_uuid_override: Option, + t: &TraceProperties, + s: &BTreeSet, + client: &mut T, + ) -> Result { + // TimelineIds are a composite of the trace UUID and the stream ID + // Use the override if present, otherwise use the trace's UUID + // Fallback to making a new random UUID + let trace_uuid = trace_uuid_override.or(t.uuid).unwrap_or_else(Uuid::new_v4); + + let stream_count = s.len() as u64; + let trace = + CtfTraceProperties::new(run_id, trace_uuid_override, stream_count, t, client).await?; + let mut streams = BTreeMap::default(); + for stream in s.iter() { + streams.insert( + stream.id, + CtfStreamProperties::new(&trace_uuid, stream, client).await?, + ); + } + Ok(Self { trace, streams }) + } + + #[allow(clippy::type_complexity)] + pub fn timelines( + &self, + ) -> Box)> + '_> { + let trace_attr_kvs = self.trace.attr_kvs(); + Box::new(self.streams.iter().map(move |(_sid, p)| { + let mut attr_kvs = p.attr_kvs(); + attr_kvs.extend_from_slice(&trace_attr_kvs); + (p.timeline_id(), attr_kvs) + })) + } +} diff --git a/src/properties/stream.rs b/src/properties/stream.rs new file mode 100644 index 0000000..7a1031c --- /dev/null +++ b/src/properties/stream.rs @@ -0,0 +1,139 @@ +use crate::attrs::{TimelineAttrKey, TimelineAttrKeyExt, TIMELINE_INGEST_SOURCE_VAL}; +use crate::error::Error; +use babeltrace2_sys::StreamProperties; +use modality_api::{AttrVal, BigInt, TimelineId}; +use modality_ingest_protocol::InternedAttrKey; +use std::collections::HashMap; +use std::path::Path; +use uuid::Uuid; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct CtfStreamProperties { + timeline_id: TimelineId, + attrs: HashMap, +} + +impl CtfStreamProperties { + pub async fn new( + trace_uuid: &Uuid, + s: &StreamProperties, + client: &mut T, + ) -> Result { + let mut attrs = HashMap::default(); + let timeline_id = TimelineId::from(Uuid::new_v5(trace_uuid, &s.id.to_le_bytes())); + + // The stream name produced by babeltrace is the path to the stream file within + // a trace. This is rather ugly and hard to write specs against + // (event @ "/some annoyingly/long path to/a trace/stream_0"). + // So instead of making a timeline name directly from a stream name, we first + // attempt to use the file name component if possible, and fallback to + // "stream{stream_id}" which is the default naming convention used within + // the LTTng ecosystem when not provided by babeltrace. + let stream_name_from_path = s.name.as_ref().and_then(|sn| { + let p = Path::new(sn); + if p.exists() { + p.file_name().map(|s| s.to_string_lossy()) + } else { + None + } + }); + let stream_name = stream_name_from_path + .map(|s| s.to_string()) + .or_else(|| s.name.clone()) + .unwrap_or_else(|| format!("stream{}", s.id)); + + attrs.insert( + client.interned_key(TimelineAttrKey::Description).await?, + format!("CTF stream '{stream_name}'").into(), + ); + attrs.insert( + client.interned_key(TimelineAttrKey::Name).await?, + stream_name.clone().into(), + ); + + attrs.insert( + client.interned_key(TimelineAttrKey::StreamName).await?, + stream_name.into(), + ); + attrs.insert( + client.interned_key(TimelineAttrKey::StreamId).await?, + BigInt::new_attr_val(s.id.into()), + ); + + attrs.insert( + client.interned_key(TimelineAttrKey::IngestSource).await?, + TIMELINE_INGEST_SOURCE_VAL.into(), + ); + + if let Some(c) = &s.clock { + attrs.insert( + client + .interned_key(TimelineAttrKey::StreamClockFreq) + .await?, + BigInt::new_attr_val(c.frequency.into()), + ); + attrs.insert( + client + .interned_key(TimelineAttrKey::StreamClockOffsetSeconds) + .await?, + c.offset_seconds.into(), + ); + attrs.insert( + client + .interned_key(TimelineAttrKey::StreamClockOffsetCycles) + .await?, + BigInt::new_attr_val(c.offset_cycles.into()), + ); + attrs.insert( + client + .interned_key(TimelineAttrKey::StreamClockPrecision) + .await?, + BigInt::new_attr_val(c.precision.into()), + ); + attrs.insert( + client + .interned_key(TimelineAttrKey::StreamClockUnixEpoch) + .await?, + c.unix_epoch_origin.into(), + ); + if let Some(cn) = &c.name { + attrs.insert( + client + .interned_key(TimelineAttrKey::StreamClockName) + .await?, + cn.to_owned().into(), + ); + } + if let Some(cd) = &c.description { + attrs.insert( + client + .interned_key(TimelineAttrKey::StreamClockDesc) + .await?, + cd.to_owned().into(), + ); + } + if let Some(cid) = &c.uuid { + attrs.insert( + client + .interned_key(TimelineAttrKey::StreamClockUuid) + .await?, + cid.to_string().into(), + ); + attrs.insert( + client.interned_key(TimelineAttrKey::TimeDomain).await?, + cid.to_string().into(), + ); + } + } + + Ok(Self { timeline_id, attrs }) + } + + pub fn timeline_id(&self) -> TimelineId { + self.timeline_id + } + + pub fn attr_kvs(&self) -> Vec<(InternedAttrKey, AttrVal)> { + self.attrs.clone().into_iter().collect() + } +} diff --git a/src/properties/trace.rs b/src/properties/trace.rs new file mode 100644 index 0000000..8970498 --- /dev/null +++ b/src/properties/trace.rs @@ -0,0 +1,73 @@ +use crate::attrs::{TimelineAttrKey, TimelineAttrKeyExt}; +use crate::error::Error; +use babeltrace2_sys::{EnvValue, TraceProperties}; +use modality_api::{AttrVal, BigInt}; +use modality_ingest_protocol::InternedAttrKey; +use std::collections::HashMap; +use uuid::Uuid; + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct CtfTraceProperties { + attrs: HashMap, +} + +impl CtfTraceProperties { + pub async fn new( + run_id: Option, + trace_uuid_override: Option, + stream_count: u64, + t: &TraceProperties, + client: &mut T, + ) -> Result { + let mut attrs = HashMap::default(); + + attrs.insert( + client.interned_key(TimelineAttrKey::RunId).await?, + run_id.unwrap_or_else(Uuid::new_v4).to_string().into(), + ); + + if let Some(uuid) = trace_uuid_override.or(t.uuid) { + attrs.insert( + client.interned_key(TimelineAttrKey::TraceUuid).await?, + uuid.to_string().into(), + ); + } + + attrs.insert( + client + .interned_key(TimelineAttrKey::TraceStreamCount) + .await?, + BigInt::new_attr_val(stream_count.into()), + ); + + if let Some(name) = t.name.as_ref() { + attrs.insert( + client.interned_key(TimelineAttrKey::Name).await?, + name.to_owned().into(), + ); + attrs.insert( + client.interned_key(TimelineAttrKey::TraceName).await?, + name.to_owned().into(), + ); + } + + if let Some(e) = &t.env { + for (k, v) in e.entries() { + let key = TimelineAttrKey::TraceEnv(k.to_owned()); + attrs.insert( + client.interned_key(key).await?, + match v { + EnvValue::Integer(int) => AttrVal::Integer(*int), + EnvValue::String(s) => AttrVal::String(s.clone()), + }, + ); + } + } + + Ok(Self { attrs }) + } + + pub fn attr_kvs(&self) -> Vec<(InternedAttrKey, AttrVal)> { + self.attrs.clone().into_iter().collect() + } +} diff --git a/src/tracing.rs b/src/tracing.rs new file mode 100644 index 0000000..bac9b08 --- /dev/null +++ b/src/tracing.rs @@ -0,0 +1,20 @@ +pub fn try_init_tracing_subscriber() -> Result<(), Box> { + let builder = tracing_subscriber::fmt::Subscriber::builder(); + let env_filter = std::env::var(tracing_subscriber::EnvFilter::DEFAULT_ENV) + .map(tracing_subscriber::EnvFilter::new) + .unwrap_or_else(|_| { + let level = tracing::Level::WARN; + tracing_subscriber::EnvFilter::new(format!( + "{}={},modality_ctf_import={},modality_lttng_live={}", + env!("CARGO_PKG_NAME").replace('-', "_"), + level, + level, + level, + )) + }); + let builder = builder.with_env_filter(env_filter); + let subscriber = builder.finish(); + use tracing_subscriber::util::SubscriberInitExt; + subscriber.try_init()?; + Ok(()) +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..5c37941 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,112 @@ +use derive_more::{Display, From, Into}; +use serde::Deserialize; +use std::convert::TryFrom; +use std::num::ParseIntError; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; +use std::sync::Arc; + +#[derive(Clone, Debug)] +#[repr(transparent)] +pub struct Interruptor(Arc); + +impl Interruptor { + pub fn new() -> Self { + Interruptor(Arc::new(AtomicBool::new(false))) + } + + pub fn set(&self) { + self.0.store(true, SeqCst); + } + + pub fn is_set(&self) -> bool { + self.0.load(SeqCst) + } +} + +impl Default for Interruptor { + fn default() -> Self { + Self::new() + } +} + +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, From, Into, Display, +)] +#[repr(transparent)] +pub struct RetryDurationUs(pub u64); + +impl Default for RetryDurationUs { + fn default() -> Self { + // 100ms + RetryDurationUs(100000) + } +} + +impl FromStr for RetryDurationUs { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + Ok(RetryDurationUs(s.trim().parse::()?)) + } +} + +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, From, Into, Display, +)] +#[serde(try_from = "String", into = "String")] +pub struct LoggingLevel(pub babeltrace2_sys::LoggingLevel); + +impl Default for LoggingLevel { + fn default() -> Self { + LoggingLevel(babeltrace2_sys::LoggingLevel::None) + } +} + +impl TryFrom for LoggingLevel { + type Error = String; + + fn try_from(s: String) -> Result { + Ok(LoggingLevel(babeltrace2_sys::LoggingLevel::from_str(&s)?)) + } +} + +impl FromStr for LoggingLevel { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(LoggingLevel(babeltrace2_sys::LoggingLevel::from_str(s)?)) + } +} + +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, From, Into, Display, +)] +#[serde(try_from = "String", into = "String")] +pub struct SessionNotFoundAction(pub babeltrace2_sys::SessionNotFoundAction); + +impl Default for SessionNotFoundAction { + fn default() -> Self { + SessionNotFoundAction(babeltrace2_sys::SessionNotFoundAction::Continue) + } +} + +impl TryFrom for SessionNotFoundAction { + type Error = String; + + fn try_from(s: String) -> Result { + Ok(SessionNotFoundAction( + babeltrace2_sys::SessionNotFoundAction::from_str(&s)?, + )) + } +} + +impl FromStr for SessionNotFoundAction { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(SessionNotFoundAction( + babeltrace2_sys::SessionNotFoundAction::from_str(s)?, + )) + } +}