├── .cargo └── config.toml ├── .dockerignore ├── .drone.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── Dockerfile.dev ├── Dockerfile.memprof ├── LICENSE ├── README.md ├── bpf-memprof-common ├── Cargo.toml └── src │ ├── client.rs │ ├── event.rs │ └── lib.rs ├── bpf-memprof ├── Cargo.toml ├── build.rs └── src │ └── main.rs ├── bpf-recorder ├── Cargo.toml ├── build.rs └── src │ ├── address.rs │ ├── client.rs │ ├── lib.rs │ ├── main.rs │ ├── send.rs │ └── syscall_context.rs ├── bpf-ring-buffer ├── Cargo.toml └── src │ └── lib.rs ├── config-docker.toml ├── config.toml ├── docker-compose.yml ├── docker ├── Dockerfile ├── README.md └── bpf.dockerfile ├── docs ├── messages.svg └── system.svg ├── extractor ├── Cargo.toml └── src │ └── main.rs ├── pseudonode ├── Cargo.toml └── src │ ├── buffer.rs │ ├── handshake.rs │ └── lib.rs ├── record-replay.md ├── rustfmt.toml ├── san.sh ├── size_check.sh ├── tezedge-memprof ├── Cargo.toml ├── openapi.json ├── src │ ├── collector │ │ ├── aggregator.rs │ │ ├── consumer.rs │ │ └── mod.rs │ ├── history │ │ ├── abstract_tracker.rs │ │ ├── allocation.rs │ │ ├── error.rs │ │ ├── history.rs │ │ ├── mod.rs │ │ ├── page.rs │ │ ├── page_history.rs │ │ ├── report.rs │ │ └── tests.rs │ ├── lib.rs │ ├── memory_map.rs │ ├── server.rs │ ├── stack.rs │ ├── state.rs │ └── table.rs └── tests │ ├── compare.rs │ └── positive.rs └── tezedge-recorder ├── Cargo.toml ├── build.rs ├── identity_i.json ├── identity_r.json ├── openapi.json ├── src ├── bin │ ├── main.rs │ ├── operation_example.json │ ├── pseudonode.rs │ └── replayer.rs ├── common.rs ├── database │ ├── mock.rs │ ├── mod.rs │ ├── rocks.rs │ ├── search.rs │ └── sorted_intersect.rs ├── lib.rs ├── log_client.rs ├── main_loop.rs ├── processor │ ├── chunk_parser │ │ ├── buffer.rs │ │ ├── key.rs │ │ ├── mod.rs │ │ ├── parser.rs │ │ └── state.rs │ ├── connection.rs │ ├── message_parser.rs │ └── mod.rs ├── server.rs ├── system.rs └── tables │ ├── chunk.rs │ ├── connection.rs │ ├── message.rs │ ├── mod.rs │ ├── node_log.rs │ └── secondary_indexes │ ├── log_level.rs │ ├── message_addr.rs │ ├── message_initiator.rs │ ├── message_sender.rs │ ├── message_ty.rs │ ├── mod.rs │ └── timestamp.rs ├── test.sh └── tests ├── log.rs └── p2p.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target-dir = "target/none" 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.project 5 | **/.settings 6 | **/.toolstarget 7 | **/.vs 8 | **/.vscode 9 | **/*.*proj.user 10 | **/*.dbmdl 11 | **/*.jfm 12 | **/azds.yaml 13 | **/charts 14 | **/docker-compose* 15 | **/Dockerfile* 16 | **/node_modules 17 | **/npm-debug.log 18 | **/obj 19 | **/secrets.dev.yaml 20 | **/values.dev.yaml 21 | README.md 22 | target/ 23 | setcap.sh 24 | LICENSE 25 | rustfmt.toml 26 | *.heap 27 | .drone.yml 28 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: test-memprof 4 | 5 | steps: 6 | - name: test-memprof 7 | image: tezedge/tezedge-bpf-builder:latest 8 | privileged: true 9 | volumes: 10 | - name: kernel-debug 11 | path: /sys/kernel/debug 12 | - name: proc-dir 13 | path: /proc 14 | - name: docker_sock 15 | path: /var/run/docker.sock 16 | commands: 17 | - apt-get install -y pkg-config 18 | - rustup install nightly-2021-03-23 19 | # docker client 20 | - curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-20.10.0.tgz 21 | - tar xzf docker-20.10.0.tgz --strip 1 -C /usr/local/bin docker/docker 22 | - rm docker-20.10.0.tgz 23 | # build memprof 24 | - cargo +nightly-2021-03-23 install bpf-linker --git https://github.com/tezedge/bpf-linker.git --branch main 25 | - cargo +nightly-2021-03-23 test --package tezedge-memprof --lib --release -- history::history history::tests 26 | - cargo +nightly-2021-03-23 build -p bpf-memprof --release 27 | - cargo +nightly-2021-03-23 build -p tezedge-memprof --tests --release 28 | # run tezedge-node in docker 29 | - docker stop tezedge_node || true 30 | - TEZEDGE_NODE_NAME=tezedge_node ./target/none/release/bpf-memprof-user & 31 | - docker run --rm -d --name tezedge_node tezedge/tezedge:latest-frame-pointers-enabled --network hangzhounet 32 | # do tests 33 | - URL=http://localhost:17832 cargo +nightly-2021-03-23 test -p tezedge-memprof --release -- positive compare --nocapture 34 | # stop the node 35 | - docker stop tezedge_node 36 | 37 | volumes: 38 | - name: kernel-debug 39 | host: 40 | path: /sys/kernel/debug 41 | - name: proc-dir 42 | host: 43 | path: /proc 44 | - name: docker_sock 45 | host: 46 | path: /var/run/docker.sock 47 | 48 | --- 49 | 50 | kind: pipeline 51 | type: docker 52 | name: test-recorder 53 | 54 | steps: 55 | - name: test-recorder 56 | image: tezedge/tezedge-bpf-builder:latest 57 | privileged: true 58 | volumes: 59 | - name: tezedge-shared-data 60 | path: /tmp/volume 61 | - name: kernel-debug 62 | path: /sys/kernel/debug 63 | - name: docker_sock 64 | path: /var/run/docker.sock 65 | commands: 66 | - apt-get install -y pkg-config clang libev-dev 67 | - rustup install nightly-2021-03-23 68 | - rustup install stable 69 | # docker client 70 | - curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-20.10.0.tgz 71 | - tar xzf docker-20.10.0.tgz --strip 1 -C /usr/local/bin docker/docker 72 | - rm docker-20.10.0.tgz 73 | # build recorder 74 | - cargo +stable install bpf-linker --git https://github.com/tezedge/bpf-linker.git --branch main 75 | - cargo +stable install --path bpf-recorder 76 | - cargo +nightly-2021-03-23 build -p tezedge-recorder --tests --release 77 | # stop old node if any 78 | - docker stop tezedge_node || true 79 | # remove old debugger's db if any 80 | - rm -Rf /tmp/volume/tezedge_debugger || true 81 | # prepare 82 | - cp /usr/local/cargo/git/checkouts/tezedge-*/*/tezos/sys/lib_tezos/artifacts/libtezos.so /usr/lib/x86_64-linux-gnu/libtezos.so 83 | - mkdir target/debugger_db 84 | # test 85 | - LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu DEBUGGER_URL="http://localhost:17732" ./tezedge-recorder/test.sh 86 | # test with real node 87 | - LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu ./target/none/release/tezedge-recorder --run-bpf & sleep 2 88 | # run tezedge-node in docker 89 | - docker run --rm -d --name tezedge_node -v /tmp/volume/tezedge:/tmp/tezedge tezedge/tezedge:latest-frame-pointers-enabled --network mainnet --p2p-port 29734 90 | # test p2p 91 | - DEBUGGER_URL="http://localhost:17732" cargo +nightly-2021-03-23 test -p tezedge-recorder --release -- wait --nocapture 92 | - DEBUGGER_URL="http://localhost:17732" cargo +nightly-2021-03-23 test -p tezedge-recorder --release -- p2p_limit p2p_cursor p2p_types_filter --nocapture 93 | # stop the node 94 | - docker stop tezedge_node 95 | 96 | volumes: 97 | - name: tezedge-shared-data 98 | host: 99 | path: /tmp/volume 100 | - name: kernel-debug 101 | host: 102 | path: /sys/kernel/debug 103 | - name: docker_sock 104 | host: 105 | path: /var/run/docker.sock 106 | 107 | --- 108 | 109 | kind: pipeline 110 | name: docker-deploy-develop 111 | 112 | steps: 113 | - name: build-tezedge-debugger-image 114 | image: plugins/docker 115 | settings: 116 | repo: tezedge/tezedge-debugger 117 | tag: latest 118 | dockerfile: Dockerfile 119 | username: 120 | from_secret: docker_hub_username 121 | password: 122 | from_secret: docker_hub_pswd 123 | - name: build-tezedge-memprof-image 124 | image: plugins/docker 125 | settings: 126 | repo: tezedge/tezedge-memprof 127 | tag: latest 128 | dockerfile: Dockerfile.memprof 129 | username: 130 | from_secret: docker_hub_username 131 | password: 132 | from_secret: docker_hub_pswd 133 | 134 | image_pull_secrets: 135 | - docker_pull_secret 136 | 137 | depends_on: 138 | - test-memprof 139 | - test-recorder 140 | 141 | trigger: 142 | branch: develop 143 | event: push 144 | 145 | --- 146 | 147 | kind: pipeline 148 | name: docker-deploy-release 149 | 150 | steps: 151 | - name: build-tezedge-debugger-image 152 | image: plugins/docker 153 | settings: 154 | repo: tezedge/tezedge-debugger 155 | tags: 156 | - ${DRONE_TAG} 157 | - latest-release 158 | dockerfile: Dockerfile 159 | username: 160 | from_secret: docker_hub_username 161 | password: 162 | from_secret: docker_hub_pswd 163 | - name: build-tezedge-memprof-image 164 | image: plugins/docker 165 | settings: 166 | repo: tezedge/tezedge-memprof 167 | tags: 168 | - ${DRONE_TAG} 169 | - latest-release 170 | dockerfile: Dockerfile.memprof 171 | username: 172 | from_secret: docker_hub_username 173 | password: 174 | from_secret: docker_hub_pswd 175 | 176 | image_pull_secrets: 177 | - docker_pull_secret 178 | 179 | depends_on: 180 | - test-memprof 181 | - test-recorder 182 | 183 | trigger: 184 | ref: refs/tags/** 185 | event: tag 186 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | /bin/ 3 | *.heap 4 | dev-dir 5 | *.gz 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "sourceLanguages": ["rust"], 10 | "request": "launch", 11 | "name": "Launch", 12 | "cwd": "${workspaceFolder}", 13 | "program": "${workspaceFolder}/target/none/debug/tezedge-memprof", 14 | "args": [], 15 | "env": { 16 | // "MALLOC_CONF": "prof_leak:true,lg_prof_sample:0,prof_final:true", 17 | // "LD_PRELOAD": "/usr/local/lib/libjemalloc.so.2" 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "BACKTRACE", 4 | "Bincode", 5 | "Cryptobox", 6 | "EINPROGRESS", 7 | "EMSGSIZE", 8 | "Inet", 9 | "Jemalloc", 10 | "SIGTERM", 11 | "SPDX", 12 | "Shleft", 13 | "Syscall", 14 | "Tezedge", 15 | "Tezos", 16 | "UNSPEC", 17 | "appuser", 18 | "clippy", 19 | "compat", 20 | "concat", 21 | "curr", 22 | "deallocated", 23 | "ebpf", 24 | "entrypoint", 25 | "eoan", 26 | "execve", 27 | "execveat", 28 | "extfrag", 29 | "getsockname", 30 | "greylisted", 31 | "hasher", 32 | "hashset", 33 | "heapify", 34 | "heaptrack", 35 | "itertools", 36 | "jemallocator", 37 | "kfree", 38 | "kmalloc", 39 | "kmem", 40 | "kmerge", 41 | "kprobe", 42 | "kprobes", 43 | "kretprobe", 44 | "ksys", 45 | "ktime", 46 | "memprof", 47 | "memtable", 48 | "mmap", 49 | "msvc", 50 | "munmap", 51 | "nanos", 52 | "oneshot", 53 | "passfd", 54 | "pcpu", 55 | "percpu", 56 | "pgoff", 57 | "precompute", 58 | "prevalidator", 59 | "printk", 60 | "pswd", 61 | "recvfrom", 62 | "redbpf", 63 | "regs", 64 | "replayer", 65 | "repr", 66 | "reqwest", 67 | "ringbuf", 68 | "rocksdb", 69 | "rustfmt", 70 | "sendmsg", 71 | "sendto", 72 | "simplestakingcom", 73 | "smallvec", 74 | "strtab", 75 | "structopt", 76 | "symtab", 77 | "syscalls", 78 | "syslogs", 79 | "tgid", 80 | "thiserror", 81 | "tracepoint", 82 | "typenum", 83 | "unactivated", 84 | "userspace" 85 | ] 86 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "label": "compose-up-rust", 7 | "command": "docker-compose -f docker-compose.rust.yml up --build --remove-orphans", 8 | "presentation": { 9 | "reveal": "always", 10 | "panel": "new" 11 | }, 12 | "problemMatcher": [ 13 | "$rustc" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "extractor", 4 | 5 | # recorder and its bpf tools 6 | "tezedge-recorder", 7 | "bpf-recorder", 8 | "bpf-ring-buffer", 9 | "pseudonode", 10 | 11 | #memprof and its bpf tools 12 | "bpf-memprof-common", 13 | "tezedge-memprof", 14 | "bpf-memprof", 15 | ] 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tezedge/tezedge-bpf-builder:latest as builder 2 | 3 | RUN apt install -y g++ git libssl-dev pkg-config libev-dev 4 | 5 | RUN rustup update stable && rustup update nightly-2021-03-23 6 | RUN cargo +stable install bpf-linker --git https://github.com/tezedge/bpf-linker.git --branch main 7 | 8 | COPY . . 9 | RUN cargo +stable build -p bpf-recorder --release && \ 10 | cargo +nightly-2021-03-23 build -p tezedge-recorder --release 11 | 12 | FROM tezedge/tezedge-libs:latest-profile 13 | 14 | COPY --from=builder /usr/local/cargo/git/checkouts/tezedge-*/*/tezos/sys/lib_tezos/artifacts/libtezos.so /usr/lib/x86_64-linux-gnu/libtezos.so 15 | COPY --from=builder /usr/lib/x86_64-linux-gnu/libev.so.4 /usr/lib/x86_64-linux-gnu/libev.so.4 16 | COPY --from=builder /usr/lib/x86_64-linux-gnu/libffi.so.7 /usr/lib/x86_64-linux-gnu/libffi.so.7 17 | COPY --from=builder /usr/lib/x86_64-linux-gnu/libelf.so.1 /usr/lib/x86_64-linux-gnu/libelf.so.1 18 | COPY --from=builder /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1 19 | COPY --from=builder /home/appuser/target/none/release/bpf-recorder /usr/local/bin/bpf-recorder 20 | COPY --from=builder /home/appuser/target/none/release/tezedge-recorder /usr/local/bin/tezedge-recorder 21 | 22 | ENTRYPOINT [ "tezedge-recorder", "--run-bpf" ] 23 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM tezedge/tezedge-bpf-builder:latest as builder 2 | 3 | FROM tezedge/tezedge-libs:latest-profile 4 | 5 | COPY --from=builder /usr/lib/x86_64-linux-gnu/libelf.so.1 /usr/lib/x86_64-linux-gnu/libelf.so.1 6 | COPY --from=builder /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1 7 | COPY dev-dir/bpf-recorder /usr/local/bin/bpf-recorder 8 | COPY dev-dir/tezedge-recorder /usr/local/bin/tezedge-recorder 9 | 10 | ENTRYPOINT [ "tezedge-recorder", "--run-bpf" ] 11 | -------------------------------------------------------------------------------- /Dockerfile.memprof: -------------------------------------------------------------------------------- 1 | FROM tezedge/tezedge-bpf-builder:latest as builder 2 | 3 | RUN cargo install bpf-linker --git https://github.com/tezedge/bpf-linker.git --branch main && \ 4 | DEBIAN_FRONTEND='noninteractive' apt-get install -y libelf-dev 5 | 6 | RUN curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz && \ 7 | tar xzf docker-20.10.6.tgz --strip 1 -C /usr/bin docker/docker && \ 8 | rm docker-*.tgz 9 | 10 | COPY . . 11 | RUN cargo build -p bpf-memprof --release 12 | 13 | FROM tezedge/tezedge-libs:latest-profile 14 | 15 | COPY --from=builder /usr/lib/x86_64-linux-gnu/libelf.so.1 /usr/lib/x86_64-linux-gnu/libelf.so.1 16 | COPY --from=builder /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1 17 | COPY --from=builder /home/appuser/target/none/release/bpf-memprof-user /usr/bin/bpf-memprof-user 18 | 19 | COPY --from=builder /usr/bin/docker /usr/bin/docker 20 | 21 | ENTRYPOINT ["bpf-memprof-user"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 SimpleStaking and Tezos-RS Contributors 2 | Copyright (c) 2021 viablesystems.io 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /bpf-memprof-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bpf-memprof-common" 3 | version = "0.1.0" 4 | authors = ["Vladislav Melnik "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ebpf-user = { version = "0.1", optional = true } 9 | serde = { version = "1.0", features = ["derive"], optional = true } 10 | passfd = { version = "0.1", optional = true } 11 | 12 | [features] 13 | client = ["serde/derive", "ebpf-user", "passfd"] 14 | -------------------------------------------------------------------------------- /bpf-memprof-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #![cfg_attr(not(feature = "client"), no_std)] 5 | 6 | mod event; 7 | pub use self::event::{Pod, Hex32, Hex64, CommonHeader}; 8 | pub use self::event::{ 9 | KFree, KMAlloc, KMAllocNode, CacheAlloc, CacheAllocNode, CacheFree, PageAlloc, PageFree, 10 | PageFreeBatched, 11 | }; 12 | pub use self::event::{RssStat, PercpuAlloc, PercpuFree, AddToPageCache, RemoveFromPageCache}; 13 | 14 | #[cfg(feature = "client")] 15 | mod client; 16 | #[cfg(feature = "client")] 17 | pub use self::client::{Client, ClientCallback, EventKind, Event, Stack}; 18 | 19 | pub const STACK_MAX_DEPTH: usize = 127; 20 | -------------------------------------------------------------------------------- /bpf-memprof/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bpf-memprof" 3 | version = "0.1.0" 4 | authors = ["Vladislav Melnik "] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "bpf-memprof-kern" 9 | path = "src/main.rs" 10 | required-features = ["kern"] 11 | 12 | [[bin]] 13 | name = "bpf-memprof-user" 14 | path = "src/main.rs" 15 | required-features = ["user"] 16 | 17 | [dependencies] 18 | ebpf-kern = { version = "0.1", optional = true } 19 | ebpf-user = { version = "0.1", optional = true } 20 | passfd = { version = "0.1", optional = true } 21 | sudo = { version = "0.6", optional = true } 22 | ctrlc = { version = "3.1", optional = true } 23 | log = { version = "0.4", optional = true } 24 | tracing = { version = "0.1", optional = true } 25 | tracing-subscriber = { version = "0.2", optional = true } 26 | bpf-memprof-common = { path = "../bpf-memprof-common" } 27 | tezedge-memprof = { path = "../tezedge-memprof", optional = true } 28 | 29 | [features] 30 | default = ["user"] 31 | kern = ["ebpf-kern/macros"] 32 | user = [ 33 | "ebpf-user/macros", 34 | "passfd", 35 | "sudo", 36 | "ctrlc", 37 | "tracing", 38 | "tracing-subscriber", 39 | "log", 40 | "tezedge-memprof", 41 | ] 42 | -------------------------------------------------------------------------------- /bpf-memprof/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | fn main() { 5 | #[cfg(feature = "user")] 6 | build_bpf() 7 | } 8 | 9 | #[cfg(feature = "user")] 10 | fn build_bpf() { 11 | use std::{env, process::Command}; 12 | 13 | let target_dir = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "../target".to_string()); 14 | let target_dir = format!("{}/bpf", target_dir); 15 | 16 | let args = &[ 17 | "+nightly-2020-12-31", 18 | "rustc", 19 | "--package=bpf-memprof", 20 | "--bin=bpf-memprof-kern", 21 | "--features=kern", 22 | "--no-default-features", 23 | "--", 24 | "-Clinker-plugin-lto", 25 | "-Clinker-flavor=wasm-ld", 26 | "-Clinker=bpf-linker", 27 | "-Clink-arg=--target=bpf", 28 | "-Clink-arg=-O3", 29 | ]; 30 | let output = Command::new("cargo") 31 | .env("RUSTFLAGS", "") 32 | .env("CARGO_TARGET_DIR", &target_dir) 33 | .args(args) 34 | .output() 35 | .expect("failed to build bpf code"); 36 | if !output.status.success() { 37 | let error = String::from_utf8(output.stderr).expect("malformed error message"); 38 | panic!("{}", error); 39 | } 40 | Command::new("sed") 41 | .current_dir(&target_dir) 42 | .arg("-i") 43 | .arg("s/ty__/type/g") 44 | .arg("debug/bpf-memprof-kern") 45 | .output() 46 | .expect("failed to patch bpf object"); 47 | 48 | println!("cargo:rustc-env=BPF_CODE_MEMPROF={}/debug/bpf-memprof-kern", target_dir); 49 | println!("cargo:rerun-if-changed=src/main.rs"); 50 | println!("cargo:rerun-if-changed=src/event.rs"); 51 | } 52 | -------------------------------------------------------------------------------- /bpf-recorder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bpf-recorder" 3 | version = "0.1.0" 4 | authors = ["Vladislav Melnik "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "bpf_recorder" 9 | path = "src/lib.rs" 10 | 11 | [[bin]] 12 | name = "bpf-recorder-kern" 13 | path = "src/main.rs" 14 | required-features = ["kern"] 15 | 16 | [[bin]] 17 | name = "bpf-recorder" 18 | path = "src/main.rs" 19 | required-features = ["user"] 20 | 21 | [dependencies] 22 | ebpf-kern = { version = "0.1", optional = true } 23 | typenum = { version = "1.13", optional = true } 24 | ebpf-user = { version = "0.1", optional = true } 25 | passfd = { version = "0.1", optional = true } 26 | sudo = { version = "0.6", optional = true } 27 | ctrlc = { version = "3.1", optional = true } 28 | log = { version = "0.4", optional = true } 29 | tracing = { version = "0.1", optional = true } 30 | tracing-subscriber = { version = "0.2", optional = true } 31 | serde = { version = "1.0", features = ["derive"], optional = true } 32 | hex = { version = "0.4", optional = true } 33 | bpf-ring-buffer = { path = "../bpf-ring-buffer", optional = true } 34 | 35 | [features] 36 | default = ["user"] 37 | kern = ["ebpf-kern/macros", "typenum"] 38 | user = [ 39 | "ebpf-user/macros", 40 | "passfd", 41 | "sudo", 42 | "ctrlc", 43 | "log", 44 | "tracing", 45 | "tracing-subscriber", 46 | ] 47 | client = [ 48 | "serde/derive", 49 | "ebpf-user", 50 | "passfd", 51 | "hex", 52 | "bpf-ring-buffer", 53 | ] 54 | -------------------------------------------------------------------------------- /bpf-recorder/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | fn main() { 5 | #[cfg(feature = "user")] 6 | build_bpf() 7 | } 8 | 9 | #[cfg(feature = "user")] 10 | fn build_bpf() { 11 | use std::{env, process::Command}; 12 | 13 | let target_dir = env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "../target".to_string()); 14 | let target_dir = format!("{}/bpf", target_dir); 15 | 16 | let args = &[ 17 | "+nightly-2020-12-31", 18 | "rustc", 19 | "--package=bpf-recorder", 20 | "--bin=bpf-recorder-kern", 21 | "--features=kern", 22 | "--no-default-features", 23 | "--", 24 | "-Clinker-plugin-lto", 25 | "-Clinker-flavor=wasm-ld", 26 | "-Clinker=bpf-linker", 27 | "-Clink-arg=--target=bpf", 28 | "-Clink-arg=-O3", 29 | ]; 30 | let output = Command::new("cargo") 31 | .env("RUSTFLAGS", "") 32 | .env("CARGO_TARGET_DIR", &target_dir) 33 | .args(args) 34 | .output() 35 | .expect("failed to build bpf code"); 36 | if !output.status.success() { 37 | let error = String::from_utf8(output.stderr).expect("malformed error message"); 38 | panic!("{}", error); 39 | } 40 | Command::new("sed") 41 | .current_dir(&target_dir) 42 | .arg("-i") 43 | .arg("s/ty__/type/g") 44 | .arg("debug/bpf-recorder-kern") 45 | .output() 46 | .expect("failed to patch bpf object"); 47 | 48 | println!( 49 | "cargo:rustc-env=BPF_CODE_RECORDER={}/debug/bpf-recorder-kern", 50 | target_dir 51 | ); 52 | println!("cargo:rerun-if-changed=src/main.rs"); 53 | println!("cargo:rerun-if-changed=src/event.rs"); 54 | } 55 | -------------------------------------------------------------------------------- /bpf-recorder/src/address.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use ebpf_kern::helpers; 5 | 6 | pub struct Address { 7 | sa_family: u16, 8 | port: u16, 9 | } 10 | 11 | impl Address { 12 | const AF_INET: u16 = 2; 13 | const AF_INET6: u16 = 10; 14 | 15 | #[inline(always)] 16 | pub fn read(addr_ptr: u64, addr_len: u64) -> Result, i32> { 17 | if addr_len < 4 { 18 | return Err(-1); 19 | } 20 | 21 | let mut address_header = [[0; 2]; 2]; 22 | let c = unsafe { 23 | helpers::probe_read_user(address_header.as_mut_ptr() as _, 4, addr_ptr as *const _) 24 | }; 25 | if c < 0 { 26 | return Err(c as _); 27 | } 28 | let address = Address { 29 | sa_family: u16::from_ne_bytes(address_header[0]), 30 | port: u16::from_be_bytes(address_header[1]), 31 | }; 32 | if address.sa_family != Self::AF_INET && address.sa_family != Self::AF_INET6 { 33 | return Ok(None); 34 | } 35 | 36 | Ok(Some(address)) 37 | } 38 | 39 | #[inline(always)] 40 | pub fn port(&self) -> u16 { 41 | self.port 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bpf-recorder/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #![cfg_attr(feature = "kern", no_std)] 5 | 6 | #[cfg(feature = "client")] 7 | mod client; 8 | #[cfg(feature = "client")] 9 | pub use self::client::{SnifferEvent, SnifferError, SnifferErrorCode, BpfModuleClient}; 10 | 11 | use core::{fmt, mem, ptr, convert::TryFrom}; 12 | 13 | #[cfg(feature = "user")] 14 | use core::str::FromStr; 15 | 16 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 17 | pub struct SocketId { 18 | pub pid: u32, 19 | pub fd: u32, 20 | } 21 | 22 | impl SocketId { 23 | #[inline(always)] 24 | pub fn to_ne_bytes(self) -> [u8; mem::size_of::()] { 25 | (((self.pid as u64) << 32) + (self.fd as u64)).to_ne_bytes() 26 | } 27 | } 28 | 29 | impl fmt::Display for SocketId { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | write!(f, "{}:{}", self.pid, self.fd) 32 | } 33 | } 34 | 35 | pub enum Command { 36 | WatchPort { port: u16 }, 37 | IgnoreConnection { pid: u32, fd: u32 }, 38 | FetchCounter, 39 | } 40 | 41 | #[cfg(feature = "user")] 42 | impl FromStr for Command { 43 | type Err = String; 44 | 45 | fn from_str(s: &str) -> Result { 46 | let mut words = s.split(' '); 47 | match words.next() { 48 | Some("watch_port") => { 49 | let port = words 50 | .next() 51 | .ok_or_else(|| "bad port".to_string())? 52 | .parse() 53 | .map_err(|e| format!("failed to parse port: {}", e))?; 54 | Ok(Command::WatchPort { port }) 55 | }, 56 | Some("ignore_connection") => { 57 | let pid = words 58 | .next() 59 | .ok_or_else(|| "bad pid".to_string())? 60 | .parse() 61 | .map_err(|e| format!("failed to parse pid: {}", e))?; 62 | let fd = words 63 | .next() 64 | .ok_or_else(|| "bad fd".to_string())? 65 | .parse() 66 | .map_err(|e| format!("failed to parse fd: {}", e))?; 67 | Ok(Command::IgnoreConnection { pid, fd }) 68 | }, 69 | Some("fetch_counter") => Ok(Command::FetchCounter), 70 | _ => Err("unexpected command".to_string()), 71 | } 72 | } 73 | } 74 | 75 | impl fmt::Display for Command { 76 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 77 | match self { 78 | Command::WatchPort { port } => write!(f, "watch_port {}", port), 79 | Command::IgnoreConnection { pid, fd } => write!(f, "ignore_connection {} {}", pid, fd), 80 | Command::FetchCounter => write!(f, "fetch_counter"), 81 | } 82 | } 83 | } 84 | 85 | #[repr(C)] 86 | pub struct DataDescriptor { 87 | pub id: EventId, 88 | pub tag: DataTag, 89 | pub size: i32, 90 | } 91 | 92 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 93 | pub struct EventId { 94 | pub socket_id: SocketId, 95 | //ts: Range, 96 | ts: u64, 97 | } 98 | 99 | impl EventId { 100 | #[inline(always)] 101 | pub fn new(socket_id: SocketId, _ts_start: u64, ts_finish: u64) -> Self { 102 | EventId { 103 | socket_id, 104 | ts: ts_finish, 105 | } 106 | } 107 | 108 | pub fn ts_start(&self) -> u64 { 109 | 0 110 | } 111 | 112 | pub fn ts_finish(&self) -> u64 { 113 | self.ts 114 | } 115 | } 116 | 117 | impl fmt::Display for EventId { 118 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 119 | write!(f, "{}:{}", self.socket_id, self.ts) 120 | } 121 | } 122 | 123 | impl TryFrom<&[u8]> for DataDescriptor { 124 | type Error = (); 125 | 126 | // TODO: rewrite safe 127 | fn try_from(v: &[u8]) -> Result { 128 | if v.len() >= mem::size_of::() { 129 | Ok(unsafe { ptr::read(v as *const [u8] as *const Self) }) 130 | } else { 131 | Err(()) 132 | } 133 | } 134 | } 135 | 136 | #[repr(u32)] 137 | #[derive(Debug)] 138 | pub enum DataTag { 139 | Write, 140 | Read, 141 | Send, 142 | Recv, 143 | 144 | Connect, 145 | Bind, 146 | Listen, 147 | Accept, 148 | Close, 149 | 150 | GetFd, 151 | Debug, 152 | } 153 | -------------------------------------------------------------------------------- /bpf-recorder/src/send.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use core::{mem, ptr, ops::Sub}; 5 | use typenum::{Unsigned, Bit, Shleft}; 6 | use ebpf_kern::{RingBufferRef, helpers}; 7 | use bpf_recorder::{EventId, DataDescriptor, DataTag}; 8 | 9 | #[inline(always)] 10 | pub fn sized(id: EventId, tag: DataTag, data: *const u8, len: usize, rb: &mut RingBufferRef) 11 | where 12 | S: Unsigned, 13 | K: Bit, 14 | { 15 | if let Ok(mut buffer) = rb.reserve(S::U64 as usize + mem::size_of::()) { 16 | let p_buffer = buffer.as_mut().as_mut_ptr() as *mut DataDescriptor; 17 | 18 | let to_copy = S::USIZE.min(len); 19 | 20 | let result = if to_copy > 0 { 21 | unsafe { 22 | if K::BOOL { 23 | helpers::probe_read_kernel( 24 | p_buffer.offset(1) as *mut _, 25 | to_copy as u32, 26 | data as *const _, 27 | ) 28 | } else { 29 | helpers::probe_read_user( 30 | p_buffer.offset(1) as *mut _, 31 | to_copy as u32, 32 | data as *const _, 33 | ) 34 | } 35 | } 36 | } else { 37 | 0 38 | }; 39 | 40 | let size = if result == 0 { 41 | to_copy as i32 42 | } else { 43 | result as i32 44 | }; 45 | let descriptor = DataDescriptor { id, tag, size }; 46 | unsafe { 47 | ptr::write(p_buffer, descriptor); 48 | } 49 | 50 | buffer.submit(); 51 | return; 52 | } 53 | 54 | // failed to allocate buffer, try allocate smaller buffer to report error 55 | if let Ok(mut buffer) = rb.reserve(mem::size_of::()) { 56 | let descriptor = DataDescriptor { id, tag, size: -90 }; 57 | unsafe { 58 | ptr::write(buffer.as_mut().as_mut_ptr() as *mut _, descriptor); 59 | } 60 | buffer.submit(); 61 | } 62 | } 63 | 64 | type SizeOfDataDescriptor = typenum::U24; 65 | type DecByDataDescriptor = >::Output; 66 | 67 | #[inline(always)] 68 | fn sized_inner(id: EventId, tag: DataTag, data: *const u8, len: usize, rb: &mut RingBufferRef) 69 | where 70 | S: Unsigned + Sub, 71 | DecByDataDescriptor: Unsigned, 72 | K: Bit, 73 | { 74 | sized::, K>(id, tag, data, len, rb) 75 | } 76 | 77 | #[inline(always)] 78 | pub fn dyn_sized(id: EventId, tag: DataTag, data: *const u8, len: usize, rb: &mut RingBufferRef) 79 | where 80 | K: Bit, 81 | { 82 | // data len 124 happens often, let's have special case 148 = 124 + sizeof DataDescriptor 83 | let length_to_send = len + mem::size_of::(); 84 | if length_to_send <= typenum::U148::USIZE { 85 | sized_inner::(id, tag, data, len, rb) 86 | } else if length_to_send <= Shleft::::USIZE { 87 | sized_inner::, K>(id, tag, data, len, rb) 88 | } else if length_to_send <= Shleft::::USIZE { 89 | sized_inner::, K>(id, tag, data, len, rb) 90 | } else if length_to_send <= Shleft::::USIZE { 91 | sized_inner::, K>(id, tag, data, len, rb) 92 | } else if length_to_send <= Shleft::::USIZE { 93 | sized_inner::, K>(id, tag, data, len, rb) 94 | } else if length_to_send <= Shleft::::USIZE { 95 | sized_inner::, K>(id, tag, data, len, rb) 96 | } else if length_to_send <= Shleft::::USIZE { 97 | sized_inner::, K>(id, tag, data, len, rb) 98 | } else if length_to_send <= Shleft::::USIZE { 99 | sized_inner::, K>(id, tag, data, len, rb) 100 | } else if length_to_send <= Shleft::::USIZE { 101 | sized_inner::, K>(id, tag, data, len, rb) 102 | } else if length_to_send <= Shleft::::USIZE { 103 | sized_inner::, K>(id, tag, data, len, rb) 104 | } else if length_to_send <= Shleft::::USIZE { 105 | sized_inner::, K>(id, tag, data, len, rb) 106 | } else if length_to_send <= Shleft::::USIZE { 107 | sized_inner::, K>(id, tag, data, len, rb) 108 | } else if length_to_send <= Shleft::::USIZE { 109 | sized_inner::, K>(id, tag, data, len, rb) 110 | } else if length_to_send <= Shleft::::USIZE { 111 | sized_inner::, K>(id, tag, data, len, rb) 112 | } else if length_to_send <= Shleft::::USIZE { 113 | sized_inner::, K>(id, tag, data, len, rb) 114 | } else if length_to_send <= Shleft::::USIZE { 115 | sized_inner::, K>(id, tag, data, len, rb) 116 | } else if length_to_send <= Shleft::::USIZE { 117 | sized_inner::, K>(id, tag, data, len, rb) 118 | } else if length_to_send <= Shleft::::USIZE { 119 | sized_inner::, K>(id, tag, data, len, rb) 120 | } else if length_to_send <= Shleft::::USIZE { 121 | sized_inner::, K>(id, tag, data, len, rb) 122 | } else if length_to_send <= Shleft::::USIZE { 123 | sized_inner::, K>(id, tag, data, len, rb) 124 | } else if length_to_send <= Shleft::::USIZE { 125 | sized_inner::, K>(id, tag, data, len, rb) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /bpf-recorder/src/syscall_context.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use core::convert::TryFrom; 5 | use bpf_recorder::DataTag; 6 | 7 | #[derive(Clone, Copy)] 8 | #[repr(C)] 9 | pub struct SyscallContext { 10 | pub data: SyscallContextData, 11 | pub ts: u64, 12 | } 13 | 14 | impl SyscallContext { 15 | #[allow(dead_code)] 16 | #[inline(always)] 17 | pub fn to_ne_bytes(self) -> [u8; 0x20] { 18 | let SyscallContext { data, ts } = self; 19 | let mut b = [0; 0x20]; 20 | let (p, q) = match data { 21 | SyscallContextData::Empty => (0, 0), 22 | SyscallContextData::Bind { 23 | fd, 24 | addr_ptr, 25 | addr_len, 26 | } => { 27 | b[..4].clone_from_slice(&0x5u32.to_ne_bytes()); 28 | b[4..8].clone_from_slice(&fd.to_ne_bytes()); 29 | (addr_ptr, addr_len) 30 | }, 31 | SyscallContextData::Connect { 32 | fd, 33 | addr_ptr, 34 | addr_len, 35 | } => { 36 | b[..4].clone_from_slice(&0x6u32.to_ne_bytes()); 37 | b[4..8].clone_from_slice(&fd.to_ne_bytes()); 38 | (addr_ptr, addr_len) 39 | }, 40 | SyscallContextData::Accept { 41 | listen_on_fd, 42 | addr_ptr, 43 | addr_len, 44 | } => { 45 | b[..4].clone_from_slice(&0x7u32.to_ne_bytes()); 46 | b[4..8].clone_from_slice(&listen_on_fd.to_ne_bytes()); 47 | (addr_ptr, addr_len) 48 | }, 49 | SyscallContextData::Write { fd, data_ptr } => { 50 | b[..4].clone_from_slice(&0x8u32.to_ne_bytes()); 51 | b[4..8].clone_from_slice(&fd.to_ne_bytes()); 52 | (data_ptr, 0) 53 | }, 54 | SyscallContextData::Read { fd, data_ptr } => { 55 | b[..4].clone_from_slice(&0x9u32.to_ne_bytes()); 56 | b[4..8].clone_from_slice(&fd.to_ne_bytes()); 57 | (data_ptr, 0) 58 | }, 59 | SyscallContextData::Send { fd, data_ptr } => { 60 | b[..4].clone_from_slice(&0xau32.to_ne_bytes()); 61 | b[4..8].clone_from_slice(&fd.to_ne_bytes()); 62 | (data_ptr, 0) 63 | }, 64 | SyscallContextData::Recv { fd, data_ptr } => { 65 | b[..4].clone_from_slice(&0xbu32.to_ne_bytes()); 66 | b[4..8].clone_from_slice(&fd.to_ne_bytes()); 67 | (data_ptr, 0) 68 | }, 69 | }; 70 | b[0x08..0x10].clone_from_slice(&p.to_ne_bytes()); 71 | b[0x10..0x18].clone_from_slice(&q.to_ne_bytes()); 72 | b[0x18..0x20].clone_from_slice(&ts.to_ne_bytes()); 73 | b 74 | } 75 | 76 | #[allow(dead_code)] 77 | #[inline(always)] 78 | pub fn from_ne_bytes(bytes: &[u8; 0x20]) -> Self { 79 | let data = match u32::from_ne_bytes(TryFrom::try_from(&bytes[0x00..0x04]).unwrap()) { 80 | 0x5 => { 81 | let fd = u32::from_ne_bytes(TryFrom::try_from(&bytes[0x04..0x08]).unwrap()); 82 | let addr_ptr = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x08..0x10]).unwrap()); 83 | let addr_len = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x10..0x18]).unwrap()); 84 | SyscallContextData::Bind { 85 | fd, 86 | addr_ptr, 87 | addr_len, 88 | } 89 | }, 90 | 0x6 => { 91 | let fd = u32::from_ne_bytes(TryFrom::try_from(&bytes[0x04..0x08]).unwrap()); 92 | let addr_ptr = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x08..0x10]).unwrap()); 93 | let addr_len = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x10..0x18]).unwrap()); 94 | SyscallContextData::Connect { 95 | fd, 96 | addr_ptr, 97 | addr_len, 98 | } 99 | }, 100 | 0x7 => { 101 | let fd = u32::from_ne_bytes(TryFrom::try_from(&bytes[0x04..0x08]).unwrap()); 102 | let addr_ptr = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x08..0x10]).unwrap()); 103 | let addr_len = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x10..0x18]).unwrap()); 104 | SyscallContextData::Accept { 105 | listen_on_fd: fd, 106 | addr_ptr, 107 | addr_len, 108 | } 109 | }, 110 | 0x8 => { 111 | let fd = u32::from_ne_bytes(TryFrom::try_from(&bytes[0x04..0x08]).unwrap()); 112 | let data_ptr = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x08..0x10]).unwrap()); 113 | SyscallContextData::Write { fd, data_ptr } 114 | }, 115 | 0x9 => { 116 | let fd = u32::from_ne_bytes(TryFrom::try_from(&bytes[0x04..0x08]).unwrap()); 117 | let data_ptr = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x08..0x10]).unwrap()); 118 | SyscallContextData::Read { fd, data_ptr } 119 | }, 120 | 0xa => { 121 | let fd = u32::from_ne_bytes(TryFrom::try_from(&bytes[0x04..0x08]).unwrap()); 122 | let data_ptr = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x08..0x10]).unwrap()); 123 | SyscallContextData::Send { fd, data_ptr } 124 | }, 125 | 0xb => { 126 | let fd = u32::from_ne_bytes(TryFrom::try_from(&bytes[0x04..0x08]).unwrap()); 127 | let data_ptr = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x08..0x10]).unwrap()); 128 | SyscallContextData::Recv { fd, data_ptr } 129 | }, 130 | _ => SyscallContextData::Empty, 131 | }; 132 | let ts = u64::from_ne_bytes(TryFrom::try_from(&bytes[0x18..0x20]).unwrap()); 133 | SyscallContext { data, ts } 134 | } 135 | } 136 | 137 | #[derive(Clone, Copy)] 138 | pub enum SyscallContextData { 139 | Empty, 140 | 141 | Bind { 142 | fd: u32, 143 | addr_ptr: u64, 144 | addr_len: u64, 145 | }, 146 | Connect { 147 | fd: u32, 148 | addr_ptr: u64, 149 | addr_len: u64, 150 | }, 151 | Accept { 152 | listen_on_fd: u32, 153 | addr_ptr: u64, 154 | addr_len: u64, 155 | }, 156 | Write { 157 | fd: u32, 158 | data_ptr: u64, 159 | }, 160 | Read { 161 | fd: u32, 162 | data_ptr: u64, 163 | }, 164 | Send { 165 | fd: u32, 166 | data_ptr: u64, 167 | }, 168 | Recv { 169 | fd: u32, 170 | data_ptr: u64, 171 | }, 172 | } 173 | 174 | impl SyscallContextData { 175 | #[inline(always)] 176 | pub fn tag(&self) -> DataTag { 177 | match self { 178 | &SyscallContextData::Empty => DataTag::Close, 179 | &SyscallContextData::Bind { .. } => DataTag::Bind, 180 | &SyscallContextData::Connect { .. } => DataTag::Connect, 181 | &SyscallContextData::Accept { .. } => DataTag::Accept, 182 | &SyscallContextData::Write { .. } => DataTag::Write, 183 | &SyscallContextData::Read { .. } => DataTag::Read, 184 | &SyscallContextData::Send { .. } => DataTag::Send, 185 | &SyscallContextData::Recv { .. } => DataTag::Recv, 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /bpf-ring-buffer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bpf-ring-buffer" 3 | version = "0.1.0" 4 | authors = ["Vladislav Melnik "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | libc = "0.2" 9 | futures = "0.3" 10 | tokio = { version = "1.8", features = ["net"] } 11 | smallvec = "1.6" 12 | log = "0.4" 13 | -------------------------------------------------------------------------------- /config-docker.toml: -------------------------------------------------------------------------------- 1 | http_v2 = 17732 2 | 3 | [[nodes]] 4 | name = "9732" 5 | http_v3 = 17742 6 | db = "/tmp/volume/tezedge_debugger" 7 | p2p = { identity = "/tmp/volume/tezedge/identity.json", port = 9732, store_limit = 1_000_000 } 8 | log = { port = 10000, store_limit = 1_000_000 } 9 | 10 | [[nodes]] 11 | name = "9733" 12 | http_v3 = 17743 13 | db = "/tmp/volume/tezos_debugger" 14 | p2p = { identity = "/tmp/volume/tezos/data/identity.json", port = 9733, store_limit = 1_000_000 } 15 | log = { port = 10001, store_limit = 1_000_000 } 16 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # legacy v2 http server (optional) 2 | http_v2 = 17732 3 | 4 | [[nodes]] 5 | # name of the node, used in http requests as parameter `node_name` 6 | # for example: `http://localhost:17732/v2/p2p?node_name=tezedge&limit=1000` 7 | name = "initiator" 8 | # new v3 http server (optional) 9 | http_v3 = 17742 10 | # path to db 11 | db = "./target/debugger_db/i" 12 | # path to search identity at 13 | # debugger expect the node will listen its p2p incoming connections on this port 14 | # the process which bind a socket on this port first, will be considered as the node 15 | # this is an inner port inside docker's virtual network of the node's container 16 | p2p = { identity = "tezedge-recorder/identity_i.json", port = 29732 } 17 | # debugger will run syslog server on this port 18 | # this is an inner port inside docker's virtual network of the debugger's container 19 | log = { port = 10000 } 20 | 21 | [[nodes]] 22 | name = "responder" 23 | http_v3 = 17743 24 | db = "./target/debugger_db/r" 25 | p2p = { identity = "tezedge-recorder/identity_r.json", port = 29733 } 26 | 27 | [[nodes]] 28 | name = "tezedge" 29 | http_v3 = 17744 30 | db = "./target/debugger_db/tezedge" 31 | p2p = { identity = "/tmp/volume/tezedge/identity.json", port = 29734 } 32 | 33 | [[nodes]] 34 | name = "local_node" 35 | http_v3 = 17745 36 | db = "/volume/debugger_db/tezedge" 37 | p2p = { identity = "../tezedge/light_node/etc/tezedge/identity.json", port = 29735 } 38 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | memprof: 6 | image: tezedge/tezedge-memprof:${TAG:-v1.6.9} 7 | build: 8 | context: . 9 | dockerfile: Dockerfile.memprof 10 | privileged: true 11 | init: true 12 | environment: 13 | - TEZEDGE_NODE_NAME=tezedge-debugger_tezedge-node_1 14 | volumes: 15 | - "/sys/kernel/debug:/sys/kernel/debug:rw" 16 | - "/proc:/proc:rw" 17 | - "/var/run/docker.sock:/var/run/docker.sock:rw" 18 | ports: 19 | - "17832:17832" 20 | 21 | debugger: 22 | image: tezedge/tezedge-debugger:${TAG:-v1.6.9} 23 | build: 24 | context: . 25 | dockerfile: Dockerfile 26 | privileged: true 27 | init: true 28 | environment: 29 | - RUST_BACKTRACE=1 30 | volumes: 31 | - "tezedge-shared-data:/tmp/volume/tezedge:ro" 32 | - "ocaml-shared-data:/tmp/volume/tezos:ro" 33 | # /var/lib/docker/volumes/remote-workspace/_data/tezedge-debugger 34 | - "/var/lib/docker/volumes/remote-workspace/_data/tezedge-debugger/config-docker.toml:/etc/config.toml:ro" 35 | - "/sys/kernel/debug:/sys/kernel/debug:rw" 36 | ports: 37 | - "17732:17732" # debugger RPC port compatibility server 38 | - "17742:17742" # debugger RPC port for tezos node 39 | - "17743:17743" # debugger RPC port for tezos node 40 | - "10000:10000/udp" # debugger syslog port for tezedge node 41 | - "11001:10001/udp" # debugger syslog port for tezos node 42 | 43 | tezedge-node: 44 | image: tezedge/tezedge:${TAG:-v1.6.9}-frame-pointers-enabled 45 | command: ["--network", "mainnet", "--log", "terminal", "file", "--log-file", "/tmp/tezedge/tezedge.log", "--tezos-context-storage", "irmin", "--peer-thresh-low", "30", "--peer-thresh-high", "45"] 46 | depends_on: 47 | - "memprof" 48 | init: true 49 | logging: 50 | # Produce syslogs instead of terminal logs 51 | driver: "syslog" 52 | options: 53 | # Send the logs to syslog (UDP only) server (running on debugger) 54 | syslog-address: "udp://0.0.0.0:10000" # Port must match debugger syslog port in 'ports' section 55 | # Always in same RFC 5424 format (with microseconds precision) 56 | syslog-format: "rfc5424micro" 57 | volumes: 58 | - "tezedge-shared-data:/tmp/tezedge" 59 | ports: 60 | - "4927:4927" # node WS port (required only for tezedge) 61 | - "9732:9732" # node P2P port 62 | - "18732:18732" # node RPC port 63 | - "3030:3030" # sandbox launcher port 64 | 65 | ocaml-node: 66 | image: tezos/tezos:v9-release 67 | entrypoint: sh -c "sleep 5 && /usr/local/bin/entrypoint.sh tezos-node --cors-header='content-type' --cors-origin='*' --rpc-addr=[::]:18733 --net-addr=[::]:9733 --history-mode archive --network mainnet" 68 | logging: 69 | # Produce syslogs instead of terminal logs 70 | driver: "syslog" 71 | options: 72 | # Send the logs to syslog (UDP only) server (running on debugger) 73 | syslog-address: "udp://0.0.0.0:11001" # Port must match debugger syslog port in 'ports' section 74 | # Always in same RFC 5424 format (with microseconds precision) 75 | syslog-format: "rfc5424micro" 76 | volumes: 77 | - "ocaml-shared-data:/var/run/tezos/node" 78 | ports: 79 | # should be equal inside docker and outside, because the node tells this port in its connection message, 80 | # that is how peers can connect to it later 81 | - "9733:9733" 82 | - "18733:18733" # node RPC port 83 | 84 | explorer: 85 | image: tezedge/tezedge-explorer:${TAG:-v1.6.9} 86 | environment: 87 | # need a better way to provide such information 88 | - API=[{"id":"rust","name":"rust.localhost","http":"http://localhost:18732","p2p_port":9732,"features":[{"name":"ws","url":"ws://localhost:4927"},{"name":"debugger","url":"http://localhost:17732"},{"name":"resources/memory","memoryProfilerUrl":"http://localhost:17832"},{"name":"commit","id":""},{"name":"monitoring"},{"name":"resources/system","monitoringUrl":"http://localhost:38732/resources/tezedge"},{"name":"resources/storage"},{"name":"mempool"},{"name":"storage"},{"name":"network"},{"name":"logs"}]},{"id":"ocaml","name":"ocaml.localhost","http":"http://localhost:18733","p2p_port":9733,"features":[{"name":"debugger","url":"http://localhost:17732"},{"name":"monitoring"},{"name":"resources/system","monitoringUrl":"http://localhost:38732/resources/ocaml"},{"name":"resources/storage"},{"name":"mempool"},{"name":"network"},{"name":"logs"}]}] 89 | ports: 90 | - "80:80" 91 | 92 | volumes: 93 | tezedge-shared-data: 94 | external: false 95 | ocaml-shared-data: 96 | external: false 97 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:21.04 as build-env 2 | 3 | RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y tzdata && \ 4 | ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime && \ 5 | dpkg-reconfigure --frontend noninteractive tzdata 6 | RUN apt-get install -y wget build-essential m4 flex gawk bison python python3 7 | 8 | ARG GLIBC_VERSION=2.33 9 | ARG CFLAGS=-O2\ -fno-omit-frame-pointer 10 | 11 | RUN wget -q https://ftpmirror.gnu.org/glibc/glibc-${GLIBC_VERSION}.tar.gz && \ 12 | tar xzf glibc-${GLIBC_VERSION}.tar.gz 13 | RUN mkdir /glibc-build && cd /glibc-build && \ 14 | CFLAGS="${CFLAGS}" ../glibc-${GLIBC_VERSION}/configure --prefix=/usr/local/lib/glibc-${GLIBC_VERSION} && \ 15 | make -j$(nproc) && make install 16 | 17 | RUN wget -q https://ftpmirror.gnu.org/gcc/gcc-10.3.0/gcc-10.3.0.tar.xz && \ 18 | tar xf gcc-10.3.0.tar.xz && cd gcc-10.3.0 && contrib/download_prerequisites 19 | RUN mkdir /gcc-build && cd /gcc-build && \ 20 | CFLAGS="${CFLAGS}" ../gcc-10.3.0/configure -v --build=x86_64-linux-gnu --host=x86_64-linux-gnu \ 21 | --target=x86_64-linux-gnu --prefix=/usr/local/gcc-10.3.0 --enable-checking=release \ 22 | --enable-languages=c,c++ --disable-multilib --program-suffix=-10.3 && \ 23 | make -j$(nproc) && make install 24 | 25 | # there must be a way to build it along with gcc 26 | RUN wget -q https://ftpmirror.gnu.org/gnu/gmp/gmp-6.1.0.tar.bz2 && \ 27 | tar xf gmp-6.1.0.tar.bz2 28 | RUN cd /gmp-6.1.0 && \ 29 | CFLAGS="${CFLAGS}" ./configure --prefix=/usr/local/lib/gmp-6.1.0 && \ 30 | make -j$(nproc) && make install && make check 31 | 32 | RUN wget -q https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable.tar.gz && \ 33 | tar xf libsodium-1.0.18-stable.tar.gz 34 | RUN cd /libsodium-stable && \ 35 | CFLAGS="${CFLAGS}" ./configure --prefix=/usr/local/lib/libsodium-1.0.18-stable && \ 36 | make -j$(nproc) && make install 37 | 38 | FROM scratch 39 | 40 | ARG GLIBC_VERSION=2.33 41 | COPY --from=build-env /usr/local/lib/glibc-${GLIBC_VERSION}/lib/ld-${GLIBC_VERSION}.so /lib64/ld-linux-x86-64.so.2 42 | COPY --from=build-env /usr/local/lib/glibc-${GLIBC_VERSION}/lib/libc-${GLIBC_VERSION}.so /lib/x86_64-linux-gnu/libc.so.6 43 | COPY --from=build-env /usr/local/lib/glibc-${GLIBC_VERSION}/lib/libdl-${GLIBC_VERSION}.so /lib/x86_64-linux-gnu/libdl.so.2 44 | COPY --from=build-env /usr/local/lib/glibc-${GLIBC_VERSION}/lib/libm-${GLIBC_VERSION}.so /lib/x86_64-linux-gnu/libm.so.6 45 | COPY --from=build-env /usr/local/lib/glibc-${GLIBC_VERSION}/lib/librt-${GLIBC_VERSION}.so /lib/x86_64-linux-gnu/librt.so.1 46 | COPY --from=build-env /usr/local/lib/glibc-${GLIBC_VERSION}/lib/libpthread-${GLIBC_VERSION}.so /lib/x86_64-linux-gnu/libpthread.so.0 47 | COPY --from=build-env /usr/local/gcc-10.3.0/lib64/libgcc_s.so.1 /lib/x86_64-linux-gnu/libgcc_s.so.1 48 | COPY --from=build-env /usr/local/gcc-10.3.0/lib64/libstdc++.so.6 /lib/x86_64-linux-gnu/libstdc++.so.6 49 | COPY --from=build-env /usr/local/lib/gmp-6.1.0/lib/libgmp.so.10 /lib/x86_64-linux-gnu/libgmp.so.10 50 | COPY --from=build-env /usr/local/lib/libsodium-1.0.18-stable/lib/libsodium.so.23 /lib/x86_64-linux-gnu/libsodium.so.23 51 | ENV LD_LIBRARY_PATH=/lib:/lib/x86_64-linux-gnu:/usr/lib:/usr/lib/x86_64-linux-gnu 52 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | Bpf builder: 2 | 3 | ``` 4 | docker login docker.io 5 | 6 | docker build -t tezedge/tezedge-bpf-builder:latest -f bpf.dockerfile . 7 | docker push tezedge/tezedge-bpf-builder:latest 8 | ``` 9 | 10 | TezEdge libs: 11 | 12 | ``` 13 | docker login docker.io 14 | 15 | docker build -t tezedge/tezedge-libs:latest-profile . 16 | docker push tezedge/tezedge-libs:latest-profile 17 | 18 | docker build -t tezedge/tezedge-libs:latest --build-arg CFLAGS=-O2 . 19 | docker push tezedge/tezedge-libs:latest 20 | ``` 21 | -------------------------------------------------------------------------------- /docker/bpf.dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | WORKDIR /home/appuser/ 4 | RUN apt-get update && \ 5 | DEBIAN_FRONTEND='noninteractive' apt-get install -y \ 6 | git wget curl gcc libsodium-dev make zlib1g-dev \ 7 | lsb-release software-properties-common \ 8 | libarchive-tools flex bison libssl-dev bc libelf-dev 9 | 10 | # rust 11 | ENV RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo 12 | RUN set -eux && \ 13 | wget "https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init" && \ 14 | chmod +x rustup-init && \ 15 | ./rustup-init -y --no-modify-path --default-toolchain nightly-2020-12-31 && \ 16 | rm rustup-init && \ 17 | chmod -R a+w $RUSTUP_HOME $CARGO_HOME 18 | ENV PATH=/usr/local/cargo/bin:$PATH 19 | 20 | # llvm 11 21 | RUN wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 11 && rm llvm.sh 22 | ENV LLVM_SYS_110_PREFIX=/usr/lib/llvm-11 23 | 24 | RUN wget -cq https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.8.18.tar.xz && \ 25 | tar xf linux-5.8.18.tar.xz && cd linux-5.8.18 && make defconfig && make modules_prepare 26 | ENV KERNEL_SOURCE=/home/appuser/linux-5.8.18 KERNEL_VERSION=5.8.18 27 | -------------------------------------------------------------------------------- /extractor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "extractor" 3 | version = "0.1.0" 4 | authors = ["Vladislav Melnik "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | reqwest = "0.11" 11 | tokio = { version = "1.8", features = ["full"] } 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = "1.0" 14 | hex = "0.4" 15 | -------------------------------------------------------------------------------- /extractor/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #[tokio::main] 5 | async fn main() { 6 | let types = "connection_message,metadata,ack_message,disconnect,advertise,swap_request,\ 7 | swap_ack,bootstrap,get_current_branch,current_branch,deactivate,\ 8 | get_current_head,current_head,get_block_headers,block_header,get_operations,operation\ 9 | get_protocols,protocol,get_operation_hashes_for_blocks,operation_hashes_for_block,\ 10 | get_operations_for_blocks,operations_for_blocks"; 11 | 12 | #[derive(serde::Serialize)] 13 | struct Example<'a> { 14 | ty: &'a str, 15 | hex: String, 16 | } 17 | 18 | let mut examples = Vec::::new(); 19 | for ty in types.split(',') { 20 | let url = format!("http://debug.dev.tezedge.com:17742/v3/messages?types={}&limit=1", ty); 21 | let list = reqwest::get(url) 22 | .await.unwrap() 23 | .text() 24 | .await.unwrap(); 25 | let list = serde_json::from_str::(&list).unwrap(); 26 | 27 | if let Some(item) = list.as_array().and_then(|x| x.first()) { 28 | let id = item.as_object().unwrap().get("id").unwrap().as_u64().unwrap(); 29 | let url = format!("http://debug.dev.tezedge.com:17742/v3/message/{}", id); 30 | let item = reqwest::get(url) 31 | .await.unwrap() 32 | .text() 33 | .await.unwrap(); 34 | let item = serde_json::from_str::(&item).unwrap(); 35 | let o = item.as_object().unwrap().get("decrypted_bytes").unwrap().as_array().unwrap(); 36 | let data = o.iter() 37 | .map(|i| { 38 | let s = i.as_str().unwrap(); 39 | hex::decode(s).unwrap().into_iter() 40 | }) 41 | .flatten() 42 | .collect::>(); 43 | examples.push(Example { 44 | ty, 45 | hex: hex::encode(&data), 46 | }); 47 | } else { 48 | eprintln!("warning: no example for type: {}", ty); 49 | } 50 | } 51 | 52 | let json = serde_json::to_string(&examples).unwrap(); 53 | println!("{}", json); 54 | } 55 | -------------------------------------------------------------------------------- /pseudonode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pseudonode" 3 | version = "0.1.0" 4 | authors = ["Vladislav Melnik "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | crypto = { tag = "v1.6.5", git = "https://github.com/tezedge/tezedge" } 9 | tezos_messages = { tag = "v1.6.5", git = "https://github.com/tezedge/tezedge" } 10 | tezos_identity = { tag = "v1.6.5", git = "https://github.com/tezedge/tezedge" } 11 | -------------------------------------------------------------------------------- /pseudonode/src/buffer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{convert::{TryInto, TryFrom}, io::{self, Read, Write}}; 5 | 6 | use tezos_messages::p2p::binary_message::{BinaryRead, BinaryMessage, BinaryChunk}; 7 | use crypto::{crypto_box::PrecomputedKey, nonce::Nonce}; 8 | 9 | pub trait Message 10 | where 11 | Self: Sized, 12 | { 13 | fn read_msg( 14 | stream: &mut impl Read, 15 | buffer: &mut ChunkBuffer, 16 | key: &PrecomputedKey, 17 | nonce: Nonce, 18 | // whether peer message, or meta/ack 19 | peer_message: bool, 20 | ) -> io::Result<(Nonce, Self)>; 21 | 22 | fn write_msg( 23 | &self, 24 | stream: &mut impl Write, 25 | key: &PrecomputedKey, 26 | nonce: Nonce, 27 | ) -> Nonce; 28 | } 29 | 30 | impl Message for M 31 | where 32 | M: BinaryMessage, 33 | { 34 | fn write_msg( 35 | &self, 36 | stream: &mut impl Write, 37 | key: &PrecomputedKey, 38 | nonce: Nonce, 39 | ) -> Nonce { 40 | let bytes = self.as_bytes().unwrap(); 41 | let mut nonce = nonce; 42 | for bytes in bytes.as_slice().chunks(0xffe0) { 43 | let temp = key.encrypt(&bytes, &nonce).unwrap(); 44 | let chunk = BinaryChunk::from_content(&temp).unwrap().raw().clone(); 45 | stream.write_all(&chunk).unwrap(); 46 | nonce = nonce.increment(); 47 | } 48 | 49 | nonce 50 | } 51 | 52 | fn read_msg( 53 | stream: &mut impl Read, 54 | buffer: &mut ChunkBuffer, 55 | key: &PrecomputedKey, 56 | nonce: Nonce, 57 | peer_message: bool, 58 | ) -> io::Result<(Nonce, Self)> 59 | where 60 | M: BinaryRead, 61 | { 62 | const HEADER_LENGTH: usize = 4; 63 | 64 | let mut nonce = nonce; 65 | let mut bytes = Vec::new(); 66 | let mut length = 0; 67 | loop { 68 | let chunk = buffer.read_chunk(stream)?; 69 | bytes.extend_from_slice(&key.decrypt(chunk.content(), &nonce).unwrap()); 70 | if length == 0 && peer_message { 71 | let b = TryFrom::try_from(&bytes[..HEADER_LENGTH]).unwrap(); 72 | length = u32::from_be_bytes(b) as usize + HEADER_LENGTH; 73 | } 74 | nonce = nonce.increment(); 75 | 76 | if bytes.len() == length || !peer_message { 77 | break Ok((nonce, M::from_bytes(bytes).unwrap())); 78 | } 79 | } 80 | } 81 | } 82 | 83 | pub struct ChunkBuffer { 84 | len: usize, 85 | data: [u8; 0x10000], 86 | } 87 | 88 | impl Default for ChunkBuffer { 89 | fn default() -> Self { 90 | ChunkBuffer { 91 | len: 0, 92 | data: [0; 0x10000], 93 | } 94 | } 95 | } 96 | 97 | impl ChunkBuffer { 98 | pub fn read_chunk(&mut self, stream: &mut impl Read) -> io::Result { 99 | const HEADER_LENGTH: usize = 2; 100 | loop { 101 | if self.len >= HEADER_LENGTH { 102 | let chunk_len = (self.data[0] as usize) * 256 + (self.data[1] as usize); 103 | let raw_len = chunk_len + HEADER_LENGTH; 104 | if self.len >= raw_len { 105 | let chunk = self.data[..(raw_len)].to_vec(); 106 | for i in raw_len..self.len { 107 | self.data[(i - raw_len)] = self.data[i]; 108 | } 109 | self.len -= raw_len; 110 | return Ok(chunk.try_into().unwrap()); 111 | } 112 | } 113 | let read = stream.read(&mut self.data[self.len..])?; 114 | self.len += read; 115 | } 116 | } 117 | 118 | pub fn is_empty(&self) -> bool { 119 | self.len == 0 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pseudonode/src/handshake.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::net::TcpStream; 5 | 6 | use crypto::{ 7 | nonce::{NoncePair, Nonce, generate_nonces}, 8 | crypto_box::{CryptoKey, PrecomputedKey, PublicKey, SecretKey}, 9 | }; 10 | use tezos_messages::p2p::{ 11 | binary_message::{BinaryChunk, BinaryRead, BinaryWrite}, 12 | encoding::{ 13 | connection::ConnectionMessage, 14 | version::NetworkVersion, 15 | }, 16 | }; 17 | use super::buffer::ChunkBuffer; 18 | 19 | fn identity(json: &str, port: u16, version: NetworkVersion) -> (ConnectionMessage, SecretKey) { 20 | use tezos_identity::Identity; 21 | 22 | let identity = Identity::from_json(&json).unwrap(); 23 | let connection_message = ConnectionMessage::try_new( 24 | port, 25 | &identity.public_key, 26 | &identity.proof_of_work_stamp, 27 | Nonce::random(), 28 | version, 29 | ).unwrap(); 30 | 31 | (connection_message, identity.secret_key) 32 | } 33 | 34 | pub fn initiator(this: u16, stream: &mut TcpStream, identity_json: &str, version: NetworkVersion) -> (PrecomputedKey, NoncePair) { 35 | use std::io::Write; 36 | 37 | let (connection_message, sk) = identity(identity_json, this, version); 38 | 39 | let temp = connection_message.as_bytes().unwrap(); 40 | let initiator_chunk = BinaryChunk::from_content(&temp).unwrap(); 41 | stream.write_all(initiator_chunk.raw()).unwrap(); 42 | let responder_chunk = ChunkBuffer::default().read_chunk(stream).unwrap(); 43 | 44 | let connection_message = ConnectionMessage::from_bytes(responder_chunk.content()).unwrap(); 45 | let pk = PublicKey::from_bytes(connection_message.public_key()).unwrap(); 46 | 47 | let key = PrecomputedKey::precompute(&pk, &sk); 48 | let pair = generate_nonces(initiator_chunk.raw(), &responder_chunk.raw(), false).unwrap(); 49 | (key, pair) 50 | } 51 | 52 | pub fn responder(this: u16, stream: &mut TcpStream, identity_json: &str, version: NetworkVersion) -> (PrecomputedKey, NoncePair) { 53 | use std::io::Write; 54 | 55 | let initiator_chunk = ChunkBuffer::default().read_chunk(stream).unwrap(); 56 | let connection_message = ConnectionMessage::from_bytes(initiator_chunk.content()).unwrap(); 57 | let pk = PublicKey::from_bytes(connection_message.public_key()).unwrap(); 58 | let (connection_message, sk) = identity(identity_json, this, version); 59 | let temp = connection_message.as_bytes().unwrap(); 60 | let responder_chunk = BinaryChunk::from_content(&temp).unwrap(); 61 | stream.write_all(responder_chunk.raw()).unwrap(); 62 | 63 | let key = PrecomputedKey::precompute(&pk, &sk); 64 | let pair = generate_nonces(responder_chunk.raw(), initiator_chunk.raw(), true).unwrap(); 65 | (key, pair) 66 | } 67 | -------------------------------------------------------------------------------- /pseudonode/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #![forbid(unsafe_code)] 5 | 6 | mod buffer; 7 | pub mod handshake; 8 | 9 | pub use self::buffer::{ChunkBuffer, Message}; 10 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = false 2 | reorder_modules = false 3 | trailing_comma = "Vertical" 4 | match_block_trailing_comma = true 5 | -------------------------------------------------------------------------------- /san.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $1 == "none" ]; then 4 | export CARGO_TARGET_DIR="target/none" RUSTFLAGS="" 5 | cargo +nightly-2021-03-23 build -p tezedge-recorder 6 | else 7 | export CARGO_TARGET_DIR="target/sanitizer-$1" RUSTFLAGS="-Z sanitizer=$1" 8 | cargo +nightly-2021-03-23 build -Zbuild-std --target x86_64-unknown-linux-gnu -p tezedge-recorder 9 | fi 10 | -------------------------------------------------------------------------------- /size_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | OUTPUT="./sizes" 4 | 5 | function pid_size() { 6 | PID="$1" 7 | pmap "$PID"|grep total|grep -o "[0-9]*" 8 | } 9 | 10 | function get_pids() { 11 | NAME="$1" 12 | ps | grep "$NAME" | 13 | } 14 | 15 | function store_process_size() { 16 | PROCESS="$1" 17 | PIDS="$(get_pids "$PROCESS")" 18 | for PID in $PIDS; 19 | do 20 | SIZE=$(pid_size "$PID") 21 | LINE="$(date -u --rfc-3339=seconds): $PROCESS ($PID) - ${SIZE}KB" 22 | echo "$LINE" 23 | echo "$LINE" >> "$OUTPUT" 24 | done 25 | } 26 | 27 | while : 28 | do 29 | store_process_size light-node 30 | store_process_size protocol-runner 31 | sleep 10s 32 | done 33 | -------------------------------------------------------------------------------- /tezedge-memprof/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tezedge-memprof" 3 | version = "0.1.0" 4 | authors = ["Vladislav Melnik "] 5 | edition = "2018" 6 | 7 | [dev-dependencies] 8 | reqwest = "0.11" 9 | rand = "0.8" 10 | 11 | [dependencies] 12 | log = "0.4" 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "1.0" 15 | bincode = "1.3" 16 | elf64 = "0.1" 17 | thiserror = { version = "1.0" } 18 | rustc-demangle = { version = "0.1" } 19 | cpp_demangle = { version = "0.3" } 20 | 21 | ctrlc = { version = "3.1" } 22 | tracing-subscriber = "0.2" 23 | tracing = "0.1" 24 | 25 | warp = "0.3" 26 | tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } 27 | 28 | bpf-memprof-common = { path = "../bpf-memprof-common", features = ["client"] } 29 | -------------------------------------------------------------------------------- /tezedge-memprof/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "Tezedge Memory Profiler", 5 | "description": "Monitor memory usage of each machine code function of light-node binary", 6 | "contact": { 7 | "name": "API Support", 8 | "url": "https://github.com/tezedge/tezedge-debugger", 9 | "email": "vladislav.melnik@viablesystems.io" 10 | }, 11 | "license": { 12 | "name": "MIT License", 13 | "url": "https://github.com/tezedge/tezedge-debugger/blob/master/LICENSE" 14 | }, 15 | "version": "1.6.7" 16 | }, 17 | "servers": [ 18 | { 19 | "url": "http://develop.dev.tezedge.com:{port}", 20 | "description": "Memory profiler", 21 | "variables": { 22 | "port": { 23 | "enum": [ 24 | "17832" 25 | ], 26 | "default": "17832" 27 | } 28 | } 29 | }, 30 | { 31 | "url": "http://master.dev.tezedge.com:{port}", 32 | "description": "Memory profiler", 33 | "variables": { 34 | "port": { 35 | "enum": [ 36 | "17832" 37 | ], 38 | "default": "17832" 39 | } 40 | } 41 | } 42 | ], 43 | "paths": { 44 | "/v1/tree": { 45 | "get": { 46 | "description": "The tree of light-node functions in machine code", 47 | "parameters": [ 48 | { 49 | "name": "threshold", 50 | "in": "query", 51 | "description": "Threshold memory usage to include the tree branch into response", 52 | "required": false, 53 | "schema": { 54 | "type": "integer", 55 | "minimum": 0 56 | } 57 | }, 58 | { 59 | "name": "reverse", 60 | "in": "query", 61 | "description": "Reverse the tree", 62 | "required": false, 63 | "schema": { 64 | "type": "boolean" 65 | } 66 | } 67 | ], 68 | "responses": { 69 | "200": { 70 | "description": "The memory usage in each light-node function in machine code", 71 | "content": { 72 | "application/json": { 73 | "schema": { 74 | "$ref": "#/components/schemas/tree" 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | }, 83 | "components": { 84 | "schemas": { 85 | "tree": { 86 | "type": "object", 87 | "properties": { 88 | "name": { 89 | "oneOf": [ 90 | { 91 | "type": "object", 92 | "properties": { 93 | "offset": { 94 | "type": "string" 95 | }, 96 | "executable": { 97 | "type": "string" 98 | }, 99 | "functionName": { 100 | "type": "string" 101 | }, 102 | "functionCategory": { 103 | "type": "string" 104 | } 105 | } 106 | }, 107 | { 108 | "type": "string" 109 | } 110 | ] 111 | }, 112 | "value": { 113 | "type": "integer" 114 | }, 115 | "cacheValue": { 116 | "type": "integer" 117 | }, 118 | "frames": { 119 | "type": "array", 120 | "items": { 121 | "$ref": "#/components/schemas/tree" 122 | } 123 | } 124 | }, 125 | "required": ["value", "cacheValue"] 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /tezedge-memprof/src/collector/consumer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::ops::Deref; 5 | use std::sync::{Arc, Mutex, atomic::{Ordering, AtomicU32}}; 6 | use bpf_memprof_common::{EventKind, Event}; 7 | use super::{Reporter, StackResolver, FrameReport, aggregator::Aggregator}; 8 | 9 | impl Reporter for Aggregator { 10 | fn short_report(&self) -> (u64, u64) { 11 | let (mut value, mut cache_value) = (0, 0); 12 | for (v, c, _) in self.report() { 13 | value += v; 14 | cache_value += c; 15 | } 16 | 17 | (value, cache_value) 18 | } 19 | 20 | fn tree_report(&self, resolver: R, threshold: u64, reverse: bool) -> FrameReport 21 | where 22 | R: Deref, 23 | { 24 | let mut report = FrameReport::new(resolver); 25 | for (value, cache_value, stack) in self.report() { 26 | if reverse { 27 | report.inner.insert(stack.iter().rev(), value, cache_value); 28 | } else { 29 | report.inner.insert(stack.iter(), value, cache_value); 30 | } 31 | } 32 | report.inner.strip(threshold); 33 | 34 | report 35 | 36 | } 37 | } 38 | 39 | #[derive(Default)] 40 | pub struct Consumer { 41 | has_pid: bool, 42 | pid: Arc, 43 | aggregator: Arc>, 44 | last: Option, 45 | } 46 | 47 | impl Consumer { 48 | pub fn reporter(&self) -> Arc> { 49 | self.aggregator.clone() 50 | } 51 | 52 | pub fn pid(&self) -> Arc { 53 | self.pid.clone() 54 | } 55 | } 56 | 57 | impl Consumer { 58 | pub fn arrive(&mut self, data: &[u8]) { 59 | let event = match Event::from_slice(data) { 60 | Ok(v) => v, 61 | Err(error) => { 62 | log::error!("failed to read slice from kernel: {}", error); 63 | return; 64 | } 65 | }; 66 | 67 | if let Some(last) = &self.last { 68 | if last.eq(&event.event) { 69 | log::trace!("repeat"); 70 | return; 71 | } 72 | } 73 | match &event.event { 74 | &EventKind::PageAlloc(ref v) if v.pfn.0 != 0 => { 75 | self.has_pid = true; 76 | self.pid.store(event.pid, Ordering::SeqCst); 77 | self.aggregator.lock().unwrap().track_alloc(v.pfn.0 as u32, v.order as u8, &event.stack); 78 | } 79 | &EventKind::PageFree(ref v) if v.pfn.0 != 0 && self.has_pid => { 80 | self.aggregator.lock().unwrap().track_free(v.pfn.0 as u32); 81 | }, 82 | &EventKind::AddToPageCache(ref v) if v.pfn.0 != 0 && self.has_pid => { 83 | self.aggregator.lock().unwrap().mark_cache(v.pfn.0 as u32, true); 84 | }, 85 | &EventKind::RemoveFromPageCache(ref v) if v.pfn.0 != 0 && self.has_pid => { 86 | self.aggregator.lock().unwrap().mark_cache(v.pfn.0 as u32, false); 87 | }, 88 | &EventKind::RssStat(ref v) if v.member == 1 && self.has_pid => { 89 | self.aggregator.lock().unwrap().track_rss_anon(v.size as _); 90 | } 91 | _ => (), 92 | } 93 | self.last = Some(event.event); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tezedge-memprof/src/collector/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use super::{Reporter, StackResolver, FrameReport}; 5 | 6 | mod aggregator; 7 | pub use self::aggregator::{Aggregator, RawEvent}; 8 | 9 | mod consumer; 10 | pub use self::consumer::Consumer; 11 | -------------------------------------------------------------------------------- /tezedge-memprof/src/history/abstract_tracker.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::ops::Deref; 5 | use bpf_memprof_common::{Hex32, Stack}; 6 | use super::{page::Page, report::FrameReport, stack::StackResolver}; 7 | 8 | pub trait Tracker { 9 | fn track_alloc(&mut self, page: Page, stack: &Stack, flags: Hex32, pid: u32); 10 | fn track_free(&mut self, page: Page, pid: u32); 11 | fn mark_page_cache(&mut self, page: Page, b: bool); 12 | } 13 | 14 | pub trait Reporter { 15 | fn short_report(&self) -> (u64, u64); 16 | 17 | fn tree_report( 18 | &self, 19 | resolver: R, 20 | threshold: u64, 21 | reverse: bool, 22 | ) -> FrameReport 23 | where 24 | R: Deref; 25 | } 26 | -------------------------------------------------------------------------------- /tezedge-memprof/src/history/allocation.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{collections::HashMap, ops::Deref}; 5 | use serde::Serialize; 6 | use bpf_memprof_common::{Hex32, Stack}; 7 | use super::{ 8 | page::Page, 9 | report::FrameReport, 10 | stack::StackResolver, 11 | history::StackShort, 12 | abstract_tracker::{Tracker, Reporter}, 13 | }; 14 | 15 | #[derive(Serialize, Hash, PartialEq, Eq, Clone)] 16 | pub struct StackHash(u32); 17 | 18 | #[derive(Serialize)] 19 | pub struct Usage { 20 | node: u32, 21 | cache: u32, 22 | stack: StackShort, 23 | } 24 | 25 | impl Usage { 26 | pub fn new(stack: StackShort) -> Self { 27 | Usage { 28 | node: 0, 29 | cache: 0, 30 | stack, 31 | } 32 | } 33 | 34 | pub fn decrease(&mut self, page: &Page) { 35 | if self.node < page.number() { 36 | panic!(); 37 | } 38 | self.node -= page.number(); 39 | } 40 | 41 | pub fn increase(&mut self, page: &Page) { 42 | self.node += page.number(); 43 | } 44 | 45 | pub fn cache(&mut self, page: &Page, b: bool) { 46 | if b { 47 | self.cache += page.number(); 48 | } else { 49 | if self.cache < page.number() { 50 | self.cache = 0; 51 | log::warn!("page {} was not marked as cache by mistake", page); 52 | } else { 53 | self.cache -= page.number(); 54 | } 55 | } 56 | } 57 | } 58 | 59 | #[derive(Serialize)] 60 | pub struct PageState { 61 | stack_hash: StackHash, 62 | for_cache: bool, 63 | order: u8, 64 | } 65 | 66 | impl PageState { 67 | pub fn new(stack_hash: StackHash, for_cache: bool, order: u8) -> Self { 68 | PageState { 69 | stack_hash, 70 | for_cache, 71 | order, 72 | } 73 | } 74 | } 75 | 76 | #[derive(Default, Serialize)] 77 | pub struct Group { 78 | last_stack: HashMap, 79 | group: HashMap, 80 | collision_detector: HashMap, 81 | counter: u32, 82 | } 83 | 84 | impl Group { 85 | pub fn insert(&mut self, page: Page, stack: StackShort) { 86 | let &mut Group { ref mut collision_detector, ref mut counter, .. } = self; 87 | let stack_hash = collision_detector 88 | .entry(stack.clone()) 89 | .or_insert_with(|| { 90 | *counter += 1; 91 | StackHash(*counter) 92 | }) as &_; 93 | 94 | let mut for_cache = false; 95 | // if `self.last_stack` contains state for some page 96 | // then `self.group` contains `usage` for the stack 97 | if let Some(state) = self.last_stack.get(&page) { 98 | log::trace!("double alloc {}", page); 99 | if state.stack_hash.eq(&stack_hash) { 100 | // double alloc in the same stack, do nothing 101 | return; 102 | } else { 103 | // double alloc in different stack, free in this stack and proceed 104 | for_cache = state.for_cache; 105 | let usage = self.group.get_mut(&state.stack_hash).unwrap(); 106 | usage.decrease(&page); 107 | self.last_stack.remove(&page); 108 | } 109 | } 110 | 111 | // ensure `self.group` contains usage, and insert the state into `self.last_stack` 112 | if let Some(usage) = self.group.get_mut(&stack_hash) { 113 | usage.increase(&page); 114 | } else { 115 | let mut usage = Usage::new(stack); 116 | usage.increase(&page); 117 | self.group.insert(stack_hash.clone(), usage); 118 | } 119 | self.last_stack.insert(page, PageState::new(stack_hash.clone(), for_cache, page.order())); 120 | } 121 | 122 | pub fn remove(&mut self, page: &Page) { 123 | // if `self.last_stack` contains state for some page 124 | // then `self.group` contains `usage` for the stack 125 | if let Some(state) = self.last_stack.remove(page) { 126 | let usage = self.group.get_mut(&state.stack_hash).unwrap(); 127 | if state.for_cache { 128 | usage.cache(page, false); 129 | } 130 | usage.decrease(page); 131 | } else { 132 | log::trace!("double free, or free without alloc {}", page); 133 | } 134 | } 135 | 136 | pub fn mark_cache(&mut self, page: Page, b: bool) { 137 | // if `self.last_stack` contains state for some page 138 | // then `self.group` contains `usage` for the stack 139 | if let Some(state) = self.last_stack.get_mut(&page) { 140 | if state.for_cache != b { 141 | let usage = self.group.get_mut(&state.stack_hash).unwrap(); 142 | let mut page = page; 143 | page.set_order(state.order); 144 | usage.cache(&page, b); 145 | state.for_cache = b; 146 | if !b { 147 | usage.decrease(&page); 148 | self.last_stack.remove(&page); 149 | } 150 | } else { 151 | if b { 152 | log::warn!("seems the kernel marks as a cache the page already marked as a cache"); 153 | } else { 154 | log::warn!("seems the kernel marks as not a cache the page that was not marked as a cache"); 155 | } 156 | } 157 | } 158 | } 159 | 160 | pub fn iter(&self) -> impl Iterator { 161 | self.group.iter().map(|(_, usage)| usage) 162 | } 163 | } 164 | 165 | #[derive(Default, Serialize)] 166 | pub struct AllocationState { 167 | pid: Option, 168 | group: Group, 169 | } 170 | 171 | impl Tracker for AllocationState { 172 | fn track_alloc(&mut self, page: Page, stack: &Stack, _flags: Hex32, pid: u32) { 173 | self.pid = Some(pid); 174 | let stack = StackShort::new(stack); 175 | self.group.insert(page, stack); 176 | } 177 | 178 | fn track_free(&mut self, page: Page, pid: u32) { 179 | if self.pid != Some(pid) { 180 | return; 181 | } 182 | self.group.remove(&page); 183 | } 184 | 185 | fn mark_page_cache(&mut self, page: Page, b: bool) { 186 | self.group.mark_cache(page, b); 187 | } 188 | } 189 | 190 | impl Reporter for AllocationState { 191 | fn short_report(&self) -> (u64, u64) { 192 | let (mut node, mut cache) = (0, 0); 193 | for usage in self.group.iter() { 194 | node += (usage.node * 4) as u64; 195 | cache += (usage.cache * 4) as u64; 196 | } 197 | 198 | (node, cache) 199 | } 200 | 201 | fn tree_report( 202 | &self, 203 | resolver: R, 204 | threshold: u64, 205 | reverse: bool, 206 | ) -> FrameReport 207 | where 208 | R: Deref, 209 | { 210 | let mut report = FrameReport::new(resolver); 211 | for usage in self.group.iter() { 212 | let value = (usage.node as u64) * 4; 213 | let cache_value = (usage.cache as u64) * 4; 214 | 215 | if reverse { 216 | report.inner.insert(usage.stack.0.iter().rev(), value, cache_value); 217 | } else { 218 | report.inner.insert(usage.stack.0.iter(), value, cache_value); 219 | } 220 | } 221 | report.inner.strip(threshold); 222 | 223 | report 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /tezedge-memprof/src/history/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use serde::Serialize; 5 | use super::page::Page; 6 | 7 | #[derive(Default, Serialize)] 8 | pub struct ErrorReport { 9 | enabled: bool, 10 | double_free: Vec, 11 | without_alloc: Vec, 12 | double_alloc: Vec, 13 | } 14 | 15 | impl ErrorReport { 16 | #[allow(dead_code)] 17 | pub fn enable(&mut self) { 18 | self.enabled = true; 19 | } 20 | 21 | pub fn double_free(&mut self, page: &Page) { 22 | if self.enabled { 23 | self.double_free.push(page.clone()); 24 | } 25 | } 26 | 27 | pub fn without_alloc(&mut self, page: &Page) { 28 | if self.enabled { 29 | self.without_alloc.push(page.clone()); 30 | } 31 | } 32 | 33 | pub fn double_alloc(&mut self, page: &Page) { 34 | if self.enabled { 35 | self.double_alloc.push(page.clone()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tezedge-memprof/src/history/history.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{collections::HashMap, ops::Deref, sync::Arc}; 5 | use serde::{Serialize, ser}; 6 | use bpf_memprof_common::{Hex32, Hex64, Stack}; 7 | use super::{ 8 | page::Page, 9 | error::ErrorReport, 10 | page_history::{PageHistory, AllocError, FreeError}, 11 | report::FrameReport, 12 | stack::StackResolver, 13 | abstract_tracker::{Tracker, Reporter}, 14 | }; 15 | 16 | #[derive(Clone, Hash, PartialEq, Eq)] 17 | pub struct StackShort(pub Arc>); 18 | 19 | impl StackShort { 20 | pub fn new(stack: &Stack) -> Self { 21 | StackShort(Arc::new(stack.ips().to_vec())) 22 | } 23 | } 24 | 25 | impl Serialize for StackShort { 26 | fn serialize(&self, serializer: S) -> Result 27 | where 28 | S: ser::Serializer, 29 | { 30 | let stack = self.0.iter().fold(String::new(), |s, f| s + &format!("{:?}/", f)); 31 | serializer.serialize_str(&stack) 32 | } 33 | } 34 | 35 | #[derive(Default, Serialize)] 36 | pub struct History { 37 | error_report: ErrorReport, 38 | group: HashMap>, 39 | last_stack: HashMap, 40 | } 41 | 42 | impl Tracker for History 43 | where 44 | H: PageHistory + Default, 45 | { 46 | fn track_alloc(&mut self, page: Page, stack: &Stack, flags: Hex32, pid: u32) { 47 | let _ = pid; 48 | let stack = StackShort::new(stack); 49 | 50 | // if we have a last_stack for some page then `self.group` contains entry for this stack 51 | // and the entry contains history for the page, so unwrap here is ok 52 | if let Some(last_stack) = self.last_stack.get(&page) { 53 | if last_stack.eq(&stack) { 54 | let history = self.group.get_mut(last_stack).unwrap().get_mut(&page).unwrap(); 55 | Self::track_alloc_error(&mut self.error_report, history, &page, flags); 56 | } else { 57 | // fix it to track precise history, do not remove it in previous stack 58 | let mut history = self.group.get_mut(last_stack).unwrap().remove(&page).unwrap(); 59 | Self::track_alloc_error(&mut self.error_report, &mut history, &page, flags); 60 | self.group.entry(stack.clone()).or_default().insert(page.clone(), history); 61 | self.last_stack.insert(page, stack); 62 | } 63 | } else { 64 | let group = self.group.entry(stack.clone()).or_default(); 65 | let history = group.entry(page.clone()).or_default(); 66 | Self::track_alloc_error(&mut self.error_report, history, &page, flags); 67 | self.last_stack.insert(page, stack); 68 | } 69 | } 70 | 71 | fn track_free(&mut self, page: Page, pid: u32) { 72 | let _ = pid; // TODO: 73 | if let Some(stack) = self.last_stack.get(&page).cloned() { 74 | let history = self.group.entry(stack.clone()).or_default().entry(page.clone()).or_default(); 75 | Self::track_free_error(&mut self.error_report, history, &page); 76 | 77 | if history.is_empty() { 78 | let group = self.group.get_mut(&stack).unwrap(); 79 | group.remove(&page); 80 | if group.is_empty() { 81 | self.group.remove(&stack); 82 | } 83 | self.last_stack.remove(&page); 84 | } 85 | } else { 86 | // self.error_report.without_alloc(&page); 87 | } 88 | } 89 | 90 | fn mark_page_cache(&mut self, page: Page, b: bool) { 91 | if let Some(stack) = self.last_stack.get(&page) { 92 | self.group.get_mut(stack).unwrap().get_mut(&page).unwrap().mark_page_cache(b); 93 | } 94 | } 95 | } 96 | 97 | impl Reporter for History 98 | where 99 | H: PageHistory, 100 | { 101 | fn short_report(&self) -> (u64, u64) { 102 | let mut value_kib = 0; 103 | let mut cache_value_kib = 0; 104 | for (_, group) in &self.group { 105 | for (page, history) in group { 106 | if history.is_allocated(None) { 107 | value_kib += page.size_kib(); 108 | if history.page_cache() { 109 | cache_value_kib += page.size_kib(); 110 | } 111 | } 112 | } 113 | } 114 | 115 | (value_kib, cache_value_kib) 116 | } 117 | 118 | fn tree_report( 119 | &self, 120 | resolver: R, 121 | threshold: u64, 122 | reverse: bool, 123 | ) -> FrameReport 124 | where 125 | R: Deref, 126 | { 127 | let mut report = FrameReport::new(resolver); 128 | for (stack, group) in &self.group { 129 | let mut value = 0; 130 | let mut cache_value = 0; 131 | for (page, history) in group { 132 | if history.is_allocated(None) { 133 | value += page.size_kib(); 134 | if history.page_cache() { 135 | cache_value += page.size_kib(); 136 | } 137 | } 138 | } 139 | if reverse { 140 | report.inner.insert(stack.0.iter().rev(), value, cache_value); 141 | } else { 142 | report.inner.insert(stack.0.iter(), value, cache_value); 143 | } 144 | } 145 | report.inner.strip(threshold); 146 | 147 | report 148 | } 149 | } 150 | 151 | impl History 152 | where 153 | H: PageHistory + Default, 154 | { 155 | fn track_alloc_error(error_report: &mut ErrorReport, history: &mut H, page: &Page, flags: Hex32) { 156 | if let Err(AllocError) = history.track_alloc(flags) { 157 | error_report.double_alloc(page); 158 | } 159 | } 160 | 161 | #[allow(dead_code)] 162 | fn track_free_error(error_report: &mut ErrorReport, history: &mut H, page: &Page) { 163 | match history.track_free() { 164 | Ok(()) => (), 165 | Err(FreeError::DoubleFree) => error_report.double_free(&page), 166 | Err(FreeError::WithoutAlloc) => { 167 | error_report.without_alloc(&page); 168 | debug_assert!(false); 169 | }, 170 | } 171 | } 172 | 173 | #[cfg(test)] 174 | fn is_empty(&self) -> bool { 175 | self.last_stack.is_empty() && self.group.is_empty() 176 | } 177 | } 178 | 179 | #[cfg(test)] 180 | mod test { 181 | use bpf_memprof_common::{Hex64, Hex32, Stack}; 182 | use crate::{History, EventLast, Page, Tracker, Reporter}; 183 | 184 | #[test] 185 | fn overflow() { 186 | let mut h = History::::default(); 187 | for _ in 0..0x100 { 188 | for i in 1..100 { 189 | h.track_alloc(Page::new(Hex64(i), 0), &Stack::from_frames(&[i / 3]), Hex32(0), 0); 190 | } 191 | for i in 1..100 { 192 | h.track_free(Page::new(Hex64(i), 0), 0); 193 | } 194 | } 195 | 196 | assert_eq!(h.short_report(), (0, 0)); 197 | assert!(h.is_empty()); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /tezedge-memprof/src/history/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use super::stack; 5 | 6 | mod abstract_tracker; 7 | mod page; 8 | mod page_history; 9 | mod error; 10 | mod allocation; 11 | mod history; 12 | mod report; 13 | 14 | pub use self::abstract_tracker::{Tracker, Reporter}; 15 | pub use self::allocation::AllocationState; 16 | pub use self::{ 17 | page::Page, 18 | page_history::{PageHistory, EventLast}, 19 | history::History, 20 | report::FrameReport, 21 | }; 22 | 23 | #[cfg(test)] 24 | mod tests; 25 | -------------------------------------------------------------------------------- /tezedge-memprof/src/history/page.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{fmt, hash::{Hash, Hasher}}; 5 | use bpf_memprof_common::Hex64; 6 | use serde::{Serialize, ser}; 7 | 8 | #[derive(Clone, Copy, PartialEq, Eq)] 9 | pub struct Page { 10 | // last 4 bits is order, 0..28 bits are pfn 11 | inner: u32, 12 | } 13 | 14 | impl Hash for Page { 15 | fn hash(&self, state: &mut H) 16 | where 17 | H: Hasher, 18 | { 19 | (self.inner & 0x0fffffff).hash::(state) 20 | } 21 | } 22 | 23 | impl fmt::Display for Page { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | let pfn = Hex64((self.inner & 0x0fffffff) as u64); 26 | let order = self.inner >> 28; 27 | write!(f, "{:?}-{}", pfn, order) 28 | } 29 | } 30 | 31 | impl Serialize for Page { 32 | fn serialize(&self, serializer: S) -> Result 33 | where 34 | S: ser::Serializer, 35 | { 36 | serializer.serialize_str(&self.to_string()) 37 | } 38 | } 39 | 40 | impl Page { 41 | pub fn new(pfn: Hex64, order: u32) -> Self { 42 | let inner = ((pfn.0 & 0x0fffffff) as u32) + (order << 28); 43 | Page { inner } 44 | } 45 | 46 | pub fn pfn(&self) -> u32 { 47 | self.inner & 0x0fffffff 48 | } 49 | 50 | pub fn size_kib(&self) -> u64 { 51 | 4u64 << (self.inner >> 28) 52 | } 53 | 54 | pub fn number(&self) -> u32 { 55 | 1 << (self.inner >> 28) 56 | } 57 | 58 | pub fn order(&self) -> u8 { 59 | (self.inner >> 28) as u8 60 | } 61 | 62 | pub fn set_order(&mut self, order: u8) { 63 | self.inner = (self.inner & 0x0fffffff) + ((order as u32) << 28) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tezedge-memprof/src/history/page_history.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::ops::Range; 5 | use bpf_memprof_common::Hex32; 6 | use thiserror::Error; 7 | use serde::Serialize; 8 | 9 | #[derive(Serialize)] 10 | pub struct TimeRange(Range); 11 | 12 | impl TimeRange { 13 | pub fn open_end(&self) -> bool { 14 | self.0.end == u64::MAX 15 | } 16 | 17 | fn now() -> u64 { 18 | use std::time::{SystemTime, Duration}; 19 | 20 | SystemTime::now() 21 | .duration_since(SystemTime::UNIX_EPOCH) 22 | .unwrap_or(Duration::default()) 23 | .as_millis() as u64 24 | } 25 | 26 | pub fn set_end(&mut self) { 27 | self.0.end = Self::now(); 28 | } 29 | 30 | pub fn begin_here() -> Self { 31 | TimeRange(Self::now()..u64::MAX) 32 | } 33 | 34 | pub fn end_here() -> Self { 35 | TimeRange(0..Self::now()) 36 | } 37 | } 38 | 39 | #[derive(Serialize)] 40 | pub struct Event { 41 | time_range: TimeRange, 42 | flags: Hex32, 43 | } 44 | 45 | impl Event { 46 | const EVENT_BASED_CACHE_FLAG: u32 = 1 << 31; 47 | 48 | #[allow(dead_code)] 49 | const GFP_WRITE: u32 = 0x1000; 50 | 51 | pub fn page_cache(&self) -> bool { 52 | (self.flags.0 & Self::EVENT_BASED_CACHE_FLAG) != 0 53 | } 54 | 55 | pub fn mark_page_cache(&mut self, b: bool) { 56 | if b { 57 | self.flags.0 |= Self::EVENT_BASED_CACHE_FLAG; 58 | } else { 59 | self.flags.0 &= !Self::EVENT_BASED_CACHE_FLAG; 60 | } 61 | } 62 | } 63 | 64 | #[derive(Debug, Error)] 65 | #[error("double alloc")] 66 | pub struct AllocError; 67 | 68 | #[derive(Debug, Error)] 69 | pub enum FreeError { 70 | #[error("double free")] 71 | DoubleFree, 72 | #[error("free without alloc")] 73 | WithoutAlloc, 74 | } 75 | 76 | pub trait PageHistory { 77 | fn track_alloc(&mut self, flags: Hex32) -> Result<(), AllocError>; 78 | fn track_free(&mut self) -> Result<(), FreeError>; 79 | fn is_allocated(&self, time: Option) -> bool; 80 | 81 | fn mark_page_cache(&mut self, b: bool); 82 | fn page_cache(&self) -> bool; 83 | 84 | fn is_empty(&self) -> bool; 85 | } 86 | 87 | #[derive(Default, Serialize)] 88 | pub struct EventLast(Option); 89 | 90 | impl PageHistory for EventLast { 91 | fn track_alloc(&mut self, flags: Hex32) -> Result<(), AllocError> { 92 | // if have some event in history and time range is open, track double allocation 93 | // if there is nothing in history or some old event, track a new allocation 94 | match self.0.as_mut() { 95 | Some(event) if event.time_range.open_end() => { 96 | Err(AllocError) 97 | }, 98 | _ => { 99 | self.0 = Some(Event { 100 | time_range: TimeRange::begin_here(), 101 | flags, 102 | }); 103 | Ok(()) 104 | }, 105 | } 106 | } 107 | 108 | fn track_free(&mut self) -> Result<(), FreeError> { 109 | match self.0.as_mut() { 110 | // have some allocation event, but end is open, set the end now 111 | Some(event) if event.time_range.open_end() => { 112 | event.time_range.set_end(); 113 | Ok(()) 114 | }, 115 | // have some allocation event, already end, so it is double free 116 | Some(event) => { 117 | event.time_range.set_end(); 118 | Err(FreeError::DoubleFree) 119 | }, 120 | // have nothing, it is free without alloc 121 | None => { 122 | self.0 = Some(Event { 123 | time_range: TimeRange::end_here(), 124 | flags: Hex32(0), 125 | }); 126 | Err(FreeError::WithoutAlloc) 127 | }, 128 | } 129 | } 130 | 131 | fn is_allocated(&self, time: Option) -> bool { 132 | match (time, &self.0) { 133 | (_, &None) => false, 134 | (None, &Some(Event { ref time_range, .. })) => time_range.open_end(), 135 | (Some(time), &Some(Event { ref time_range, .. })) => time_range.0.contains(&time), 136 | } 137 | } 138 | 139 | fn mark_page_cache(&mut self, b: bool) { 140 | if let &mut Some(ref mut event) = &mut self.0 { 141 | event.mark_page_cache(b); 142 | } 143 | } 144 | 145 | fn page_cache(&self) -> bool { 146 | if let &Some(ref event) = &self.0 { 147 | event.page_cache() 148 | } else { 149 | false 150 | } 151 | } 152 | 153 | fn is_empty(&self) -> bool { 154 | !self.is_allocated(None) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tezedge-memprof/src/history/report.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{collections::{HashMap, BTreeMap}, ops::Deref, cmp::Ordering}; 5 | use bpf_memprof_common::Hex64; 6 | use serde::ser::{self, SerializeSeq}; 7 | use super::stack::{SymbolInfo, StackResolver}; 8 | 9 | #[derive(Default)] 10 | pub struct FrameReportInner { 11 | value: u64, 12 | cache_value: u64, 13 | frames: HashMap, 14 | under_threshold: u64, 15 | cache_under_threshold: u64, 16 | } 17 | 18 | struct SortKey { 19 | inv_value: u64, 20 | name: SymbolInfo, 21 | } 22 | 23 | impl PartialEq for SortKey { 24 | fn eq(&self, other: &Self) -> bool { 25 | self.inv_value.eq(&other.inv_value) && self.name.eq(&other.name) 26 | } 27 | } 28 | 29 | impl Eq for SortKey {} 30 | 31 | impl PartialOrd for SortKey { 32 | fn partial_cmp(&self, other: &Self) -> Option { 33 | Some(self.cmp(other)) 34 | } 35 | } 36 | 37 | impl Ord for SortKey { 38 | fn cmp(&self, other: &Self) -> Ordering { 39 | self.inv_value.cmp(&other.inv_value) 40 | .then(self.name.cmp(&other.name)) 41 | } 42 | } 43 | 44 | pub struct FrameReportSorted { 45 | name: Option, 46 | value: u64, 47 | cache_value: u64, 48 | frames: BTreeMap, 49 | under_threshold: u64, 50 | cache_under_threshold: u64, 51 | unknown: u64, 52 | cache_unknown: u64, 53 | } 54 | 55 | impl FrameReportInner { 56 | pub fn insert<'a, StackIter>(&mut self, stack: StackIter, value: u64, cache_value: u64) 57 | where 58 | StackIter: Iterator, 59 | { 60 | let mut node = self; 61 | for stack_frame in stack { 62 | node.value += value; 63 | node.cache_value += cache_value; 64 | node = node.frames.entry(*stack_frame).or_default(); 65 | } 66 | node.value += value; 67 | node.cache_value += cache_value; 68 | } 69 | 70 | pub fn strip(&mut self, threshold: u64) { 71 | let mut under_threshold = 0; 72 | let mut cache_under_threshold = 0; 73 | self.frames.retain(|_, frame| { 74 | frame.strip(threshold); 75 | let retain = frame.value >= threshold; 76 | if !retain { 77 | under_threshold += frame.value; 78 | cache_under_threshold += frame.cache_value; 79 | } 80 | retain 81 | }); 82 | self.under_threshold = under_threshold; 83 | self.cache_under_threshold = cache_under_threshold; 84 | } 85 | 86 | pub fn sorted(&self, resolver: &StackResolver, name: Option) -> FrameReportSorted { 87 | let mut frames = BTreeMap::new(); 88 | let mut unknown = self.value - self.under_threshold; 89 | let mut cache_unknown = self.cache_value - self.cache_under_threshold; 90 | for (key, value) in &self.frames { 91 | if let Some(name) = resolver.resolve(key.0) { 92 | let key = SortKey { 93 | inv_value: !value.value, 94 | name: name.clone(), 95 | }; 96 | frames.insert(key, value.sorted(resolver, Some(name))); 97 | unknown -= value.value; 98 | cache_unknown -= value.cache_value; 99 | } 100 | } 101 | 102 | FrameReportSorted { 103 | name, 104 | value: self.value, 105 | cache_value: self.cache_value, 106 | frames, 107 | under_threshold: self.under_threshold, 108 | cache_under_threshold: self.cache_under_threshold, 109 | unknown, 110 | cache_unknown, 111 | } 112 | } 113 | } 114 | 115 | pub struct FrameReport { 116 | resolver: R, 117 | pub(crate) inner: FrameReportInner, 118 | } 119 | 120 | impl FrameReport { 121 | pub fn new(resolver: R) -> Self { 122 | FrameReport { resolver, inner: FrameReportInner::default() } 123 | } 124 | 125 | pub fn value(&self) -> u64 { 126 | self.inner.value 127 | } 128 | 129 | pub fn cache_value(&self) -> u64 { 130 | self.inner.cache_value 131 | } 132 | } 133 | 134 | impl ser::Serialize for FrameReportSorted { 135 | fn serialize(&self, serializer: S) -> Result 136 | where 137 | S: ser::Serializer, 138 | { 139 | use self::ser::SerializeMap; 140 | 141 | struct Helper<'a> { 142 | inner: &'a BTreeMap, 143 | under_threshold: Option, 144 | unknown: Option, 145 | } 146 | 147 | #[derive(serde::Serialize)] 148 | #[serde(rename_all = "camelCase")] 149 | struct FakeFrame { 150 | name: String, 151 | value: u64, 152 | cache_value: u64, 153 | } 154 | 155 | impl FakeFrame { 156 | pub fn under_threshold(value: u64, cache_value: u64) -> Option { 157 | if value != 0 { 158 | Some(FakeFrame { 159 | name: "underThreshold".to_string(), 160 | value, 161 | cache_value, 162 | }) 163 | } else { 164 | None 165 | } 166 | } 167 | 168 | pub fn unknown(value: u64, cache_value: u64) -> Option { 169 | if value != 0 { 170 | Some(FakeFrame { 171 | name: "unknown".to_string(), 172 | value, 173 | cache_value, 174 | }) 175 | } else { 176 | None 177 | } 178 | } 179 | } 180 | 181 | impl<'a> ser::Serialize for Helper<'a> { 182 | fn serialize(&self, serializer: S) -> Result 183 | where 184 | S: ser::Serializer, 185 | { 186 | if self.inner.is_empty() { 187 | return serializer.serialize_seq(Some(0))?.end(); 188 | } 189 | let l = self.inner.len() 190 | + (self.under_threshold.is_some() as usize) 191 | + (self.unknown.is_some() as usize); 192 | let mut map = serializer.serialize_seq(Some(l))?; 193 | for (_, inner_frame) in self.inner { 194 | map.serialize_element(inner_frame)?; 195 | } 196 | if let &Some(ref f) = &self.under_threshold { 197 | map.serialize_element(f)?; 198 | } 199 | if let &Some(ref f) = &self.unknown { 200 | map.serialize_element(f)?; 201 | } 202 | map.end() 203 | } 204 | } 205 | 206 | let helper = Helper { 207 | inner: &self.frames, 208 | under_threshold: FakeFrame::under_threshold(self.under_threshold, self.cache_under_threshold), 209 | unknown: FakeFrame::unknown(self.unknown, self.cache_unknown), 210 | }; 211 | 212 | let l = 3 + (self.name.is_some() as usize); 213 | let mut map = serializer.serialize_map(Some(l))?; 214 | if let &Some(ref name) = &self.name { 215 | map.serialize_entry("name", name)?; 216 | } 217 | map.serialize_entry("value", &self.value)?; 218 | map.serialize_entry("cacheValue", &self.cache_value)?; 219 | map.serialize_entry("frames", &helper)?; 220 | map.end() 221 | } 222 | } 223 | 224 | impl ser::Serialize for FrameReport 225 | where 226 | R: Deref, 227 | { 228 | fn serialize(&self, serializer: S) -> Result 229 | where 230 | S: ser::Serializer, 231 | { 232 | let sorted = self.inner.sorted(&self.resolver, None); 233 | sorted.serialize(serializer) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /tezedge-memprof/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #![forbid(unsafe_code)] 5 | 6 | mod memory_map; 7 | 8 | mod state; 9 | pub use self::state::{AtomicState, Reporter as StateReporter}; 10 | 11 | mod history; 12 | pub use self::history::{Page, History, AllocationState, FrameReport, EventLast, Tracker, Reporter}; 13 | 14 | mod stack; 15 | pub use self::stack::StackResolver; 16 | 17 | mod table; 18 | 19 | pub mod server; 20 | 21 | mod collector; 22 | pub use self::collector::{Consumer, Aggregator, RawEvent}; 23 | -------------------------------------------------------------------------------- /tezedge-memprof/src/memory_map.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ops::Range, num::Wrapping, str::FromStr, io::{self, Read}, fs::File, path::PathBuf}; 5 | 6 | #[derive(Default, Clone, PartialEq, Eq)] 7 | pub struct ProcessMap(Vec); 8 | 9 | impl ProcessMap { 10 | pub fn new(pid: u32) -> io::Result { 11 | MemoryMapEntry::new(pid).map(ProcessMap) 12 | } 13 | 14 | pub fn files(&self) -> Vec { 15 | self.0 16 | .iter() 17 | .filter_map(|entry| entry.name.string()) 18 | .collect() 19 | } 20 | 21 | pub fn find(&self, ip: usize) -> Option<(String, usize)> { 22 | self.0.iter() 23 | .find_map(|entry| { 24 | if !entry.range.contains(&ip) { 25 | return None; 26 | } 27 | 28 | let name = entry.name.clone(); 29 | if !entry.exec() { 30 | //log::warn!("have non-exec pointer in stacktrace {:016x}@{:?}", ip, name); 31 | return None; 32 | } 33 | 34 | let s = name.string()?; 35 | 36 | let Wrapping(ptr) = Wrapping(entry.offset) + Wrapping(ip) - Wrapping(entry.range.start); 37 | Some((s, ptr)) 38 | }) 39 | } 40 | } 41 | 42 | #[derive(Clone, PartialEq, Eq)] 43 | struct MemoryMapEntry { 44 | range: Range, 45 | flags: String, 46 | offset: usize, 47 | name: EntryName, 48 | } 49 | 50 | #[derive(Debug, Clone, PartialEq, Eq)] 51 | enum EntryName { 52 | Nothing, 53 | FileName(PathBuf), 54 | Remark(String), 55 | } 56 | 57 | impl EntryName { 58 | pub fn string(&self) -> Option { 59 | match self { 60 | EntryName::FileName(filename) => { 61 | if let Some(s) = filename.to_str() { 62 | Some(s.to_string()) 63 | } else { 64 | // WARNING: os string could be invalid utf-8 65 | // handle this case 66 | None 67 | } 68 | }, 69 | _ => None, 70 | } 71 | } 72 | } 73 | 74 | impl MemoryMapEntry { 75 | fn new(pid: u32) -> Result, io::Error> { 76 | let mut entries = String::new(); 77 | File::open(&format!("/proc/{}/maps", pid))? 78 | .read_to_string(&mut entries)?; 79 | 80 | let mut map = vec![]; 81 | for line in entries.lines() { 82 | map.push(line.parse()?); 83 | } 84 | Ok(map) 85 | } 86 | 87 | fn exec(&self) -> bool { 88 | self.flags.contains('x') 89 | } 90 | } 91 | 92 | impl FromStr for MemoryMapEntry { 93 | // TODO: proper error 94 | type Err = io::Error; 95 | 96 | fn from_str(s: &str) -> Result { 97 | let mut columns = s.split_ascii_whitespace(); 98 | 99 | let range_str = columns.next().ok_or(io::ErrorKind::Other)?; 100 | let range = { 101 | let mut range_items = range_str.split('-'); 102 | let range_start = range_items.next().ok_or(io::ErrorKind::Other)?; 103 | let range_end = range_items.next().ok_or(io::ErrorKind::Other)?; 104 | let start = usize::from_str_radix(range_start, 16) 105 | .map_err(|_| io::ErrorKind::Other)?; 106 | let end = usize::from_str_radix(range_end, 16) 107 | .map_err(|_| io::ErrorKind::Other)?; 108 | start..end 109 | }; 110 | 111 | let flags = columns.next().ok_or(io::ErrorKind::Other)?.to_string(); 112 | 113 | let offset_str = columns.next().ok_or(io::ErrorKind::Other)?; 114 | let offset = usize::from_str_radix(offset_str, 16) 115 | .map_err(|_| io::ErrorKind::Other)?; 116 | 117 | let _ = columns.next().ok_or(io::ErrorKind::Other)?; 118 | let _ = columns.next().ok_or(io::ErrorKind::Other)?; 119 | 120 | let name = match columns.next() { 121 | None => EntryName::Nothing, 122 | Some(name) => { 123 | if name.is_empty() { 124 | EntryName::Nothing 125 | } else if name.starts_with('[') { 126 | EntryName::Remark(name.to_string()) 127 | } else { 128 | match PathBuf::from_str(name) { 129 | Ok(path) => EntryName::FileName(path), 130 | Err(_) => EntryName::Remark(name.to_string()), 131 | } 132 | } 133 | }, 134 | }; 135 | 136 | Ok(MemoryMapEntry { range, flags, offset, name }) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tezedge-memprof/src/server.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | sync::{Arc, atomic::{Ordering, AtomicU32}, Mutex, RwLock}, 6 | fs::File, 7 | io::{Error, BufReader, BufRead}, 8 | }; 9 | use warp::{ 10 | Filter, Rejection, Reply, 11 | reply::{WithStatus, Json, self}, 12 | http::StatusCode, 13 | }; 14 | use serde::{Serialize, Deserialize}; 15 | use super::{StackResolver, Reporter}; 16 | 17 | pub fn run( 18 | reporter: Arc>, 19 | resolver: Arc>, 20 | pid: Arc, 21 | ) -> (tokio::task::JoinHandle<()>, tokio::runtime::Runtime) 22 | where 23 | T: Reporter + Send + 'static, 24 | { 25 | let runtime = tokio::runtime::Runtime::new().unwrap(); 26 | let server = routes(reporter, resolver, pid.clone()); 27 | let handler = runtime.spawn(warp::serve(server).run(([0, 0, 0, 0], 17832))); 28 | (handler, runtime) 29 | } 30 | 31 | fn routes( 32 | reporter: Arc>, 33 | resolver: Arc>, 34 | pid: Arc, 35 | ) -> impl Filter + Clone + Sync + Send + 'static 36 | where 37 | T: Reporter + Send + 'static, 38 | { 39 | use warp::reply::with; 40 | 41 | warp::get() 42 | .and(tree(reporter, resolver, pid.clone()).or(get_pid(pid)).or(openapi())) 43 | .with(with::header("Content-Type", "application/json")) 44 | .with(with::header("Access-Control-Allow-Origin", "*")) 45 | } 46 | 47 | fn get_pid(p: Arc) -> impl Filter,), Error = Rejection> + Clone + Sync + Send + 'static { 48 | warp::path!("v1" / "pid") 49 | .and(warp::query::query()) 50 | .map(move |()| -> WithStatus { 51 | reply::with_status(reply::json(&p.load(Ordering::Relaxed)), StatusCode::OK) 52 | }) 53 | } 54 | 55 | fn rss_anon(p: Arc) -> Result { 56 | let pid = p.load(Ordering::Relaxed); 57 | let f = File::open(format!("/proc/{}/status", pid))?; 58 | let reader = BufReader::new(f); 59 | let mut v = 0; 60 | for line in reader.lines() { 61 | let line = line?; 62 | let mut words = line.split_whitespace(); 63 | if let Some("RssAnon:") = words.next() { 64 | v = words.next().map(|s| s.parse().unwrap_or(0)).unwrap_or(0); 65 | } else { 66 | continue; 67 | } 68 | } 69 | 70 | Ok(v) 71 | } 72 | 73 | fn tree( 74 | history: Arc>, 75 | resolver: Arc>, 76 | pid: Arc, 77 | ) -> impl Filter,), Error = Rejection> + Clone + Sync + Send + 'static 78 | where 79 | T: Reporter + Send + 'static, 80 | { 81 | #[derive(Deserialize)] 82 | struct Params { 83 | threshold: Option, 84 | reverse: Option, 85 | short: Option, 86 | } 87 | 88 | #[derive(Serialize)] 89 | #[serde(rename_all = "camelCase")] 90 | struct ShortReport { 91 | total: u64, 92 | cache: u64, 93 | anon: u64, 94 | system_report_anon: u64, 95 | } 96 | 97 | warp::path!("v1" / "tree") 98 | .and(warp::query::query()) 99 | .map(move |params: Params| -> WithStatus { 100 | let resolver = resolver.read().unwrap(); 101 | let history = history.lock().unwrap(); 102 | if params.short.unwrap_or(false) { 103 | let (total, cache) = history.short_report(); 104 | let system_report_anon = rss_anon(pid.clone()).unwrap_or(0); 105 | let report = ShortReport { 106 | total, 107 | cache, 108 | anon: total - cache, 109 | system_report_anon, 110 | }; 111 | reply::with_status(reply::json(&report), StatusCode::OK) 112 | } else { 113 | let report = history.tree_report( 114 | resolver, 115 | params.threshold.unwrap_or(512), 116 | params.reverse.unwrap_or(false), 117 | ); 118 | reply::with_status(reply::json(&report), StatusCode::OK) 119 | } 120 | }) 121 | } 122 | 123 | pub fn openapi() -> impl Filter, ), Error=Rejection> + Clone + Sync + Send + 'static { 124 | warp::path!("openapi" / "memory-profiler-openapi.json") 125 | .and(warp::query::query()) 126 | .map(move |()| -> reply::WithStatus { 127 | let s = include_str!("../openapi.json"); 128 | let d = serde_json::from_str::(s).unwrap(); 129 | reply::with_status( 130 | reply::json(&d), 131 | StatusCode::OK, 132 | ) 133 | }) 134 | } 135 | -------------------------------------------------------------------------------- /tezedge-memprof/src/table.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ops::Range, path::Path}; 5 | 6 | pub struct SymbolTable { 7 | inner: Vec, 8 | name: String, 9 | strings: Vec, 10 | } 11 | 12 | struct Symbol { 13 | range: Range, 14 | name_offset: u32, 15 | } 16 | 17 | impl Symbol { 18 | fn code(&self) -> Range { 19 | (self.range.start as u64)..(self.range.end as u64) 20 | } 21 | } 22 | 23 | impl SymbolTable { 24 | pub fn load

(path: P) -> Result 25 | where 26 | P: AsRef, 27 | { 28 | use std::{fs, io::Read}; 29 | use elf64::{Elf64, SectionData}; 30 | 31 | let mut f = fs::File::open(&path).map_err(|e| e.to_string())?; 32 | let mut data = Vec::new(); 33 | f.read_to_end(&mut data).map_err(|e| e.to_string())?; 34 | 35 | let mut symbols = Vec::new(); 36 | 37 | let elf = Elf64::new(&data).map_err(|e| format!("{:?}", e))?; 38 | let s = elf.section_number(); 39 | let symbol_tables = (0..s) 40 | .filter_map(|i| { 41 | let section = elf.section(i).ok()??; 42 | match (section.link, section.data) { 43 | (link, SectionData::SymbolTable { table, .. }) => Some((link, table)), 44 | (link, SectionData::DynamicSymbolTable { table, .. }) => Some((link, table)), 45 | _ => None, 46 | } 47 | }); 48 | 49 | let mut strings = Vec::new(); 50 | for (link, symtab) in symbol_tables { 51 | let index = u16::from(link) as usize; 52 | if index >= elf.section_number() { 53 | log::warn!("no strtab table corresponding to symtab"); 54 | } 55 | let strtab = if let Ok(Some(section)) = elf.section(index) { 56 | if let SectionData::StringTable(table) = section.data { 57 | table 58 | } else { 59 | log::warn!("symtab linked to bad strtab {}", index); 60 | continue; 61 | } 62 | } else { 63 | log::warn!("symtab linked to bad strtab {}", index); 64 | continue; 65 | }; 66 | for i in 0..symtab.length() { 67 | let symbol = symtab.pick(i).map_err(|e| format!("{:?}", e))?; 68 | let range = (symbol.value as u32)..((symbol.value + symbol.size) as u32); 69 | let name_offset = (strings.len() as u32) + symbol.name; 70 | symbols.push(Symbol { range, name_offset }) 71 | } 72 | strings.extend_from_slice(strtab.as_raw()); 73 | } 74 | symbols.sort_by(|a, b| a.range.start.cmp(&b.range.start)); 75 | 76 | Ok(SymbolTable { 77 | inner: symbols, 78 | name: path.as_ref().file_name().and_then(|n| n.to_str()).unwrap_or("").to_string(), 79 | strings, 80 | }) 81 | } 82 | 83 | pub fn name(&self) -> &str { 84 | &self.name 85 | } 86 | 87 | pub fn len(&self) -> usize { 88 | self.inner.len() 89 | } 90 | 91 | pub fn is_empty(&self) -> bool { 92 | self.len() == 0 93 | } 94 | 95 | pub fn find(&self, offset: u64) -> Option { 96 | if self.is_empty() { 97 | return None; 98 | } 99 | 100 | let mut length = 1 << (63 - (self.len() as u64).leading_zeros() as usize); 101 | // pos points somewhere in the middle of symbols array, and is power of two 102 | // if 4 <= symbols.len() < 8 => pos == 4 103 | // do binary search in symbols 104 | let mut pos = length; 105 | while length > 0 { 106 | length >>= 1; 107 | if pos >= self.len() { 108 | pos -= length; 109 | } else { 110 | let symbol = &self.inner[pos]; 111 | let code = symbol.code(); 112 | if code.contains(&offset) { 113 | let strtab = elf64::StringTable::new(&self.strings); 114 | return match strtab.pick(symbol.name_offset as usize) { 115 | Ok(name) => Some(name.to_string()), 116 | Err(e) => Some(format!("\"string table error {:?}\"", e)), 117 | }; 118 | } else if code.start > offset { 119 | pos -= length; 120 | } else { 121 | pos += length; 122 | } 123 | } 124 | } 125 | None 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tezedge-memprof/tests/compare.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use thiserror::Error; 5 | use serde_json::{value, error}; 6 | 7 | #[derive(Debug, Error)] 8 | enum E { 9 | #[error("serde: {}", _0)] 10 | Serde(serde_json::error::Error), 11 | #[error("bad json")] 12 | BadJson, 13 | } 14 | 15 | async fn get(path: &str, params: &str) -> Result { 16 | use std::env; 17 | 18 | let url = env::var("URL").unwrap(); 19 | let res = reqwest::get(&format!("{}{}/?{}", url, path, params)).await.unwrap().text().await.unwrap(); 20 | serde_json::from_str(&res) 21 | } 22 | 23 | async fn get_usage_kib() -> Result { 24 | let tree = get("/v1/tree", "").await.map_err(E::Serde)?; 25 | let tree = tree.as_object().ok_or(E::BadJson)?; 26 | let value = tree.get("value").ok_or(E::BadJson)?.as_i64().ok_or(E::BadJson)?; 27 | let cache_value = tree.get("cacheValue").ok_or(E::BadJson)?.as_i64().ok_or(E::BadJson)?; 28 | Ok(value - cache_value) 29 | } 30 | 31 | async fn get_pid() -> Result { 32 | let pid = get("/v1/pid", "").await.map_err(E::Serde)?; 33 | let pid = pid.as_i64().ok_or(E::BadJson)?; 34 | Ok(pid as _) 35 | } 36 | 37 | async fn compare() { 38 | use std::{fs::File, io::{BufRead, BufReader}}; 39 | 40 | let pid = get_pid().await.unwrap(); 41 | let usage_kib = get_usage_kib().await.unwrap() as i32; 42 | 43 | let f = File::open(format!("/proc/{}/status", pid)).expect("no such process"); 44 | let usage_system_kib = BufReader::new(f).lines() 45 | .find_map(|line| { 46 | let line = line.ok()?; 47 | let mut words = line.split_whitespace(); 48 | if let Some("RssAnon:") = words.next() { 49 | words.next().map(|s| s.parse().unwrap_or(0)) 50 | } else { 51 | None 52 | } 53 | }) 54 | .unwrap_or(0); 55 | 56 | if usage_system_kib == 0 { 57 | println!("failed to get memory usage"); 58 | return; 59 | } 60 | 61 | let diff = usage_kib - usage_system_kib; 62 | if diff.abs() < 10 * 1024 { 63 | println!("system report: {}, memprof report: {}, difference: {}", usage_system_kib, usage_kib, diff); 64 | } else { 65 | panic!("system report: {}, memprof report: {}, difference: {}", usage_system_kib, usage_kib, diff); 66 | } 67 | } 68 | 69 | #[tokio::test] 70 | async fn compare_3() { 71 | use std::time::Duration; 72 | 73 | let duration = Duration::from_secs(20); 74 | 75 | loop { 76 | let t = get_usage_kib().await.unwrap() as i32; 77 | if t > 32 * 1024 { 78 | break t; 79 | } else { 80 | println!("wait {:?}...", duration); 81 | tokio::time::sleep(duration).await; 82 | } 83 | }; 84 | 85 | compare().await; 86 | println!("wait {:?}...", duration); 87 | tokio::time::sleep(duration).await; 88 | compare().await; 89 | println!("wait {:?}...", duration); 90 | tokio::time::sleep(duration).await; 91 | compare().await; 92 | } 93 | -------------------------------------------------------------------------------- /tezedge-memprof/tests/positive.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | async fn get(path: &str, params: &str) -> serde_json::Value { 5 | use std::env; 6 | 7 | let url = env::var("URL").unwrap(); 8 | let uri = format!("{}{}?{}", url, path, params); 9 | println!("get {}", uri); 10 | let res = reqwest::get(&uri).await.unwrap().text().await.unwrap(); 11 | serde_json::from_str(&res).unwrap() 12 | } 13 | 14 | fn check(tree: &serde_json::Value) { 15 | let tree = tree.as_object().unwrap(); 16 | let value = tree.get("value").unwrap().as_i64().unwrap(); 17 | let cache_value = tree.get("cacheValue").unwrap().as_i64().unwrap(); 18 | assert!(value >= cache_value); 19 | if let Some(frames) = tree.get("frames") { 20 | let frames = frames.as_array().unwrap(); 21 | for frame in frames { 22 | check(frame); 23 | } 24 | } 25 | } 26 | 27 | #[tokio::test] 28 | async fn positive() { 29 | let tree = get("/v1/tree", "threshold=0").await; 30 | check(&tree); 31 | } 32 | -------------------------------------------------------------------------------- /tezedge-recorder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tezedge-recorder" 3 | version = "0.1.0" 4 | authors = ["Vladislav Melnik "] 5 | edition = "2018" 6 | 7 | [lib] 8 | doctest = false 9 | 10 | [[bin]] 11 | name = "tezedge-recorder" 12 | path = "src/bin/main.rs" 13 | 14 | [[bin]] 15 | name = "replayer" 16 | path = "src/bin/replayer.rs" 17 | 18 | [[bin]] 19 | name = "pseudonode" 20 | path = "src/bin/pseudonode.rs" 21 | 22 | [dev-dependencies] 23 | reqwest = "0.11" 24 | tokio = { version = "1.8", features = ["full"] } 25 | tezedge-recorder = { path = "../tezedge-recorder" } 26 | 27 | [dependencies] 28 | toml = "0.5" 29 | serde = "1.0" 30 | serde_json = "1.0" 31 | hex = "0.4" 32 | rocksdb = "0.15" 33 | tantivy = "0.15" 34 | anyhow = "1.0" 35 | thiserror = "1.0" 36 | log = "0.4" 37 | either = "1.6" 38 | typenum = "1.13" 39 | syslog_loose = "0.14" 40 | itertools = "0.10" 41 | 42 | structopt = { version = "0.3"} 43 | chrono = { version = "0.4" } 44 | rand = { version = "0.8.4", features = ["small_rng"] } 45 | 46 | ctrlc = "3.1" 47 | tracing-subscriber = "0.2" 48 | tracing = "0.1" 49 | 50 | warp = "0.3" 51 | tokio = { version = "1.8", features = ["rt-multi-thread"] } 52 | 53 | [target.'cfg(not(target_env = "msvc"))'.dependencies] 54 | jemallocator = { version = "0.3", optional = true } 55 | 56 | [target.'cfg(target_os = "linux")'.dependencies] 57 | bpf-recorder = { path = "../bpf-recorder", features = ["client"] } 58 | 59 | crypto = { tag = "v1.6.5", git = "https://github.com/tezedge/tezedge" } 60 | tezos_messages = { tag = "v1.6.5", git = "https://github.com/tezedge/tezedge" } 61 | storage = { tag = "v1.6.5", git = "https://github.com/tezedge/tezedge" } 62 | 63 | pseudonode = { path = "../pseudonode" } 64 | -------------------------------------------------------------------------------- /tezedge-recorder/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::process::Command; 5 | 6 | fn main() { 7 | let output = Command::new("git") 8 | .args(&["rev-parse", "HEAD"]) 9 | .output() 10 | .unwrap(); 11 | let git_hash = String::from_utf8(output.stdout).unwrap(); 12 | println!("cargo:rustc-env=GIT_HASH={}", git_hash); 13 | } 14 | -------------------------------------------------------------------------------- /tezedge-recorder/identity_i.json: -------------------------------------------------------------------------------- 1 | { 2 | "peer_id":"idssJHDL1z8fkryZaYVF9fQRMktoWg", 3 | "public_key":"d8246d13d0270cbfff4046b6d94b05ab19920bc5ad9fb77f3e945c40b340e874", 4 | "secret_key":"8b4622bc512c8621a35fa19ff252129b208c8cdffb57e2d29c7974df718c7ff2", 5 | "proof_of_work_stamp":"d1d0ebd55784bc92852d913dbf0fb5152d505b567d930fb2" 6 | } -------------------------------------------------------------------------------- /tezedge-recorder/identity_r.json: -------------------------------------------------------------------------------- 1 | {"proof_of_work_stamp":"4507cc103bda951a5993ccc792a084ab711247c26fc87d2b","secret_key":"acf3773a13401e3bfb86051d91c446cd39860d786d045f0b1c655bba66fe71fc","public_key":"ec76f8df4b578a90a154528c44a63ec581ac2088764dcf7db2a827d7b15e2705","peer_id":"idtfHtY9LK9r47LyxPU53KdzeGPNog"} -------------------------------------------------------------------------------- /tezedge-recorder/src/bin/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #[cfg(all(not(target_env = "msvc"), feature = "jemallocator"))] 5 | #[global_allocator] 6 | static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; 7 | 8 | fn main() -> anyhow::Result<()> { 9 | use std::{ 10 | env, 11 | process::Command, 12 | time::Duration, 13 | thread, 14 | sync::{ 15 | Arc, 16 | atomic::{Ordering, AtomicBool}, 17 | }, 18 | io::ErrorKind, 19 | }; 20 | use tezedge_recorder::{System, database::rocks::Db, main_loop}; 21 | 22 | tracing_subscriber::fmt() 23 | .with_max_level(tracing::Level::INFO) 24 | .init(); 25 | 26 | let running = Arc::new(AtomicBool::new(true)); 27 | { 28 | let running = running.clone(); 29 | ctrlc::set_handler(move || running.store(false, Ordering::Relaxed))?; 30 | } 31 | 32 | let mut system = System::::load_config()?; 33 | system.run_dbs(running.clone()); 34 | 35 | if system.need_bpf() { 36 | let bpf = if env::args().find(|a| a == "--run-bpf").is_some() { 37 | let h = Command::new("bpf-recorder").spawn().or_else(|e| { 38 | if e.kind() == ErrorKind::NotFound { 39 | Command::new("./target/none/release/bpf-recorder").spawn() 40 | } else { 41 | Err(e) 42 | } 43 | }); 44 | match h { 45 | Ok(h) => { 46 | thread::sleep(Duration::from_millis(500)); 47 | if let Err(error) = main_loop::run(&mut system, running) { 48 | log::error!("cannot intercept p2p messages: {}", error) 49 | } 50 | Some(h) 51 | }, 52 | Err(error) => { 53 | log::error!("cannot run bpf: {:?}", error); 54 | None 55 | }, 56 | } 57 | } else { 58 | None 59 | }; 60 | 61 | let _ = bpf; 62 | } 63 | system.join(); 64 | 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /tezedge-recorder/src/bin/operation_example.json: -------------------------------------------------------------------------------- 1 | {"operation":{"branch":[118,99,6,27,216,62,124,147,176,99,118,76,184,37,188,201,137,2,61,28,181,16,55,76,105,170,189,232,220,71,71,102],"data":[108,0,0,181,200,176,255,71,77,217,231,163,174,96,125,56,58,3,60,26,76,83,136,39,227,69,236,186,2,129,2,128,146,244,1,0,0,44,83,87,28,17,9,77,125,180,4,239,1,120,22,67,117,193,213,250,112,0,108,0,0,181,200,176,255,71,77,217,231,163,174,96,125,56,58,3,60,26,76,83,136,39,228,69,236,186,2,129,2,192,141,183,1,0,0,192,22,56,150,148,235,200,128,53,175,237,188,209,46,101,250,105,183,171,94,0,173,195,217,5,24,110,173,32,59,49,103,78,83,240,239,125,119,152,60,22,125,16,163,160,184,96,129,22,151,247,197,220,104,64,167,210,195,41,3,189,248,57,94,26,86,58,50,118,62,141,79,111,219,94,205,22,175,176,52,159,188,90,48,13]}} -------------------------------------------------------------------------------- /tezedge-recorder/src/bin/pseudonode.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #![forbid(unsafe_code)] 5 | 6 | use std::{ops::Range, env}; 7 | use structopt::StructOpt; 8 | 9 | #[derive(StructOpt)] 10 | enum Args { 11 | Log { 12 | range: u8, 13 | }, 14 | P2pInitiator { 15 | this: u16, 16 | peer: u16, 17 | }, 18 | P2pResponder { 19 | this: u16, 20 | peer: u16, 21 | }, 22 | } 23 | 24 | fn main() { 25 | match Args::from_args() { 26 | Args::Log { range: 0 } => { 27 | prepare_db_range(0..5_000); 28 | }, 29 | Args::Log { range: 1 } => { 30 | prepare_db_range(5_000..10_000); 31 | }, 32 | Args::Log { range: 2 } => { 33 | prepare_db_log_words(); 34 | }, 35 | Args::Log { range: _ } => { 36 | panic!(); 37 | } 38 | Args::P2pInitiator { this, peer } => { 39 | generate_p2p(this, peer, true); 40 | }, 41 | Args::P2pResponder { this, peer } => { 42 | generate_p2p(this, peer, false); 43 | }, 44 | } 45 | } 46 | 47 | fn generate_p2p(this: u16, peer: u16, initiator: bool) { 48 | use std::net::{SocketAddr, TcpListener, TcpStream}; 49 | use pseudonode::{handshake, Message, ChunkBuffer}; 50 | use crypto::nonce::NoncePair; 51 | use tezos_messages::p2p::encoding::{ 52 | metadata::MetadataMessage, 53 | ack::AckMessage, 54 | peer::{PeerMessage, PeerMessageResponse}, 55 | version::NetworkVersion, 56 | }; 57 | 58 | let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], this))).unwrap(); 59 | let version = NetworkVersion::new("TEZOS_MAINNET".to_string(), 0, 1); 60 | 61 | if initiator { 62 | let addr = SocketAddr::from(([127, 0, 0, 1], peer)); 63 | let mut stream = TcpStream::connect(addr).unwrap(); 64 | 65 | let (key, NoncePair { local, remote }) = handshake::initiator(this, &mut stream, include_str!("../../identity_i.json"), version); 66 | let mut buffer = ChunkBuffer::default(); 67 | 68 | let local = MetadataMessage::new(false, false).write_msg(&mut stream, &key, local); 69 | let (remote, _msg) = 70 | MetadataMessage::read_msg(&mut stream, &mut buffer, &key, remote, false).unwrap(); 71 | 72 | let local = AckMessage::Ack.write_msg(&mut stream, &key, local); 73 | let (_, _msg) = AckMessage::read_msg(&mut stream, &mut buffer, &key, remote, false) 74 | .unwrap(); 75 | 76 | let fake_operation = serde_json::from_str(include_str!("operation_example.json")).unwrap(); 77 | let local = PeerMessageResponse::from(PeerMessage::Operation(fake_operation)).write_msg(&mut stream, &key, local); 78 | let _ = local; 79 | } else { 80 | let mut stream = { 81 | let (stream, _) = listener.accept().unwrap(); 82 | stream 83 | }; 84 | 85 | let (key, NoncePair { local, remote }) = handshake::responder(this, &mut stream, include_str!("../../identity_r.json"), version); 86 | let mut buffer = ChunkBuffer::default(); 87 | let (remote, _msg) = 88 | MetadataMessage::read_msg(&mut stream, &mut buffer, &key, remote, false).unwrap(); 89 | let local = MetadataMessage::new(false, false).write_msg(&mut stream, &key, local); 90 | 91 | let (remote, _msg) = AckMessage::read_msg(&mut stream, &mut buffer, &key, remote, false) 92 | .unwrap(); 93 | let _ = AckMessage::Ack.write_msg(&mut stream, &key, local); 94 | 95 | let (remote, msg) = PeerMessageResponse::read_msg(&mut stream, &mut buffer, &key, remote, true).unwrap(); 96 | assert!(matches!(msg.message(), &PeerMessage::Operation(_))); 97 | 98 | let _ = remote; 99 | } 100 | let _ = listener; 101 | } 102 | 103 | fn prepare_message(timestamp: i64, level: &str, text: &str) -> String { 104 | use chrono::prelude::*; 105 | 106 | let local = Local.timestamp(timestamp, 0).to_rfc3339(); 107 | let fake = "Jul 14 12:00:00.000"; 108 | format!( 109 | "<27>1 {} wsvl eb3fdbc716e5 665 eb3fdbc716e5 - {} {} some {}", 110 | local, 111 | fake, 112 | level, 113 | text, 114 | ) 115 | } 116 | 117 | fn send_stream(msgs: impl Iterator) { 118 | use std::{time::Duration, thread, net::UdpSocket}; 119 | 120 | let socket = UdpSocket::bind("127.0.0.1:54254").unwrap(); 121 | for msg in msgs { 122 | let _ = socket.send_to(msg.as_bytes(), "127.0.0.1:10000").unwrap(); 123 | thread::sleep(Duration::from_millis(1)); 124 | } 125 | } 126 | 127 | fn start_time() -> i64 { 128 | env::var("START_TIME") 129 | .map(|s| s.parse::().unwrap_or(0)) 130 | .unwrap_or(0) 131 | } 132 | 133 | fn prepare_db_range(range: Range) { 134 | let it = range 135 | .map(|i| { 136 | let timestamp = start_time() + i; 137 | 138 | let level = match timestamp % 19 { 139 | 1 | 4 | 5 | 8 => "WARN", 140 | 7 | 10 => "ERROR", 141 | _ => "INFO", 142 | }; 143 | 144 | // 16 words 145 | let text = (0..16) 146 | .fold(String::new(), |acc, _| { 147 | // 8 * 2 = 16 symbols each 148 | format!("{} {}", acc, hex::encode((0..8).map(|_| rand::random()).collect::>())) 149 | }); 150 | prepare_message(timestamp, level, &text) 151 | }); 152 | send_stream(it); 153 | } 154 | 155 | fn prepare_db_log_words() { 156 | use rand::seq::SliceRandom; 157 | 158 | let words = [ 159 | "peer", "branch", "head", "chain", 160 | "address", "ip", "message", "connection", 161 | ]; 162 | let it = (1..256) 163 | .map(|i| { 164 | let mut bits = [0, 1, 2, 3, 4, 5, 6, 7]; 165 | bits.shuffle(&mut rand::thread_rng()); 166 | let text = bits 167 | .iter() 168 | .fold(String::new(), |acc, j| { 169 | if i & (1 << j) != 0 { 170 | format!("{} {}", acc, words[*j as usize]) 171 | } else { 172 | acc 173 | } 174 | }); 175 | prepare_message(start_time(), "INFO", &text) 176 | }); 177 | send_stream(it); 178 | } 179 | -------------------------------------------------------------------------------- /tezedge-recorder/src/bin/replayer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | net::{TcpListener, SocketAddr, TcpStream}, 6 | time::Duration, 7 | io, 8 | }; 9 | use tezedge_recorder::{ 10 | common::MessageCategory, 11 | database::{DatabaseNew, DatabaseFetch, rocks::Db, MessagesFilter}, 12 | tables::message::{MessageFrontend, TezosMessage}, 13 | }; 14 | use pseudonode::{ChunkBuffer, Message, handshake}; 15 | use crypto::{ 16 | crypto_box::PrecomputedKey, 17 | nonce::{Nonce, NoncePair}, 18 | }; 19 | use tezos_messages::p2p::encoding::{ 20 | ack::AckMessage, metadata::MetadataMessage, peer::PeerMessageResponse, 21 | }; 22 | 23 | trait Replayer { 24 | fn replay_read(&mut self, id: u64) -> Option<()>; 25 | fn replay_write(&mut self, id: u64); 26 | } 27 | 28 | struct State { 29 | replayer: Rp, 30 | brief: Vec, 31 | read: bool, 32 | read_pos: usize, 33 | write_pos: usize, 34 | } 35 | 36 | impl Iterator for State { 37 | type Item = (u64, bool); 38 | 39 | fn next(&mut self) -> Option { 40 | if self.read { 41 | let id = self.brief[self.read_pos..].iter().find_map(|m| { 42 | if !m.incoming { 43 | Some(m.id) 44 | } else { 45 | None 46 | } 47 | })?; 48 | Some((id, true)) 49 | } else { 50 | let id = self.brief[self.write_pos..].iter().find_map(|m| { 51 | if m.incoming { 52 | Some(m.id) 53 | } else { 54 | None 55 | } 56 | })?; 57 | Some((id, false)) 58 | } 59 | } 60 | } 61 | 62 | impl State { 63 | pub fn new(brief: Vec, replayer: Rp) -> Option { 64 | if brief.len() < 6 { 65 | None 66 | } else { 67 | Some(State { 68 | replayer, 69 | brief, 70 | read: true, 71 | read_pos: 6, 72 | write_pos: 6, 73 | }) 74 | } 75 | } 76 | } 77 | 78 | impl State 79 | where 80 | Rp: Replayer, 81 | { 82 | pub fn run(self) { 83 | let mut s = self; 84 | while let Some((id, read)) = s.next() { 85 | if read { 86 | if s.replayer.replay_read(id).is_some() { 87 | s.read = s.brief.get(s.write_pos).map(|m| m.incoming).unwrap_or(false); 88 | } else { 89 | s.read = false; 90 | } 91 | s.read_pos = (id as usize) + 1; 92 | } else { 93 | s.replayer.replay_write(id); 94 | s.write_pos = (id as usize) + 1; 95 | s.read = s.brief.get(s.write_pos).map(|m| m.incoming).unwrap_or(false); 96 | } 97 | } 98 | /*for brief in &s.brief[6..] { 99 | let id = brief.id; 100 | if brief.incoming { 101 | s.replayer.replay_write(id); 102 | } else { 103 | s.replayer.replay_read(id).unwrap(); 104 | } 105 | }*/ 106 | } 107 | } 108 | 109 | pub struct SimpleReplayer { 110 | db: Db, 111 | stream: TcpStream, 112 | buffer: ChunkBuffer, 113 | key: PrecomputedKey, 114 | local: Nonce, 115 | remote: Nonce, 116 | } 117 | 118 | impl Replayer for SimpleReplayer { 119 | fn replay_read(&mut self, id: u64) -> Option<()> { 120 | let message = self.db.fetch_message(id).unwrap().unwrap(); 121 | let peer_message = match &message.message { 122 | &Some(TezosMessage::PeerMessage(ref v)) => v, 123 | _ => panic!(), 124 | }; 125 | 126 | let r = PeerMessageResponse::read_msg( 127 | &mut self.stream, 128 | &mut self.buffer, 129 | &self.key, 130 | self.remote.clone(), 131 | true, 132 | ); 133 | let (remote, msg) = match r { 134 | Ok(v) => v, 135 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 136 | log::info!("pending read {}", id); 137 | return None; 138 | }, 139 | Err(e) => { 140 | panic!("{:?}", e); 141 | }, 142 | }; 143 | log::info!("replay read {}", id); 144 | self.remote = remote; 145 | 146 | let _ = (peer_message, msg.message()); 147 | /*let read = serde_json::to_string(&msg.message()).unwrap(); 148 | let have = serde_json::to_string(peer_message).unwrap(); 149 | if read != have { 150 | log::error!("read: {:?}", msg.message()); 151 | log::error!("have: {:?}", peer_message); 152 | //panic!(); 153 | }*/ 154 | 155 | Some(()) 156 | } 157 | 158 | fn replay_write(&mut self, id: u64) { 159 | log::info!("replay write {}", id); 160 | 161 | let message = self.db.fetch_message(id).unwrap().unwrap(); 162 | let peer_message = match message.message { 163 | Some(TezosMessage::PeerMessage(v)) => v, 164 | _ => panic!(), 165 | }; 166 | 167 | let local = PeerMessageResponse::from(peer_message).write_msg( 168 | &mut self.stream, 169 | &self.key, 170 | self.local.clone(), 171 | ); 172 | self.local = local; 173 | } 174 | } 175 | 176 | fn main() { 177 | tracing_subscriber::fmt() 178 | .with_max_level(tracing::Level::INFO) 179 | .init(); 180 | 181 | let db = Db::open("/volume/debugger_db/tezedge", false, None, None).unwrap(); 182 | 183 | let mut filter = MessagesFilter::default(); 184 | filter.cursor = Some(0); 185 | filter.limit = Some(100_000); 186 | filter.direction = Some("forward".to_string()); 187 | let brief = db.fetch_messages(&filter).unwrap(); 188 | 189 | assert!(matches!(&brief[0].category, &MessageCategory::Connection)); 190 | assert!(!brief[0].incoming); 191 | assert!(matches!(&brief[1].category, &MessageCategory::Connection)); 192 | assert!(brief[1].incoming); 193 | 194 | assert!(matches!(&brief[2].category, &MessageCategory::Meta)); 195 | assert!(!brief[2].incoming); 196 | assert!(matches!(&brief[3].category, &MessageCategory::Meta)); 197 | assert!(brief[3].incoming); 198 | 199 | assert!(matches!(&brief[4].category, &MessageCategory::Ack)); 200 | assert!(!brief[4].incoming); 201 | assert!(matches!(&brief[5].category, &MessageCategory::Ack)); 202 | assert!(brief[5].incoming); 203 | 204 | let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], 9732))).unwrap(); 205 | let (mut stream, _) = listener.accept().unwrap(); 206 | stream 207 | .set_read_timeout(Some(Duration::from_millis(1_000))) 208 | .unwrap(); 209 | 210 | let version = match db.fetch_message(0).unwrap().unwrap().message.unwrap() { 211 | TezosMessage::ConnectionMessage(cm) => Some(cm.version().clone()), 212 | _ => None, 213 | }; 214 | let (key, NoncePair { local, remote }) = handshake::responder( 215 | 9732, 216 | &mut stream, 217 | include_str!("../../identity_i.json"), 218 | version.unwrap(), 219 | ); 220 | 221 | let mut buffer = ChunkBuffer::default(); 222 | let (remote, _msg) = 223 | MetadataMessage::read_msg(&mut stream, &mut buffer, &key, remote, false).unwrap(); 224 | let local = MetadataMessage::new(false, false).write_msg(&mut stream, &key, local); 225 | 226 | let (remote, _msg) = 227 | AckMessage::read_msg(&mut stream, &mut buffer, &key, remote, false).unwrap(); 228 | let local = AckMessage::Ack.write_msg(&mut stream, &key, local); 229 | 230 | let replayer = SimpleReplayer { 231 | db, 232 | stream, 233 | buffer: ChunkBuffer::default(), 234 | key, 235 | local, 236 | remote, 237 | }; 238 | State::new(brief, replayer).map(State::run); 239 | } 240 | -------------------------------------------------------------------------------- /tezedge-recorder/src/database/mock.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | path::Path, 6 | sync::Mutex, 7 | fs::File, 8 | io::{self, Write}, 9 | }; 10 | use anyhow::Result; 11 | #[rustfmt::skip] 12 | use super::{ 13 | // core traits 14 | Database, DatabaseNew, DatabaseFetch, 15 | // filters 16 | ConnectionsFilter, ChunksFilter, MessagesFilter, LogsFilter, 17 | // tables 18 | connection, chunk, message, node_log, 19 | }; 20 | 21 | pub struct Db { 22 | file: Mutex, 23 | } 24 | 25 | impl DatabaseNew for Db { 26 | type Error = io::Error; 27 | 28 | fn open

( 29 | path: P, 30 | log_full_text_index: bool, 31 | log_store_limit: Option, 32 | message_store_limit: Option, 33 | ) -> Result 34 | where 35 | P: AsRef, 36 | { 37 | let _ = (log_full_text_index, log_store_limit, message_store_limit); 38 | 39 | Ok(Db { 40 | file: Mutex::new(File::create(path)?), 41 | }) 42 | } 43 | } 44 | 45 | impl Database for Db { 46 | fn store_connection(&self, item: connection::Item) { 47 | self.file 48 | .lock() 49 | .unwrap() 50 | .write_fmt(format_args!("cn: {:?}", item)) 51 | .unwrap(); 52 | } 53 | 54 | fn update_connection(&self, item: connection::Item) { 55 | self.file 56 | .lock() 57 | .unwrap() 58 | .write_fmt(format_args!("cn_: {:?}", item)) 59 | .unwrap(); 60 | } 61 | 62 | fn store_chunk(&self, item: chunk::Item) { 63 | let (key, value) = item.split(); 64 | self.file 65 | .lock() 66 | .unwrap() 67 | .write_fmt(format_args!( 68 | "chunk: {}, length: {}", 69 | key, 70 | value.plain.len() 71 | )) 72 | .unwrap(); 73 | } 74 | 75 | fn store_message(&self, item: message::Item) { 76 | self.file 77 | .lock() 78 | .unwrap() 79 | .write_fmt(format_args!("message: {:?}", item.ty)) 80 | .unwrap(); 81 | } 82 | 83 | fn store_log(&self, item: node_log::Item) { 84 | self.file 85 | .lock() 86 | .unwrap() 87 | .write_fmt(format_args!("log: {:?}", item.level)) 88 | .unwrap(); 89 | } 90 | } 91 | 92 | impl DatabaseFetch for Db { 93 | fn fetch_connections( 94 | &self, 95 | filter: &ConnectionsFilter, 96 | ) -> Result, Self::Error> { 97 | let _ = filter; 98 | Ok(vec![]) 99 | } 100 | 101 | fn fetch_chunks_truncated( 102 | &self, 103 | filter: &ChunksFilter, 104 | ) -> Result, Self::Error> { 105 | let _ = filter; 106 | Ok(vec![]) 107 | } 108 | 109 | fn fetch_chunk(&self, key: &chunk::Key) -> Result, Self::Error> { 110 | let _ = key; 111 | Ok(None) 112 | } 113 | 114 | fn fetch_messages( 115 | &self, 116 | filter: &MessagesFilter, 117 | ) -> Result, Self::Error> { 118 | let _ = filter; 119 | Ok(vec![]) 120 | } 121 | 122 | fn fetch_message(&self, id: u64) -> Result, Self::Error> { 123 | let _ = id; 124 | Ok(None) 125 | } 126 | 127 | fn fetch_log(&self, filter: &LogsFilter) -> Result, Self::Error> { 128 | let _ = filter; 129 | Ok(vec![]) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tezedge-recorder/src/database/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | pub mod rocks; 5 | pub mod mock; 6 | pub mod search; 7 | 8 | mod sorted_intersect; 9 | 10 | use std::{error::Error, path::Path}; 11 | use serde::Deserialize; 12 | use super::{tables::*, common}; 13 | 14 | pub trait Database { 15 | fn store_connection(&self, item: connection::Item); 16 | fn update_connection(&self, item: connection::Item); 17 | fn store_chunk(&self, item: chunk::Item); 18 | fn store_message(&self, item: message::Item); 19 | fn store_log(&self, item: node_log::Item); 20 | } 21 | 22 | #[derive(Deserialize)] 23 | pub struct ConnectionsFilter { 24 | pub limit: Option, 25 | } 26 | 27 | #[derive(Deserialize)] 28 | pub struct ChunksFilter { 29 | pub limit: Option, 30 | pub cn: Option, 31 | } 32 | 33 | #[derive(Deserialize, Default)] 34 | pub struct MessagesFilter { 35 | pub direction: Option, 36 | pub limit: Option, 37 | pub cursor: Option, 38 | pub remote_addr: Option, 39 | pub source_type: Option, 40 | pub incoming: Option, 41 | pub types: Option, 42 | pub from: Option, 43 | pub to: Option, 44 | pub timestamp: Option, 45 | // compatibility 46 | pub node_name: Option, 47 | } 48 | 49 | #[derive(Deserialize)] 50 | pub struct LogsFilter { 51 | pub direction: Option, 52 | pub limit: Option, 53 | pub cursor: Option, 54 | pub log_level: Option, 55 | pub from: Option, 56 | pub to: Option, 57 | pub timestamp: Option, 58 | pub query: Option, 59 | // compatibility 60 | pub node_name: Option, 61 | } 62 | 63 | pub trait DatabaseFetch 64 | where 65 | Self: DatabaseNew, 66 | { 67 | fn fetch_connections( 68 | &self, 69 | filter: &ConnectionsFilter, 70 | ) -> Result, Self::Error>; 71 | 72 | fn fetch_chunks_truncated( 73 | &self, 74 | filter: &ChunksFilter, 75 | ) -> Result, Self::Error>; 76 | 77 | fn fetch_chunk(&self, key: &chunk::Key) -> Result, Self::Error>; 78 | 79 | fn fetch_messages( 80 | &self, 81 | filter: &MessagesFilter, 82 | ) -> Result, Self::Error>; 83 | 84 | fn fetch_message(&self, id: u64) -> Result, Self::Error>; 85 | 86 | fn fetch_log(&self, filter: &LogsFilter) -> Result, Self::Error>; 87 | } 88 | 89 | pub trait DatabaseNew 90 | where 91 | Self: Sized, 92 | { 93 | type Error: 'static + Send + Sync + Error; 94 | 95 | fn open

( 96 | path: P, 97 | log_full_text_index: bool, 98 | log_store_limit: Option, 99 | message_store_limit: Option, 100 | ) -> Result 101 | where 102 | P: AsRef; 103 | } 104 | -------------------------------------------------------------------------------- /tezedge-recorder/src/database/search.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | ops::DerefMut, 4 | path::Path, 5 | sync::{ 6 | Arc, Mutex, MutexGuard, TryLockError, 7 | atomic::{Ordering, AtomicBool}, 8 | }, 9 | thread, 10 | }; 11 | use tantivy::{ 12 | directory::MmapDirectory, schema, Index, IndexWriter, Document, ReloadPolicy, 13 | query::QueryParser, collector::TopDocs, TantivyError, 14 | }; 15 | 16 | pub struct LogIndexer { 17 | index: Index, 18 | writer_thread: Option>, 19 | commit_state: Arc, 20 | } 21 | 22 | #[derive(Default)] 23 | struct DocumentQueue { 24 | start_id: u64, 25 | messages: Vec, 26 | } 27 | 28 | impl DocumentQueue { 29 | fn enqueue(&mut self, id: u64, msg: &str) { 30 | if self.messages.is_empty() { 31 | self.start_id = id; 32 | } 33 | self.messages.push(msg.to_string()); 34 | } 35 | 36 | fn is_empty(&self) -> bool { 37 | self.messages.is_empty() 38 | } 39 | 40 | fn drain(self) -> impl Iterator { 41 | let id = self.start_id; 42 | self.messages 43 | .into_iter() 44 | .enumerate() 45 | .map(move |(i, msg)| (id + (i as u64), msg)) 46 | } 47 | } 48 | 49 | struct CommitState { 50 | id_field: schema::Field, 51 | message_field: schema::Field, 52 | queue: Arc>, 53 | dirty: AtomicBool, 54 | running: AtomicBool, 55 | writer: Mutex, 56 | } 57 | 58 | impl CommitState { 59 | fn run(&self) { 60 | use std::time::{Instant, Duration}; 61 | 62 | const TICK: Duration = Duration::from_millis(1000); 63 | 64 | fn align(duration: Duration, tick: Duration) -> Duration { 65 | let duration = duration.as_micros() as u64; 66 | let tick = tick.as_micros() as u64; 67 | Duration::from_micros(tick - (duration % tick)) 68 | } 69 | 70 | loop { 71 | let commit_begin = Instant::now(); 72 | if self.dirty.fetch_and(false, Ordering::SeqCst) { 73 | let mut writer = self.finalize(); 74 | match writer.commit() { 75 | Ok(_) => (), 76 | Err(error) => log::error!("cannot commit log index: {:?}", error), 77 | } 78 | } 79 | if !self.running.load(Ordering::Relaxed) { 80 | break; 81 | } 82 | let commit_end = Instant::now(); 83 | let elapsed = commit_end.duration_since(commit_begin); 84 | thread::sleep(align(elapsed, TICK)); 85 | } 86 | } 87 | 88 | fn finalize(&self) -> MutexGuard { 89 | use std::mem; 90 | 91 | let writer = self.writer.lock().unwrap(); 92 | let mut queue_lock = self.queue.lock().unwrap(); 93 | if !queue_lock.is_empty() { 94 | let queue = mem::replace(queue_lock.deref_mut(), DocumentQueue::default()); 95 | drop(queue_lock); 96 | for (id, message) in queue.drain() { 97 | writer.add_document(self.prepare_doc(&message, id)); 98 | } 99 | self.dirty.fetch_or(true, Ordering::SeqCst); 100 | } 101 | writer 102 | } 103 | 104 | fn prepare_doc(&self, message: &str, id: u64) -> Document { 105 | let mut doc = Document::default(); 106 | doc.add_text(self.message_field, message); 107 | doc.add_u64(self.id_field, id); 108 | doc 109 | } 110 | } 111 | 112 | impl Drop for LogIndexer { 113 | fn drop(&mut self) { 114 | if let Some(log_writer) = self.writer_thread.take() { 115 | self.commit_state.running.store(false, Ordering::Relaxed); 116 | match log_writer.join() { 117 | Ok(()) => (), 118 | Err(error) => log::error!("error joining log indexer thread: {:?}", error), 119 | } 120 | } 121 | } 122 | } 123 | 124 | impl LogIndexer { 125 | const HEAP_BYTES: usize = 32 * 1024 * 1024; // 32Mb 126 | 127 | pub fn try_new

(path: P) -> Result 128 | where 129 | P: AsRef, 130 | { 131 | let mut schema_builder = schema::Schema::builder(); 132 | schema_builder.add_text_field("message", schema::TEXT); 133 | schema_builder.add_text_field("id", schema::STORED); 134 | let schema = schema_builder.build(); 135 | 136 | let _ = fs::create_dir_all(&path); 137 | let index = Index::open_or_create(MmapDirectory::open(path)?, schema.clone())?; 138 | let message_field = schema.get_field("message").unwrap(); 139 | let id_field = schema.get_field("id").unwrap(); 140 | let queue = Default::default(); 141 | let commit_state = Arc::new(CommitState { 142 | dirty: AtomicBool::new(false), 143 | running: AtomicBool::new(true), 144 | writer: Mutex::new(index.writer(Self::HEAP_BYTES)?), 145 | id_field, 146 | message_field, 147 | queue, 148 | }); 149 | 150 | let writer_thread = { 151 | let commit_state = commit_state.clone(); 152 | Some(thread::spawn(move || commit_state.run())) 153 | }; 154 | 155 | Ok(LogIndexer { 156 | index, 157 | writer_thread, 158 | commit_state, 159 | }) 160 | } 161 | 162 | pub fn write(&self, message: &str, id: u64) { 163 | use std::mem; 164 | 165 | match self.commit_state.writer.try_lock() { 166 | Ok(writer) => { 167 | let mut queue_lock = self.commit_state.queue.lock().unwrap(); 168 | let queue = mem::replace(queue_lock.deref_mut(), DocumentQueue::default()); 169 | drop(queue_lock); 170 | for (id, message) in queue.drain() { 171 | writer.add_document(self.commit_state.prepare_doc(&message, id)); 172 | } 173 | writer.add_document(self.commit_state.prepare_doc(message, id)); 174 | self.commit_state.dirty.fetch_or(true, Ordering::SeqCst); 175 | }, 176 | Err(TryLockError::Poisoned(e)) => Err::<(), _>(e).unwrap(), 177 | Err(TryLockError::WouldBlock) => { 178 | self.commit_state.queue.lock().unwrap().enqueue(id, message) 179 | }, 180 | } 181 | } 182 | 183 | pub fn read( 184 | &self, 185 | query: &str, 186 | limit: usize, 187 | ) -> Result, TantivyError> { 188 | let reader = self 189 | .index 190 | .reader_builder() 191 | .reload_policy(ReloadPolicy::OnCommit) 192 | .try_into()?; 193 | let searcher = reader.searcher(); 194 | let query_parser = 195 | QueryParser::for_index(&self.index, vec![self.commit_state.message_field]); 196 | let query = query_parser.parse_query(query)?; 197 | let id_field = self.commit_state.id_field; 198 | let it = searcher 199 | .search(&query, &TopDocs::with_limit(limit))? 200 | .into_iter() 201 | .filter_map(move |(score, doc_address)| { 202 | let retrieved_doc = searcher.doc(doc_address).ok()?; 203 | let f = retrieved_doc 204 | .field_values() 205 | .iter() 206 | .find(|x| x.field() == id_field)?; 207 | match f.value() { 208 | &schema::Value::U64(ref id) => Some((score, *id)), 209 | _ => None, 210 | } 211 | }); 212 | Ok(it) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /tezedge-recorder/src/database/sorted_intersect.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | /// Module implements sorted intersection algorithm 5 | /// Intersection is an *set* operation returning values 6 | /// that are present in both sets 7 | /// For sets: 8 | /// - A = {1,2,3,4,5} 9 | /// - B = {3,4,5,6,7} 10 | /// Intersection of A and B is set {3,4,5} 11 | /// 12 | /// Sorted intersect works on any sorted vectors. 13 | 14 | // TODO: try it 15 | /*use generic_array::{GenericArray, ArrayLength}; 16 | 17 | pub struct SortedIntersect 18 | where 19 | S: SortedIntersectSource, 20 | { 21 | inner: S, 22 | } 23 | 24 | pub trait SortedIntersectSource { 25 | type Item; 26 | type Width: ArrayLength>; 27 | 28 | fn len(&self) -> usize; 29 | fn next_all(&mut self) -> &mut GenericArray, Self::Width>; 30 | fn next_at(&mut self, index: usize) -> Option; 31 | } 32 | 33 | impl SortedIntersect 34 | where 35 | S: SortedIntersectSource, 36 | { 37 | pub fn new(source: S) -> Self { 38 | SortedIntersect { 39 | inner: source, 40 | } 41 | } 42 | } 43 | 44 | impl Iterator for SortedIntersect 45 | where 46 | S: SortedIntersectSource, 47 | { 48 | type Item = S::Item; 49 | 50 | fn next(&mut self) -> Option { 51 | let _ = &mut self.inner; 52 | None 53 | } 54 | }*/ 55 | 56 | /// For given vector of *sorted* iterators, return new vector containing values 57 | /// present in *every* iterator 58 | pub fn sorted_intersect(iters: &mut [I], limit: usize, forward: bool) -> Vec 59 | where 60 | I: Iterator, 61 | I::Item: Ord, 62 | { 63 | let mut ret = Default::default(); 64 | if iters.is_empty() { 65 | return ret; 66 | } else if iters.len() == 1 { 67 | let iter = iters.iter_mut().next().unwrap(); 68 | ret.extend(iter.take(limit)); 69 | return ret; 70 | } 71 | let mut heap = Vec::with_capacity(iters.len()); 72 | // Fill the heap with values 73 | if !fill_heap(iters.iter_mut(), &mut heap, forward) { 74 | // Hit an exhausted iterator, finish 75 | return ret; 76 | } 77 | 78 | while ret.len() < limit { 79 | if is_hit(&heap) { 80 | // We hit intersected item 81 | if let Some((item, _)) = heap.pop() { 82 | // Push it into the intersect values 83 | ret.push(item); 84 | // Clear the rest of the heap 85 | heap.clear(); 86 | // Build a new heap from new values 87 | if !fill_heap(iters.iter_mut(), &mut heap, forward) { 88 | // Hit an exhausted iterator, finish 89 | return ret; 90 | } 91 | } else { 92 | // Hit an exhausted iterator, finish 93 | return ret; 94 | } 95 | } else { 96 | // Remove max element from the heap 97 | if let Some((_, iter_num)) = heap.pop() { 98 | if let Some(item) = iters[iter_num].next() { 99 | // Insert replacement from the corresponding iterator to heap 100 | heap.push((item, iter_num)); 101 | heapify(&mut heap, forward); 102 | } else { 103 | // Hit an exhausted iterator, finish 104 | return ret; 105 | } 106 | } else { 107 | // Hit an exhausted iterator, finish 108 | return ret; 109 | } 110 | } 111 | } 112 | 113 | ret 114 | } 115 | 116 | /// Create heap out of vector 117 | fn heapify(heap: &mut Vec<(Item, usize)>, forward: bool) { 118 | heap.sort_by(|(a, _), (b, _)| { 119 | if forward { 120 | a.cmp(b).reverse() 121 | } else { 122 | a.cmp(b) 123 | } 124 | }); 125 | } 126 | 127 | /// Fill heap with new values 128 | fn fill_heap< 129 | 'a, 130 | Item: Ord, 131 | Inner: 'a + Iterator, 132 | Outer: Iterator, 133 | >( 134 | iters: Outer, 135 | heap: &mut Vec<(Inner::Item, usize)>, 136 | forward: bool, 137 | ) -> bool { 138 | for (i, iter) in iters.enumerate() { 139 | let value = iter.next(); 140 | if let Some(value) = value { 141 | heap.push((value, i)) 142 | } else { 143 | return false; 144 | } 145 | } 146 | heapify(heap, forward); 147 | true 148 | } 149 | 150 | /// Check if top of the heap is a hit, meaning if it should be contained in the 151 | /// resulting set 152 | fn is_hit(heap: &[(Item, usize)]) -> bool { 153 | let value = heap.iter().next().map(|(value, _)| { 154 | heap.iter() 155 | .fold((value, true), |(a, eq), (b, _)| (b, eq & a.eq(b))) 156 | }); 157 | 158 | matches!(value, Some((_, true))) 159 | } 160 | -------------------------------------------------------------------------------- /tezedge-recorder/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | #![forbid(unsafe_code)] 5 | 6 | pub use crypto; 7 | pub use tezos_messages; 8 | 9 | pub mod common; 10 | pub mod tables; 11 | mod system; 12 | mod log_client; 13 | mod processor; 14 | pub mod main_loop; 15 | pub mod database; 16 | mod server; 17 | 18 | pub use self::system::System; 19 | -------------------------------------------------------------------------------- /tezedge-recorder/src/log_client.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | sync::{ 6 | Arc, 7 | atomic::{AtomicBool, Ordering}, 8 | }, 9 | thread, io, 10 | net::UdpSocket, 11 | time::Duration, 12 | }; 13 | use super::{database::Database, tables::node_log}; 14 | 15 | pub fn spawn( 16 | port: u16, 17 | db: Arc, 18 | running: Arc, 19 | ) -> io::Result> 20 | where 21 | Db: Database + Sync + Send + 'static, 22 | { 23 | let socket = UdpSocket::bind(("0.0.0.0", port))?; 24 | socket.set_read_timeout(Some(Duration::from_secs(5)))?; 25 | Ok(thread::spawn(move || { 26 | let mut buffer = [0u8; 0x10000]; 27 | while running.load(Ordering::Relaxed) { 28 | match socket.recv(&mut buffer) { 29 | Ok(read) => { 30 | if let Ok(log) = std::str::from_utf8(&buffer[..read]) { 31 | let msg = syslog_loose::parse_message(log); 32 | let item = node_log::Item::from(msg); 33 | db.store_log(item); 34 | } 35 | }, 36 | Err(error) => { 37 | if error.kind() == io::ErrorKind::WouldBlock { 38 | log::trace!("receiving log timeout"); 39 | } else { 40 | log::error!("receiving log error: {}", error) 41 | } 42 | }, 43 | } 44 | } 45 | })) 46 | } 47 | -------------------------------------------------------------------------------- /tezedge-recorder/src/main_loop.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | collections::HashMap, 6 | net::SocketAddr, 7 | sync::{ 8 | Arc, 9 | atomic::{Ordering, AtomicBool}, 10 | }, 11 | }; 12 | use anyhow::Result; 13 | use bpf_recorder::{BpfModuleClient, SnifferEvent, Command, EventId, SocketId}; 14 | 15 | use super::{ 16 | processor::Connection, 17 | database::{Database, DatabaseNew, DatabaseFetch}, 18 | system::System, 19 | }; 20 | 21 | pub fn run(system: &mut System, running: Arc) -> Result<()> 22 | where 23 | Db: Database + DatabaseNew + DatabaseFetch + Sync + Send + 'static, 24 | { 25 | let (client, mut rb) = BpfModuleClient::new_sync(system.sniffer_path())?; 26 | let mut list = ConnectionList::new(client, system); 27 | list.watching()?; 28 | 29 | while running.load(Ordering::Relaxed) { 30 | let events = rb.read_blocking::(&running)?; 31 | for event in events { 32 | match event { 33 | SnifferEvent::Bind { id, address } => { 34 | // TODO: remove old connections on this port 35 | if let Err(error) = list.system.handle_bind(id.socket_id.pid, address.port()) { 36 | log::error!("failed to handle bind syscall: {}", error); 37 | } 38 | }, 39 | SnifferEvent::Listen { id } => { 40 | let _ = id; 41 | }, 42 | SnifferEvent::Connect { id, address } => { 43 | list.handle_connection(id, address, false); 44 | }, 45 | SnifferEvent::Accept { 46 | id, 47 | address, 48 | listen_on_fd, 49 | } => { 50 | let _ = listen_on_fd; 51 | list.handle_connection(id, address, true); 52 | }, 53 | SnifferEvent::Data { 54 | id, 55 | data, 56 | net, 57 | incoming, 58 | } => { 59 | if !data.is_empty() { 60 | list.handle_data(id, data, net, incoming); 61 | } 62 | }, 63 | SnifferEvent::Close { id } => { 64 | list.handle_close(id); 65 | }, 66 | SnifferEvent::GetFd { id } => { 67 | list.handle_get_fd(id); 68 | }, 69 | SnifferEvent::Debug { id, msg } => { 70 | log::warn!("{} {}", id, msg); 71 | }, 72 | } 73 | } 74 | } 75 | 76 | Ok(()) 77 | } 78 | 79 | struct ConnectionList<'a, Db> { 80 | client: BpfModuleClient, 81 | system: &'a mut System, 82 | connections: HashMap>, 83 | } 84 | 85 | impl<'a, Db> ConnectionList<'a, Db> 86 | where 87 | Db: Database + DatabaseNew + DatabaseFetch + Sync + Send + 'static, 88 | { 89 | fn new(client: BpfModuleClient, system: &'a mut System) -> Self { 90 | ConnectionList { 91 | client, 92 | system, 93 | connections: HashMap::new(), 94 | } 95 | } 96 | 97 | fn watching(&mut self) -> Result<()> { 98 | for p2p_config in self.system.p2p_configs() { 99 | self.client.send_command(Command::WatchPort { 100 | port: p2p_config.port, 101 | })?; 102 | } 103 | 104 | Ok(()) 105 | } 106 | 107 | fn handle_connection(&mut self, event_id: EventId, address: SocketAddr, incoming: bool) { 108 | let socket_id = event_id.socket_id; 109 | let pid = socket_id.pid; 110 | let fd = socket_id.fd; 111 | if !self.system.should_ignore(&address) { 112 | if let Some((info, db)) = self.system.get_mut(pid) { 113 | let connection = Connection::new(address, incoming, info.identity(), db); 114 | if let Some(old) = self.connections.insert(socket_id, connection) { 115 | old.join(); 116 | } 117 | return; 118 | } 119 | } 120 | match self 121 | .client 122 | .send_command(Command::IgnoreConnection { pid, fd }) 123 | { 124 | Ok(()) => (), 125 | Err(error) => { 126 | log::error!( 127 | "cannot ignore connection id: {}, error: {}", 128 | socket_id, 129 | error 130 | ) 131 | }, 132 | } 133 | } 134 | 135 | fn handle_data(&mut self, id: EventId, payload: Vec, net: bool, incoming: bool) { 136 | if payload.len() > 0x1000000 { 137 | log::warn!("received from ring buffer big payload {}", payload.len()); 138 | } 139 | if let Some(connection) = self.connections.get_mut(&id.socket_id) { 140 | connection.handle_data(&payload, net, incoming); 141 | } else { 142 | log::debug!("failed to handle data, connection does not exist: {}", id); 143 | } 144 | } 145 | 146 | fn handle_get_fd(&mut self, id: EventId) { 147 | let socket_id = id.socket_id; 148 | if let Some(c) = self.connections.remove(&socket_id) { 149 | c.warn_fd_changed(); 150 | c.join(); 151 | } 152 | } 153 | 154 | fn handle_close(&mut self, id: EventId) { 155 | let socket_id = id.socket_id; 156 | if let Some(old) = self.connections.remove(&socket_id) { 157 | old.join(); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /tezedge-recorder/src/processor/chunk_parser/buffer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | pub struct Buffer { 5 | counter: u64, 6 | buffer: Vec, 7 | } 8 | 9 | impl Default for Buffer { 10 | fn default() -> Self { 11 | Buffer { 12 | counter: 0, 13 | buffer: Vec::with_capacity(0x10000), 14 | } 15 | } 16 | } 17 | 18 | impl Buffer { 19 | pub fn handle_data(&mut self, payload: &[u8]) { 20 | if self.have_chunk().is_some() { 21 | log::debug!( 22 | "append new data while not consumed chunk, buffer len: {}, counter: {}", 23 | self.buffer.len(), 24 | self.counter, 25 | ); 26 | } 27 | self.buffer.extend_from_slice(payload); 28 | } 29 | 30 | pub fn remaining(&self) -> usize { 31 | self.buffer.len() 32 | } 33 | 34 | // length (including 2 bytes header) of the chunk at offset 35 | fn len(&self, offset: usize) -> Option { 36 | use std::convert::TryFrom; 37 | 38 | if self.buffer.len() >= offset + 2 { 39 | let b = <[u8; 2]>::try_from(&self.buffer[offset..(offset + 2)]).unwrap(); 40 | Some(u16::from_be_bytes(b) as usize + 2) 41 | } else { 42 | None 43 | } 44 | } 45 | 46 | pub fn have_chunk(&self) -> Option<&[u8]> { 47 | let len = self.len(0)?; 48 | if self.buffer.len() >= len { 49 | Some(&self.buffer[..len]) 50 | } else { 51 | None 52 | } 53 | } 54 | 55 | pub fn cleanup(&mut self) -> Option<(u64, Vec)> { 56 | use std::mem; 57 | 58 | if self.buffer.is_empty() { 59 | return None; 60 | } 61 | 62 | let counter = self.counter; 63 | self.counter += 1; 64 | Some((counter, mem::replace(&mut self.buffer, Vec::new()))) 65 | } 66 | } 67 | 68 | impl Iterator for Buffer { 69 | type Item = (u64, Vec); 70 | 71 | fn next(&mut self) -> Option { 72 | let len = self.len(0)?; 73 | if self.buffer.len() < len { 74 | None 75 | } else { 76 | let counter = self.counter; 77 | self.counter += 1; 78 | 79 | let mut new = vec![0; len]; 80 | new.copy_from_slice(&self.buffer[..len]); 81 | assert!(self.buffer.as_ptr() as usize != new.as_ptr() as usize); 82 | 83 | if self.buffer.len() > len { 84 | let mut remaining = vec![0; self.buffer.len() - len]; 85 | remaining.copy_from_slice(&self.buffer[len..]); 86 | self.buffer.clear(); 87 | self.buffer = remaining; 88 | } else { 89 | self.buffer = Vec::with_capacity(0x10000); 90 | } 91 | 92 | Some((counter, new)) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tezedge-recorder/src/processor/chunk_parser/key.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use crypto::{ 5 | crypto_box::PrecomputedKey, 6 | CryptoError, 7 | nonce::{NoncePair, Nonce, generate_nonces}, 8 | }; 9 | use super::{Identity, common::Initiator}; 10 | 11 | #[derive(Clone)] 12 | pub struct Keys { 13 | pub local: Key, 14 | pub remote: Key, 15 | } 16 | 17 | #[derive(Clone)] 18 | pub struct Key { 19 | key: PrecomputedKey, 20 | nonce: Nonce, 21 | } 22 | 23 | impl Keys { 24 | pub fn new( 25 | identity: &Identity, 26 | local: &[u8], 27 | remote: &[u8], 28 | initiator: Initiator, 29 | ) -> Result { 30 | use crypto::crypto_box::CryptoKey; 31 | 32 | if local.len() < 36 { 33 | return Err(CryptoError::InvalidKeySize { 34 | expected: 32, 35 | actual: local.len().max(4) - 4, 36 | }); 37 | } 38 | 39 | if remote.len() < 36 { 40 | return Err(CryptoError::InvalidKeySize { 41 | expected: 32, 42 | actual: remote.len().max(4) - 4, 43 | }); 44 | } 45 | 46 | // check if the identity belong to one of the parties 47 | if identity.public_key.as_ref() != local[4..36].as_ref() { 48 | return Err(CryptoError::InvalidKey { 49 | reason: "The communication does not belong to the local node".to_string(), 50 | }); 51 | }; 52 | 53 | let pk = CryptoKey::from_bytes(&remote[4..36]).unwrap(); 54 | let sk = CryptoKey::from_bytes(&identity.secret_key).unwrap(); 55 | 56 | let NoncePair { local, remote } = 57 | generate_nonces(local, remote, initiator.incoming()).unwrap(); 58 | let key = PrecomputedKey::precompute(&pk, &sk); 59 | Ok(Keys { 60 | local: Key { 61 | key: key.clone(), 62 | nonce: local, 63 | }, 64 | remote: Key { key, nonce: remote }, 65 | }) 66 | } 67 | } 68 | 69 | impl Key { 70 | pub fn decrypt(&mut self, payload: &[u8]) -> Result, CryptoError> { 71 | let plain = self.key.decrypt(&payload[2..], &self.nonce)?; 72 | self.nonce = self.nonce.increment(); 73 | Ok(plain) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tezedge-recorder/src/processor/chunk_parser/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use super::{common, tables, Identity}; 5 | 6 | mod buffer; 7 | mod key; 8 | mod state; 9 | mod parser; 10 | 11 | pub use self::parser::{Handshake, HandshakeOutput, HandshakeDone, ChunkHandler}; 12 | -------------------------------------------------------------------------------- /tezedge-recorder/src/processor/connection.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{net::SocketAddr, sync::Arc}; 5 | use either::Either; 6 | use super::{ 7 | chunk_parser::{Handshake, HandshakeOutput, HandshakeDone, ChunkHandler}, 8 | message_parser::MessageParser, 9 | Identity, Database, 10 | common::{Local, Remote, Initiator}, 11 | tables::connection, 12 | }; 13 | 14 | pub struct Connection { 15 | state: Option>, 16 | item: connection::Item, 17 | db: Arc, 18 | } 19 | 20 | #[allow(clippy::large_enum_variant)] 21 | enum ConnectionState { 22 | Handshake(Handshake), 23 | HandshakeDone { 24 | local: HandshakeDone, 25 | local_mp: MessageParser, 26 | remote: HandshakeDone, 27 | remote_mp: MessageParser, 28 | }, 29 | } 30 | 31 | impl Connection 32 | where 33 | Db: Database, 34 | { 35 | pub fn new(remote_addr: SocketAddr, incoming: bool, identity: Identity, db: Arc) -> Self { 36 | let item = connection::Item::new(Initiator::new(incoming), remote_addr); 37 | let state = ConnectionState::Handshake(Handshake::new(&item.key(), identity)); 38 | Connection { 39 | state: Some(state), 40 | item, 41 | db, 42 | } 43 | } 44 | 45 | pub fn handle_data(&mut self, payload: &[u8], net: bool, incoming: bool) { 46 | let state = match self.state.take().unwrap() { 47 | ConnectionState::Handshake(h) => { 48 | match h.handle_data(payload, net, incoming, &mut self.item) { 49 | Either::Left(h) => ConnectionState::Handshake(h), 50 | Either::Right(HandshakeOutput { 51 | local, 52 | l_chunk, 53 | remote, 54 | r_chunk, 55 | }) => { 56 | let mut local_mp = MessageParser::new(self.db.clone()); 57 | let mut remote_mp = MessageParser::new(self.db.clone()); 58 | self.db.store_connection(self.item.clone()); 59 | if let Some(chunk) = l_chunk { 60 | local_mp.handle_chunk(chunk, &mut self.item); 61 | } 62 | if let Some(chunk) = r_chunk { 63 | remote_mp.handle_chunk(chunk, &mut self.item); 64 | } 65 | ConnectionState::HandshakeDone { 66 | local, 67 | local_mp, 68 | remote, 69 | remote_mp, 70 | } 71 | }, 72 | } 73 | }, 74 | ConnectionState::HandshakeDone { 75 | local, 76 | mut local_mp, 77 | remote, 78 | mut remote_mp, 79 | } => { 80 | if !incoming { 81 | ConnectionState::HandshakeDone { 82 | local: local.handle_data(payload, net, &mut self.item, &mut local_mp), 83 | local_mp, 84 | remote, 85 | remote_mp, 86 | } 87 | } else { 88 | ConnectionState::HandshakeDone { 89 | local, 90 | local_mp, 91 | remote: remote.handle_data(payload, net, &mut self.item, &mut remote_mp), 92 | remote_mp, 93 | } 94 | } 95 | }, 96 | }; 97 | self.state = Some(state); 98 | } 99 | 100 | pub fn warn_fd_changed(&self) { 101 | if !matches!(&self.state, &Some(ConnectionState::Handshake(ref h)) if h.is_empty()) { 102 | log::warn!( 103 | "fd of: {} has took for other file, the connection is closed", 104 | self.item.remote_addr 105 | ); 106 | } 107 | } 108 | 109 | pub fn join(self) {} 110 | } 111 | -------------------------------------------------------------------------------- /tezedge-recorder/src/processor/message_parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::sync::Arc; 5 | use super::{ 6 | chunk_parser::ChunkHandler, 7 | Database, 8 | tables::{connection, chunk, message}, 9 | }; 10 | 11 | pub struct MessageParser { 12 | builder: Option, 13 | error: bool, 14 | db: Arc, 15 | } 16 | 17 | impl MessageParser 18 | where 19 | Db: Database, 20 | { 21 | pub fn new(db: Arc) -> Self { 22 | MessageParser { 23 | builder: None, 24 | error: false, 25 | db, 26 | } 27 | } 28 | } 29 | 30 | impl ChunkHandler for MessageParser 31 | where 32 | Db: Database, 33 | { 34 | fn handle_chunk(&mut self, chunk: chunk::Item, cn: &mut connection::Item) { 35 | use std::convert::TryFrom; 36 | use self::message::MessageBuilder; 37 | use super::common::MessageKind; 38 | 39 | let too_small = match chunk.counter { 40 | 0 => chunk.plain.len() < 82, 41 | 1 => chunk.plain.len() < 2, 42 | 2 => chunk.plain.is_empty(), 43 | _ => { 44 | if self.builder.is_some() { 45 | chunk.plain.is_empty() 46 | } else { 47 | chunk.plain.len() < 6 48 | } 49 | }, 50 | }; 51 | 52 | if self.error || too_small { 53 | self.error = true; 54 | if !chunk.bytes.is_empty() { 55 | self.db.store_chunk(chunk); 56 | } 57 | return; 58 | } 59 | 60 | let sender = &chunk.sender; 61 | 62 | let message = match chunk.counter { 63 | 0 => Some(MessageBuilder::connection_message().build(&sender, &cn)), 64 | 1 => Some(MessageBuilder::metadata_message().build(&sender, &cn)), 65 | 2 => Some(MessageBuilder::acknowledge_message().build(&sender, &cn)), 66 | c => { 67 | let building_result = self 68 | .builder 69 | .take() 70 | .and_then(|builder| { 71 | // we already have some builder 72 | if chunk.bytes.len() < 6 { 73 | return Some(builder); 74 | } 75 | let bytes = &chunk.bytes[..6]; 76 | let len = u32::from_be_bytes(<[u8; 4]>::try_from(&bytes[..4]).unwrap()); 77 | let tag = u16::from_be_bytes(<[u8; 2]>::try_from(&bytes[4..]).unwrap()); 78 | // but first 6 bytes seems it is a new message 79 | // the probability that arbitrary 6 bytes pass such check is: 80 | // `(20 / 2 ^ 16) * (1 << 24) / (1 << 32)`, fairly small 81 | if MessageKind::from_tag(tag).valid_tag() && len < 1 << 24 { 82 | cn.add_comment().incoming_suspicious = Some(c); 83 | // return here `None` to reset the builder 84 | Some(builder) 85 | } else { 86 | Some(builder) 87 | } 88 | }) 89 | .unwrap_or_else(|| { 90 | let six_bytes = <[u8; 6]>::try_from(&chunk.plain[0..6]).unwrap(); 91 | MessageBuilder::peer_message(six_bytes, chunk.counter) 92 | }) 93 | .link_chunk(chunk.plain.len()); 94 | match building_result { 95 | Ok(builder_full) => Some(builder_full.build(&sender, &cn)), 96 | Err(builder) => { 97 | self.builder = builder; 98 | None 99 | }, 100 | } 101 | }, 102 | }; 103 | 104 | self.db.store_chunk(chunk); 105 | if let Some(message) = message { 106 | self.db.store_message(message); 107 | } 108 | } 109 | 110 | fn update_cn(&mut self, cn: &connection::Item) { 111 | self.db.update_connection(cn.clone()); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tezedge-recorder/src/processor/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use super::{system::Identity, database::Database, tables, common}; 5 | 6 | mod chunk_parser; 7 | mod message_parser; 8 | mod connection; 9 | 10 | pub use self::connection::Connection; 11 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use super::common; 5 | 6 | pub mod connection; 7 | pub mod chunk; 8 | pub mod message; 9 | pub mod node_log; 10 | 11 | mod secondary_indexes; 12 | pub use self::secondary_indexes::*; 13 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/node_log.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{str::FromStr, convert::TryFrom}; 5 | use thiserror::Error; 6 | use serde::{Serialize, Deserialize}; 7 | use storage::persistent::{BincodeEncoded, KeyValueSchema, database::RocksDbKeyValueSchema}; 8 | 9 | #[derive(Serialize, Deserialize)] 10 | pub struct ItemWithId { 11 | pub id: u64, 12 | pub level: LogLevel, 13 | #[serde(alias = "time")] 14 | pub timestamp: u128, 15 | #[serde(alias = "module")] 16 | pub section: String, 17 | #[serde(alias = "msg")] 18 | pub message: String, 19 | } 20 | 21 | impl ItemWithId { 22 | pub fn new(item: Item, id: u64) -> Self { 23 | ItemWithId { 24 | id, 25 | level: item.level, 26 | timestamp: item.timestamp, 27 | section: item.section, 28 | message: item.message, 29 | } 30 | } 31 | } 32 | 33 | /// Received logs saved in the database 34 | #[derive(Debug, Clone, Serialize, Deserialize)] 35 | pub struct Item { 36 | pub level: LogLevel, 37 | pub timestamp: u128, 38 | pub section: String, 39 | pub message: String, 40 | } 41 | 42 | #[repr(u8)] 43 | #[derive(Debug, Clone, Serialize, Deserialize)] 44 | pub enum LogLevel { 45 | Trace = 0x1 << 0, 46 | Debug = 0x1 << 1, 47 | Info = 0x1 << 2, 48 | Notice = 0x1 << 3, 49 | Warning = 0x1 << 4, 50 | Error = 0x1 << 5, 51 | Fatal = 0x1 << 6, 52 | } 53 | 54 | #[derive(Error, Debug)] 55 | pub enum ParseLogLevelError { 56 | #[error("Invalid log level name {}", _0)] 57 | InvalidName(String), 58 | #[error("Invalid log level value {}", _0)] 59 | InvalidValue(u8), 60 | } 61 | 62 | impl FromStr for LogLevel { 63 | type Err = ParseLogLevelError; 64 | 65 | fn from_str(level: &str) -> Result { 66 | let level = level.to_lowercase(); 67 | Ok(match level.as_ref() { 68 | "trace" => LogLevel::Trace, 69 | "debug" => LogLevel::Debug, 70 | "info" => LogLevel::Info, 71 | "notice" => LogLevel::Notice, 72 | "warn" | "warning" => LogLevel::Warning, 73 | "error" => LogLevel::Error, 74 | "fatal" => LogLevel::Fatal, 75 | _ => return Err(ParseLogLevelError::InvalidName(level)), 76 | }) 77 | } 78 | } 79 | 80 | impl TryFrom for LogLevel { 81 | type Error = ParseLogLevelError; 82 | 83 | fn try_from(value: u8) -> Result { 84 | match value { 85 | x if x == Self::Trace as u8 => Ok(Self::Trace), 86 | x if x == Self::Debug as u8 => Ok(Self::Debug), 87 | x if x == Self::Info as u8 => Ok(Self::Info), 88 | x if x == Self::Notice as u8 => Ok(Self::Notice), 89 | x if x == Self::Warning as u8 => Ok(Self::Warning), 90 | x if x == Self::Error as u8 => Ok(Self::Error), 91 | x if x == Self::Fatal as u8 => Ok(Self::Fatal), 92 | x => Err(ParseLogLevelError::InvalidValue(x)), 93 | } 94 | } 95 | } 96 | 97 | impl From> for Item 98 | where 99 | S: AsRef + Ord + PartialEq + Clone, 100 | { 101 | /// Create LogMessage from received syslog message 102 | /// Syslog messages are of format: 103 | /// <27>1 2020-06-24T10:32:37.026683+02:00 Ubuntu-1910-eoan-64-minimal 451e91e7df18 1482 451e91e7df18 - Jun 24 08:32:37.026 INFO Blacklisting IP because peer failed at bootstrap process, ip: 104.248.136.94 104 | // TODO: handle error 105 | fn from(msg: syslog_loose::Message) -> Self { 106 | use std::time::{SystemTime, UNIX_EPOCH}; 107 | 108 | /// Parse rust formatted log 109 | fn rust_log_line(line: &str) -> Option<(&str, &str)> { 110 | let (_, level_msg) = line.split_at(20); 111 | let level = level_msg.split_whitespace().next()?; 112 | let msg = &level_msg[(level.len() + 1)..]; 113 | Some((level, msg)) 114 | } 115 | 116 | /// Parse ocaml formatted log 117 | fn ocaml_log_line(line: &str) -> Option<(&str, &str)> { 118 | let mut parts = line.split('-'); 119 | let _ = parts.next(); 120 | let msg = parts.next(); 121 | if let Some(value) = msg { 122 | let mut parts = value.split(':'); 123 | let _ = parts.next(); 124 | let msg = parts.next(); 125 | if let Some(msg) = msg { 126 | Some(("info", msg.trim())) 127 | } else { 128 | Some(("info", value.trim())) 129 | } 130 | } else { 131 | Some(("info", line.trim())) 132 | } 133 | } 134 | 135 | let timestamp = msg 136 | .timestamp 137 | .map(|dt| dt.timestamp_nanos() as u128) 138 | .unwrap_or_else(|| { 139 | SystemTime::now() 140 | .duration_since(UNIX_EPOCH) 141 | .unwrap() 142 | .as_nanos() 143 | }); 144 | let line = msg.msg.as_ref(); 145 | 146 | let pos = line.find('.').unwrap_or_default(); 147 | #[allow(clippy::collapsible_else_if)] 148 | if pos == 15 { 149 | if let Some((level, message)) = rust_log_line(line) { 150 | Item { 151 | timestamp, 152 | level: LogLevel::from_str(level).unwrap_or(LogLevel::Fatal), 153 | message: message.to_string(), 154 | section: "".to_string(), 155 | } 156 | } else { 157 | Item { 158 | timestamp, 159 | level: LogLevel::Fatal, 160 | section: "".to_string(), 161 | message: line.to_string(), 162 | } 163 | } 164 | } else { 165 | if let Some((level, message)) = ocaml_log_line(line) { 166 | Item { 167 | timestamp, 168 | level: LogLevel::from_str(level).unwrap_or(LogLevel::Fatal), 169 | message: message.to_string(), 170 | section: "".to_string(), 171 | } 172 | } else { 173 | Item { 174 | timestamp, 175 | level: LogLevel::Fatal, 176 | section: "".to_string(), 177 | message: line.to_string(), 178 | } 179 | } 180 | } 181 | } 182 | } 183 | 184 | impl BincodeEncoded for Item {} 185 | 186 | pub struct Schema; 187 | 188 | impl KeyValueSchema for Schema { 189 | type Key = u64; 190 | type Value = Item; 191 | } 192 | 193 | impl RocksDbKeyValueSchema for Schema { 194 | fn name() -> &'static str { 195 | "log_storage" 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/secondary_indexes/log_level.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::convert::TryFrom; 5 | use storage::persistent::{ 6 | KeyValueSchema, Encoder, Decoder, SchemaError, database::RocksDbKeyValueSchema, 7 | }; 8 | use rocksdb::{ColumnFamilyDescriptor, Cache}; 9 | use super::*; 10 | 11 | /// WARNING: this index work only with 56 bit index, should be enough 12 | /// * bytes layout: `[level(1)][index(7)]` 13 | pub struct Item { 14 | pub lv: LogLevel, 15 | pub index: u64, 16 | } 17 | 18 | impl Encoder for Item { 19 | fn encode(&self) -> Result, SchemaError> { 20 | let mut v = self.index.to_be_bytes(); 21 | v[0] = self.lv.clone() as u8; 22 | Ok(v.into()) 23 | } 24 | } 25 | 26 | impl Decoder for Item { 27 | fn decode(bytes: &[u8]) -> Result { 28 | let mut bytes = <[u8; 8]>::try_from(bytes).map_err(|_| SchemaError::DecodeError)?; 29 | let lv = LogLevel::try_from(bytes[0]) 30 | .map_err(|e| SchemaError::DecodeValidationError(e.to_string()))?; 31 | bytes[0] = 0; 32 | Ok(Item { 33 | lv, 34 | index: u64::from_be_bytes(bytes), 35 | }) 36 | } 37 | } 38 | 39 | pub struct Schema; 40 | 41 | impl KeyValueSchema for Schema { 42 | type Key = Item; 43 | type Value = (); 44 | } 45 | 46 | impl RocksDbKeyValueSchema for Schema { 47 | fn descriptor(_cache: &Cache) -> ColumnFamilyDescriptor { 48 | use rocksdb::{Options, SliceTransform}; 49 | 50 | let mut cf_opts = Options::default(); 51 | cf_opts.set_prefix_extractor(SliceTransform::create_fixed_prefix(1)); 52 | cf_opts.set_memtable_prefix_bloom_ratio(0.2); 53 | ColumnFamilyDescriptor::new(Self::name(), cf_opts) 54 | } 55 | 56 | fn name() -> &'static str { 57 | "log_level_secondary_index" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/secondary_indexes/message_addr.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | convert::TryFrom, 6 | net::{SocketAddr, IpAddr}, 7 | }; 8 | use storage::persistent::{ 9 | KeyValueSchema, Encoder, Decoder, SchemaError, database::RocksDbKeyValueSchema, 10 | }; 11 | use rocksdb::{ColumnFamilyDescriptor, Cache}; 12 | 13 | /// WARNING: this index work only with 48 bit index, should be enough 14 | /// * bytes layout: `[addr(16)][port(2)][index(6)]` 15 | pub struct Item { 16 | pub addr: SocketAddr, 17 | pub index: u64, 18 | } 19 | 20 | impl Encoder for Item { 21 | fn encode(&self) -> Result, SchemaError> { 22 | let mut v = Vec::with_capacity(24); 23 | 24 | let ip = match self.addr.ip() { 25 | IpAddr::V4(ip) => ip.to_ipv6_mapped().octets(), 26 | IpAddr::V6(ip) => ip.octets(), 27 | }; 28 | v.extend_from_slice(&ip); 29 | v.extend_from_slice(&self.addr.port().to_le_bytes()); 30 | v.extend_from_slice(&self.index.to_be_bytes()[2..]); 31 | 32 | Ok(v) 33 | } 34 | } 35 | 36 | impl Decoder for Item { 37 | fn decode(bytes: &[u8]) -> Result { 38 | if bytes.len() != 24 { 39 | return Err(SchemaError::DecodeError); 40 | } 41 | 42 | Ok(Item { 43 | addr: { 44 | let ip = <[u8; 16]>::try_from(&bytes[..16]).unwrap(); 45 | let port = u16::from_le_bytes(TryFrom::try_from(&bytes[16..18]).unwrap()); 46 | (ip, port).into() 47 | }, 48 | index: { 49 | let mut bytes = <[u8; 8]>::try_from(&bytes[16..]).unwrap(); 50 | bytes[0] = 0; 51 | bytes[1] = 0; 52 | u64::from_be_bytes(bytes) 53 | }, 54 | }) 55 | } 56 | } 57 | 58 | pub struct Schema; 59 | 60 | impl KeyValueSchema for Schema { 61 | type Key = Item; 62 | type Value = (); 63 | } 64 | 65 | impl RocksDbKeyValueSchema for Schema { 66 | fn descriptor(_cache: &Cache) -> ColumnFamilyDescriptor { 67 | use rocksdb::{Options, SliceTransform}; 68 | 69 | let mut cf_opts = Options::default(); 70 | cf_opts.set_prefix_extractor(SliceTransform::create_fixed_prefix(18)); 71 | cf_opts.set_memtable_prefix_bloom_ratio(0.2); 72 | ColumnFamilyDescriptor::new(Self::name(), cf_opts) 73 | } 74 | 75 | fn name() -> &'static str { 76 | "message_address_secondary_index" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/secondary_indexes/message_initiator.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::convert::TryFrom; 5 | use storage::persistent::{ 6 | KeyValueSchema, Encoder, Decoder, SchemaError, database::RocksDbKeyValueSchema, 7 | }; 8 | use rocksdb::{ColumnFamilyDescriptor, Cache}; 9 | use super::*; 10 | 11 | /// WARNING: this index work only with 56 bit index, should be enough 12 | /// * bytes layout: `[initiator(1)][index(7)]` 13 | pub struct Item { 14 | pub initiator: Initiator, 15 | pub index: u64, 16 | } 17 | 18 | impl Encoder for Item { 19 | fn encode(&self) -> Result, SchemaError> { 20 | let mut v = self.index.to_be_bytes(); 21 | v[0] = if self.initiator.incoming() { 22 | 0xff 23 | } else { 24 | 0x00 25 | }; 26 | Ok(v.into()) 27 | } 28 | } 29 | 30 | impl Decoder for Item { 31 | fn decode(bytes: &[u8]) -> Result { 32 | let mut bytes = <[u8; 8]>::try_from(bytes).map_err(|_| SchemaError::DecodeError)?; 33 | let initiator = Initiator::new(bytes[0] != 0); 34 | bytes[0] = 0; 35 | Ok(Item { 36 | initiator, 37 | index: u64::from_be_bytes(bytes), 38 | }) 39 | } 40 | } 41 | 42 | pub struct Schema; 43 | 44 | impl KeyValueSchema for Schema { 45 | type Key = Item; 46 | type Value = (); 47 | } 48 | 49 | impl RocksDbKeyValueSchema for Schema { 50 | fn descriptor(_cache: &Cache) -> ColumnFamilyDescriptor { 51 | use rocksdb::{Options, SliceTransform}; 52 | 53 | let mut cf_opts = Options::default(); 54 | cf_opts.set_prefix_extractor(SliceTransform::create_fixed_prefix(1)); 55 | cf_opts.set_memtable_prefix_bloom_ratio(0.2); 56 | ColumnFamilyDescriptor::new(Self::name(), cf_opts) 57 | } 58 | 59 | fn name() -> &'static str { 60 | "message_initiator_secondary_index" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/secondary_indexes/message_sender.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::convert::TryFrom; 5 | use storage::persistent::{ 6 | KeyValueSchema, Encoder, Decoder, SchemaError, database::RocksDbKeyValueSchema, 7 | }; 8 | use rocksdb::{ColumnFamilyDescriptor, Cache}; 9 | use super::*; 10 | 11 | /// WARNING: this index work only with 56 bit index, should be enough 12 | /// * bytes layout: `[sender(1)][index(7)]` 13 | pub struct Item { 14 | pub sender: Sender, 15 | pub index: u64, 16 | } 17 | 18 | impl Encoder for Item { 19 | fn encode(&self) -> Result, SchemaError> { 20 | let mut v = self.index.to_be_bytes(); 21 | v[0] = if self.sender.incoming() { 0xff } else { 0x00 }; 22 | Ok(v.into()) 23 | } 24 | } 25 | 26 | impl Decoder for Item { 27 | fn decode(bytes: &[u8]) -> Result { 28 | let mut bytes = <[u8; 8]>::try_from(bytes).map_err(|_| SchemaError::DecodeError)?; 29 | let sender = Sender::new(bytes[0] != 0); 30 | bytes[0] = 0; 31 | Ok(Item { 32 | sender, 33 | index: u64::from_be_bytes(bytes), 34 | }) 35 | } 36 | } 37 | 38 | pub struct Schema; 39 | 40 | impl KeyValueSchema for Schema { 41 | type Key = Item; 42 | type Value = (); 43 | } 44 | 45 | impl RocksDbKeyValueSchema for Schema { 46 | fn descriptor(_cache: &Cache) -> ColumnFamilyDescriptor { 47 | use rocksdb::{Options, SliceTransform}; 48 | 49 | let mut cf_opts = Options::default(); 50 | cf_opts.set_prefix_extractor(SliceTransform::create_fixed_prefix(1)); 51 | cf_opts.set_memtable_prefix_bloom_ratio(0.2); 52 | ColumnFamilyDescriptor::new(Self::name(), cf_opts) 53 | } 54 | 55 | fn name() -> &'static str { 56 | "message_sender_secondary_index" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/secondary_indexes/message_ty.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::convert::TryFrom; 5 | use storage::persistent::{ 6 | KeyValueSchema, Encoder, Decoder, SchemaError, database::RocksDbKeyValueSchema, 7 | }; 8 | use rocksdb::{ColumnFamilyDescriptor, Cache}; 9 | use super::*; 10 | 11 | /// WARNING: this index work only with 56 bit index, should be enough 12 | /// * bytes layout: `[type(1)][index(7)]` 13 | pub struct Item { 14 | pub ty: MessageType, 15 | pub index: u64, 16 | } 17 | 18 | impl Encoder for Item { 19 | fn encode(&self) -> Result, SchemaError> { 20 | let mut v = self.index.to_be_bytes(); 21 | v[0] = self.ty.clone().into_int(); 22 | Ok(v.into()) 23 | } 24 | } 25 | 26 | impl Decoder for Item { 27 | fn decode(bytes: &[u8]) -> Result { 28 | let mut bytes = <[u8; 8]>::try_from(bytes).map_err(|_| SchemaError::DecodeError)?; 29 | let ty = MessageType::from_int(bytes[0]); 30 | bytes[0] = 0; 31 | Ok(Item { 32 | ty, 33 | index: u64::from_be_bytes(bytes), 34 | }) 35 | } 36 | } 37 | 38 | pub struct Schema; 39 | 40 | impl KeyValueSchema for Schema { 41 | type Key = Item; 42 | type Value = (); 43 | } 44 | 45 | impl RocksDbKeyValueSchema for Schema { 46 | fn descriptor(_cache: &Cache) -> ColumnFamilyDescriptor { 47 | use rocksdb::{Options, SliceTransform}; 48 | 49 | let mut cf_opts = Options::default(); 50 | cf_opts.set_prefix_extractor(SliceTransform::create_fixed_prefix(1)); 51 | cf_opts.set_memtable_prefix_bloom_ratio(0.2); 52 | ColumnFamilyDescriptor::new(Self::name(), cf_opts) 53 | } 54 | 55 | fn name() -> &'static str { 56 | "message_type_secondary_index" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/secondary_indexes/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use super::{ 5 | common::{MessageType, Sender, Initiator}, 6 | node_log::LogLevel, 7 | }; 8 | 9 | pub mod message_ty; 10 | pub mod message_sender; 11 | pub mod message_initiator; 12 | pub mod message_addr; 13 | pub mod timestamp; 14 | pub mod log_level; 15 | -------------------------------------------------------------------------------- /tezedge-recorder/src/tables/secondary_indexes/timestamp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::convert::TryFrom; 5 | use storage::persistent::{ 6 | KeyValueSchema, Encoder, Decoder, SchemaError, database::RocksDbKeyValueSchema, 7 | }; 8 | 9 | /// * bytes layout: `[timestamp(8)][index(8)]` 10 | pub struct Item { 11 | pub timestamp: u64, 12 | pub index: u64, 13 | } 14 | 15 | impl Encoder for Item { 16 | fn encode(&self) -> Result, SchemaError> { 17 | let mut v = Vec::with_capacity(16); 18 | 19 | v.extend_from_slice(&self.timestamp.to_be_bytes()); 20 | v.extend_from_slice(&self.index.to_be_bytes()); 21 | 22 | Ok(v) 23 | } 24 | } 25 | 26 | impl Decoder for Item { 27 | fn decode(bytes: &[u8]) -> Result { 28 | if bytes.len() != 16 { 29 | return Err(SchemaError::DecodeError); 30 | } 31 | 32 | Ok(Item { 33 | timestamp: u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[..8]).unwrap()), 34 | index: u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[8..]).unwrap()), 35 | }) 36 | } 37 | } 38 | 39 | pub struct MessageSchema; 40 | 41 | impl KeyValueSchema for MessageSchema { 42 | type Key = Item; 43 | type Value = (); 44 | } 45 | 46 | impl RocksDbKeyValueSchema for MessageSchema { 47 | fn name() -> &'static str { 48 | "message_timestamp_secondary_index" 49 | } 50 | } 51 | 52 | pub struct LogSchema; 53 | 54 | impl KeyValueSchema for LogSchema { 55 | type Key = Item; 56 | type Value = (); 57 | } 58 | 59 | impl RocksDbKeyValueSchema for LogSchema { 60 | fn name() -> &'static str { 61 | "log_timestamp_secondary_index" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tezedge-recorder/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export START_TIME=1626264000 4 | 5 | function fail { 6 | kill -SIGTERM $RECORDER_PID 7 | exit 1 8 | } 9 | 10 | function run_recorder { 11 | ./target/none/release/tezedge-recorder --run-bpf & 12 | export RECORDER_PID=$! 13 | sleep 4 14 | } 15 | 16 | function stop_recorder { 17 | kill -SIGTERM $RECORDER_PID 18 | sleep 2 19 | } 20 | 21 | run_recorder 22 | ./target/none/release/pseudonode log 0 && sleep 1 # populate first half log messages 23 | ./target/none/release/deps/log-???????????????? --nocapture \ 24 | pagination level timestamp_and_level || fail 25 | stop_recorder 26 | 27 | run_recorder 28 | ./target/none/release/deps/log-???????????????? --nocapture \ 29 | pagination level timestamp_and_level || fail 30 | ./target/none/release/pseudonode log 1 && sleep 1 # populate second half log messages 31 | ./target/none/release/deps/log-???????????????? --nocapture \ 32 | pagination level timestamp timestamp_and_level || fail 33 | stop_recorder 34 | 35 | run_recorder 36 | ./target/none/release/deps/log-???????????????? --nocapture \ 37 | pagination level timestamp timestamp_and_level || fail 38 | # populate p2p messages 39 | ./target/none/release/pseudonode p2p-responder 29733 29732 & RESPONDER_PID=$! && sleep 1 40 | ./target/none/release/pseudonode p2p-initiator 29732 29733 && wait $RESPONDER_PID && sleep 5 41 | ./target/none/release/deps/p2p-???????????????? --nocapture check_messages || fail 42 | ./target/none/release/pseudonode log 2 && sleep 4 # populate words log messages 43 | ./target/none/release/deps/log-???????????????? --nocapture full_text_search || fail 44 | stop_recorder 45 | -------------------------------------------------------------------------------- /tezedge-recorder/tests/log.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::env; 5 | use tezedge_recorder::tables::node_log; 6 | 7 | pub async fn get_log(params: &str) -> Result, serde_json::error::Error> { 8 | let debugger = env::var("DEBUGGER_URL") 9 | .unwrap(); 10 | let res = reqwest::get(&format!("{}/v2/log?node_name=initiator&{}", debugger, params)) 11 | .await.unwrap() 12 | .text() 13 | .await.unwrap(); 14 | serde_json::from_str(&res) 15 | } 16 | 17 | fn start_time() -> i64 { 18 | env::var("START_TIME") 19 | .map(|s| s.parse::().unwrap_or(0)) 20 | .unwrap_or(0) 21 | } 22 | 23 | #[tokio::test] 24 | async fn level() { 25 | use tezedge_recorder::tables::node_log::LogLevel; 26 | 27 | struct TestCase { 28 | query: &'static str, 29 | predicate: fn(LogLevel) -> bool, 30 | } 31 | 32 | impl TestCase { 33 | async fn run(&self, forward: bool) { 34 | let direction = if forward { "direction=forward&cursor=0" } else { "direction=backward" }; 35 | let params = format!("limit=1000&log_level={}&{}", self.query, direction); 36 | let items = get_log(¶ms).await.unwrap(); 37 | assert!(!items.is_empty()); 38 | for item in items { 39 | assert!((self.predicate)(item.level)); 40 | } 41 | } 42 | } 43 | 44 | let cases = [ 45 | TestCase { query: "info", predicate: |l| matches!(l, LogLevel::Info) }, 46 | TestCase { query: "warn", predicate: |l| matches!(l, LogLevel::Warning) }, 47 | TestCase { query: "error", predicate: |l| matches!(l, LogLevel::Error) }, 48 | 49 | TestCase { query: "warn,error", predicate: |l| matches!(l, LogLevel::Warning | LogLevel::Error) }, 50 | TestCase { query: "error,info", predicate: |l| matches!(l, LogLevel::Error | LogLevel::Info) }, 51 | TestCase { query: "info,warn", predicate: |l| matches!(l, LogLevel::Info | LogLevel::Warning) }, 52 | ]; 53 | 54 | for case in &cases { 55 | case.run(false).await; 56 | case.run(true).await; 57 | } 58 | } 59 | 60 | #[tokio::test] 61 | async fn pagination() { 62 | async fn request_cursor(cursor: usize, result: usize) { 63 | let params = format!("cursor={}&limit=1000", cursor); 64 | let items = get_log(¶ms).await.unwrap(); 65 | assert_eq!(items.len(), 1000); 66 | for (n, item) in items.into_iter().enumerate() { 67 | assert_eq!((item.id as usize) + n, result); 68 | } 69 | } 70 | 71 | let last = get_log("limit=1").await.unwrap(); 72 | let mut last_id = last.last().unwrap().id as usize; 73 | 74 | // deliberate out of range, should give last 75 | request_cursor(last_id * 2, last_id).await; 76 | 77 | loop { 78 | request_cursor(last_id, last_id).await; 79 | if last_id >= 1000 { 80 | last_id -= 1000; 81 | } else { 82 | break; 83 | } 84 | } 85 | 86 | request_cursor(1234, 1234).await; 87 | } 88 | 89 | #[tokio::test] 90 | async fn timestamp() { 91 | struct TestCase { 92 | shift: u64, 93 | expected: usize, 94 | forward: bool, 95 | } 96 | 97 | impl TestCase { 98 | async fn run(self) { 99 | let time = (start_time() as u64 + self.shift) * 1000; 100 | let direction = if self.forward { "forward" } else { "backward" }; 101 | let params = format!("timestamp={}&limit=500&direction={}", time, direction); 102 | let items = get_log(¶ms).await.unwrap(); 103 | assert_eq!(items.len(), self.expected); 104 | let mut time = time; 105 | for item in items { 106 | let this = (item.timestamp / 1_000_000) as u64; 107 | if self.forward { 108 | assert!(this > time); 109 | } else { 110 | assert!(this <= time); 111 | } 112 | time = this; 113 | } 114 | } 115 | } 116 | 117 | let test_cases = vec![ 118 | TestCase { shift: 321, expected: 322, forward: false }, 119 | TestCase { shift: 321, expected: 500, forward: true }, 120 | TestCase { shift: 6789, expected: 500, forward: false }, 121 | TestCase { shift: 6789, expected: 500, forward: true }, 122 | TestCase { shift: 9876, expected: 500, forward: false }, 123 | TestCase { shift: 9876, expected: 9999 - 9876, forward: true }, 124 | ]; 125 | 126 | for test_case in test_cases { 127 | test_case.run().await; 128 | } 129 | } 130 | 131 | #[tokio::test] 132 | async fn timestamp_and_level() { 133 | let time = (start_time() as u64 + 3_000) * 1000; 134 | let params = format!("timestamp={}&limit=500&direction=backward&log_level=warn", time); 135 | let items = get_log(¶ms).await.unwrap(); 136 | assert!(!items.is_empty()); 137 | let params = format!("timestamp={}&limit=500&direction=forward&log_level=warn", time); 138 | let items = get_log(¶ms).await.unwrap(); 139 | assert!(!items.is_empty()); 140 | } 141 | 142 | #[tokio::test] 143 | async fn full_text_search() { 144 | #[derive(Debug)] 145 | struct TestCase { 146 | query: &'static str, 147 | limit: usize, 148 | has: &'static [&'static str], 149 | not: &'static [&'static str], 150 | must: &'static [&'static str], 151 | } 152 | 153 | impl TestCase { 154 | async fn run(&self) { 155 | let items = get_log(&format!("limit={}&query={}", self.limit, self.query)).await.unwrap(); 156 | assert!(!items.is_empty(), "{:?}", self); 157 | for item in items { 158 | if !self.has.is_empty() { 159 | assert!(self.has.iter().any(|&has| item.message.contains(has)), "{:?}", self); 160 | } 161 | for ¬ in self.not { 162 | assert!(!item.message.contains(not), "{:?}", self); 163 | } 164 | for &must in self.must { 165 | assert!(item.message.contains(must), "{:?} in {:?}", self, item.message); 166 | } 167 | } 168 | } 169 | } 170 | 171 | let cases = [ 172 | TestCase { query: "peer", limit: 500, has: &["peer"], not: &[], must: &[] }, 173 | TestCase { query: "peer -branch", limit: 500, has: &["peer"], not: &["branch"], must: &[] }, 174 | TestCase { query: "peer chain -branch", limit: 500, has: &["peer", "chain"], not: &["branch"], must: &[] }, 175 | TestCase { query: "peer -branch -head", limit: 500, has: &["peer"], not: &["branch", "head"], must: &[] }, 176 | TestCase { query: "ip address -peer", limit: 500, has: &["ip", "address"], not: &["peer"], must: &[] }, 177 | // limit is lower here, because no many records meet the condition 178 | TestCase { query: "+ip +address", limit: 16, has: &[], not: &[], must: &["ip", "address"] }, 179 | TestCase { query: "+head +chain +branch -peer -connection", limit: 4, has: &[], not: &["peer", "connection"], must: &["head", "chain", "branch"] }, 180 | ]; 181 | 182 | for case in &cases { 183 | case.run().await; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /tezedge-recorder/tests/p2p.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) SimpleStaking, Viable Systems and Tezedge Contributors 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{env, time::Duration}; 5 | use tezedge_recorder::tables::message; 6 | 7 | pub async fn get_p2p(params: &str, name: &str) -> Result, serde_json::error::Error> { 8 | let debugger = env::var("DEBUGGER_URL") 9 | .unwrap(); 10 | let res = reqwest::get(&format!("{}/v2/p2p?node_name={}&{}", debugger, name, params)) 11 | .await.unwrap() 12 | .text() 13 | .await.unwrap(); 14 | serde_json::from_str(&res) 15 | } 16 | 17 | #[tokio::test] 18 | async fn check_messages() { 19 | use tezedge_recorder::common::{MessageCategory, MessageKind}; 20 | 21 | let items = get_p2p("limit=100", "initiator").await.unwrap(); 22 | 23 | let expected = [ 24 | (0, MessageCategory::Connection, None, false), 25 | (1, MessageCategory::Connection, None, true), 26 | (2, MessageCategory::Meta, None, false), 27 | (3, MessageCategory::Meta, None, true), 28 | (4, MessageCategory::Ack, None, false), 29 | (5, MessageCategory::Ack, None, true), 30 | (6, MessageCategory::P2p, Some(MessageKind::Operation), false), 31 | ]; 32 | 33 | for (id, category, kind, incoming) in &expected { 34 | let inc = if *incoming { "incoming" } else { "outgoing" }; 35 | items.iter() 36 | .find(|msg| { 37 | msg.id == *id && 38 | msg.category.eq(category) && 39 | msg.kind.eq(kind) && 40 | msg.incoming == *incoming 41 | }) 42 | .expect(&format!("not found an {} message {:?} {:?}", inc, category, kind)); 43 | println!("found an {} message {:?} {:?}", inc, category, kind); 44 | } 45 | } 46 | 47 | #[tokio::test] 48 | async fn wait() { 49 | let mut t = 0u8; 50 | 51 | // timeout * duration = 4 minutes 52 | let timeout = 24u8; 53 | let duration = Duration::from_secs(10); 54 | 55 | while t < timeout { 56 | let response = get_p2p("limit=1000", "tezedge") 57 | .await.unwrap(); 58 | if response.len() == 1000 { 59 | break; 60 | } else { 61 | tokio::time::sleep(duration).await; 62 | t += 1; 63 | } 64 | } 65 | assert!(t < timeout); 66 | } 67 | 68 | #[tokio::test] 69 | async fn p2p_limit() { 70 | for limit in 0..8 { 71 | let response = get_p2p(&format!("limit={}", limit), "tezedge") 72 | .await.unwrap(); 73 | assert_eq!(response.len(), limit); 74 | } 75 | } 76 | 77 | #[tokio::test] 78 | async fn p2p_cursor() { 79 | for cursor in 0..8 { 80 | let response = get_p2p(&format!("cursor={}", cursor), "tezedge") 81 | .await.unwrap(); 82 | assert_eq!(response[0].id, cursor); 83 | } 84 | } 85 | 86 | #[tokio::test] 87 | async fn p2p_types_filter() { 88 | let mut types = [ 89 | ("connection_message", 0), 90 | ("metadata", 0), 91 | ("advertise", 0), 92 | ("get_block_headers", 0), 93 | ("block_header", 0), 94 | ]; 95 | for &mut (ty, ref mut number) in &mut types { 96 | let response = get_p2p(&format!("cursor=999&limit=1000&types={}", ty), "tezedge") 97 | .await.unwrap(); 98 | *number = response.len(); 99 | } 100 | 101 | // for all type combination 102 | for i in 0..(types.len() - 1) { 103 | let &(ty_i, n_ty_i) = &types[i]; 104 | for j in (i + 1)..types.len() { 105 | let &(ty_j, n_ty_j) = &types[j]; 106 | let response = get_p2p(&format!("cursor=999&limit=1000&types={},{}", ty_i, ty_j), "tezedge") 107 | .await.unwrap(); 108 | assert_eq!(response.len(), n_ty_i + n_ty_j); 109 | } 110 | } 111 | } 112 | --------------------------------------------------------------------------------