├── .github └── workflows │ ├── audit.yml │ ├── ci.yml │ ├── docker.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE-MIT ├── README.md ├── assets └── trace_compass_freertos.png ├── macros ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs └── src ├── convert.rs ├── events.rs ├── interruptor.rs ├── main.rs └── types.rs /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | audit: 7 | name: Audit 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: rustsec/audit-check@v1.4.1 12 | with: 13 | token: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | rust: [stable] 11 | os: [ubuntu-20.04] 12 | 13 | steps: 14 | - name: Checkout sources 15 | uses: actions/checkout@v4 16 | 17 | - name: Cache target 18 | uses: actions/cache@v4 19 | with: 20 | path: | 21 | ~/.cargo/registry 22 | ~/.cargo/git 23 | target 24 | key: ${{ matrix.os }}-cargo--${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }} 25 | 26 | - name: Install system dependencies 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install --no-install-recommends --assume-yes patchelf gzip cmake make gcc g++ build-essential 30 | sudo apt-get install --no-install-recommends --assume-yes libssl-dev libglib2.0-dev pkg-config libtool flex bison autoconf automake 31 | 32 | - name: Install toolchain 33 | uses: dtolnay/rust-toolchain@stable 34 | with: 35 | components: clippy, rustfmt 36 | toolchain: ${{ matrix.rust }} 37 | 38 | - name: Clippy 39 | run: cargo clippy --all-features -- -W clippy::all -D warnings 40 | 41 | - name: Format 42 | run: cargo fmt --all -- --check 43 | 44 | - name: Doc Generation 45 | run: cargo doc --bins --examples --all-features --no-deps 46 | 47 | build: 48 | runs-on: ${{ matrix.os }} 49 | strategy: 50 | matrix: 51 | rust: [stable] 52 | os: [ubuntu-20.04] 53 | 54 | steps: 55 | - name: Checkout sources 56 | uses: actions/checkout@v4 57 | 58 | - name: Cache target 59 | uses: actions/cache@v4 60 | with: 61 | path: | 62 | ~/.cargo/registry 63 | ~/.cargo/git 64 | target 65 | key: ${{ matrix.os }}-cargo--${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }} 66 | 67 | - name: Install system dependencies 68 | run: | 69 | sudo apt-get update 70 | sudo apt-get install --no-install-recommends --assume-yes patchelf gzip cmake make gcc g++ build-essential 71 | sudo apt-get install --no-install-recommends --assume-yes libssl-dev libglib2.0-dev pkg-config libtool flex bison autoconf automake 72 | 73 | - name: Install toolchain 74 | uses: dtolnay/rust-toolchain@stable 75 | with: 76 | components: clippy, rustfmt 77 | toolchain: ${{ matrix.rust }} 78 | 79 | - name: Build debug binary 80 | run: cargo build 81 | 82 | - name: Build release binary 83 | run: cargo build --release 84 | 85 | test: 86 | runs-on: ${{ matrix.os }} 87 | strategy: 88 | matrix: 89 | rust: [stable] 90 | os: [ubuntu-20.04] 91 | 92 | steps: 93 | - name: Checkout sources 94 | uses: actions/checkout@v4 95 | 96 | - name: Cache target 97 | uses: actions/cache@v4 98 | with: 99 | path: | 100 | ~/.cargo/registry 101 | ~/.cargo/git 102 | target 103 | key: ${{ matrix.os }}-cargo--${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }} 104 | 105 | - name: Install system dependencies 106 | run: | 107 | sudo apt-get update 108 | sudo apt-get install --no-install-recommends --assume-yes patchelf gzip cmake make gcc g++ build-essential 109 | sudo apt-get install --no-install-recommends --assume-yes libssl-dev libglib2.0-dev pkg-config libtool flex bison autoconf automake 110 | 111 | - name: Install toolchain 112 | uses: dtolnay/rust-toolchain@stable 113 | with: 114 | components: clippy, rustfmt 115 | toolchain: ${{ matrix.rust }} 116 | 117 | - name: Test 118 | run: cargo test --all-features 119 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | package: 8 | name: Build 22.04 Docker Image 9 | timeout-minutes: 60 10 | runs-on: ubuntu-20.04 11 | permissions: 12 | packages: write 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Login to GitHub Container Registry 18 | uses: docker/login-action@v3 19 | with: 20 | registry: ghcr.io 21 | username: ${{ github.actor }} 22 | password: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Build docker image 25 | run: docker build -t ghcr.io/jonlamb-gh/trace-recorder-to-ctf:latest . 26 | 27 | - name: Push docker image 28 | run: docker push ghcr.io/jonlamb-gh/trace-recorder-to-ctf:latest 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | # Push events to matching v*, i.e. v1.0, v20.15.10 4 | on: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | build: 11 | name: Build Release Artifacts 12 | runs-on: ubuntu-20.04 13 | permissions: 14 | contents: write 15 | steps: 16 | - name: Print version 17 | run: | 18 | RELEASE_TAG=${{ github.ref }} 19 | RELEASE_TAG="${RELEASE_TAG#refs/tags/}" 20 | RELEASE_VERSION="${RELEASE_TAG#v}" 21 | echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV 22 | echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV 23 | echo "Release tag: $RELEASE_TAG" 24 | echo "Release version: $RELEASE_VERSION" 25 | 26 | - name: Checkout sources 27 | uses: actions/checkout@v4 28 | 29 | - name: Install system dependencies 30 | run: | 31 | sudo apt-get update 32 | sudo apt-get install --no-install-recommends --assume-yes patchelf gzip cmake make gcc g++ build-essential 33 | sudo apt-get install --no-install-recommends --assume-yes libssl-dev libglib2.0-dev pkg-config libtool flex bison autoconf automake 34 | 35 | - name: Install toolchain 36 | uses: dtolnay/rust-toolchain@stable 37 | with: 38 | toolchain: stable 39 | 40 | - name: Fetch dependencies 41 | run: cargo fetch 42 | 43 | - name: Build release binaries 44 | run: cargo build --release 45 | 46 | - name: Create github release 47 | id: create_release 48 | uses: softprops/action-gh-release@v2 49 | with: 50 | draft: false 51 | prerelease: false 52 | name: Release ${{ env.RELEASE_VERSION }} 53 | files: | 54 | target/release/trace-recorder-to-ctf 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "android-tzdata" 16 | version = "0.1.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 19 | 20 | [[package]] 21 | name = "android_system_properties" 22 | version = "0.1.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 25 | dependencies = [ 26 | "libc", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.6" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys", 76 | ] 77 | 78 | [[package]] 79 | name = "autocfg" 80 | version = "1.4.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 83 | 84 | [[package]] 85 | name = "autotools" 86 | version = "0.2.7" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "ef941527c41b0fc0dd48511a8154cd5fc7e29200a0ff8b7203c5d777dbc795cf" 89 | dependencies = [ 90 | "cc", 91 | ] 92 | 93 | [[package]] 94 | name = "babeltrace2-sys" 95 | version = "0.3.1" 96 | source = "git+https://github.com/auxoncorp/babeltrace2-sys.git?branch=src-component-support#795546a204aa52753730b8ac4b3ab82adc2c8cd3" 97 | dependencies = [ 98 | "autotools", 99 | "libc", 100 | "ordered-float 3.9.2", 101 | "pkg-config", 102 | "thiserror", 103 | "tracing", 104 | "uuid", 105 | ] 106 | 107 | [[package]] 108 | name = "bitflags" 109 | version = "2.6.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 112 | 113 | [[package]] 114 | name = "bumpalo" 115 | version = "3.16.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 118 | 119 | [[package]] 120 | name = "byteorder" 121 | version = "1.5.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 124 | 125 | [[package]] 126 | name = "byteordered" 127 | version = "0.6.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "bbf2cd9424f5ff404aba1959c835cbc448ee8b689b870a9981c76c0fd46280e6" 130 | dependencies = [ 131 | "byteorder", 132 | ] 133 | 134 | [[package]] 135 | name = "cc" 136 | version = "1.2.2" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" 139 | dependencies = [ 140 | "shlex", 141 | ] 142 | 143 | [[package]] 144 | name = "cfg-if" 145 | version = "1.0.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 148 | 149 | [[package]] 150 | name = "cfg_aliases" 151 | version = "0.2.1" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 154 | 155 | [[package]] 156 | name = "chrono" 157 | version = "0.4.38" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 160 | dependencies = [ 161 | "android-tzdata", 162 | "iana-time-zone", 163 | "js-sys", 164 | "num-traits", 165 | "wasm-bindgen", 166 | "windows-targets", 167 | ] 168 | 169 | [[package]] 170 | name = "clap" 171 | version = "4.5.21" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 174 | dependencies = [ 175 | "clap_builder", 176 | "clap_derive", 177 | ] 178 | 179 | [[package]] 180 | name = "clap_builder" 181 | version = "4.5.21" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 184 | dependencies = [ 185 | "anstream", 186 | "anstyle", 187 | "clap_lex", 188 | "strsim", 189 | ] 190 | 191 | [[package]] 192 | name = "clap_derive" 193 | version = "4.5.18" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 196 | dependencies = [ 197 | "heck", 198 | "proc-macro2", 199 | "quote", 200 | "syn", 201 | ] 202 | 203 | [[package]] 204 | name = "clap_lex" 205 | version = "0.7.3" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 208 | 209 | [[package]] 210 | name = "colorchoice" 211 | version = "1.0.3" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 214 | 215 | [[package]] 216 | name = "convert_case" 217 | version = "0.4.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 220 | 221 | [[package]] 222 | name = "convert_case" 223 | version = "0.6.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 226 | dependencies = [ 227 | "unicode-segmentation", 228 | ] 229 | 230 | [[package]] 231 | name = "core-foundation-sys" 232 | version = "0.8.7" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 235 | 236 | [[package]] 237 | name = "ctf-macros" 238 | version = "0.1.0" 239 | dependencies = [ 240 | "convert_case 0.6.0", 241 | "proc-macro2", 242 | "quote", 243 | "syn", 244 | ] 245 | 246 | [[package]] 247 | name = "ctrlc" 248 | version = "3.4.5" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" 251 | dependencies = [ 252 | "nix", 253 | "windows-sys", 254 | ] 255 | 256 | [[package]] 257 | name = "derive_more" 258 | version = "0.99.18" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" 261 | dependencies = [ 262 | "convert_case 0.4.0", 263 | "proc-macro2", 264 | "quote", 265 | "rustc_version", 266 | "syn", 267 | ] 268 | 269 | [[package]] 270 | name = "enum-iterator" 271 | version = "2.1.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "c280b9e6b3ae19e152d8e31cf47f18389781e119d4013a2a2bb0180e5facc635" 274 | dependencies = [ 275 | "enum-iterator-derive", 276 | ] 277 | 278 | [[package]] 279 | name = "enum-iterator-derive" 280 | version = "1.4.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" 283 | dependencies = [ 284 | "proc-macro2", 285 | "quote", 286 | "syn", 287 | ] 288 | 289 | [[package]] 290 | name = "heck" 291 | version = "0.5.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 294 | 295 | [[package]] 296 | name = "iana-time-zone" 297 | version = "0.1.61" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 300 | dependencies = [ 301 | "android_system_properties", 302 | "core-foundation-sys", 303 | "iana-time-zone-haiku", 304 | "js-sys", 305 | "wasm-bindgen", 306 | "windows-core", 307 | ] 308 | 309 | [[package]] 310 | name = "iana-time-zone-haiku" 311 | version = "0.1.2" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 314 | dependencies = [ 315 | "cc", 316 | ] 317 | 318 | [[package]] 319 | name = "is_terminal_polyfill" 320 | version = "1.70.1" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 323 | 324 | [[package]] 325 | name = "js-sys" 326 | version = "0.3.74" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" 329 | dependencies = [ 330 | "once_cell", 331 | "wasm-bindgen", 332 | ] 333 | 334 | [[package]] 335 | name = "lazy_static" 336 | version = "1.5.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 339 | 340 | [[package]] 341 | name = "libc" 342 | version = "0.2.167" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" 345 | 346 | [[package]] 347 | name = "log" 348 | version = "0.4.22" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 351 | 352 | [[package]] 353 | name = "matchers" 354 | version = "0.1.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 357 | dependencies = [ 358 | "regex-automata 0.1.10", 359 | ] 360 | 361 | [[package]] 362 | name = "memchr" 363 | version = "2.7.4" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 366 | 367 | [[package]] 368 | name = "nix" 369 | version = "0.29.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 372 | dependencies = [ 373 | "bitflags", 374 | "cfg-if", 375 | "cfg_aliases", 376 | "libc", 377 | ] 378 | 379 | [[package]] 380 | name = "nu-ansi-term" 381 | version = "0.46.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 384 | dependencies = [ 385 | "overload", 386 | "winapi", 387 | ] 388 | 389 | [[package]] 390 | name = "num-traits" 391 | version = "0.2.19" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 394 | dependencies = [ 395 | "autocfg", 396 | ] 397 | 398 | [[package]] 399 | name = "once_cell" 400 | version = "1.20.2" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 403 | 404 | [[package]] 405 | name = "ordered-float" 406 | version = "3.9.2" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" 409 | dependencies = [ 410 | "num-traits", 411 | ] 412 | 413 | [[package]] 414 | name = "ordered-float" 415 | version = "4.5.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "c65ee1f9701bf938026630b455d5315f490640234259037edb259798b3bcf85e" 418 | dependencies = [ 419 | "num-traits", 420 | ] 421 | 422 | [[package]] 423 | name = "overload" 424 | version = "0.1.1" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 427 | 428 | [[package]] 429 | name = "pin-project-lite" 430 | version = "0.2.15" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 433 | 434 | [[package]] 435 | name = "pkg-config" 436 | version = "0.3.31" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 439 | 440 | [[package]] 441 | name = "proc-macro2" 442 | version = "1.0.92" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 445 | dependencies = [ 446 | "unicode-ident", 447 | ] 448 | 449 | [[package]] 450 | name = "quote" 451 | version = "1.0.37" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 454 | dependencies = [ 455 | "proc-macro2", 456 | ] 457 | 458 | [[package]] 459 | name = "regex" 460 | version = "1.11.1" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 463 | dependencies = [ 464 | "aho-corasick", 465 | "memchr", 466 | "regex-automata 0.4.9", 467 | "regex-syntax 0.8.5", 468 | ] 469 | 470 | [[package]] 471 | name = "regex-automata" 472 | version = "0.1.10" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 475 | dependencies = [ 476 | "regex-syntax 0.6.29", 477 | ] 478 | 479 | [[package]] 480 | name = "regex-automata" 481 | version = "0.4.9" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 484 | dependencies = [ 485 | "aho-corasick", 486 | "memchr", 487 | "regex-syntax 0.8.5", 488 | ] 489 | 490 | [[package]] 491 | name = "regex-syntax" 492 | version = "0.6.29" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 495 | 496 | [[package]] 497 | name = "regex-syntax" 498 | version = "0.8.5" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 501 | 502 | [[package]] 503 | name = "rustc_version" 504 | version = "0.4.1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 507 | dependencies = [ 508 | "semver", 509 | ] 510 | 511 | [[package]] 512 | name = "semver" 513 | version = "1.0.23" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 516 | 517 | [[package]] 518 | name = "sha1_smol" 519 | version = "1.0.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" 522 | 523 | [[package]] 524 | name = "sharded-slab" 525 | version = "0.1.7" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 528 | dependencies = [ 529 | "lazy_static", 530 | ] 531 | 532 | [[package]] 533 | name = "shlex" 534 | version = "1.3.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 537 | 538 | [[package]] 539 | name = "smallvec" 540 | version = "1.13.2" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 543 | 544 | [[package]] 545 | name = "strsim" 546 | version = "0.11.1" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 549 | 550 | [[package]] 551 | name = "syn" 552 | version = "2.0.90" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 555 | dependencies = [ 556 | "proc-macro2", 557 | "quote", 558 | "unicode-ident", 559 | ] 560 | 561 | [[package]] 562 | name = "thiserror" 563 | version = "1.0.69" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 566 | dependencies = [ 567 | "thiserror-impl", 568 | ] 569 | 570 | [[package]] 571 | name = "thiserror-impl" 572 | version = "1.0.69" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 575 | dependencies = [ 576 | "proc-macro2", 577 | "quote", 578 | "syn", 579 | ] 580 | 581 | [[package]] 582 | name = "thread_local" 583 | version = "1.1.8" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 586 | dependencies = [ 587 | "cfg-if", 588 | "once_cell", 589 | ] 590 | 591 | [[package]] 592 | name = "trace-recorder-parser" 593 | version = "0.19.0" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "eeb6823b3d87e1899b36507b7662814f031b0ef3d2905ccfe127381cf84d598d" 596 | dependencies = [ 597 | "byteordered", 598 | "derive_more", 599 | "enum-iterator", 600 | "ordered-float 4.5.0", 601 | "thiserror", 602 | "tracing", 603 | ] 604 | 605 | [[package]] 606 | name = "trace-recorder-to-ctf" 607 | version = "0.3.1" 608 | dependencies = [ 609 | "babeltrace2-sys", 610 | "chrono", 611 | "clap", 612 | "ctf-macros", 613 | "ctrlc", 614 | "enum-iterator", 615 | "thiserror", 616 | "trace-recorder-parser", 617 | "tracing", 618 | "tracing-subscriber", 619 | ] 620 | 621 | [[package]] 622 | name = "tracing" 623 | version = "0.1.41" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 626 | dependencies = [ 627 | "pin-project-lite", 628 | "tracing-attributes", 629 | "tracing-core", 630 | ] 631 | 632 | [[package]] 633 | name = "tracing-attributes" 634 | version = "0.1.28" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 637 | dependencies = [ 638 | "proc-macro2", 639 | "quote", 640 | "syn", 641 | ] 642 | 643 | [[package]] 644 | name = "tracing-core" 645 | version = "0.1.33" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 648 | dependencies = [ 649 | "once_cell", 650 | "valuable", 651 | ] 652 | 653 | [[package]] 654 | name = "tracing-log" 655 | version = "0.2.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 658 | dependencies = [ 659 | "log", 660 | "once_cell", 661 | "tracing-core", 662 | ] 663 | 664 | [[package]] 665 | name = "tracing-subscriber" 666 | version = "0.3.19" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 669 | dependencies = [ 670 | "matchers", 671 | "nu-ansi-term", 672 | "once_cell", 673 | "regex", 674 | "sharded-slab", 675 | "smallvec", 676 | "thread_local", 677 | "tracing", 678 | "tracing-core", 679 | "tracing-log", 680 | ] 681 | 682 | [[package]] 683 | name = "unicode-ident" 684 | version = "1.0.14" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 687 | 688 | [[package]] 689 | name = "unicode-segmentation" 690 | version = "1.12.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 693 | 694 | [[package]] 695 | name = "utf8parse" 696 | version = "0.2.2" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 699 | 700 | [[package]] 701 | name = "uuid" 702 | version = "1.11.0" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 705 | dependencies = [ 706 | "sha1_smol", 707 | ] 708 | 709 | [[package]] 710 | name = "valuable" 711 | version = "0.1.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 714 | 715 | [[package]] 716 | name = "wasm-bindgen" 717 | version = "0.2.97" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" 720 | dependencies = [ 721 | "cfg-if", 722 | "once_cell", 723 | "wasm-bindgen-macro", 724 | ] 725 | 726 | [[package]] 727 | name = "wasm-bindgen-backend" 728 | version = "0.2.97" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" 731 | dependencies = [ 732 | "bumpalo", 733 | "log", 734 | "once_cell", 735 | "proc-macro2", 736 | "quote", 737 | "syn", 738 | "wasm-bindgen-shared", 739 | ] 740 | 741 | [[package]] 742 | name = "wasm-bindgen-macro" 743 | version = "0.2.97" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" 746 | dependencies = [ 747 | "quote", 748 | "wasm-bindgen-macro-support", 749 | ] 750 | 751 | [[package]] 752 | name = "wasm-bindgen-macro-support" 753 | version = "0.2.97" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" 756 | dependencies = [ 757 | "proc-macro2", 758 | "quote", 759 | "syn", 760 | "wasm-bindgen-backend", 761 | "wasm-bindgen-shared", 762 | ] 763 | 764 | [[package]] 765 | name = "wasm-bindgen-shared" 766 | version = "0.2.97" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" 769 | 770 | [[package]] 771 | name = "winapi" 772 | version = "0.3.9" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 775 | dependencies = [ 776 | "winapi-i686-pc-windows-gnu", 777 | "winapi-x86_64-pc-windows-gnu", 778 | ] 779 | 780 | [[package]] 781 | name = "winapi-i686-pc-windows-gnu" 782 | version = "0.4.0" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 785 | 786 | [[package]] 787 | name = "winapi-x86_64-pc-windows-gnu" 788 | version = "0.4.0" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 791 | 792 | [[package]] 793 | name = "windows-core" 794 | version = "0.52.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 797 | dependencies = [ 798 | "windows-targets", 799 | ] 800 | 801 | [[package]] 802 | name = "windows-sys" 803 | version = "0.59.0" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 806 | dependencies = [ 807 | "windows-targets", 808 | ] 809 | 810 | [[package]] 811 | name = "windows-targets" 812 | version = "0.52.6" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 815 | dependencies = [ 816 | "windows_aarch64_gnullvm", 817 | "windows_aarch64_msvc", 818 | "windows_i686_gnu", 819 | "windows_i686_gnullvm", 820 | "windows_i686_msvc", 821 | "windows_x86_64_gnu", 822 | "windows_x86_64_gnullvm", 823 | "windows_x86_64_msvc", 824 | ] 825 | 826 | [[package]] 827 | name = "windows_aarch64_gnullvm" 828 | version = "0.52.6" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 831 | 832 | [[package]] 833 | name = "windows_aarch64_msvc" 834 | version = "0.52.6" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 837 | 838 | [[package]] 839 | name = "windows_i686_gnu" 840 | version = "0.52.6" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 843 | 844 | [[package]] 845 | name = "windows_i686_gnullvm" 846 | version = "0.52.6" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 849 | 850 | [[package]] 851 | name = "windows_i686_msvc" 852 | version = "0.52.6" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 855 | 856 | [[package]] 857 | name = "windows_x86_64_gnu" 858 | version = "0.52.6" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 861 | 862 | [[package]] 863 | name = "windows_x86_64_gnullvm" 864 | version = "0.52.6" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 867 | 868 | [[package]] 869 | name = "windows_x86_64_msvc" 870 | version = "0.52.6" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 873 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trace-recorder-to-ctf" 3 | version = "0.3.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Convert FreeRTOS trace-recorder traces to LTTng-shaped CTF" 7 | categories = ["command-line-utilities", "embedded", "parsing"] 8 | keywords = ["ctf", "freertos"] 9 | authors = ["Jon Lamb"] 10 | repository = "https://github.com/jonlamb-gh/trace-recorder-to-ctf" 11 | 12 | [dependencies] 13 | ctf-macros = { path = "macros" } 14 | clap = { version = "4.5", features = ["derive", "env", "color"] } 15 | ctrlc = { version = "3.4", features=["termination"] } 16 | tracing = "0.1" 17 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 18 | thiserror = "1.0" 19 | enum-iterator = "2.1" 20 | chrono = "0.4" 21 | babeltrace2-sys = { git = "https://github.com/auxoncorp/babeltrace2-sys.git", branch = "src-component-support" } 22 | trace-recorder-parser = "0.19" 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | LABEL org.opencontainers.image.source="https://github.com/jonlamb-gh/trace-recorder-to-ctf" 4 | LABEL org.opencontainers.image.description="Docker image for trace-recorder-to-ctf" 5 | LABEL org.opencontainers.image.licenses=MIT 6 | 7 | ENV DEBIAN_FRONTEND=noninteractive 8 | 9 | RUN apt-get update && apt-get upgrade -y && apt-get install --no-install-recommends --assume-yes \ 10 | patchelf lintian adduser help2man gzip \ 11 | cmake make gcc g++ libusb-1.0-0-dev stunnel \ 12 | curl build-essential protobuf-compiler libssl-dev \ 13 | python3 python3-pip python3-venv \ 14 | libglib2.0-dev pkg-config libtool flex bison autoconf ca-certificates automake 15 | 16 | RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --profile minimal -c rustfmt -c clippy 17 | ENV PATH="/root/.cargo/bin:${PATH}" 18 | 19 | RUN mkdir -p /trace-recorder-to-ctf 20 | COPY assets/ /trace-recorder-to-ctf/assets/ 21 | COPY Cargo.lock /trace-recorder-to-ctf/ 22 | COPY Cargo.toml /trace-recorder-to-ctf/ 23 | COPY LICENSE-MIT /trace-recorder-to-ctf/ 24 | COPY macros/ /trace-recorder-to-ctf/macros/ 25 | COPY README.md /trace-recorder-to-ctf/ 26 | COPY src/ /trace-recorder-to-ctf/src/ 27 | 28 | RUN cd /trace-recorder-to-ctf && ls -l && cargo install --path . 29 | 30 | ENTRYPOINT ["/root/.cargo/bin/trace-recorder-to-ctf"] 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jon Lamb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trace-recorder-to-ctf 2 | 3 | Convert FreeRTOS [trace recorder](https://github.com/percepio/TraceRecorderSource) traces to LTTng-shaped [CTF](https://diamon.org/ctf/v1.8.3/). 4 | 5 | ![trace-compass](assets/trace_compass_freertos.png) 6 | 7 | ## Getting Started 8 | 9 | 1. Collect streaming protocol trace recorder data from your device (i.e. using the RTT or TCP stream port) 10 | 1. Install `trace-recorder-to-ctf` from github Releases or build from source (`cargo install --path .`) 11 | 1. Convert to CTF 12 | 1. View the trace data in [Trace Compass](https://eclipse.dev/tracecompass/) or with [babeltrace2](https://babeltrace.org/) 13 | 14 | ```bash 15 | trace-recorder-to-ctf trc.psf 16 | 17 | # Default output directory is ./ctf_trace 18 | tree ctf_trace/ 19 | ctf_trace/ 20 | ├── metadata 21 | └── stream 22 | ``` 23 | 24 | ``` 25 | # Inspect the metadata 26 | babeltrace2 ./ctf_trace --output-format=ctf-metadata 27 | 28 | babeltrace2 --clock-seconds ./ctf_trace 29 | ``` 30 | 31 | ```text 32 | [0.000231544] (+?.?????????) trace-recorder TRACE_START: { cpu_id = 0 }, { id = 0x1, event_count = 6, timer = 41678 }, { task_handle = 2, task = "(startup)" } 33 | [0.000237850] (+0.000006306) trace-recorder OBJECT_NAME: { cpu_id = 0 }, { id = 0x3, event_count = 7, timer = 42813 } 34 | [0.000245077] (+0.000007227) trace-recorder OBJECT_NAME: { cpu_id = 0 }, { id = 0x3, event_count = 8, timer = 44114 } 35 | [0.000252305] (+0.000007228) trace-recorder OBJECT_NAME: { cpu_id = 0 }, { id = 0x3, event_count = 9, timer = 45415 } 36 | [0.000260338] (+0.000008033) trace-recorder MEMORY_ALLOC: { cpu_id = 0 }, { id = 0x38, event_count = 10, timer = 46861 } 37 | [0.000269688] (+0.000009350) trace-recorder QUEUE_CREATE: { cpu_id = 0 }, { id = 0x11, event_count = 11, timer = 48544 } 38 | [0.000280583] (+0.000010895) trace-recorder OBJECT_NAME: { cpu_id = 0 }, { id = 0x3, event_count = 12, timer = 50505 } 39 | [0.000291166] (+0.000010583) trace-recorder OBJECT_NAME: { cpu_id = 0 }, { id = 0x3, event_count = 13, timer = 52410 } 40 | [0.000297744] (+0.000006578) trace-recorder UNKNOWN: { cpu_id = 0 }, { id = 0x14, event_count = 14, timer = 53594 }, { type = "TIMER_CREATE" } 41 | [0.000303427] (+0.000005683) trace-recorder QUEUE_SEND: { cpu_id = 0 }, { id = 0x50, event_count = 15, timer = 54617 } 42 | [0.000307061] (+0.000003634) trace-recorder UNKNOWN: { cpu_id = 0 }, { id = 0xA0, event_count = 16, timer = 55271 }, { type = "TIMER_START" } 43 | [0.000319888] (+0.000012827) trace-recorder MEMORY_ALLOC: { cpu_id = 0 }, { id = 0x38, event_count = 17, timer = 57580 } 44 | [0.000328116] (+0.000008228) trace-recorder MEMORY_ALLOC: { cpu_id = 0 }, { id = 0x38, event_count = 18, timer = 59061 } 45 | [0.000336655] (+0.000008539) trace-recorder MESSAGEBUFFER_CREATE: { cpu_id = 0 }, { id = 0x19, event_count = 19, timer = 60598 } 46 | [0.000346344] (+0.000009689) trace-recorder OBJECT_NAME: { cpu_id = 0 }, { id = 0x3, event_count = 20, timer = 62342 } 47 | [0.000355266] (+0.000008922) trace-recorder MEMORY_ALLOC: { cpu_id = 0 }, { id = 0x38, event_count = 21, timer = 63948 } 48 | [0.000362450] (+0.000007184) trace-recorder MEMORY_ALLOC: { cpu_id = 0 }, { id = 0x38, event_count = 22, timer = 65241 } 49 | [0.000389055] (+0.000026605) trace-recorder OBJECT_NAME: { cpu_id = 0 }, { id = 0x3, event_count = 23, timer = 70030 } 50 | [0.000395288] (+0.000006233) trace-recorder TASK_CREATE: { cpu_id = 0 }, { id = 0x10, event_count = 24, timer = 71152 } 51 | [0.000535150] (+0.000139862) trace-recorder sched_wakeup: { cpu_id = 0 }, { id = 0x30, event_count = 25, timer = 96327 }, { src_event_type = "TASK_READY", comm = "CLI", tid = 536904392, prio = 1, target_cpu = 0 } 52 | ``` 53 | 54 | ## Docker 55 | 56 | You can also use the Docker image `ghcr.io/jonlamb-gh/trace-recorder-to-ctf:latest`: 57 | 58 | ```bash 59 | mkdir -p /tmp/output 60 | 61 | # Volumes: 62 | # * input file test_system.psf at /test_system.psf 63 | # * output directory /tmp/output at /output 64 | docker run -u $(id -u):$(id -g) -it -v /tmp/test_system.psf:/test_system.psf:ro -v /tmp/output:/output:rw ghcr.io/jonlamb-gh/trace-recorder-to-ctf:latest -o /output/test_system /test_system.psf 65 | 66 | tree /tmp/output/ 67 | /tmp/output/ 68 | └── test_system 69 | ├── metadata 70 | └── stream 71 | ``` 72 | 73 | ## Concept Mapping 74 | 75 | The converter produces CTF data that integrates with several of the out-of-box trace-compass LTTng kernel analyses. 76 | 77 | It does this by adding well-known metadata environment key/value pairs and mapping a handful of 78 | scheduling and ISR related events. 79 | 80 | ### Environment Key/Value Pairs 81 | 82 | | Key | Value | 83 | | :--- | ---: | 84 | | tracer_name | lttng-modules | 85 | | tracer_major | 2 | 86 | | tracer_minor | 12 | 87 | | tracer_patchlevel | 5 | 88 | | trace_buffering_scheme | global | 89 | | trace_creation_datetime | `` | 90 | 91 | Example `metadata` section: 92 | ``` 93 | env { 94 | hostname = "trace-recorder"; 95 | domain = "kernel"; 96 | tracer_name = "lttng-modules"; 97 | tracer_major = 2; 98 | tracer_minor = 12; 99 | tracer_patchlevel = 5; 100 | trace_buffering_scheme = "global"; 101 | trc_endianness = "little-endian"; 102 | trc_format_version = 14; 103 | trc_kernel_version = "KernelVersion([A1, 1A])"; 104 | trc_kernel_port = "FreeRTOS"; 105 | trc_platform_cfg = "FreeRTOS"; 106 | trc_platform_cfg_version = "1.2.0"; 107 | input_file = "trc.psf"; 108 | trace_creation_datetime = "20240609T113144+0000"; 109 | trace_creation_datetime_utc = "2024-06-09 11:31:44.264757838 UTC"; 110 | }; 111 | ``` 112 | 113 | ### Event Types 114 | 115 | | Trace Recorder Event | CTF Event | 116 | | :--- | :--- | 117 | | TASK_READY | sched_wakeup | 118 | | TASK_ACTIVATE | sched_switch when starting/resuming from a task
irq_handler_exit when exiting an ISR | 119 | | TASK_RESUME | sched_switch when starting/resuming from a task
irq_handler_exit when exiting an ISR | 120 | | TASK_SWITCH_ISR_BEGIN | irq_handler_entry | 121 | | TASK_SWITCH_ISR_RESUME | irq_handler_exit | 122 | 123 | ## License 124 | 125 | See [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT. 126 | -------------------------------------------------------------------------------- /assets/trace_compass_freertos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonlamb-gh/trace-recorder-to-ctf/4e322ce181ba86219da1bcfe1956f2eb0ef57e99/assets/trace_compass_freertos.png -------------------------------------------------------------------------------- /macros/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctf-macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | quote = "1" 11 | proc-macro2 = "1.0" 12 | syn = "2.0" 13 | convert_case = "0.6" 14 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use convert_case::{Case, Casing}; 2 | use proc_macro::TokenStream; 3 | use proc_macro2::{Literal, TokenStream as TokenStream2}; 4 | use quote::{quote, quote_spanned}; 5 | use std::{fs, path::Path}; 6 | use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Expr, Fields, Ident, Lit, Type}; 7 | 8 | // example: 9 | // #[derive(CtfEventClass)] 10 | // #[event_name = "TRACE_START"] 11 | // pub struct TraceStart<'a> { 12 | // pub task_handle: i64, 13 | // pub task: &'a CStr, 14 | // } 15 | #[proc_macro_derive(CtfEventClass, attributes(event_name, event_name_from_event_type))] 16 | pub fn derive_ctf_event_class(input: TokenStream) -> TokenStream { 17 | // TODO generic enum handling, TaskState is an enum 18 | let supported_types = ["i64", "u64", "CStr", "TaskState"]; 19 | 20 | let input = parse_macro_input!(input as DeriveInput); 21 | 22 | let type_name = input.ident; 23 | 24 | let attr_event_name = input.attrs.iter().find_map(|a| { 25 | if let Ok(val) = a.meta.require_name_value() { 26 | if val.path.is_ident("event_name") { 27 | if let Expr::Lit(lit) = &val.value { 28 | if let Lit::Str(s) = &lit.lit { 29 | let name = s.value(); 30 | return Some(Ident::new(&name, val.path.span())); 31 | } 32 | } 33 | } 34 | } 35 | None 36 | }); 37 | let name_from_event_type = input 38 | .attrs 39 | .iter() 40 | .any(|a| a.meta.path().is_ident("event_name_from_event_type")); 41 | 42 | let event_name = if let Some(n) = attr_event_name { 43 | n 44 | } else { 45 | Ident::new( 46 | &type_name.to_string().to_case(Case::Snake), 47 | type_name.span(), 48 | ) 49 | }; 50 | let event_name_bytes = format!("{}\0", event_name); 51 | let event_name_raw_str = Literal::byte_string(event_name_bytes.as_bytes()); 52 | 53 | let struct_fields = if let Data::Struct(s) = input.data { 54 | s.fields 55 | } else { 56 | return quote_spanned! { 57 | type_name.span() => compile_error!( 58 | "Can only derive CtfEventClass on structs." 59 | ); 60 | } 61 | .into(); 62 | }; 63 | 64 | let mut field_class_impls = Vec::new(); 65 | let mut field_impls = Vec::new(); 66 | match struct_fields { 67 | Fields::Named(fields) => { 68 | for (field_index, field) in fields.named.into_iter().enumerate() { 69 | let field_name = field 70 | .ident 71 | .as_ref() 72 | .expect("Failed to get struct field identifier."); 73 | match field.ty { 74 | Type::Path(t) => { 75 | let typ = t 76 | .path 77 | .get_ident() 78 | .expect("Failed to get struct field type.") 79 | .to_string(); 80 | if !supported_types.contains(&typ.as_str()) { 81 | return quote_spanned! { 82 | type_name.span() => compile_error!( 83 | "Deriving CtfEventClass for the type is not supported." 84 | ); 85 | } 86 | .into(); 87 | } 88 | field_class_impls.push(event_class_field_class(field_name, &typ)); 89 | field_impls.push(event_field(field_index, field_name, &typ)); 90 | } 91 | Type::Reference(t) => { 92 | let typ = if let Type::Path(t) = t.elem.as_ref() { 93 | t.path 94 | .get_ident() 95 | .expect("Failed to get struct field type.") 96 | .to_string() 97 | } else { 98 | return quote_spanned! { 99 | type_name.span() => compile_error!( 100 | "Deriving CtfEventClass for the type is not supported." 101 | ); 102 | } 103 | .into(); 104 | }; 105 | if !supported_types.contains(&typ.as_str()) { 106 | return quote_spanned! { 107 | type_name.span() => compile_error!( 108 | "Deriving CtfEventClass for the type is not supported." 109 | ); 110 | } 111 | .into(); 112 | } 113 | field_class_impls.push(event_class_field_class(field_name, &typ)); 114 | field_impls.push(event_field(field_index, field_name, &typ)); 115 | } 116 | _ => { 117 | return quote_spanned! { 118 | type_name.span() => compile_error!( 119 | "Deriving CtfEventClass for the type is not supported." 120 | ); 121 | } 122 | .into() 123 | } 124 | } 125 | } 126 | } 127 | _ => { 128 | return quote_spanned! { 129 | type_name.span() => compile_error!( 130 | "Deriving CtfEventClass for the type is not supported." 131 | ); 132 | } 133 | .into() 134 | } 135 | } 136 | 137 | let has_payload_field = !field_class_impls.is_empty(); 138 | let mut field_classes = TokenStream2::new(); 139 | field_classes.extend(field_class_impls); 140 | let mut field_setters = TokenStream2::new(); 141 | field_setters.extend(field_impls); 142 | 143 | let payload_fc_begin = has_payload_field.then(|| { 144 | quote! { 145 | let payload_fc = ffi::bt_field_class_structure_create(trace_class); 146 | } 147 | }); 148 | let payload_fc_end = has_payload_field.then(|| { 149 | quote! { 150 | let ret = ffi::bt_event_class_set_payload_field_class(event_class, payload_fc); 151 | ret.capi_result()?; 152 | ffi::bt_field_class_put_ref(payload_fc); 153 | } 154 | }); 155 | 156 | let payload_f_begin = has_payload_field.then(|| { 157 | quote! { 158 | let payload_f = ffi::bt_event_borrow_payload_field(ctf_event); 159 | } 160 | }); 161 | 162 | let event_class_impl = if name_from_event_type { 163 | quote! { 164 | pub(crate) fn event_class(event_type: trace_recorder_parser::streaming::event::EventType, stream_class: *mut babeltrace2_sys::ffi::bt_stream_class) -> Result<*mut babeltrace2_sys::ffi::bt_event_class, babeltrace2_sys::Error> { 165 | use babeltrace2_sys::{ffi, BtResultExt}; 166 | use std::ffi::CString; 167 | 168 | unsafe { 169 | let trace_class = ffi::bt_stream_class_borrow_trace_class(stream_class); 170 | 171 | let event_class = ffi::bt_event_class_create(stream_class); 172 | let event_name = CString::new(event_type.to_string())?; 173 | let ret = ffi::bt_event_class_set_name(event_class, event_name.as_c_str().as_ptr() as _); 174 | ret.capi_result()?; 175 | 176 | #payload_fc_begin 177 | 178 | #field_classes 179 | 180 | #payload_fc_end 181 | 182 | Ok(event_class) 183 | } 184 | } 185 | } 186 | } else { 187 | quote! { 188 | pub(crate) fn event_class(stream_class: *mut babeltrace2_sys::ffi::bt_stream_class) -> Result<*mut babeltrace2_sys::ffi::bt_event_class, babeltrace2_sys::Error> { 189 | use babeltrace2_sys::{ffi, BtResultExt}; 190 | 191 | unsafe { 192 | let trace_class = ffi::bt_stream_class_borrow_trace_class(stream_class); 193 | 194 | let event_class = ffi::bt_event_class_create(stream_class); 195 | let ret = ffi::bt_event_class_set_name(event_class, #event_name_raw_str.as_ptr() as _); 196 | ret.capi_result()?; 197 | 198 | #payload_fc_begin 199 | 200 | #field_classes 201 | 202 | #payload_fc_end 203 | 204 | Ok(event_class) 205 | } 206 | } 207 | } 208 | }; 209 | 210 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 211 | let impl_block = quote! { 212 | impl #impl_generics #type_name #ty_generics #where_clause { 213 | #event_class_impl 214 | 215 | pub(crate) fn emit_event(&self, ctf_event: *mut babeltrace2_sys::ffi::bt_event) -> Result<(), babeltrace2_sys::Error> { 216 | use babeltrace2_sys::{ffi, BtResultExt}; 217 | 218 | unsafe { 219 | #payload_f_begin 220 | 221 | #field_setters 222 | 223 | Ok(()) 224 | } 225 | } 226 | } 227 | }; 228 | 229 | let ts = TokenStream::from(impl_block); 230 | 231 | let target_dir = Path::new("target"); 232 | if target_dir.exists() { 233 | let out_dir = target_dir.join("ctf_events"); 234 | if !out_dir.exists() { 235 | fs::create_dir_all(&out_dir).ok(); 236 | } 237 | fs::write( 238 | format!( 239 | "{}/ctf_event_expansion__{}.rs", 240 | out_dir.display(), 241 | type_name 242 | ), 243 | ts.to_string(), 244 | ) 245 | .ok(); 246 | } 247 | ts 248 | } 249 | 250 | fn event_class_field_class(field_name: &Ident, typ: &str) -> TokenStream2 { 251 | let name_bytes = format!("{}\0", field_name); 252 | let byte_str = Literal::byte_string(name_bytes.as_bytes()); 253 | let fc_create = match typ { 254 | "i64" => { 255 | quote! { 256 | let fc = ffi::bt_field_class_integer_signed_create(trace_class); 257 | } 258 | } 259 | "u64" => { 260 | quote! { 261 | let fc = ffi::bt_field_class_integer_unsigned_create(trace_class); 262 | } 263 | } 264 | "CStr" => { 265 | quote! { 266 | let fc = ffi::bt_field_class_string_create(trace_class); 267 | } 268 | } 269 | // enums 270 | "TaskState" => { 271 | quote! { 272 | let fc = ffi::bt_field_class_enumeration_signed_create(trace_class); 273 | let variants = enum_iterator::all::().collect::>(); 274 | for variant in variants.into_iter() { 275 | let variant_rs = ffi::bt_integer_range_set_signed_create(); 276 | let ret = ffi::bt_integer_range_set_signed_add_range( 277 | variant_rs, 278 | variant.as_i64(), 279 | variant.as_i64(), 280 | ); 281 | ret.capi_result()?; 282 | let ret = ffi::bt_field_class_enumeration_signed_add_mapping( 283 | fc, 284 | variant.as_ffi(), 285 | variant_rs, 286 | ); 287 | ret.capi_result()?; 288 | ffi::bt_integer_range_set_signed_put_ref(variant_rs); 289 | } 290 | } 291 | } 292 | // Checked by the caller 293 | _ => unreachable!(), 294 | }; 295 | 296 | quote! { 297 | #fc_create 298 | let ret = ffi::bt_field_class_structure_append_member( 299 | payload_fc, 300 | #byte_str.as_ptr() as _, 301 | fc, 302 | ); 303 | ret.capi_result()?; 304 | ffi::bt_field_class_put_ref(fc); 305 | } 306 | } 307 | 308 | fn event_field(field_index: usize, field_name: &Ident, typ: &str) -> TokenStream2 { 309 | let f_set = match typ { 310 | "i64" => { 311 | quote! { 312 | ffi::bt_field_integer_signed_set_value(f, self.#field_name); 313 | } 314 | } 315 | "u64" => { 316 | quote! { 317 | ffi::bt_field_integer_unsigned_set_value(f, self.#field_name); 318 | } 319 | } 320 | "CStr" => { 321 | quote! { 322 | let ret = ffi::bt_field_string_set_value(f, self.#field_name.as_ptr()); 323 | ret.capi_result()?; 324 | } 325 | } 326 | // enums 327 | "TaskState" => { 328 | quote! { 329 | ffi::bt_field_integer_signed_set_value(f, self.#field_name.as_i64()); 330 | } 331 | } 332 | // Checked by the caller 333 | _ => unreachable!(), 334 | }; 335 | 336 | quote! { 337 | let f = ffi::bt_field_structure_borrow_member_field_by_index(payload_f, #field_index as u64); 338 | #f_set 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | use crate::events::*; 2 | use crate::types::{BorrowedCtfState, Context, StringCache}; 3 | use babeltrace2_sys::{ffi, BtResultExt, Error}; 4 | use std::collections::{hash_map::Entry, HashMap}; 5 | use std::ptr; 6 | use trace_recorder_parser::{streaming::event::*, time::Timestamp, types::*}; 7 | use tracing::warn; 8 | 9 | pub struct TrcCtfConverter { 10 | unknown_event_class: *mut ffi::bt_event_class, 11 | user_event_class: *mut ffi::bt_event_class, 12 | sched_switch_event_class: *mut ffi::bt_event_class, 13 | irq_handler_entry_event_class: *mut ffi::bt_event_class, 14 | irq_handler_exit_event_class: *mut ffi::bt_event_class, 15 | sched_wakeup_event_class: *mut ffi::bt_event_class, 16 | event_classes: HashMap, 17 | string_cache: StringCache, 18 | active_context: Context, 19 | pending_isrs: Vec, 20 | } 21 | 22 | impl Drop for TrcCtfConverter { 23 | fn drop(&mut self) { 24 | unsafe { 25 | for (_, event_class) in self.event_classes.drain() { 26 | ffi::bt_event_class_put_ref(event_class); 27 | } 28 | ffi::bt_event_class_put_ref(self.sched_wakeup_event_class); 29 | ffi::bt_event_class_put_ref(self.irq_handler_entry_event_class); 30 | ffi::bt_event_class_put_ref(self.irq_handler_exit_event_class); 31 | ffi::bt_event_class_put_ref(self.sched_switch_event_class); 32 | ffi::bt_event_class_put_ref(self.user_event_class); 33 | ffi::bt_event_class_put_ref(self.unknown_event_class); 34 | } 35 | } 36 | } 37 | 38 | impl TrcCtfConverter { 39 | pub fn new() -> Self { 40 | Self { 41 | unknown_event_class: ptr::null_mut(), 42 | user_event_class: ptr::null_mut(), 43 | sched_switch_event_class: ptr::null_mut(), 44 | irq_handler_entry_event_class: ptr::null_mut(), 45 | irq_handler_exit_event_class: ptr::null_mut(), 46 | sched_wakeup_event_class: ptr::null_mut(), 47 | event_classes: Default::default(), 48 | string_cache: Default::default(), 49 | active_context: Context { 50 | handle: ObjectHandle::NO_TASK, 51 | name: STARTUP_TASK_NAME.to_string().into(), 52 | priority: 0_u32.into(), 53 | }, 54 | pending_isrs: Default::default(), 55 | } 56 | } 57 | 58 | pub fn create_event_common_context( 59 | &mut self, 60 | trace_class: *mut ffi::bt_trace_class, 61 | ) -> Result<*mut ffi::bt_field_class, Error> { 62 | unsafe { 63 | // Create common event context 64 | // event ID, event type, event count, timer ticks 65 | let base_event_context = ffi::bt_field_class_structure_create(trace_class); 66 | 67 | let event_id_field = ffi::bt_field_class_integer_unsigned_create(trace_class); 68 | ffi::bt_field_class_integer_set_preferred_display_base( 69 | event_id_field, 70 | ffi::bt_field_class_integer_preferred_display_base::BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL, 71 | ); 72 | let ret = ffi::bt_field_class_structure_append_member( 73 | base_event_context, 74 | b"id\0".as_ptr() as _, 75 | event_id_field, 76 | ); 77 | ret.capi_result()?; 78 | 79 | let event_count_field = ffi::bt_field_class_integer_unsigned_create(trace_class); 80 | let ret = ffi::bt_field_class_structure_append_member( 81 | base_event_context, 82 | b"event_count\0".as_ptr() as _, 83 | event_count_field, 84 | ); 85 | ret.capi_result()?; 86 | 87 | let timer_field = ffi::bt_field_class_integer_unsigned_create(trace_class); 88 | let ret = ffi::bt_field_class_structure_append_member( 89 | base_event_context, 90 | b"timer\0".as_ptr() as _, 91 | timer_field, 92 | ); 93 | ret.capi_result()?; 94 | 95 | ffi::bt_field_class_put_ref(timer_field); 96 | ffi::bt_field_class_put_ref(event_count_field); 97 | ffi::bt_field_class_put_ref(event_id_field); 98 | 99 | Ok(base_event_context) 100 | } 101 | } 102 | 103 | /// Create the special event classes upfront, remaining classes will get 104 | /// created on the fly 105 | pub fn create_event_classes(&mut self, stream: *mut ffi::bt_stream) -> Result<(), Error> { 106 | let stream_class = unsafe { ffi::bt_stream_borrow_class(stream) }; 107 | self.unknown_event_class = Unknown::event_class(stream_class)?; 108 | self.user_event_class = User::event_class(stream_class)?; 109 | self.sched_switch_event_class = SchedSwitch::event_class(stream_class)?; 110 | self.irq_handler_entry_event_class = IrqHandlerEntry::event_class(stream_class)?; 111 | self.irq_handler_exit_event_class = IrqHandlerExit::event_class(stream_class)?; 112 | self.sched_wakeup_event_class = SchedWakeup::event_class(stream_class)?; 113 | Ok(()) 114 | } 115 | 116 | fn add_event_common_ctx( 117 | &mut self, 118 | event_id: EventId, 119 | event_count: u64, 120 | timer: Timestamp, 121 | event: *mut ffi::bt_event, 122 | ) -> Result<(), Error> { 123 | unsafe { 124 | let common_ctx_field = ffi::bt_event_borrow_common_context_field(event); 125 | 126 | let event_id_field = 127 | ffi::bt_field_structure_borrow_member_field_by_index(common_ctx_field, 0); 128 | ffi::bt_field_integer_unsigned_set_value(event_id_field, event_id.0 as u64); 129 | 130 | let event_count_field = 131 | ffi::bt_field_structure_borrow_member_field_by_index(common_ctx_field, 1); 132 | ffi::bt_field_integer_unsigned_set_value(event_count_field, event_count); 133 | 134 | let timer_field = 135 | ffi::bt_field_structure_borrow_member_field_by_index(common_ctx_field, 2); 136 | ffi::bt_field_integer_unsigned_set_value(timer_field, timer.ticks()); 137 | 138 | Ok(()) 139 | } 140 | } 141 | 142 | fn event_class( 143 | &mut self, 144 | stream_class: *mut ffi::bt_stream_class, 145 | event_type: EventType, 146 | f: F, 147 | ) -> Result<*const ffi::bt_event_class, Error> 148 | where 149 | F: FnOnce(*mut ffi::bt_stream_class) -> Result<*mut ffi::bt_event_class, Error>, 150 | { 151 | let event_class_ref = match self.event_classes.entry(event_type) { 152 | Entry::Occupied(o) => o.into_mut(), 153 | Entry::Vacant(v) => { 154 | let event_class = f(stream_class)?; 155 | v.insert(event_class) 156 | } 157 | }; 158 | Ok(*event_class_ref as *const _) 159 | } 160 | 161 | pub fn convert( 162 | &mut self, 163 | event_code: EventCode, 164 | tracked_event_count: u64, 165 | tracked_timestamp: Timestamp, 166 | event: Event, 167 | ctf_state: &mut BorrowedCtfState, 168 | ) -> Result<(), Error> { 169 | let event_id = event_code.event_id(); 170 | let event_type = event_code.event_type(); 171 | let raw_timestamp = event.timestamp(); 172 | 173 | let stream_class = unsafe { ffi::bt_stream_borrow_class(ctf_state.stream_mut()) }; 174 | 175 | match event { 176 | Event::TraceStart(ev) => { 177 | let event_class = 178 | self.event_class(stream_class, event_type, TraceStart::event_class)?; 179 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 180 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 181 | self.add_event_common_ctx(event_id, tracked_event_count, raw_timestamp, ctf_event)?; 182 | TraceStart::try_from((&ev, &mut self.string_cache))?.emit_event(ctf_event)?; 183 | ctf_state.push_message(msg)?; 184 | } 185 | 186 | Event::Unknown(_) => { 187 | let event_class = self.unknown_event_class; 188 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 189 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 190 | self.add_event_common_ctx(event_id, tracked_event_count, raw_timestamp, ctf_event)?; 191 | Unknown::try_from((event_type, &mut self.string_cache))?.emit_event(ctf_event)?; 192 | ctf_state.push_message(msg)?; 193 | } 194 | 195 | Event::User(ev) => { 196 | let event_class = self.user_event_class; 197 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 198 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 199 | self.add_event_common_ctx(event_id, tracked_event_count, raw_timestamp, ctf_event)?; 200 | User::try_from((&ev, &mut self.string_cache))?.emit_event(ctf_event)?; 201 | ctf_state.push_message(msg)?; 202 | } 203 | 204 | Event::TaskReady(ev) => { 205 | let event_class = self.sched_wakeup_event_class; 206 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 207 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 208 | self.add_event_common_ctx(event_id, tracked_event_count, raw_timestamp, ctf_event)?; 209 | SchedWakeup::try_from((event_type, &ev, &mut self.string_cache))? 210 | .emit_event(ctf_event)?; 211 | ctf_state.push_message(msg)?; 212 | } 213 | 214 | Event::TaskResume(ev) | Event::TaskActivate(ev) => { 215 | // Check for return from ISR 216 | if let Some(isr) = self.pending_isrs.pop() { 217 | // TODO should sched_switch be created if on the same context? 218 | // depends on the arg given to xTraceISREnd(arg) 219 | let event_class = self.irq_handler_exit_event_class; 220 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 221 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 222 | self.add_event_common_ctx( 223 | event_id, 224 | tracked_event_count, 225 | raw_timestamp, 226 | ctf_event, 227 | )?; 228 | let ctx = isr; 229 | IrqHandlerExit::try_from((event_type, &ctx, &mut self.string_cache))? 230 | .emit_event(ctf_event)?; 231 | ctf_state.push_message(msg)?; 232 | } 233 | 234 | let event_class = self.sched_switch_event_class; 235 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 236 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 237 | self.add_event_common_ctx(event_id, tracked_event_count, raw_timestamp, ctf_event)?; 238 | let next_ctx = Context::from(ev); 239 | let prev_ctx = &self.active_context; 240 | SchedSwitch::try_from((event_type, prev_ctx, &next_ctx, &mut self.string_cache))? 241 | .emit_event(ctf_event)?; 242 | self.active_context = next_ctx; 243 | ctf_state.push_message(msg)?; 244 | } 245 | 246 | Event::IsrBegin(ev) => { 247 | let context = Context { 248 | handle: ev.handle, 249 | name: ev.name.clone(), 250 | priority: ev.priority, 251 | }; 252 | self.pending_isrs.push(context); 253 | let event_class = self.irq_handler_entry_event_class; 254 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 255 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 256 | self.add_event_common_ctx(event_id, tracked_event_count, raw_timestamp, ctf_event)?; 257 | IrqHandlerEntry::try_from((event_type, &ev, &mut self.string_cache))? 258 | .emit_event(ctf_event)?; 259 | ctf_state.push_message(msg)?; 260 | } 261 | 262 | // Return to the interrupted ISR (nested ISR) 263 | Event::IsrResume(ev) if !self.pending_isrs.is_empty() => { 264 | // This event indicates the previous ISR context before the active context 265 | // top of the stack contains the active context 266 | let ctx = self.pending_isrs.pop().unwrap(); 267 | let previous_isr = self.pending_isrs.last(); 268 | let previous_ctx = Context::from(ev); 269 | assert_eq!(Some(&previous_ctx), previous_isr); 270 | 271 | let event_class = self.irq_handler_exit_event_class; 272 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 273 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 274 | self.add_event_common_ctx(event_id, tracked_event_count, raw_timestamp, ctf_event)?; 275 | IrqHandlerExit::try_from((event_type, &ctx, &mut self.string_cache))? 276 | .emit_event(ctf_event)?; 277 | ctf_state.push_message(msg)?; 278 | } 279 | 280 | // The rest are named events with no payload 281 | _ => { 282 | if let Event::IsrResume(ev) = event { 283 | warn!(%event_type, event = %ev, "Got ISR resume but no pending IRS"); 284 | } 285 | 286 | let event_class = self.event_class(stream_class, event_type, |stream_class| { 287 | Unsupported::event_class(event_type, stream_class) 288 | })?; 289 | let msg = ctf_state.create_message(event_class, tracked_timestamp); 290 | let ctf_event = unsafe { ffi::bt_message_event_borrow_event(msg) }; 291 | self.add_event_common_ctx(event_id, tracked_event_count, raw_timestamp, ctf_event)?; 292 | Unsupported {}.emit_event(ctf_event)?; 293 | ctf_state.push_message(msg)?; 294 | } 295 | } 296 | 297 | Ok(()) 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{Context, StringCache}; 2 | use babeltrace2_sys::Error; 3 | use ctf_macros::CtfEventClass; 4 | use enum_iterator::Sequence; 5 | use std::convert::TryFrom; 6 | use std::ffi::CStr; 7 | use trace_recorder_parser::{streaming::event::*, types::UserEventChannel}; 8 | 9 | // TODO - any way to use serde-reflection to synthesize these? 10 | 11 | #[derive(CtfEventClass)] 12 | #[event_name = "TRACE_START"] 13 | pub struct TraceStart<'a> { 14 | pub task_handle: i64, 15 | pub task: &'a CStr, 16 | } 17 | 18 | impl<'a> TryFrom<(&TraceStartEvent, &'a mut StringCache)> for TraceStart<'a> { 19 | type Error = Error; 20 | 21 | fn try_from(value: (&TraceStartEvent, &'a mut StringCache)) -> Result { 22 | value.1.insert_str(&value.0.current_task)?; 23 | Ok(Self { 24 | task_handle: u32::from(value.0.current_task_handle).into(), 25 | task: value.1.get_str(value.0.current_task.as_ref()), 26 | }) 27 | } 28 | } 29 | 30 | #[derive(CtfEventClass)] 31 | #[event_name = "UNKNOWN"] 32 | pub struct Unknown<'a> { 33 | pub event_type: &'a CStr, 34 | } 35 | 36 | impl<'a> TryFrom<(EventType, &'a mut StringCache)> for Unknown<'a> { 37 | type Error = Error; 38 | 39 | fn try_from(value: (EventType, &'a mut StringCache)) -> Result { 40 | value.1.insert_type(value.0)?; 41 | Ok(Self { 42 | event_type: value.1.get_type(&value.0), 43 | }) 44 | } 45 | } 46 | 47 | #[derive(CtfEventClass)] 48 | #[event_name = "USER_EVENT"] 49 | pub struct User<'a> { 50 | pub channel: &'a CStr, 51 | pub format_string: &'a CStr, 52 | pub formatted_string: &'a CStr, 53 | // TODO args 54 | } 55 | 56 | impl<'a> TryFrom<(&UserEvent, &'a mut StringCache)> for User<'a> { 57 | type Error = Error; 58 | 59 | fn try_from(value: (&UserEvent, &'a mut StringCache)) -> Result { 60 | let ch = match &value.0.channel { 61 | UserEventChannel::Default => UserEventChannel::DEFAULT, 62 | UserEventChannel::Custom(c) => c.as_str(), 63 | }; 64 | value.1.insert_str(ch)?; 65 | value.1.insert_str(&value.0.format_string)?; 66 | value.1.insert_str(&value.0.formatted_string)?; 67 | Ok(Self { 68 | channel: value.1.get_str(ch), 69 | format_string: value.1.get_str(&value.0.format_string), 70 | formatted_string: value.1.get_str(&value.0.formatted_string), 71 | }) 72 | } 73 | } 74 | 75 | #[repr(i64)] 76 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Sequence)] 77 | pub enum TaskState { 78 | Running = 0, 79 | Interruptible = 1, 80 | UnInterruptible = 2, 81 | Stopped = 4, 82 | Traced = 8, 83 | ExitDead = 16, 84 | ExitZombie = 32, 85 | Parked = 64, 86 | Dead = 128, 87 | WakeKill = 256, 88 | Waking = 512, 89 | NoLoad = 1024, 90 | New = 2048, 91 | } 92 | 93 | impl TaskState { 94 | fn as_ffi(&self) -> *const i8 { 95 | let ptr = match self { 96 | TaskState::Running => b"TASK_RUNNING\0".as_ptr(), 97 | TaskState::Interruptible => b"TASK_INTERRUPTIBLE\0".as_ptr(), 98 | TaskState::UnInterruptible => b"TASK_UNINTERRUPTIBLE\0".as_ptr(), 99 | TaskState::Stopped => b"TASK_STOPPED\0".as_ptr(), 100 | TaskState::Traced => b"TASK_TRACED\0".as_ptr(), 101 | TaskState::ExitDead => b"EXIT_DEAD\0".as_ptr(), 102 | TaskState::ExitZombie => b"EXIT_ZOMBIE\0".as_ptr(), 103 | TaskState::Parked => b"TASK_PARKED\0".as_ptr(), 104 | TaskState::Dead => b"TASK_DEAD\0".as_ptr(), 105 | TaskState::WakeKill => b"TASK_WAKEKILL\0".as_ptr(), 106 | TaskState::Waking => b"TASK_WAKING\0".as_ptr(), 107 | TaskState::NoLoad => b"TASK_NOLOAD\0".as_ptr(), 108 | TaskState::New => b"TASK_NEW\0".as_ptr(), 109 | }; 110 | ptr as *const i8 111 | } 112 | 113 | fn as_i64(&self) -> i64 { 114 | *self as i64 115 | } 116 | } 117 | 118 | #[derive(CtfEventClass)] 119 | #[event_name = "sched_switch"] 120 | pub struct SchedSwitch<'a> { 121 | pub src_event_type: &'a CStr, 122 | pub prev_comm: &'a CStr, 123 | pub prev_tid: i64, 124 | pub prev_prio: i64, 125 | pub prev_state: TaskState, 126 | pub next_comm: &'a CStr, 127 | pub next_tid: i64, 128 | pub next_prio: i64, 129 | } 130 | 131 | impl<'a> TryFrom<(EventType, &Context, &Context, &'a mut StringCache)> for SchedSwitch<'a> { 132 | type Error = Error; 133 | 134 | fn try_from( 135 | value: (EventType, &Context, &Context, &'a mut StringCache), 136 | ) -> Result { 137 | let event_type = value.0; 138 | let prev_ctx = value.1; 139 | let next_ctx = value.2; 140 | let cache = value.3; 141 | cache.insert_type(event_type)?; 142 | cache.insert_str(&prev_ctx.name)?; 143 | cache.insert_str(&next_ctx.name)?; 144 | Ok(Self { 145 | src_event_type: cache.get_type(&event_type), 146 | prev_comm: cache.get_str(&prev_ctx.name), 147 | prev_tid: u32::from(prev_ctx.handle).into(), 148 | prev_prio: u32::from(prev_ctx.priority).into(), 149 | prev_state: TaskState::Running, // TODO always running? 150 | next_comm: cache.get_str(&next_ctx.name), 151 | next_tid: u32::from(next_ctx.handle).into(), 152 | next_prio: u32::from(next_ctx.priority).into(), 153 | }) 154 | } 155 | } 156 | 157 | #[derive(CtfEventClass)] 158 | #[event_name = "sched_wakeup"] 159 | pub struct SchedWakeup<'a> { 160 | pub src_event_type: &'a CStr, 161 | pub comm: &'a CStr, 162 | pub tid: i64, 163 | pub prio: i64, 164 | pub target_cpu: i64, 165 | } 166 | 167 | impl<'a> TryFrom<(EventType, &TaskEvent, &'a mut StringCache)> for SchedWakeup<'a> { 168 | type Error = Error; 169 | 170 | fn try_from(value: (EventType, &TaskEvent, &'a mut StringCache)) -> Result { 171 | value.2.insert_type(value.0)?; 172 | value.2.insert_str(&value.1.name)?; 173 | Ok(Self { 174 | src_event_type: value.2.get_type(&value.0), 175 | comm: value.2.get_str(&value.1.name), 176 | tid: u32::from(value.1.handle).into(), 177 | prio: u32::from(value.1.priority).into(), 178 | target_cpu: 0, 179 | }) 180 | } 181 | } 182 | 183 | #[derive(CtfEventClass)] 184 | #[event_name = "irq_handler_entry"] 185 | pub struct IrqHandlerEntry<'a> { 186 | pub src_event_type: &'a CStr, 187 | pub irq: i64, 188 | pub name: &'a CStr, 189 | pub prio: i64, 190 | } 191 | 192 | impl<'a> TryFrom<(EventType, &IsrEvent, &'a mut StringCache)> for IrqHandlerEntry<'a> { 193 | type Error = Error; 194 | 195 | fn try_from(value: (EventType, &IsrEvent, &'a mut StringCache)) -> Result { 196 | value.2.insert_type(value.0)?; 197 | value.2.insert_str(&value.1.name)?; 198 | Ok(Self { 199 | src_event_type: value.2.get_type(&value.0), 200 | irq: u32::from(value.1.handle).into(), 201 | name: value.2.get_str(&value.1.name), 202 | prio: u32::from(value.1.priority).into(), 203 | }) 204 | } 205 | } 206 | 207 | #[derive(CtfEventClass)] 208 | #[event_name = "irq_handler_exit"] 209 | pub struct IrqHandlerExit<'a> { 210 | pub src_event_type: &'a CStr, 211 | pub irq: i64, 212 | pub name: &'a CStr, 213 | pub ret: i64, 214 | } 215 | 216 | impl<'a> TryFrom<(EventType, &Context, &'a mut StringCache)> for IrqHandlerExit<'a> { 217 | type Error = Error; 218 | 219 | fn try_from(value: (EventType, &Context, &'a mut StringCache)) -> Result { 220 | value.2.insert_type(value.0)?; 221 | value.2.insert_str(&value.1.name)?; 222 | Ok(Self { 223 | src_event_type: value.2.get_type(&value.0), 224 | irq: u32::from(value.1.handle).into(), 225 | name: value.2.get_str(&value.1.name), 226 | ret: 1, // was-handled 227 | }) 228 | } 229 | } 230 | 231 | #[derive(CtfEventClass)] 232 | #[event_name_from_event_type] 233 | pub struct Unsupported { 234 | // No payload fields 235 | } 236 | -------------------------------------------------------------------------------- /src/interruptor.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; 2 | use std::sync::Arc; 3 | 4 | #[derive(Clone, Debug)] 5 | #[repr(transparent)] 6 | pub struct Interruptor(Arc); 7 | 8 | impl Interruptor { 9 | pub fn new() -> Self { 10 | Interruptor(Arc::new(AtomicBool::new(false))) 11 | } 12 | 13 | pub fn set(&self) { 14 | self.0.store(true, SeqCst); 15 | } 16 | 17 | pub fn is_set(&self) -> bool { 18 | self.0.load(SeqCst) 19 | } 20 | } 21 | 22 | impl Default for Interruptor { 23 | fn default() -> Self { 24 | Self::new() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::manual_c_str_literals)] 2 | 3 | use crate::{convert::TrcCtfConverter, types::BorrowedCtfState}; 4 | use babeltrace2_sys::{ 5 | ffi, source_plugin_descriptors, BtResult, BtResultExt, CtfPluginSinkFsInitParams, 6 | EncoderPipeline, Error, LoggingLevel, MessageIteratorStatus, Plugin, RunStatus, SelfComponent, 7 | SelfMessageIterator, SourcePluginDescriptor, SourcePluginHandler, 8 | }; 9 | use chrono::prelude::{DateTime, Utc}; 10 | use clap::Parser; 11 | use interruptor::Interruptor; 12 | use std::{ 13 | ffi::{CStr, CString}, 14 | fs::File, 15 | io::BufReader, 16 | path::PathBuf, 17 | ptr, 18 | }; 19 | use trace_recorder_parser::{ 20 | streaming::event::{Event, EventCode, EventType, TrackingEventCounter}, 21 | streaming::RecorderData, 22 | time::StreamingInstant, 23 | }; 24 | use tracing::{debug, error, info, warn}; 25 | 26 | mod convert; 27 | mod events; 28 | mod interruptor; 29 | mod types; 30 | 31 | /// Convert FreeRTOS trace-recorder traces to CTF 32 | #[derive(Parser, Debug, Clone)] 33 | #[clap(version)] 34 | pub struct Opts { 35 | /// The CTF clock class name 36 | #[clap(long, default_value = "monotonic")] 37 | pub clock_name: String, 38 | 39 | /// The CTF trace name 40 | #[clap(long, default_value = "freertos")] 41 | pub trace_name: String, 42 | 43 | /// babeltrace2 log level 44 | #[clap(long, default_value = "warn")] 45 | pub log_level: LoggingLevel, 46 | 47 | /// Output directory to write traces to 48 | #[clap(short = 'o', long, default_value = "ctf_trace")] 49 | pub output: PathBuf, 50 | 51 | /// Path to the input trace recorder binary file (psf) to read 52 | pub input: PathBuf, 53 | } 54 | 55 | fn main() -> Result<(), Box> { 56 | match do_main() { 57 | Err(e) => { 58 | error!("{}", e); 59 | Err(e) 60 | } 61 | Ok(()) => Ok(()), 62 | } 63 | } 64 | 65 | fn do_main() -> Result<(), Box> { 66 | tracing_subscriber::fmt::init(); 67 | 68 | let opts = Opts::parse(); 69 | 70 | let intr = Interruptor::new(); 71 | let intr_clone = intr.clone(); 72 | ctrlc::set_handler(move || { 73 | if intr_clone.is_set() { 74 | let exit_code = if cfg!(target_family = "unix") { 75 | // 128 (fatal error signal "n") + 2 (control-c is fatal error signal 2) 76 | 130 77 | } else { 78 | // Windows code 3221225786 79 | // -1073741510 == C000013A 80 | -1073741510 81 | }; 82 | std::process::exit(exit_code); 83 | } 84 | 85 | debug!("Shutdown signal received"); 86 | intr_clone.set(); 87 | })?; 88 | 89 | info!(input = %opts.input.display(), "Reading header info"); 90 | let file = File::open(&opts.input)?; 91 | let mut reader = BufReader::new(file); 92 | 93 | let trd = RecorderData::find(&mut reader)?; 94 | 95 | let output_path = CString::new(opts.output.to_str().unwrap())?; 96 | let params = CtfPluginSinkFsInitParams::new( 97 | Some(true), // assume_single_trace 98 | None, // ignore_discarded_events 99 | None, // ignore_discarded_packets 100 | Some(true), // quiet 101 | &output_path, 102 | )?; 103 | 104 | let state_inner: Box = 105 | Box::new(TrcPluginState::new(intr, reader, trd, &opts)?); 106 | let state = Box::new(state_inner); 107 | 108 | let mut pipeline = EncoderPipeline::new::(opts.log_level, state, ¶ms)?; 109 | 110 | loop { 111 | let run_status = pipeline.graph.run_once()?; 112 | if RunStatus::End == run_status { 113 | break; 114 | } 115 | } 116 | 117 | info!("Done"); 118 | 119 | Ok(()) 120 | } 121 | 122 | struct TrcPluginState { 123 | interruptor: Interruptor, 124 | reader: BufReader, 125 | clock_name: CString, 126 | trace_name: CString, 127 | input_file_name: CString, 128 | trace_creation_time: DateTime, 129 | trd: RecorderData, 130 | first_event_observed: bool, 131 | eof_reached: bool, 132 | stream_is_open: bool, 133 | time_rollover_tracker: StreamingInstant, 134 | event_counter_tracker: TrackingEventCounter, 135 | stream: *mut ffi::bt_stream, 136 | packet: *mut ffi::bt_packet, 137 | converter: TrcCtfConverter, 138 | } 139 | 140 | impl TrcPluginState { 141 | fn new( 142 | interruptor: Interruptor, 143 | reader: BufReader, 144 | trd: RecorderData, 145 | opts: &Opts, 146 | ) -> Result { 147 | let clock_name = CString::new(opts.clock_name.as_str())?; 148 | let trace_name = CString::new(opts.trace_name.as_str())?; 149 | let input_file_name = CString::new(opts.input.file_name().unwrap().to_str().unwrap())?; 150 | Ok(Self { 151 | interruptor, 152 | reader, 153 | clock_name, 154 | trace_name, 155 | input_file_name, 156 | trace_creation_time: Utc::now(), 157 | trd, 158 | first_event_observed: false, 159 | eof_reached: false, 160 | stream_is_open: false, 161 | // NOTE: timestamp/event trackers get re-initialized on the first event 162 | time_rollover_tracker: StreamingInstant::zero(), 163 | event_counter_tracker: TrackingEventCounter::zero(), 164 | stream: ptr::null_mut(), 165 | packet: ptr::null_mut(), 166 | converter: TrcCtfConverter::new(), 167 | }) 168 | } 169 | 170 | fn create_metadata_and_stream_objects( 171 | &mut self, 172 | mut component: SelfComponent, 173 | ) -> Result<(), Error> { 174 | unsafe { 175 | let trace_class = ffi::bt_trace_class_create(component.inner_mut()); 176 | 177 | // Create common event context 178 | let base_event_context = self.converter.create_event_common_context(trace_class)?; 179 | 180 | // Setup the default clock class 181 | let clock_class = ffi::bt_clock_class_create(component.inner_mut()); 182 | let ret = 183 | ffi::bt_clock_class_set_name(clock_class, self.clock_name.as_c_str().as_ptr()); 184 | ret.capi_result()?; 185 | ffi::bt_clock_class_set_frequency( 186 | clock_class, 187 | self.trd.timestamp_info.timer_frequency.get_raw() as _, 188 | ); 189 | ffi::bt_clock_class_set_origin_is_unix_epoch(clock_class, 0); 190 | 191 | let stream_class = ffi::bt_stream_class_create(trace_class); 192 | ffi::bt_stream_class_set_default_clock_class(stream_class, clock_class); 193 | ffi::bt_stream_class_set_supports_packets( 194 | stream_class, 195 | 1, //supports_packets 196 | 0, // with_beginning_default_clock_snapshot 197 | 0, // with_end_default_clock_snapshot 198 | ); 199 | ffi::bt_stream_class_set_supports_discarded_packets( 200 | stream_class, 201 | 0, // supports_discarded_packets 202 | 0, // with_default_clock_snapshots 203 | ); 204 | ffi::bt_stream_class_set_supports_discarded_events( 205 | stream_class, 206 | 1, // supports_discarded_events 207 | 0, // with_default_clock_snapshots 208 | ); 209 | let ret = ffi::bt_stream_class_set_event_common_context_field_class( 210 | stream_class, 211 | base_event_context, 212 | ); 213 | ret.capi_result()?; 214 | 215 | // Add cpu_id packet context 216 | let packet_context_fc = ffi::bt_field_class_structure_create(trace_class); 217 | let cpu_id_fc = ffi::bt_field_class_integer_unsigned_create(trace_class); 218 | let ret = ffi::bt_field_class_structure_append_member( 219 | packet_context_fc, 220 | b"cpu_id\0".as_ptr() as _, 221 | cpu_id_fc, 222 | ); 223 | ret.capi_result()?; 224 | let ret = ffi::bt_stream_class_set_packet_context_field_class( 225 | stream_class, 226 | packet_context_fc, 227 | ); 228 | ret.capi_result()?; 229 | ffi::bt_field_class_put_ref(cpu_id_fc); 230 | ffi::bt_field_class_put_ref(packet_context_fc); 231 | 232 | let trace = ffi::bt_trace_create(trace_class); 233 | ffi::bt_trace_set_name(trace, self.trace_name.as_c_str().as_ptr()); 234 | 235 | self.stream = ffi::bt_stream_create(stream_class, trace); 236 | self.create_new_packet()?; 237 | 238 | // Put the references we don't need anymore 239 | ffi::bt_trace_put_ref(trace); 240 | ffi::bt_clock_class_put_ref(clock_class); 241 | ffi::bt_stream_class_put_ref(stream_class); 242 | ffi::bt_trace_class_put_ref(trace_class as *const _); 243 | ffi::bt_field_class_put_ref(base_event_context); 244 | } 245 | 246 | Ok(()) 247 | } 248 | 249 | fn set_trace_env(&mut self) -> Result<(), Error> { 250 | unsafe { 251 | let trace = ffi::bt_stream_borrow_trace(self.stream); 252 | let ret = ffi::bt_trace_set_environment_entry_string( 253 | trace, 254 | b"hostname\0".as_ptr() as _, 255 | b"trace-recorder\0".as_ptr() as _, 256 | ); 257 | ret.capi_result()?; 258 | let ret = ffi::bt_trace_set_environment_entry_string( 259 | trace, 260 | b"domain\0".as_ptr() as _, 261 | b"kernel\0".as_ptr() as _, 262 | ); 263 | ret.capi_result()?; 264 | let ret = ffi::bt_trace_set_environment_entry_string( 265 | trace, 266 | b"tracer_name\0".as_ptr() as _, 267 | b"lttng-modules\0".as_ptr() as _, 268 | ); 269 | ret.capi_result()?; 270 | let ret = ffi::bt_trace_set_environment_entry_integer( 271 | trace, 272 | b"tracer_major\0".as_ptr() as _, 273 | 2, 274 | ); 275 | ret.capi_result()?; 276 | let ret = ffi::bt_trace_set_environment_entry_integer( 277 | trace, 278 | b"tracer_minor\0".as_ptr() as _, 279 | 12, 280 | ); 281 | ret.capi_result()?; 282 | let ret = ffi::bt_trace_set_environment_entry_integer( 283 | trace, 284 | b"tracer_patchlevel\0".as_ptr() as _, 285 | 5, 286 | ); 287 | ret.capi_result()?; 288 | let ret = ffi::bt_trace_set_environment_entry_string( 289 | trace, 290 | b"trace_buffering_scheme\0".as_ptr() as _, 291 | b"global\0".as_ptr() as _, 292 | ); 293 | ret.capi_result()?; 294 | let val = CString::new(self.trd.header.endianness.to_string())?; 295 | let ret = ffi::bt_trace_set_environment_entry_string( 296 | trace, 297 | b"trc_endianness\0".as_ptr() as _, 298 | val.as_c_str().as_ptr(), 299 | ); 300 | ret.capi_result()?; 301 | let ret = ffi::bt_trace_set_environment_entry_integer( 302 | trace, 303 | b"trc_format_version\0".as_ptr() as _, 304 | self.trd.header.format_version.into(), 305 | ); 306 | ret.capi_result()?; 307 | let val = CString::new(format!("{:X?}", self.trd.header.kernel_version))?; 308 | let ret = ffi::bt_trace_set_environment_entry_string( 309 | trace, 310 | b"trc_kernel_version\0".as_ptr() as _, 311 | val.as_c_str().as_ptr(), 312 | ); 313 | ret.capi_result()?; 314 | let val = CString::new(self.trd.header.kernel_port.to_string())?; 315 | let ret = ffi::bt_trace_set_environment_entry_string( 316 | trace, 317 | b"trc_kernel_port\0".as_ptr() as _, 318 | val.as_c_str().as_ptr(), 319 | ); 320 | ret.capi_result()?; 321 | let val = CString::new(self.trd.header.platform_cfg.to_string())?; 322 | let ret = ffi::bt_trace_set_environment_entry_string( 323 | trace, 324 | b"trc_platform_cfg\0".as_ptr() as _, 325 | val.as_c_str().as_ptr(), 326 | ); 327 | ret.capi_result()?; 328 | let val = CString::new(self.trd.header.platform_cfg_version.to_string())?; 329 | let ret = ffi::bt_trace_set_environment_entry_string( 330 | trace, 331 | b"trc_platform_cfg_version\0".as_ptr() as _, 332 | val.as_c_str().as_ptr(), 333 | ); 334 | ret.capi_result()?; 335 | let ret = ffi::bt_trace_set_environment_entry_string( 336 | trace, 337 | b"input_file\0".as_ptr() as _, 338 | self.input_file_name.as_c_str().as_ptr(), 339 | ); 340 | ret.capi_result()?; 341 | let val = CString::new(format!( 342 | "{}", 343 | self.trace_creation_time.format("%Y%m%dT%H%M%S+0000") 344 | ))?; 345 | let ret = ffi::bt_trace_set_environment_entry_string( 346 | trace, 347 | b"trace_creation_datetime\0".as_ptr() as _, 348 | val.as_c_str().as_ptr(), 349 | ); 350 | ret.capi_result()?; 351 | let val = CString::new(format!("{}", self.trace_creation_time))?; 352 | let ret = ffi::bt_trace_set_environment_entry_string( 353 | trace, 354 | b"trace_creation_datetime_utc\0".as_ptr() as _, 355 | val.as_c_str().as_ptr(), 356 | ); 357 | ret.capi_result()?; 358 | } 359 | Ok(()) 360 | } 361 | 362 | fn create_new_packet(&mut self) -> Result<(), Error> { 363 | unsafe { 364 | if !self.packet.is_null() { 365 | ffi::bt_packet_put_ref(self.packet); 366 | } 367 | 368 | self.packet = ffi::bt_packet_create(self.stream); 369 | 370 | let packet_ctx_f = ffi::bt_packet_borrow_context_field(self.packet); 371 | let cpu_id_f = ffi::bt_field_structure_borrow_member_field_by_index(packet_ctx_f, 0); 372 | ffi::bt_field_integer_unsigned_set_value(cpu_id_f, 0); 373 | } 374 | Ok(()) 375 | } 376 | 377 | fn read_event(&mut self) -> Result, Error> { 378 | if self.eof_reached { 379 | return Ok(None); 380 | } 381 | 382 | match self.trd.read_event(&mut self.reader) { 383 | Ok(Some(ev)) => Ok(Some(ev)), 384 | Ok(None) => Ok(None), 385 | Err(e) => { 386 | use trace_recorder_parser::streaming::Error as TrcError; 387 | 388 | match e { 389 | // TODO - this should probably start a new packet 390 | TrcError::TraceRestarted(psf_start_word_endianness) => { 391 | warn!("Detected a restarted trace stream"); 392 | self.trd = RecorderData::read_with_endianness( 393 | psf_start_word_endianness, 394 | &mut self.reader, 395 | ) 396 | .map_err(|e| Error::PluginError(e.to_string()))?; 397 | self.first_event_observed = false; 398 | Ok(None) 399 | } 400 | _ => { 401 | warn!(%e, "Data error"); 402 | Ok(None) 403 | } 404 | } 405 | } 406 | } 407 | } 408 | 409 | fn process_event( 410 | &mut self, 411 | event_code: EventCode, 412 | event: Event, 413 | ctf_state: &mut BorrowedCtfState, 414 | ) -> Result<(), Error> { 415 | let event_type = event_code.event_type(); 416 | 417 | let dropped_events = if !self.first_event_observed { 418 | self.first_event_observed = true; 419 | 420 | if event_type != EventType::TraceStart { 421 | warn!(%event_type, "First event should be TRACE_START"); 422 | } 423 | 424 | self.event_counter_tracker 425 | .set_initial_count(event.event_count()); 426 | self.time_rollover_tracker = StreamingInstant::new( 427 | event.timestamp().ticks() as u32, 428 | self.trd.timestamp_info.timer_wraparounds, 429 | ); 430 | 431 | None 432 | } else { 433 | self.event_counter_tracker.update(event.event_count()) 434 | }; 435 | 436 | if let Some(dropped_events) = dropped_events { 437 | warn!( 438 | event_count = %event.event_count(), 439 | dropped_events, "Detected dropped events" 440 | ); 441 | let msg = unsafe { 442 | ffi::bt_message_discarded_events_create( 443 | ctf_state.message_iter_mut(), 444 | ctf_state.stream_mut(), 445 | ) 446 | }; 447 | unsafe { ffi::bt_message_discarded_events_set_count(msg, dropped_events) }; 448 | ctf_state.push_message(msg)?; 449 | } 450 | 451 | // Update timer/counter rollover trackers 452 | let event_count = self.event_counter_tracker.count(); 453 | let timestamp = self.time_rollover_tracker.elapsed(event.timestamp()); 454 | 455 | self.converter 456 | .convert(event_code, event_count, timestamp, event, ctf_state)?; 457 | 458 | Ok(()) 459 | } 460 | } 461 | 462 | impl SourcePluginHandler for TrcPluginState { 463 | fn initialize(&mut self, component: SelfComponent) -> Result<(), Error> { 464 | self.create_metadata_and_stream_objects(component)?; 465 | self.set_trace_env()?; 466 | 467 | assert!(!self.stream.is_null()); 468 | self.converter.create_event_classes(self.stream)?; 469 | 470 | Ok(()) 471 | } 472 | 473 | fn finalize(&mut self, _component: SelfComponent) -> Result<(), Error> { 474 | unsafe { 475 | assert!(!self.packet.is_null()); 476 | ffi::bt_packet_put_ref(self.packet); 477 | self.packet = ptr::null_mut(); 478 | 479 | assert!(!self.stream.is_null()); 480 | ffi::bt_stream_put_ref(self.stream); 481 | self.stream = ptr::null_mut(); 482 | } 483 | 484 | Ok(()) 485 | } 486 | 487 | fn iterator_next( 488 | &mut self, 489 | msg_iter: SelfMessageIterator, 490 | messages: &mut [*const ffi::bt_message], 491 | ) -> Result { 492 | assert!(!self.stream.is_null()); 493 | 494 | let mut ctf_state = BorrowedCtfState::new(self.stream, self.packet, msg_iter, messages); 495 | 496 | if self.interruptor.is_set() & !self.eof_reached { 497 | debug!("Early shutdown"); 498 | self.eof_reached = true; 499 | 500 | // Add packet end message 501 | let msg = unsafe { 502 | ffi::bt_message_packet_end_create(ctf_state.message_iter_mut(), self.packet) 503 | }; 504 | ctf_state.push_message(msg)?; 505 | 506 | // Add stream end message 507 | let msg = unsafe { 508 | ffi::bt_message_stream_end_create(ctf_state.message_iter_mut(), self.stream) 509 | }; 510 | ctf_state.push_message(msg)?; 511 | 512 | return Ok(ctf_state.release()); 513 | } 514 | 515 | match self.read_event()? { 516 | Some((event_code, event)) => { 517 | if !self.stream_is_open { 518 | debug!("Opening stream"); 519 | self.stream_is_open = true; 520 | 521 | // Add stream begin message 522 | let msg = unsafe { 523 | ffi::bt_message_stream_beginning_create( 524 | ctf_state.message_iter_mut(), 525 | self.stream, 526 | ) 527 | }; 528 | ctf_state.push_message(msg)?; 529 | 530 | // Add packet begin message 531 | let msg = unsafe { 532 | ffi::bt_message_packet_beginning_create( 533 | ctf_state.message_iter_mut(), 534 | self.packet, 535 | ) 536 | }; 537 | ctf_state.push_message(msg)?; 538 | } 539 | 540 | // TODO need to put_ref(msg) on this and/or all of the msgs? 541 | self.process_event(event_code, event, &mut ctf_state)?; 542 | 543 | Ok(ctf_state.release()) 544 | } 545 | None => { 546 | if self.stream_is_open && !self.first_event_observed { 547 | // Trace restart condition 548 | Ok(MessageIteratorStatus::NoMessages) 549 | } else if self.eof_reached { 550 | // Last iteration can't have messages 551 | Ok(MessageIteratorStatus::Done) 552 | } else { 553 | debug!("End of file reached"); 554 | self.eof_reached = true; 555 | 556 | // Add packet end message 557 | let msg = unsafe { 558 | ffi::bt_message_packet_end_create(ctf_state.message_iter_mut(), self.packet) 559 | }; 560 | ctf_state.push_message(msg)?; 561 | 562 | // Add stream end message 563 | let msg = unsafe { 564 | ffi::bt_message_stream_end_create(ctf_state.message_iter_mut(), self.stream) 565 | }; 566 | ctf_state.push_message(msg)?; 567 | 568 | Ok(ctf_state.release()) 569 | } 570 | } 571 | } 572 | } 573 | } 574 | 575 | struct TrcPlugin; 576 | 577 | impl SourcePluginDescriptor for TrcPlugin { 578 | /// Provides source.trace-recorder.output 579 | const PLUGIN_NAME: &'static [u8] = b"trace-recorder\0"; 580 | const OUTPUT_COMP_NAME: &'static [u8] = b"output\0"; 581 | const GRAPH_NODE_NAME: &'static [u8] = b"source.trace-recorder.output\0"; 582 | 583 | fn load() -> BtResult { 584 | let name = Self::plugin_name(); 585 | Plugin::load_from_statics_by_name(name) 586 | } 587 | 588 | fn plugin_name() -> &'static CStr { 589 | unsafe { CStr::from_bytes_with_nul_unchecked(Self::PLUGIN_NAME) } 590 | } 591 | 592 | fn output_name() -> &'static CStr { 593 | unsafe { CStr::from_bytes_with_nul_unchecked(Self::OUTPUT_COMP_NAME) } 594 | } 595 | 596 | fn graph_node_name() -> &'static CStr { 597 | unsafe { CStr::from_bytes_with_nul_unchecked(Self::GRAPH_NODE_NAME) } 598 | } 599 | } 600 | 601 | source_plugin_descriptors!(TrcPlugin); 602 | 603 | pub mod utils_plugin_descriptors { 604 | use babeltrace2_sys::ffi::*; 605 | 606 | #[link( 607 | name = "babeltrace-plugin-utils", 608 | kind = "static", 609 | modifiers = "+whole-archive" 610 | )] 611 | extern "C" { 612 | pub static __bt_plugin_descriptor_auto_ptr: *const __bt_plugin_descriptor; 613 | } 614 | } 615 | 616 | pub mod ctf_plugin_descriptors { 617 | use babeltrace2_sys::ffi::*; 618 | 619 | #[link( 620 | name = "babeltrace-plugin-ctf", 621 | kind = "static", 622 | modifiers = "+whole-archive" 623 | )] 624 | extern "C" { 625 | pub static __bt_plugin_descriptor_auto_ptr: *const __bt_plugin_descriptor; 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use babeltrace2_sys::{ffi, Error, MessageIteratorStatus, SelfMessageIterator}; 2 | use std::collections::{hash_map, HashMap}; 3 | use std::ffi::{CStr, CString}; 4 | use trace_recorder_parser::{ 5 | streaming::event::{EventType, IsrEvent, TaskEvent}, 6 | time::Timestamp, 7 | types::{ObjectHandle, ObjectName, Priority}, 8 | }; 9 | 10 | #[derive(Debug, PartialEq)] 11 | pub struct Context { 12 | pub handle: ObjectHandle, 13 | pub name: ObjectName, 14 | pub priority: Priority, 15 | } 16 | 17 | impl From for Context { 18 | fn from(value: TaskEvent) -> Self { 19 | Self { 20 | handle: value.handle, 21 | name: value.name, 22 | priority: value.priority, 23 | } 24 | } 25 | } 26 | 27 | impl From for Context { 28 | fn from(value: IsrEvent) -> Self { 29 | Self { 30 | handle: value.handle, 31 | name: value.name, 32 | priority: value.priority, 33 | } 34 | } 35 | } 36 | 37 | #[derive(Default)] 38 | pub struct StringCache { 39 | strings: HashMap, 40 | event_types: HashMap, 41 | } 42 | 43 | impl StringCache { 44 | pub fn insert_str(&mut self, key: &str) -> Result<(), Error> { 45 | if !self.strings.contains_key(key) { 46 | self.strings.insert(key.to_string(), CString::new(key)?); 47 | } 48 | Ok(()) 49 | } 50 | 51 | pub fn get_str(&self, key: &str) -> &CStr { 52 | self.strings 53 | .get(key) 54 | .expect("String cache string entry doesn't exist") 55 | } 56 | 57 | pub fn insert_type(&mut self, key: EventType) -> Result<(), Error> { 58 | if let hash_map::Entry::Vacant(e) = self.event_types.entry(key) { 59 | e.insert(CString::new(key.to_string())?); 60 | } 61 | Ok(()) 62 | } 63 | 64 | pub fn get_type(&self, key: &EventType) -> &CStr { 65 | self.event_types 66 | .get(key) 67 | .expect("String cache event type entry doesn't exist") 68 | } 69 | } 70 | 71 | // TODO split up the roles of this, currently just a catch all 72 | pub struct BorrowedCtfState<'a> { 73 | stream: *mut ffi::bt_stream, 74 | packet: *mut ffi::bt_packet, 75 | msg_iter: SelfMessageIterator, 76 | messages: &'a mut [*const ffi::bt_message], 77 | msgs_len: usize, 78 | } 79 | 80 | impl<'a> BorrowedCtfState<'a> { 81 | pub fn new( 82 | stream: *mut ffi::bt_stream, 83 | packet: *mut ffi::bt_packet, 84 | msg_iter: SelfMessageIterator, 85 | messages: &'a mut [*const ffi::bt_message], 86 | ) -> Self { 87 | assert!(!stream.is_null()); 88 | assert!(!packet.is_null()); 89 | assert!(!messages.is_empty()); 90 | Self { 91 | stream, 92 | packet, 93 | msg_iter, 94 | messages, 95 | msgs_len: 0, 96 | } 97 | } 98 | 99 | pub fn release(self) -> MessageIteratorStatus { 100 | if self.msgs_len == 0 { 101 | MessageIteratorStatus::NoMessages 102 | } else { 103 | MessageIteratorStatus::Messages(self.msgs_len as u64) 104 | } 105 | } 106 | 107 | pub fn stream_mut(&mut self) -> *mut ffi::bt_stream { 108 | self.stream 109 | } 110 | 111 | pub fn message_iter_mut(&mut self) -> *mut ffi::bt_self_message_iterator { 112 | self.msg_iter.inner_mut() 113 | } 114 | 115 | pub fn create_message( 116 | &mut self, 117 | event_class: *const ffi::bt_event_class, 118 | timestamp: Timestamp, 119 | ) -> *mut ffi::bt_message { 120 | unsafe { 121 | ffi::bt_message_event_create_with_packet_and_default_clock_snapshot( 122 | self.msg_iter.inner_mut(), 123 | event_class, 124 | self.packet, 125 | timestamp.ticks(), 126 | ) 127 | } 128 | } 129 | 130 | pub fn push_message(&mut self, msg: *const ffi::bt_message) -> Result<(), Error> { 131 | if msg.is_null() { 132 | Err(Error::PluginError("MessageVec: msg is NULL".to_owned())) 133 | } else if self.msgs_len >= self.messages.len() { 134 | Err(Error::PluginError("MessageVec: full".to_owned())) 135 | } else { 136 | self.messages[self.msgs_len] = msg; 137 | self.msgs_len += 1; 138 | Ok(()) 139 | } 140 | } 141 | } 142 | --------------------------------------------------------------------------------