├── .cargo └── config ├── .dockerignore ├── .github └── workflows │ ├── ci.yml │ ├── docker.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── docker ├── Dockerfile └── docker-compose.yml ├── tsdbperf ├── Cargo.toml ├── src │ ├── bin │ │ └── tsdbperf.rs │ ├── db.rs │ ├── db │ │ └── command.rs │ ├── lib.rs │ └── measurement.rs └── tests │ └── worker.rs └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --bin xtask --" 3 | 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ 2 | dist/ 3 | .git/ 4 | .github/ -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | services: 18 | timesacle: 19 | image: timescale/timescaledb:2.3.0-pg13 20 | env: 21 | POSTGRES_USER: postgres 22 | POSTGRES_PASSWORD: postgres 23 | ports: 24 | - 5432:5432 25 | 26 | steps: 27 | - name: Check out tsdbperf 28 | uses: actions/checkout@v2 29 | - name: Build tsdbperf 30 | run: cargo build --verbose 31 | - name: Test tsdbperf 32 | run: cargo test --verbose 33 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | pull_request: 8 | 9 | # From https://github.com/docker/build-push-action 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Check out tsdbperf 17 | uses: actions/checkout@v2 18 | - name: Docker meta 19 | id: docker_meta 20 | uses: crazy-max/ghaction-docker-meta@v1 21 | with: 22 | images: vincev/tsdbperf 23 | tag-semver: | 24 | {{version}} 25 | {{major}}.{{minor}} 26 | - name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v1 28 | - name: Login to Docker Hub 29 | uses: docker/login-action@v1 30 | with: 31 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 32 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 33 | - name: Build and push Docker image 34 | uses: docker/build-push-action@v2 35 | with: 36 | context: . 37 | file: ./docker/Dockerfile 38 | push: ${{ github.event_name != 'pull_request' }} 39 | tags: ${{ steps.docker_meta.outputs.tags }} 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Adapted from https://github.com/rust-analyzer/rust-analyzer/blob/master/.github/workflows/release.yaml 2 | name: Release 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | env: 9 | CARGO_INCREMENTAL: 0 10 | CARGO_NET_RETRY: 10 11 | RUSTFLAGS: "-D warnings" 12 | RUSTUP_MAX_RETRIES: 10 13 | FETCH_DEPTH: 0 # pull in the tags for the version string 14 | 15 | jobs: 16 | dist-x86_64-unknown-linux-gnu: 17 | name: dist (x86_64-unknown-linux-gnu) 18 | runs-on: ubuntu-18.04 19 | env: 20 | TSDBPERF_TARGET: x86_64-unknown-linux-gnu 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v2 25 | with: 26 | fetch-depth: ${{ env.FETCH_DEPTH }} 27 | 28 | - name: Install Rust toolchain 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: stable 32 | profile: minimal 33 | override: true 34 | components: rust-src 35 | 36 | - name: Dist 37 | run: cargo xtask dist 38 | 39 | - name: Upload artifacts 40 | uses: actions/upload-artifact@v2 41 | with: 42 | name: dist-x86_64-unknown-linux-gnu 43 | path: ./dist 44 | 45 | dist-aarch64-unknown-linux-gnu: 46 | name: dist (aarch64-unknown-linux-gnu) 47 | runs-on: ubuntu-18.04 48 | env: 49 | TSDBPERF_TARGET: aarch64-unknown-linux-gnu 50 | CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc 51 | 52 | steps: 53 | - name: Checkout repository 54 | uses: actions/checkout@v2 55 | with: 56 | fetch-depth: ${{ env.FETCH_DEPTH }} 57 | 58 | - name: Install Rust toolchain 59 | uses: actions-rs/toolchain@v1 60 | with: 61 | toolchain: stable 62 | target: aarch64-unknown-linux-gnu 63 | profile: minimal 64 | override: true 65 | 66 | - name: Install target toolchain 67 | run: sudo apt-get install gcc-aarch64-linux-gnu 68 | 69 | - name: Dist 70 | run: cargo xtask dist 71 | 72 | - run: ls -al ./dist 73 | 74 | - name: Upload artifacts 75 | uses: actions/upload-artifact@v2 76 | with: 77 | name: dist-aarch64-unknown-linux-gnu 78 | path: ./dist 79 | 80 | dist-x86_64-apple-darwin: 81 | name: dist (x86_64-apple-darwin) 82 | runs-on: macos-latest 83 | env: 84 | TSDBPERF_TARGET: x86_64-apple-darwin 85 | SELECT_XCODE: /Applications/Xcode_12.2.app 86 | 87 | steps: 88 | - name: Select XCode version 89 | run: sudo xcode-select -s "${SELECT_XCODE}" 90 | 91 | - name: Checkout repository 92 | uses: actions/checkout@v2 93 | with: 94 | fetch-depth: ${{ env.FETCH_DEPTH }} 95 | 96 | - name: Install Rust toolchain 97 | uses: actions-rs/toolchain@v1 98 | with: 99 | toolchain: stable 100 | profile: minimal 101 | override: true 102 | 103 | - name: Dist 104 | run: cargo xtask dist 105 | 106 | - name: Upload artifacts 107 | uses: actions/upload-artifact@v2 108 | with: 109 | name: dist-x86_64-apple-darwin 110 | path: ./dist 111 | 112 | dist-aarch64-apple-darwin: 113 | name: dist (aarch64-apple-darwin) 114 | runs-on: macos-latest 115 | env: 116 | TSDBPERF_TARGET: aarch64-apple-darwin 117 | SELECT_XCODE: /Applications/Xcode_12.2.app 118 | 119 | steps: 120 | - name: Select XCode version 121 | run: sudo xcode-select -s "${SELECT_XCODE}" 122 | 123 | - name: Checkout repository 124 | uses: actions/checkout@v2 125 | with: 126 | fetch-depth: ${{ env.FETCH_DEPTH }} 127 | 128 | - name: Install Rust toolchain 129 | uses: actions-rs/toolchain@v1 130 | with: 131 | toolchain: stable 132 | target: aarch64-apple-darwin 133 | profile: minimal 134 | override: true 135 | 136 | - name: Dist 137 | run: SDKROOT=$(xcrun -sdk macosx11.0 --show-sdk-path) MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx11.0 --show-sdk-platform-version) cargo xtask dist 138 | 139 | - run: ls -al ./dist 140 | 141 | - name: Upload artifacts 142 | uses: actions/upload-artifact@v2 143 | with: 144 | name: dist-aarch64-apple-darwin 145 | path: ./dist 146 | 147 | publish: 148 | name: Publish 149 | runs-on: ubuntu-latest 150 | needs: 151 | - 'dist-x86_64-unknown-linux-gnu' 152 | - 'dist-aarch64-unknown-linux-gnu' 153 | - 'dist-x86_64-apple-darwin' 154 | - 'dist-aarch64-apple-darwin' 155 | 156 | steps: 157 | - name: Checkout repository 158 | uses: actions/checkout@v2 159 | with: 160 | fetch-depth: ${{ env.FETCH_DEPTH }} 161 | 162 | - uses: actions/download-artifact@v2 163 | with: 164 | name: dist-x86_64-unknown-linux-gnu 165 | path: dist 166 | 167 | - uses: actions/download-artifact@v2 168 | with: 169 | name: dist-aarch64-unknown-linux-gnu 170 | path: dist 171 | 172 | - uses: actions/download-artifact@v2 173 | with: 174 | name: dist-x86_64-apple-darwin 175 | path: dist 176 | 177 | - uses: actions/download-artifact@v2 178 | with: 179 | name: dist-aarch64-apple-darwin 180 | path: dist 181 | 182 | - run: ls -al ./dist 183 | 184 | - name: Release 185 | uses: softprops/action-gh-release@v1 186 | if: startsWith(github.ref, 'refs/tags/') 187 | with: 188 | files: | 189 | ./dist/tsdbperf-x86_64-unknown-linux-gnu.gz 190 | ./dist/tsdbperf-aarch64-unknown-linux-gnu.gz 191 | ./dist/tsdbperf-x86_64-apple-darwin.gz 192 | ./dist/tsdbperf-aarch64-apple-darwin.gz 193 | env: 194 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 195 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler" 5 | version = "1.0.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.7.18" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 14 | dependencies = [ 15 | "memchr", 16 | ] 17 | 18 | [[package]] 19 | name = "ansi_term" 20 | version = "0.11.0" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 23 | dependencies = [ 24 | "winapi", 25 | ] 26 | 27 | [[package]] 28 | name = "anyhow" 29 | version = "1.0.40" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" 32 | 33 | [[package]] 34 | name = "async-trait" 35 | version = "0.1.50" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" 38 | dependencies = [ 39 | "proc-macro2", 40 | "quote", 41 | "syn", 42 | ] 43 | 44 | [[package]] 45 | name = "atty" 46 | version = "0.2.14" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 49 | dependencies = [ 50 | "hermit-abi", 51 | "libc", 52 | "winapi", 53 | ] 54 | 55 | [[package]] 56 | name = "autocfg" 57 | version = "1.0.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 60 | 61 | [[package]] 62 | name = "base64" 63 | version = "0.13.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "1.2.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 72 | 73 | [[package]] 74 | name = "block-buffer" 75 | version = "0.9.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 78 | dependencies = [ 79 | "generic-array", 80 | ] 81 | 82 | [[package]] 83 | name = "byteorder" 84 | version = "1.4.3" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 87 | 88 | [[package]] 89 | name = "bytes" 90 | version = "1.0.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 93 | 94 | [[package]] 95 | name = "cfg-if" 96 | version = "1.0.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 99 | 100 | [[package]] 101 | name = "chrono" 102 | version = "0.4.19" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 105 | dependencies = [ 106 | "libc", 107 | "num-integer", 108 | "num-traits", 109 | "time", 110 | "winapi", 111 | ] 112 | 113 | [[package]] 114 | name = "clap" 115 | version = "2.33.3" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 118 | dependencies = [ 119 | "ansi_term", 120 | "atty", 121 | "bitflags", 122 | "strsim", 123 | "textwrap", 124 | "unicode-width", 125 | "vec_map", 126 | ] 127 | 128 | [[package]] 129 | name = "cpufeatures" 130 | version = "0.1.4" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" 133 | dependencies = [ 134 | "libc", 135 | ] 136 | 137 | [[package]] 138 | name = "crc32fast" 139 | version = "1.2.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 142 | dependencies = [ 143 | "cfg-if", 144 | ] 145 | 146 | [[package]] 147 | name = "crypto-mac" 148 | version = "0.10.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" 151 | dependencies = [ 152 | "generic-array", 153 | "subtle", 154 | ] 155 | 156 | [[package]] 157 | name = "digest" 158 | version = "0.9.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 161 | dependencies = [ 162 | "generic-array", 163 | ] 164 | 165 | [[package]] 166 | name = "env_logger" 167 | version = "0.8.3" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" 170 | dependencies = [ 171 | "atty", 172 | "humantime", 173 | "log", 174 | "regex", 175 | "termcolor", 176 | ] 177 | 178 | [[package]] 179 | name = "fallible-iterator" 180 | version = "0.2.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 183 | 184 | [[package]] 185 | name = "flate2" 186 | version = "1.0.20" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" 189 | dependencies = [ 190 | "cfg-if", 191 | "crc32fast", 192 | "libc", 193 | "miniz_oxide", 194 | ] 195 | 196 | [[package]] 197 | name = "futures" 198 | version = "0.3.15" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" 201 | dependencies = [ 202 | "futures-channel", 203 | "futures-core", 204 | "futures-executor", 205 | "futures-io", 206 | "futures-sink", 207 | "futures-task", 208 | "futures-util", 209 | ] 210 | 211 | [[package]] 212 | name = "futures-channel" 213 | version = "0.3.15" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" 216 | dependencies = [ 217 | "futures-core", 218 | "futures-sink", 219 | ] 220 | 221 | [[package]] 222 | name = "futures-core" 223 | version = "0.3.15" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" 226 | 227 | [[package]] 228 | name = "futures-executor" 229 | version = "0.3.15" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" 232 | dependencies = [ 233 | "futures-core", 234 | "futures-task", 235 | "futures-util", 236 | ] 237 | 238 | [[package]] 239 | name = "futures-io" 240 | version = "0.3.15" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" 243 | 244 | [[package]] 245 | name = "futures-macro" 246 | version = "0.3.15" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" 249 | dependencies = [ 250 | "autocfg", 251 | "proc-macro-hack", 252 | "proc-macro2", 253 | "quote", 254 | "syn", 255 | ] 256 | 257 | [[package]] 258 | name = "futures-sink" 259 | version = "0.3.15" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" 262 | 263 | [[package]] 264 | name = "futures-task" 265 | version = "0.3.15" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" 268 | 269 | [[package]] 270 | name = "futures-util" 271 | version = "0.3.15" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" 274 | dependencies = [ 275 | "autocfg", 276 | "futures-channel", 277 | "futures-core", 278 | "futures-io", 279 | "futures-macro", 280 | "futures-sink", 281 | "futures-task", 282 | "memchr", 283 | "pin-project-lite", 284 | "pin-utils", 285 | "proc-macro-hack", 286 | "proc-macro-nested", 287 | "slab", 288 | ] 289 | 290 | [[package]] 291 | name = "generic-array" 292 | version = "0.14.4" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 295 | dependencies = [ 296 | "typenum", 297 | "version_check", 298 | ] 299 | 300 | [[package]] 301 | name = "getrandom" 302 | version = "0.2.3" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 305 | dependencies = [ 306 | "cfg-if", 307 | "libc", 308 | "wasi", 309 | ] 310 | 311 | [[package]] 312 | name = "hashbrown" 313 | version = "0.9.1" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 316 | 317 | [[package]] 318 | name = "heck" 319 | version = "0.3.3" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 322 | dependencies = [ 323 | "unicode-segmentation", 324 | ] 325 | 326 | [[package]] 327 | name = "hermit-abi" 328 | version = "0.1.18" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 331 | dependencies = [ 332 | "libc", 333 | ] 334 | 335 | [[package]] 336 | name = "hmac" 337 | version = "0.10.1" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" 340 | dependencies = [ 341 | "crypto-mac", 342 | "digest", 343 | ] 344 | 345 | [[package]] 346 | name = "humantime" 347 | version = "2.1.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 350 | 351 | [[package]] 352 | name = "indexmap" 353 | version = "1.6.2" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" 356 | dependencies = [ 357 | "autocfg", 358 | "hashbrown", 359 | ] 360 | 361 | [[package]] 362 | name = "instant" 363 | version = "0.1.9" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 366 | dependencies = [ 367 | "cfg-if", 368 | ] 369 | 370 | [[package]] 371 | name = "itoa" 372 | version = "0.4.7" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 375 | 376 | [[package]] 377 | name = "lazy_static" 378 | version = "1.4.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 381 | 382 | [[package]] 383 | name = "libc" 384 | version = "0.2.95" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" 387 | 388 | [[package]] 389 | name = "libm" 390 | version = "0.2.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" 393 | 394 | [[package]] 395 | name = "lock_api" 396 | version = "0.4.4" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" 399 | dependencies = [ 400 | "scopeguard", 401 | ] 402 | 403 | [[package]] 404 | name = "log" 405 | version = "0.4.14" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 408 | dependencies = [ 409 | "cfg-if", 410 | ] 411 | 412 | [[package]] 413 | name = "matches" 414 | version = "0.1.8" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 417 | 418 | [[package]] 419 | name = "md-5" 420 | version = "0.9.1" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" 423 | dependencies = [ 424 | "block-buffer", 425 | "digest", 426 | "opaque-debug", 427 | ] 428 | 429 | [[package]] 430 | name = "memchr" 431 | version = "2.4.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 434 | 435 | [[package]] 436 | name = "miniz_oxide" 437 | version = "0.4.4" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 440 | dependencies = [ 441 | "adler", 442 | "autocfg", 443 | ] 444 | 445 | [[package]] 446 | name = "mio" 447 | version = "0.7.11" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" 450 | dependencies = [ 451 | "libc", 452 | "log", 453 | "miow", 454 | "ntapi", 455 | "winapi", 456 | ] 457 | 458 | [[package]] 459 | name = "miow" 460 | version = "0.3.7" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 463 | dependencies = [ 464 | "winapi", 465 | ] 466 | 467 | [[package]] 468 | name = "ntapi" 469 | version = "0.3.6" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 472 | dependencies = [ 473 | "winapi", 474 | ] 475 | 476 | [[package]] 477 | name = "num-integer" 478 | version = "0.1.44" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 481 | dependencies = [ 482 | "autocfg", 483 | "num-traits", 484 | ] 485 | 486 | [[package]] 487 | name = "num-traits" 488 | version = "0.2.14" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 491 | dependencies = [ 492 | "autocfg", 493 | "libm", 494 | ] 495 | 496 | [[package]] 497 | name = "num_cpus" 498 | version = "1.13.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 501 | dependencies = [ 502 | "hermit-abi", 503 | "libc", 504 | ] 505 | 506 | [[package]] 507 | name = "once_cell" 508 | version = "1.7.2" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" 511 | 512 | [[package]] 513 | name = "opaque-debug" 514 | version = "0.3.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 517 | 518 | [[package]] 519 | name = "parking_lot" 520 | version = "0.11.1" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 523 | dependencies = [ 524 | "instant", 525 | "lock_api", 526 | "parking_lot_core", 527 | ] 528 | 529 | [[package]] 530 | name = "parking_lot_core" 531 | version = "0.8.3" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 534 | dependencies = [ 535 | "cfg-if", 536 | "instant", 537 | "libc", 538 | "redox_syscall", 539 | "smallvec", 540 | "winapi", 541 | ] 542 | 543 | [[package]] 544 | name = "percent-encoding" 545 | version = "2.1.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 548 | 549 | [[package]] 550 | name = "phf" 551 | version = "0.8.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 554 | dependencies = [ 555 | "phf_shared", 556 | ] 557 | 558 | [[package]] 559 | name = "phf_shared" 560 | version = "0.8.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 563 | dependencies = [ 564 | "siphasher", 565 | ] 566 | 567 | [[package]] 568 | name = "pin-project-lite" 569 | version = "0.2.6" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" 572 | 573 | [[package]] 574 | name = "pin-utils" 575 | version = "0.1.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 578 | 579 | [[package]] 580 | name = "postgres-protocol" 581 | version = "0.6.1" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "ff3e0f70d32e20923cabf2df02913be7c1842d4c772db8065c00fcfdd1d1bff3" 584 | dependencies = [ 585 | "base64", 586 | "byteorder", 587 | "bytes", 588 | "fallible-iterator", 589 | "hmac", 590 | "md-5", 591 | "memchr", 592 | "rand", 593 | "sha2", 594 | "stringprep", 595 | ] 596 | 597 | [[package]] 598 | name = "postgres-types" 599 | version = "0.2.1" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "430f4131e1b7657b0cd9a2b0c3408d77c9a43a042d300b8c77f981dffcc43a2f" 602 | dependencies = [ 603 | "bytes", 604 | "chrono", 605 | "fallible-iterator", 606 | "postgres-protocol", 607 | "serde", 608 | "serde_json", 609 | ] 610 | 611 | [[package]] 612 | name = "ppv-lite86" 613 | version = "0.2.10" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 616 | 617 | [[package]] 618 | name = "proc-macro-error" 619 | version = "1.0.4" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 622 | dependencies = [ 623 | "proc-macro-error-attr", 624 | "proc-macro2", 625 | "quote", 626 | "syn", 627 | "version_check", 628 | ] 629 | 630 | [[package]] 631 | name = "proc-macro-error-attr" 632 | version = "1.0.4" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 635 | dependencies = [ 636 | "proc-macro2", 637 | "quote", 638 | "version_check", 639 | ] 640 | 641 | [[package]] 642 | name = "proc-macro-hack" 643 | version = "0.5.19" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 646 | 647 | [[package]] 648 | name = "proc-macro-nested" 649 | version = "0.1.7" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 652 | 653 | [[package]] 654 | name = "proc-macro2" 655 | version = "1.0.27" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 658 | dependencies = [ 659 | "unicode-xid", 660 | ] 661 | 662 | [[package]] 663 | name = "quote" 664 | version = "1.0.9" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 667 | dependencies = [ 668 | "proc-macro2", 669 | ] 670 | 671 | [[package]] 672 | name = "rand" 673 | version = "0.8.3" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 676 | dependencies = [ 677 | "libc", 678 | "rand_chacha", 679 | "rand_core", 680 | "rand_hc", 681 | ] 682 | 683 | [[package]] 684 | name = "rand_chacha" 685 | version = "0.3.0" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 688 | dependencies = [ 689 | "ppv-lite86", 690 | "rand_core", 691 | ] 692 | 693 | [[package]] 694 | name = "rand_core" 695 | version = "0.6.2" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 698 | dependencies = [ 699 | "getrandom", 700 | ] 701 | 702 | [[package]] 703 | name = "rand_distr" 704 | version = "0.4.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "da9e8f32ad24fb80d07d2323a9a2ce8b30d68a62b8cb4df88119ff49a698f038" 707 | dependencies = [ 708 | "num-traits", 709 | "rand", 710 | ] 711 | 712 | [[package]] 713 | name = "rand_hc" 714 | version = "0.3.0" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 717 | dependencies = [ 718 | "rand_core", 719 | ] 720 | 721 | [[package]] 722 | name = "redox_syscall" 723 | version = "0.2.8" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" 726 | dependencies = [ 727 | "bitflags", 728 | ] 729 | 730 | [[package]] 731 | name = "regex" 732 | version = "1.5.4" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 735 | dependencies = [ 736 | "aho-corasick", 737 | "memchr", 738 | "regex-syntax", 739 | ] 740 | 741 | [[package]] 742 | name = "regex-syntax" 743 | version = "0.6.25" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 746 | 747 | [[package]] 748 | name = "ryu" 749 | version = "1.0.5" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 752 | 753 | [[package]] 754 | name = "scopeguard" 755 | version = "1.1.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 758 | 759 | [[package]] 760 | name = "serde" 761 | version = "1.0.126" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" 764 | 765 | [[package]] 766 | name = "serde_json" 767 | version = "1.0.64" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 770 | dependencies = [ 771 | "indexmap", 772 | "itoa", 773 | "ryu", 774 | "serde", 775 | ] 776 | 777 | [[package]] 778 | name = "sha2" 779 | version = "0.9.5" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" 782 | dependencies = [ 783 | "block-buffer", 784 | "cfg-if", 785 | "cpufeatures", 786 | "digest", 787 | "opaque-debug", 788 | ] 789 | 790 | [[package]] 791 | name = "signal-hook-registry" 792 | version = "1.4.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 795 | dependencies = [ 796 | "libc", 797 | ] 798 | 799 | [[package]] 800 | name = "siphasher" 801 | version = "0.3.5" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" 804 | 805 | [[package]] 806 | name = "slab" 807 | version = "0.4.3" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" 810 | 811 | [[package]] 812 | name = "smallvec" 813 | version = "1.6.1" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 816 | 817 | [[package]] 818 | name = "socket2" 819 | version = "0.4.0" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" 822 | dependencies = [ 823 | "libc", 824 | "winapi", 825 | ] 826 | 827 | [[package]] 828 | name = "stringprep" 829 | version = "0.1.2" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 832 | dependencies = [ 833 | "unicode-bidi", 834 | "unicode-normalization", 835 | ] 836 | 837 | [[package]] 838 | name = "strsim" 839 | version = "0.8.0" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 842 | 843 | [[package]] 844 | name = "structopt" 845 | version = "0.3.21" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" 848 | dependencies = [ 849 | "clap", 850 | "lazy_static", 851 | "structopt-derive", 852 | ] 853 | 854 | [[package]] 855 | name = "structopt-derive" 856 | version = "0.4.14" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" 859 | dependencies = [ 860 | "heck", 861 | "proc-macro-error", 862 | "proc-macro2", 863 | "quote", 864 | "syn", 865 | ] 866 | 867 | [[package]] 868 | name = "subtle" 869 | version = "2.4.0" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" 872 | 873 | [[package]] 874 | name = "syn" 875 | version = "1.0.72" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" 878 | dependencies = [ 879 | "proc-macro2", 880 | "quote", 881 | "unicode-xid", 882 | ] 883 | 884 | [[package]] 885 | name = "termcolor" 886 | version = "1.1.2" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 889 | dependencies = [ 890 | "winapi-util", 891 | ] 892 | 893 | [[package]] 894 | name = "textwrap" 895 | version = "0.11.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 898 | dependencies = [ 899 | "unicode-width", 900 | ] 901 | 902 | [[package]] 903 | name = "time" 904 | version = "0.1.43" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 907 | dependencies = [ 908 | "libc", 909 | "winapi", 910 | ] 911 | 912 | [[package]] 913 | name = "tinyvec" 914 | version = "1.2.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" 917 | dependencies = [ 918 | "tinyvec_macros", 919 | ] 920 | 921 | [[package]] 922 | name = "tinyvec_macros" 923 | version = "0.1.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 926 | 927 | [[package]] 928 | name = "tokio" 929 | version = "1.6.1" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "0a38d31d7831c6ed7aad00aa4c12d9375fd225a6dd77da1d25b707346319a975" 932 | dependencies = [ 933 | "autocfg", 934 | "bytes", 935 | "libc", 936 | "memchr", 937 | "mio", 938 | "num_cpus", 939 | "once_cell", 940 | "parking_lot", 941 | "pin-project-lite", 942 | "signal-hook-registry", 943 | "tokio-macros", 944 | "winapi", 945 | ] 946 | 947 | [[package]] 948 | name = "tokio-macros" 949 | version = "1.2.0" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37" 952 | dependencies = [ 953 | "proc-macro2", 954 | "quote", 955 | "syn", 956 | ] 957 | 958 | [[package]] 959 | name = "tokio-postgres" 960 | version = "0.7.2" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "2d2b1383c7e4fb9a09e292c7c6afb7da54418d53b045f1c1fac7a911411a2b8b" 963 | dependencies = [ 964 | "async-trait", 965 | "byteorder", 966 | "bytes", 967 | "fallible-iterator", 968 | "futures", 969 | "log", 970 | "parking_lot", 971 | "percent-encoding", 972 | "phf", 973 | "pin-project-lite", 974 | "postgres-protocol", 975 | "postgres-types", 976 | "socket2", 977 | "tokio", 978 | "tokio-util", 979 | ] 980 | 981 | [[package]] 982 | name = "tokio-util" 983 | version = "0.6.7" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" 986 | dependencies = [ 987 | "bytes", 988 | "futures-core", 989 | "futures-sink", 990 | "log", 991 | "pin-project-lite", 992 | "tokio", 993 | ] 994 | 995 | [[package]] 996 | name = "tsdbperf" 997 | version = "0.1.8" 998 | dependencies = [ 999 | "anyhow", 1000 | "async-trait", 1001 | "chrono", 1002 | "env_logger", 1003 | "futures", 1004 | "lazy_static", 1005 | "log", 1006 | "num_cpus", 1007 | "rand", 1008 | "rand_distr", 1009 | "serde", 1010 | "serde_json", 1011 | "structopt", 1012 | "tokio", 1013 | "tokio-postgres", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "typenum" 1018 | version = "1.13.0" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" 1021 | 1022 | [[package]] 1023 | name = "unicode-bidi" 1024 | version = "0.3.5" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" 1027 | dependencies = [ 1028 | "matches", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "unicode-normalization" 1033 | version = "0.1.19" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1036 | dependencies = [ 1037 | "tinyvec", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "unicode-segmentation" 1042 | version = "1.7.1" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 1045 | 1046 | [[package]] 1047 | name = "unicode-width" 1048 | version = "0.1.8" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 1051 | 1052 | [[package]] 1053 | name = "unicode-xid" 1054 | version = "0.2.2" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1057 | 1058 | [[package]] 1059 | name = "vec_map" 1060 | version = "0.8.2" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1063 | 1064 | [[package]] 1065 | name = "version_check" 1066 | version = "0.9.3" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1069 | 1070 | [[package]] 1071 | name = "wasi" 1072 | version = "0.10.2+wasi-snapshot-preview1" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1075 | 1076 | [[package]] 1077 | name = "winapi" 1078 | version = "0.3.9" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1081 | dependencies = [ 1082 | "winapi-i686-pc-windows-gnu", 1083 | "winapi-x86_64-pc-windows-gnu", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "winapi-i686-pc-windows-gnu" 1088 | version = "0.4.0" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1091 | 1092 | [[package]] 1093 | name = "winapi-util" 1094 | version = "0.1.5" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1097 | dependencies = [ 1098 | "winapi", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "winapi-x86_64-pc-windows-gnu" 1103 | version = "0.4.0" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1106 | 1107 | [[package]] 1108 | name = "xshell" 1109 | version = "0.1.14" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "c640362f1b150e186b76e88606e4b01a7b2f1d56cc50fcc184ddb683fb567c23" 1112 | dependencies = [ 1113 | "xshell-macros", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "xshell-macros" 1118 | version = "0.1.14" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "0607c095c96c1d8420ce4a018a0954fb15d73d5eb59b521a05a0f04cffc05059" 1121 | 1122 | [[package]] 1123 | name = "xtask" 1124 | version = "0.1.0" 1125 | dependencies = [ 1126 | "anyhow", 1127 | "flate2", 1128 | "structopt", 1129 | "xshell", 1130 | ] 1131 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "tsdbperf", 4 | "xtask", 5 | ] 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vince Vasta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tsdbperf 2 | 3 | [![Build Status][actions-badge]][actions-url] 4 | 5 | An async [Rust][rust-lang] application that inserts simulated 6 | time-series data into a [TimescaleDB][timescale-db] instance and 7 | reports ingestion rate statistics. 8 | 9 | [rust-lang]: https://www.rust-lang.org 10 | [timescale-db]: https://www.timescale.com/ 11 | [actions-badge]: https://github.com/vincev/tsdbperf/workflows/CI/badge.svg 12 | [actions-url]: https://github.com/vincev/tsdbperf/actions?query=workflow%3ACI+branch%3Amaster 13 | 14 | ## Overview 15 | 16 | An example of time-series data is a stream of measurements coming from 17 | a group of devices. A measurement has a timestamp, a device 18 | identifier, and a number of metrics that are generated by that device. 19 | 20 | The following command: 21 | 22 | ```bash 23 | $ time ./tsdbperf --workers 8 --devices 10 --measurements 100000 --metrics 10 24 | [2020-10-31T14:33:47Z INFO tsdbperf] Number of workers: 8 25 | [2020-10-31T14:33:47Z INFO tsdbperf] Devices per worker: 10 26 | [2020-10-31T14:33:47Z INFO tsdbperf] Metrics per device: 10 27 | [2020-10-31T14:33:47Z INFO tsdbperf] Measurements per device: 100000 28 | [2020-10-31T14:33:59Z INFO tsdbperf] Wrote 8000000 measurements in 12.59 seconds 29 | [2020-10-31T14:33:59Z INFO tsdbperf] Wrote 635677 measurements per second 30 | [2020-10-31T14:33:59Z INFO tsdbperf] Wrote 6356774 metrics per second 31 | 32 | real 0m12.791s 33 | user 0m5.981s 34 | sys 0m1.845s 35 | ``` 36 | 37 | runs 8 parallel workers, for each worker it generates data for 10 38 | devices and for each device it generates 100,000 measurements each 39 | having 10 metrics. On my laptop, that has 12 CPUs, it took 12.59 40 | seconds to insert 8,000,000 measurements. 41 | 42 | Now that we have generated the data we can run queries to inspect it: 43 | 44 | ```sql 45 | postgres@localhost> select count(distinct(device_id)) from measurement 46 | +---------+ 47 | | count | 48 | |---------| 49 | | 80 | 50 | +---------+ 51 | 52 | postgres@localhost> select count(*) from measurement 53 | +---------+ 54 | | count | 55 | |---------| 56 | | 8000000 | 57 | +---------+ 58 | 59 | postgres@localhost> select min(time), max(time) from measurement 60 | +-------------------------------+-------------------------------+ 61 | | min | max | 62 | |-------------------------------+-------------------------------| 63 | | 2020-10-30 10:47:08.315839+00 | 2020-10-31 14:33:47.316317+00 | 64 | +-------------------------------+-------------------------------+ 65 | ``` 66 | 67 | The hypertable chunk interval is 1 day, with the data spanning 2 days 68 | we should have 2 chunks: 69 | 70 | ```sql 71 | postgres@localhost> select chunk_table, table_size 72 | from chunk_relation_size_pretty('measurement') 73 | +-----------------------------------------+--------------+ 74 | | chunk_table | table_size | 75 | |-----------------------------------------+--------------| 76 | | _timescaledb_internal._hyper_5_9_chunk | 458 MB | 77 | | _timescaledb_internal._hyper_5_10_chunk | 504 MB | 78 | +-----------------------------------------+--------------+ 79 | ``` 80 | 81 | With 10 metrics per measurement the schema looks like: 82 | 83 | ```sql 84 | postgres@localhost> \d measurement 85 | +-----------+--------------------------+-------------+ 86 | | Column | Type | Modifiers | 87 | |-----------+--------------------------+-------------| 88 | | time | timestamp with time zone | not null | 89 | | device_id | oid | | 90 | | m1 | double precision | | 91 | | m2 | double precision | | 92 | | m3 | double precision | | 93 | | m4 | double precision | | 94 | | m5 | double precision | | 95 | | m6 | double precision | | 96 | | m7 | double precision | | 97 | | m8 | double precision | | 98 | | m9 | double precision | | 99 | | m10 | double precision | | 100 | +-----------+--------------------------+-------------+ 101 | Indexes: 102 | "measurement_device_id_time_idx" btree (device_id, "time" DESC) 103 | "measurement_time_idx" btree ("time" DESC) 104 | Triggers: 105 | ts_insert_blocker BEFORE INSERT ON measurement FOR EACH ROW EXECUTE FUNC... 106 | Number of child tables: 2 (Use \d+ to list them.) 107 | ``` 108 | 109 | If we run with the `--with-jsonb` option then all metrics are inserted into 110 | a `metrics` column as JSONB: 111 | 112 | ```sql 113 | postgres@localhost:postgres> select time, metrics from measurement where device_id=3 limit 5 114 | +-------------------------------+----------------------------------------------------+ 115 | | time | metrics | 116 | |-------------------------------+----------------------------------------------------| 117 | | 2020-12-19 14:34:04.116101+00 | {"m1": 995.5610470263719, "m2": 939.2307180345128} | 118 | | 2020-12-19 14:34:05.116101+00 | {"m1": 995.6124798524728, "m2": 938.6678274276368} | 119 | | 2020-12-19 14:34:06.116101+00 | {"m1": 996.0597176913179, "m2": 938.5417961830267} | 120 | | 2020-12-19 14:34:07.116101+00 | {"m1": 995.5232004280007, "m2": 938.7215776266249} | 121 | | 2020-12-19 14:34:08.116101+00 | {"m1": 995.0165418185591, "m2": 939.023750940609} | 122 | +-------------------------------+----------------------------------------------------+ 123 | ``` 124 | 125 | ## Local binary installation 126 | 127 | Github release action creates binaries for Linux and Mac, they can be 128 | downloaded from the [Releases](https://github.com/vincev/tsdbperf/releases) 129 | page as compressed binaries for both x86-64 and AArch64 architectures. 130 | 131 | The binary needs to be made executable after downloading: 132 | 133 | ```bash 134 | $ gunzip tsdbperf-x86_64-apple-darwin.gz 135 | $ chmod +x tsdbperf-x86_64-apple-darwin 136 | $ ./tsdbperf-x86_64-apple-darwin --help 137 | tsdbperf 0.1.8 138 | 139 | USAGE: 140 | tsdbperf-x86_64-apple-darwin [FLAGS] [OPTIONS] 141 | 142 | FLAGS: 143 | --dry-run Skip DB inserts, report only data generation timings 144 | -h, --help Prints help information 145 | --no-hypertables Run the tests without creating hypertables 146 | -V, --version Prints version information 147 | --with-copy-upserts Run the tests with copy in upserts 148 | --with-jsonb Insert metrics as a JSONB column 149 | --with-upserts Run the tests with upserts 150 | 151 | OPTIONS: 152 | --batch-size Number of measurements per insert [default: 10000] 153 | --chunk-interval Hypertable chunk interval in seconds [default: 86400] 154 | --db-host Database host [default: localhost] 155 | --db-name Database name [default: postgres] 156 | --db-password Database password [default: postgres] 157 | --db-user Database user [default: postgres] 158 | --devices Number of devices per worker [default: 10] 159 | --measurements Number of measurements per device [default: 100000] 160 | --metrics Number of metrics per device [default: 10] 161 | --workers Number of parallel workers [default: number of CPUs] 162 | ``` 163 | 164 | ## Run a local TimescaleDB instance with docker-compose 165 | 166 | If you have docker-compose installed the easiest way to run tsdbperf 167 | is to download this [compose file][tsdbperf-compose], run a local 168 | instance of TimescaleDB: 169 | 170 | ```bash 171 | $ docker-compose up -d timescale 172 | Creating network "docker_default" with the default driver 173 | Creating docker_timescale_1 ... done 174 | ``` 175 | 176 | then run tsdbperf with `docker-compose run --rm tsdbperf`: 177 | 178 | ```bash 179 | $ docker-compose run --rm tsdbperf 180 | [2020-11-07T20:08:06Z INFO tsdbperf] Number of workers: 12 181 | [2020-11-07T20:08:06Z INFO tsdbperf] Devices per worker: 10 182 | [2020-11-07T20:08:06Z INFO tsdbperf] Metrics per device: 10 183 | [2020-11-07T20:08:06Z INFO tsdbperf] Measurements per device: 100000 184 | [2020-11-07T20:08:27Z INFO tsdbperf] Wrote 12000000 measurements in 19.47 seconds 185 | [2020-11-07T20:08:27Z INFO tsdbperf] Wrote 616428 measurements per second 186 | [2020-11-07T20:08:27Z INFO tsdbperf] Wrote 6164278 metrics per second 187 | ``` 188 | 189 | If you want to override any of the default options you can pass them 190 | to the `run` command, for example to run with 6 workers, 15 devices 191 | per worker, and 5 metrics per device: 192 | 193 | ```bash 194 | $ docker-compose run --rm tsdbperf --workers 6 --devices 15 --metrics 5 195 | [2020-11-07T19:55:12Z INFO tsdbperf] Number of workers: 6 196 | [2020-11-07T19:55:12Z INFO tsdbperf] Devices per worker: 15 197 | [2020-11-07T19:55:12Z INFO tsdbperf] Metrics per device: 5 198 | [2020-11-07T19:55:12Z INFO tsdbperf] Measurements per device: 100000 199 | [2020-11-07T19:55:28Z INFO tsdbperf] Wrote 9000000 measurements in 14.64 seconds 200 | [2020-11-07T19:55:28Z INFO tsdbperf] Wrote 614880 measurements per second 201 | [2020-11-07T19:55:28Z INFO tsdbperf] Wrote 3074400 metrics per second 202 | ``` 203 | 204 | To shutdown TimescaleDB and clean up containers run: 205 | 206 | ```bash 207 | $ docker-compose down -v 208 | Stopping docker_timescale_1 ... done 209 | Removing docker_timescale_1 ... done 210 | Removing network docker_default 211 | ``` 212 | 213 | [tsdbperf-compose]: https://github.com/vincev/tsdbperf/blob/master/docker/docker-compose.yml 214 | 215 | 216 | ## Run against a running TimescaleDB instance with docker 217 | 218 | If you have docker installed and would like to run tests against a running 219 | TimescaleDB instance you can use the following command: 220 | 221 | ```bash 222 | $ docker run --rm --network host vincev/tsdbperf --db-host localhost 223 | [2020-11-25T22:41:57Z INFO tsdbperf] Number of workers: 12 224 | [2020-11-25T22:41:57Z INFO tsdbperf] Devices per worker: 10 225 | [2020-11-25T22:41:57Z INFO tsdbperf] Metrics per device: 10 226 | [2020-11-25T22:41:57Z INFO tsdbperf] Measurements per device: 100000 227 | [2020-11-25T22:42:16Z INFO tsdbperf] Wrote 12000000 measurements in 18.73 seconds 228 | [2020-11-25T22:42:16Z INFO tsdbperf] Wrote 640752 measurements per second 229 | [2020-11-25T22:42:16Z INFO tsdbperf] Wrote 6407518 metrics per second 230 | ``` 231 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:latest as build 2 | 3 | WORKDIR /app 4 | 5 | COPY Cargo.lock Cargo.toml ./ 6 | COPY tsdbperf tsdbperf 7 | COPY xtask xtask 8 | 9 | RUN cargo build --manifest-path /app/tsdbperf/Cargo.toml --bin tsdbperf --release 10 | 11 | FROM debian:buster-slim 12 | 13 | WORKDIR /app 14 | COPY --from=build /app/target/release/tsdbperf ./ 15 | 16 | ENTRYPOINT ["/app/tsdbperf"] 17 | CMD ["--help"] 18 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | timescale: 5 | image: timescale/timescaledb:2.3.0-pg13 6 | ports: 7 | - 5432:5432 8 | environment: 9 | POSTGRES_USER: postgres 10 | POSTGRES_PASSWORD: postgres 11 | 12 | tsdbperf: 13 | image: vincev/tsdbperf 14 | entrypoint: 15 | - /app/tsdbperf 16 | - --db-host 17 | - timescale 18 | -------------------------------------------------------------------------------- /tsdbperf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tsdbperf" 3 | version = "0.1.8" 4 | authors = ["Vince "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | anyhow = "1.0" 9 | async-trait = "0.1" 10 | chrono = { version = "0.4" } 11 | env_logger = "0.8" 12 | futures = "0.3" 13 | log = "0.4" 14 | num_cpus = "1.0" 15 | rand = "0.8" 16 | rand_distr = "0.4" 17 | serde = "1.0" 18 | serde_json = { version = "1.0", features = [ "preserve_order"] } 19 | structopt = "0.3" 20 | tokio = { version = "1", features = ["full"] } 21 | tokio-postgres = { version = "0.7", features = ["with-chrono-0_4", "with-serde_json-1"] } 22 | 23 | [dev-dependencies] 24 | lazy_static = "1" -------------------------------------------------------------------------------- /tsdbperf/src/bin/tsdbperf.rs: -------------------------------------------------------------------------------- 1 | #![deny(rust_2018_idioms)] 2 | 3 | use std::time::Instant; 4 | 5 | use anyhow::{anyhow, Result}; 6 | use futures::future::try_join_all; 7 | use log::info; 8 | use structopt::StructOpt; 9 | 10 | use tsdbperf::db; 11 | 12 | #[derive(StructOpt, Debug)] 13 | #[structopt(name = "tsdbperf")] 14 | pub struct Opt { 15 | /// Number of measurements per device 16 | #[structopt(long = "measurements", default_value = "100000")] 17 | num_measurements: u32, 18 | 19 | /// Number of devices per worker 20 | #[structopt(long = "devices", default_value = "10")] 21 | num_devices: u32, 22 | 23 | /// Number of parallel workers [default: number of CPUs] 24 | #[structopt(long = "workers")] 25 | num_workers: Option, 26 | 27 | #[structopt(flatten)] 28 | dbopts: db::DbOpt, 29 | } 30 | 31 | #[tokio::main] 32 | async fn main() -> Result<()> { 33 | let log_env = env_logger::Env::default().default_filter_or("info"); 34 | env_logger::Builder::from_env(log_env).init(); 35 | 36 | let mut opt = Opt::from_args(); 37 | 38 | opt.num_workers = opt 39 | .num_workers 40 | .map(|n| n.max(1)) 41 | .or_else(|| Some(num_cpus::get() as u32)); 42 | 43 | if opt.num_workers.unwrap() > 64 { 44 | Err(anyhow!("Number of workers cannot be greater than 64")) 45 | } else if opt.num_devices > 10_000 { 46 | Err(anyhow!("Number of devices cannot be greater than 10,000")) 47 | } else if opt.dbopts.num_metrics > 100 { 48 | Err(anyhow!("Number of metrics cannot be greater than 100")) 49 | } else { 50 | info!("Number of workers: {}", opt.num_workers.unwrap()); 51 | info!("Devices per worker: {}", opt.num_devices); 52 | info!("Metrics per device: {}", opt.dbopts.num_metrics); 53 | info!("Measurements per device: {}", opt.num_measurements); 54 | 55 | run_workers(&opt).await 56 | } 57 | } 58 | 59 | async fn run_workers(opt: &Opt) -> Result<()> { 60 | db::init(&opt.dbopts).await?; 61 | 62 | let start_time = Instant::now(); 63 | 64 | let handles = (1..=opt.num_workers.unwrap()) 65 | .map(|worker_id| { 66 | let dbopts = opt.dbopts.clone(); 67 | let (num_devices, num_measurements) = (opt.num_devices, opt.num_measurements); 68 | tokio::spawn(async move { 69 | db::run_worker(&dbopts, worker_id, num_devices, num_measurements).await 70 | }) 71 | }) 72 | .collect::>(); 73 | 74 | let num_written: usize = try_join_all(handles) 75 | .await? 76 | .into_iter() 77 | .collect::>>()? 78 | .iter() 79 | .sum(); 80 | 81 | let elapsed_secs = start_time.elapsed().as_millis() as f64 / 1000.0; 82 | 83 | info!( 84 | "Wrote {:9} measurements in {:.2} seconds", 85 | num_written, elapsed_secs 86 | ); 87 | 88 | let measurements_per_sec = num_written as f64 / elapsed_secs; 89 | info!( 90 | "Wrote {:9.0} measurements per second", 91 | measurements_per_sec.round() 92 | ); 93 | 94 | info!( 95 | "Wrote {:9.0} metrics per second", 96 | (measurements_per_sec * opt.dbopts.num_metrics as f64).round() 97 | ); 98 | 99 | Ok(()) 100 | } 101 | -------------------------------------------------------------------------------- /tsdbperf/src/db.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | 3 | use anyhow::Result; 4 | use log::error; 5 | use structopt::StructOpt; 6 | use tokio::sync::mpsc; 7 | use tokio_postgres::{Client, Row}; 8 | 9 | use crate::measurement::{Measurement, MeasurementIterator}; 10 | 11 | mod command; 12 | 13 | #[derive(Debug, Clone, StructOpt)] 14 | pub struct DbOpt { 15 | /// Number of metrics per device 16 | #[structopt(long = "metrics", default_value = "10")] 17 | pub num_metrics: u32, 18 | /// Hypertable chunk interval in seconds 19 | #[structopt(long = "chunk-interval", default_value = "86400")] 20 | pub chunk_interval: usize, 21 | /// Number of measurements per insert 22 | #[structopt(long = "batch-size", default_value = "10000")] 23 | pub batch_size: usize, 24 | /// Skip DB inserts, report only data generation timings 25 | #[structopt(long = "dry-run")] 26 | pub dry_run: bool, 27 | /// Run the tests without creating hypertables 28 | #[structopt(long = "no-hypertables")] 29 | pub no_hypertables: bool, 30 | /// Run the tests with copy in upserts 31 | #[structopt(long, conflicts_with_all = &["with-upserts", "with-jsonb"])] 32 | pub with_copy_upserts: bool, 33 | /// Run the tests with upserts 34 | #[structopt(long, conflicts_with = "with-jsonb")] 35 | pub with_upserts: bool, 36 | /// Insert metrics as a JSONB column 37 | #[structopt(long)] 38 | pub with_jsonb: bool, 39 | /// Database host 40 | #[structopt(long = "db-host", default_value = "localhost")] 41 | pub db_host: String, 42 | /// Database user 43 | #[structopt(long = "db-user", default_value = "postgres")] 44 | pub db_user: String, 45 | /// Database password 46 | #[structopt(long = "db-password", default_value = "postgres")] 47 | pub db_password: String, 48 | /// Database name 49 | #[structopt(long = "db-name", default_value = "postgres")] 50 | pub db_name: String, 51 | } 52 | 53 | /// Initialize schema and close connection. 54 | pub async fn init(opts: &DbOpt) -> Result<()> { 55 | let db = Db::connect(opts).await?; 56 | db.create_schema().await?; 57 | Ok(()) 58 | } 59 | 60 | /// Run worker writes devices * measurements rows. 61 | pub async fn run_worker( 62 | opt: &DbOpt, 63 | worker_id: u32, 64 | num_devices: u32, 65 | num_measurements: u32, 66 | ) -> Result { 67 | let batch_size = opt.batch_size; 68 | let num_metrics = opt.num_metrics; 69 | let (tx, mut rx) = mpsc::channel::>(100); 70 | 71 | thread::spawn(move || { 72 | let mut measurements = 73 | MeasurementIterator::new(worker_id, num_devices, num_metrics, num_measurements); 74 | let mut num_sent = 0; 75 | let send_until = num_devices * num_measurements; 76 | 77 | while num_sent < send_until { 78 | let mut data = Vec::with_capacity(batch_size); 79 | for m in &mut measurements { 80 | data.push(m); 81 | num_sent += 1; 82 | 83 | if data.len() == batch_size || num_sent == send_until { 84 | break; 85 | } 86 | } 87 | 88 | if tx.blocking_send(data).is_err() { 89 | break; // Reader disconnected 90 | } 91 | } 92 | }); 93 | 94 | let mut num_written = 0; 95 | let mut db = Db::connect(opt).await?; 96 | let command = command::Command::new(opt, &db.client).await?; 97 | 98 | while let Some(data) = rx.recv().await { 99 | num_written += if opt.dry_run { 100 | data.len() 101 | } else { 102 | command.execute(&mut db.client, data).await? 103 | }; 104 | } 105 | 106 | Ok(num_written) 107 | } 108 | 109 | pub struct Db { 110 | client: Client, 111 | num_metrics: u32, 112 | chunk_interval: usize, 113 | use_hypertables: bool, 114 | with_jsonb: bool, 115 | } 116 | 117 | impl Db { 118 | pub async fn connect(opts: &DbOpt) -> Result { 119 | use tokio_postgres::{config::Config, NoTls}; 120 | 121 | let (client, connection) = Config::new() 122 | .user(&opts.db_user) 123 | .password(&opts.db_password) 124 | .host(&opts.db_host) 125 | .dbname(&opts.db_name) 126 | .connect(NoTls) 127 | .await?; 128 | 129 | // The connection object performs the actual communication 130 | // with the database, so spawn it off to run on its own. 131 | tokio::spawn(async move { 132 | if let Err(e) = connection.await { 133 | error!("connection error: {}", e); 134 | } 135 | }); 136 | 137 | Ok(Db { 138 | client, 139 | num_metrics: opts.num_metrics, 140 | chunk_interval: opts.chunk_interval * 1_000_000, 141 | use_hypertables: !opts.no_hypertables, 142 | with_jsonb: opts.with_jsonb, 143 | }) 144 | } 145 | 146 | pub async fn query(&self, query: &str) -> Result> { 147 | Ok(self.client.query(query, &[]).await?) 148 | } 149 | 150 | async fn create_schema(&self) -> Result<()> { 151 | let mut stms = vec![format!("DROP TABLE IF EXISTS measurement;")]; 152 | 153 | if self.with_jsonb { 154 | stms.push(format!( 155 | "CREATE TABLE measurement( 156 | time TIMESTAMP WITH TIME ZONE NOT NULL, 157 | device_id OID NOT NULL, 158 | metrics JSONB NOT NULL);" 159 | )); 160 | } else { 161 | let columns = (1..=self.num_metrics) 162 | .map(|c| format!("m{} DOUBLE PRECISION", c)) 163 | .collect::>() 164 | .join(", "); 165 | 166 | stms.push(format!( 167 | "CREATE TABLE measurement( 168 | time TIMESTAMP WITH TIME ZONE NOT NULL, 169 | device_id OID NOT NULL, 170 | {});", 171 | columns 172 | )); 173 | } 174 | 175 | stms.push(format!( 176 | "CREATE INDEX ON measurement(time DESC); 177 | CREATE UNIQUE INDEX ON measurement(device_id, time DESC);" 178 | )); 179 | 180 | if self.use_hypertables { 181 | stms.push(format!( 182 | "SELECT create_hypertable( 183 | 'measurement', 184 | 'time', 185 | chunk_time_interval => {});", 186 | self.chunk_interval 187 | )); 188 | } 189 | 190 | self.client.batch_execute(&stms.join("\n")).await?; 191 | Ok(()) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /tsdbperf/src/db/command.rs: -------------------------------------------------------------------------------- 1 | use std::boxed::Box; 2 | use std::iter; 3 | 4 | use anyhow::Result; 5 | use async_trait::async_trait; 6 | use futures::pin_mut; 7 | use tokio_postgres::binary_copy::BinaryCopyInWriter; 8 | use tokio_postgres::types::{ToSql, Type}; 9 | use tokio_postgres::{Client, Statement, Transaction}; 10 | 11 | use super::DbOpt; 12 | use crate::measurement::Measurement; 13 | 14 | pub struct Command { 15 | command: Box, 16 | } 17 | 18 | impl Command { 19 | pub async fn new(opt: &DbOpt, client: &Client) -> Result { 20 | let command: Box = if opt.with_upserts { 21 | Box::new(Upsert::new(opt, client).await?) 22 | } else if opt.with_copy_upserts { 23 | Box::new(CopyInUpsert::new(opt, client).await?) 24 | } else if opt.with_jsonb { 25 | Box::new(CopyInJsonb::new(opt)?) 26 | } else { 27 | Box::new(CopyIn::new(opt)?) 28 | }; 29 | 30 | Ok(Command { command }) 31 | } 32 | 33 | pub async fn execute(&self, client: &mut Client, data: Vec) -> Result { 34 | let tx = client.transaction().await?; 35 | let num_written = self.command.execute(&tx, data).await?; 36 | tx.commit().await?; 37 | Ok(num_written) 38 | } 39 | } 40 | 41 | #[async_trait] 42 | trait GenericCommand { 43 | async fn execute(&self, tx: &Transaction<'_>, data: Vec) -> Result; 44 | } 45 | 46 | struct CopyIn { 47 | col_types: Vec, 48 | copy_stm: String, 49 | } 50 | 51 | impl CopyIn { 52 | fn new(opt: &DbOpt) -> Result { 53 | let mut col_types = vec![Type::TIMESTAMP, Type::OID]; 54 | col_types.extend(iter::repeat(Type::FLOAT8).take(opt.num_metrics as usize)); 55 | 56 | Ok(Self { 57 | col_types, 58 | copy_stm: get_copy_statement("measurement", opt.num_metrics), 59 | }) 60 | } 61 | } 62 | 63 | #[async_trait] 64 | impl GenericCommand for CopyIn { 65 | async fn execute(&self, tx: &Transaction<'_>, data: Vec) -> Result { 66 | let sink = tx.copy_in(self.copy_stm.as_str()).await?; 67 | let writer = BinaryCopyInWriter::new(sink, &self.col_types); 68 | 69 | pin_mut!(writer); 70 | 71 | let mut row: Vec<&'_ (dyn ToSql + Sync)> = Vec::new(); 72 | for m in &data { 73 | row.clear(); 74 | row.push(&m.time); 75 | row.push(&m.device_id); 76 | row.extend(m.metrics.iter().map(|x| x as &(dyn ToSql + Sync))); 77 | writer.as_mut().write(&row).await?; 78 | } 79 | 80 | writer.finish().await?; 81 | 82 | Ok(data.len()) 83 | } 84 | } 85 | 86 | // The CopyIn command above inserts a column for each measurement, 87 | // the greater the number of measurements the lower is the number 88 | // of rows per second we can insert. 89 | // This command inserts the measurements in a single JSONB column 90 | // by serializing them as JSON. 91 | struct CopyInJsonb { 92 | col_types: Vec, 93 | num_metrics: u32, 94 | copy_stm: String, 95 | } 96 | 97 | impl CopyInJsonb { 98 | fn new(opt: &DbOpt) -> Result { 99 | let col_types = vec![Type::TIMESTAMP, Type::OID, Type::JSONB]; 100 | let copy_stm = "COPY measurement (time, device_id, metrics) FROM STDIN BINARY"; 101 | 102 | Ok(Self { 103 | col_types, 104 | num_metrics: opt.num_metrics, 105 | copy_stm: copy_stm.to_string(), 106 | }) 107 | } 108 | } 109 | 110 | #[async_trait] 111 | impl GenericCommand for CopyInJsonb { 112 | async fn execute(&self, tx: &Transaction<'_>, data: Vec) -> Result { 113 | use serde_json::{self, Map, Number, Value}; 114 | 115 | // Reuse the same json object for all metrics to avoid allocations 116 | let mut json = Value::Object( 117 | (0..self.num_metrics) 118 | .map(|m| { 119 | ( 120 | format!("m{}", m + 1), 121 | Value::Number(Number::from_f64(0.0).unwrap()), 122 | ) 123 | }) 124 | .collect::>(), 125 | ); 126 | 127 | let sink = tx.copy_in(self.copy_stm.as_str()).await?; 128 | let writer = BinaryCopyInWriter::new(sink, &self.col_types); 129 | 130 | pin_mut!(writer); 131 | 132 | for m in &data { 133 | let mut row: Vec<&'_ (dyn ToSql + Sync)> = Vec::new(); 134 | row.push(&m.time); 135 | row.push(&m.device_id); 136 | 137 | let values = json.as_object_mut().unwrap(); 138 | 139 | // By using the `preserve_order` feature for serde_json the 140 | // values in the map are ordered so we can simply do a zip 141 | // for the updates without having to lookup by key. 142 | for (m, v) in m.metrics.iter().zip(values.values_mut()) { 143 | *v = Value::Number(Number::from_f64(*m).unwrap()); 144 | } 145 | 146 | row.push(&json); 147 | 148 | writer.as_mut().write(&row).await?; 149 | } 150 | 151 | writer.finish().await?; 152 | 153 | Ok(data.len()) 154 | } 155 | } 156 | 157 | // An upsert operation happens when we try to insert a row that 158 | // violates a unique constraint, in this case a row with the same time 159 | // and device id. To keep ingestion rate high we use copy in binary on 160 | // a temp table and then insert its data into the final table using an 161 | // ON CONFLICT update statement. 162 | struct CopyInUpsert { 163 | col_types: Vec, 164 | copy_stm: String, 165 | upsert_stm: String, 166 | } 167 | 168 | impl CopyInUpsert { 169 | async fn new(opt: &DbOpt, client: &Client) -> Result { 170 | client 171 | .batch_execute( 172 | "CREATE TEMP TABLE upserts ON COMMIT DELETE ROWS \ 173 | AS TABLE measurement WITH NO DATA;", 174 | ) 175 | .await?; 176 | 177 | let set_columns = (1..=opt.num_metrics) 178 | .map(|c| format!("m{} = EXCLUDED.m{}", c, c)) 179 | .collect::>() 180 | .join(", "); 181 | 182 | let upsert_stm = format!( 183 | "INSERT INTO measurement 184 | SELECT * FROM upserts 185 | ON CONFLICT (device_id, time) DO UPDATE SET {}", 186 | set_columns 187 | ); 188 | 189 | let mut col_types = vec![Type::TIMESTAMP, Type::OID]; 190 | col_types.extend(iter::repeat(Type::FLOAT8).take(opt.num_metrics as usize)); 191 | 192 | Ok(Self { 193 | col_types, 194 | copy_stm: get_copy_statement("upserts", opt.num_metrics), 195 | upsert_stm, 196 | }) 197 | } 198 | } 199 | 200 | #[async_trait] 201 | impl GenericCommand for CopyInUpsert { 202 | async fn execute(&self, tx: &Transaction<'_>, data: Vec) -> Result { 203 | let sink = tx.copy_in(self.copy_stm.as_str()).await?; 204 | let writer = BinaryCopyInWriter::new(sink, &self.col_types); 205 | 206 | pin_mut!(writer); 207 | 208 | let mut row: Vec<&'_ (dyn ToSql + Sync)> = Vec::new(); 209 | for m in &data { 210 | row.clear(); 211 | row.push(&m.time); 212 | row.push(&m.device_id); 213 | row.extend(m.metrics.iter().map(|x| x as &(dyn ToSql + Sync))); 214 | writer.as_mut().write(&row).await?; 215 | } 216 | 217 | writer.finish().await?; 218 | 219 | tx.batch_execute(self.upsert_stm.as_str()).await?; 220 | Ok(data.len()) 221 | } 222 | } 223 | 224 | // The CopyInUpsert above uses PostgreSQL `copy-in` to send data to 225 | // the server as fast as possible but to work around the limitation 226 | // that copy-in doesn't support upserts we copy the data to a temp 227 | // table and then do an upsert from the temp table to the destination 228 | // table. 229 | // Mat from Timescale suggested another approach, we run an insert 230 | // with ON CONFLICT UPDATE but instead of inserting a single row 231 | // we insert N rows by passing an array of N values for each column 232 | // and then use unnest to insert them as multiple rows. 233 | struct Upsert { 234 | statement: Statement, 235 | num_metrics: u32, 236 | } 237 | 238 | impl Upsert { 239 | async fn new(opt: &DbOpt, client: &Client) -> Result { 240 | let insert_cols = (1..=opt.num_metrics) 241 | .map(|c| format!("m{}", c)) 242 | .collect::>() 243 | .join(", "); 244 | 245 | let unnest_cols = (1..=opt.num_metrics) 246 | .map(|c| format!("${}", c + 2)) 247 | .collect::>() 248 | .join(", "); 249 | 250 | let conflict_cols = (1..=opt.num_metrics) 251 | .map(|c| format!("m{} = EXCLUDED.m{}", c, c)) 252 | .collect::>() 253 | .join(", "); 254 | 255 | let insert_stm = format!( 256 | "INSERT INTO measurement(time, device_id, {}) 257 | SELECT * FROM unnest($1, $2, {}) as U 258 | ON CONFLICT (device_id, time) DO UPDATE SET {}", 259 | insert_cols, unnest_cols, conflict_cols 260 | ); 261 | 262 | // When using unnest each column is an array 263 | let mut col_types = vec![Type::TIMESTAMP_ARRAY, Type::OID_ARRAY]; 264 | col_types.extend(iter::repeat(Type::FLOAT8_ARRAY).take(opt.num_metrics as usize)); 265 | 266 | Ok(Self { 267 | statement: client.prepare_typed(&insert_stm, &col_types).await?, 268 | num_metrics: opt.num_metrics, 269 | }) 270 | } 271 | } 272 | 273 | #[async_trait] 274 | impl GenericCommand for Upsert { 275 | async fn execute(&self, tx: &Transaction<'_>, data: Vec) -> Result { 276 | let mut times = Vec::with_capacity(data.len()); 277 | let mut device_ids = Vec::with_capacity(data.len()); 278 | let mut metrics = (0..self.num_metrics) 279 | .map(|_| Vec::with_capacity(data.len())) 280 | .collect::>(); 281 | 282 | for m in &data { 283 | times.push(&m.time); 284 | device_ids.push(&m.device_id); 285 | m.metrics 286 | .iter() 287 | .zip(metrics.iter_mut()) 288 | .for_each(|(m, v)| v.push(*m)); 289 | } 290 | 291 | let mut cols: Vec<&'_ (dyn ToSql + Sync)> = Vec::new(); 292 | cols.push(×); 293 | cols.push(&device_ids); 294 | metrics.iter().for_each(|v| cols.push(v)); 295 | 296 | tx.execute(&self.statement, &cols).await?; 297 | Ok(data.len()) 298 | } 299 | } 300 | 301 | fn get_copy_statement(table: &str, num_metrics: u32) -> String { 302 | let columns = (1..=num_metrics) 303 | .map(|c| format!("m{}", c)) 304 | .collect::>() 305 | .join(", "); 306 | format!( 307 | "COPY {} (time, device_id, {}) FROM STDIN BINARY", 308 | table, columns 309 | ) 310 | } 311 | -------------------------------------------------------------------------------- /tsdbperf/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod db; 2 | mod measurement; 3 | -------------------------------------------------------------------------------- /tsdbperf/src/measurement.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Duration, NaiveDateTime, Utc}; 2 | use rand::distributions::{Distribution, Uniform}; 3 | use rand_distr::StandardNormal; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Measurement { 7 | pub time: NaiveDateTime, 8 | pub device_id: u32, 9 | pub metrics: Vec, 10 | } 11 | 12 | pub struct MeasurementIterator { 13 | worker_id: u32, 14 | num_devices: u32, 15 | metrics: Vec, 16 | time: NaiveDateTime, 17 | cur_device: u32, 18 | rng: rand::rngs::ThreadRng, 19 | } 20 | 21 | impl MeasurementIterator { 22 | pub fn new(worker_id: u32, num_devices: u32, num_metrics: u32, num_measurements: u32) -> Self { 23 | let time = Utc::now().naive_utc() - Duration::seconds(num_measurements as i64); 24 | let mut rng = rand::thread_rng(); 25 | let dis = Uniform::new(100.0, 1000.0); 26 | let metrics = (0..num_metrics).map(|_| dis.sample(&mut rng)).collect(); 27 | 28 | Self { 29 | worker_id, 30 | num_devices, 31 | metrics, 32 | time, 33 | cur_device: 0, 34 | rng, 35 | } 36 | } 37 | } 38 | 39 | impl Iterator for MeasurementIterator { 40 | type Item = Measurement; 41 | 42 | fn next(&mut self) -> Option { 43 | use rand::prelude::*; 44 | 45 | let device_id = self.worker_id | self.cur_device << 8; 46 | let mut metrics = self.metrics.clone(); 47 | metrics.rotate_left(self.cur_device as usize % self.metrics.len()); 48 | 49 | self.cur_device = (self.cur_device + 1) % self.num_devices; 50 | if self.cur_device == 0 { 51 | self.time += Duration::seconds(1); 52 | 53 | for m in self.metrics.iter_mut() { 54 | let v: f64 = self.rng.sample(StandardNormal); 55 | *m += *m * 0.0005 * v; 56 | } 57 | } 58 | 59 | Some(Measurement { 60 | time: self.time, 61 | device_id, 62 | metrics, 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tsdbperf/tests/worker.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use anyhow::Result; 4 | use lazy_static::lazy_static; 5 | use structopt::StructOpt; 6 | 7 | use tsdbperf::db; 8 | 9 | lazy_static! { 10 | static ref DB_LOCK: Mutex<()> = Mutex::new(()); 11 | } 12 | 13 | struct Runner { 14 | devices: u32, 15 | measurements: u32, 16 | metrics: u32, 17 | copy_upserts: bool, 18 | upserts: bool, 19 | jsonb: bool, 20 | } 21 | 22 | impl Runner { 23 | fn new() -> Self { 24 | Self { 25 | devices: 15, 26 | measurements: 1000, 27 | metrics: 5, 28 | copy_upserts: false, 29 | upserts: false, 30 | jsonb: false, 31 | } 32 | } 33 | 34 | fn opt(&self) -> db::DbOpt { 35 | let metrics = self.metrics.to_string(); 36 | let mut args = vec!["dbtest", "--metrics", &metrics]; 37 | if self.jsonb { 38 | args.push("--with-jsonb"); 39 | } else if self.upserts { 40 | args.push("--with-upserts"); 41 | } else if self.copy_upserts { 42 | args.push("--with-copy-upserts"); 43 | } 44 | 45 | db::DbOpt::from_iter(&args) 46 | } 47 | 48 | async fn run(&self) -> Result<()> { 49 | let (written, rows) = { 50 | // Each run recreates the schema so we need to serialize them. 51 | // Use a block to prevent asserts from poisoning the mutex. 52 | let _guard = DB_LOCK.lock().unwrap(); 53 | 54 | let opt = self.opt(); 55 | db::init(&opt).await?; 56 | 57 | let written = db::run_worker(&opt, 1, self.devices, self.measurements).await?; 58 | let db = db::Db::connect(&opt).await?; 59 | let rows = db.query("select * from measurement").await?; 60 | (written, rows) 61 | }; 62 | 63 | assert_eq!(written as u32, self.devices * self.measurements); 64 | assert_eq!(written, rows.len()); 65 | 66 | if self.jsonb { 67 | // Time, device_id and jsonb metrics 68 | assert_eq!(rows[0].len(), 3); 69 | } else { 70 | // Time, device_id and metrics columns 71 | assert_eq!(rows[0].len(), 2 + self.metrics as usize); 72 | } 73 | 74 | Ok(()) 75 | } 76 | } 77 | 78 | #[tokio::test] 79 | async fn copy_in() -> Result<()> { 80 | let runner = Runner::new(); 81 | runner.run().await 82 | } 83 | 84 | #[tokio::test] 85 | async fn copy_upsert() -> Result<()> { 86 | let runner = Runner { 87 | copy_upserts: true, 88 | ..Runner::new() 89 | }; 90 | 91 | runner.run().await 92 | } 93 | 94 | #[tokio::test] 95 | async fn upsert() -> Result<()> { 96 | let runner = Runner { 97 | upserts: true, 98 | ..Runner::new() 99 | }; 100 | 101 | runner.run().await 102 | } 103 | 104 | #[tokio::test] 105 | async fn jsonb() -> Result<()> { 106 | let runner = Runner { 107 | jsonb: true, 108 | ..Runner::new() 109 | }; 110 | 111 | runner.run().await 112 | } 113 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | authors = ["Vince "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | anyhow = "1.0" 9 | flate2 = "1.0" 10 | structopt = "0.3" 11 | xshell = "0.1" 12 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Post build tasks for distribution and install. 3 | 4 | This implementation provides the following tasks: 5 | - `cargo xtask dist` to create a compressed binary for a target 6 | - `cargo xtask install` to install tsdbperf as a binary in ~/.cargo/bin 7 | 8 | See [cargo-xtask](https://github.com/matklad/cargo-xtask) for documentation 9 | and examples. 10 | 11 | For a more comprehensive example see the source for 12 | [xtask in rust-analyzer](https://github.com/rust-analyzer/rust-analyzer/tree/master/xtask). 13 | */ 14 | #![deny(rust_2018_idioms)] 15 | 16 | use std::{ 17 | env, 18 | fs::File, 19 | path::{Path, PathBuf}, 20 | }; 21 | 22 | use anyhow::Result; 23 | use flate2::{write::GzEncoder, Compression}; 24 | use structopt::StructOpt; 25 | use xshell::{cmd, mkdir_p, rm_rf}; 26 | 27 | #[derive(Debug, StructOpt)] 28 | #[structopt(name = "xtask")] 29 | pub enum Opt { 30 | Dist, 31 | Install, 32 | } 33 | 34 | fn main() -> Result<()> { 35 | match Opt::from_args() { 36 | Opt::Dist => run_dist(), 37 | Opt::Install => run_install(), 38 | } 39 | } 40 | 41 | fn run_dist() -> Result<()> { 42 | let dist = project_root().join("dist"); 43 | rm_rf(&dist)?; 44 | mkdir_p(&dist)?; 45 | 46 | let target = get_target(); 47 | 48 | cmd!("cargo build --manifest-path ./tsdbperf/Cargo.toml --bin tsdbperf --target {target} --release").run()?; 49 | 50 | let suffix = exe_suffix(&target); 51 | let src = Path::new("target") 52 | .join(&target) 53 | .join("release") 54 | .join(format!("tsdbperf{}", suffix)); 55 | let dst = Path::new("dist").join(format!("tsdbperf-{}{}", target, suffix)); 56 | gzip(&src, &dst.with_extension("gz"))?; 57 | 58 | Ok(()) 59 | } 60 | 61 | fn run_install() -> Result<()> { 62 | cmd!("cargo install --path tsdbperf --locked --force").run()?; 63 | Ok(()) 64 | } 65 | 66 | fn get_target() -> String { 67 | match env::var("TSDBPERF_TARGET") { 68 | Ok(target) => target, 69 | _ => { 70 | if cfg!(target_os = "linux") { 71 | "x86_64-unknown-linux-gnu".to_string() 72 | } else if cfg!(target_os = "windows") { 73 | "x86_64-pc-windows-msvc".to_string() 74 | } else if cfg!(target_os = "macos") { 75 | "x86_64-apple-darwin".to_string() 76 | } else { 77 | panic!("Unsupported OS, maybe try setting TSDBPERF_TARGET") 78 | } 79 | } 80 | } 81 | } 82 | 83 | fn exe_suffix(target: &str) -> String { 84 | if target.contains("-windows-") { 85 | ".exe".into() 86 | } else { 87 | "".into() 88 | } 89 | } 90 | 91 | fn gzip(src_path: &Path, dest_path: &Path) -> Result<()> { 92 | let mut encoder = GzEncoder::new(File::create(dest_path)?, Compression::best()); 93 | let mut input = std::io::BufReader::new(File::open(src_path)?); 94 | std::io::copy(&mut input, &mut encoder)?; 95 | encoder.finish()?; 96 | Ok(()) 97 | } 98 | 99 | fn project_root() -> PathBuf { 100 | Path::new( 101 | &env::var("CARGO_MANIFEST_DIR") 102 | .unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()), 103 | ) 104 | .ancestors() 105 | .nth(1) 106 | .unwrap() 107 | .to_path_buf() 108 | } 109 | --------------------------------------------------------------------------------