From 79d3216d877fcaf1142192bbbe206061f7a39545 Mon Sep 17 00:00:00 2001 From: juice094 <160722440+juice094@users.noreply.github.com> Date: Fri, 15 May 2026 12:50:07 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(greptimedb):=20Phase=20A+B=20=E2=80=94?= =?UTF-8?q?=20feature=20gate,=20config,=20and=20health=20dual-write=20PoC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase A: - Add greptimedb Cargo feature with greptimedb-ingester optional dep - Add GreptimeConfig (endpoint, dbname, username, password, enabled) - Create src/greptime.rs with GreptimeClient placeholder module - Register greptime module in src/lib.rs - Schema design doc: docs/plans/greptimedb-integration.md Phase B: - Implement GreptimeClient::write_health with real RowInsertRequests - Hook into health::run_json to dual-write health entries after SQLite save - Connection failure degrades gracefully (warning only, non-blocking) Verified: - cargo check (no feature) — clean - cargo check --features greptimedb — clean - cargo clippy --all-targets -- -D warnings — clean --- Cargo.lock | 902 +++++++++++++++++++++++++-- Cargo.toml | 2 + docs/plans/greptimedb-integration.md | 75 +++ src/commands/repo.rs | 3 + src/config.rs | 44 ++ src/greptime.rs | 126 ++++ src/health.rs | 18 +- src/lib.rs | 1 + 8 files changed, 1129 insertions(+), 42 deletions(-) create mode 100644 docs/plans/greptimedb-integration.md create mode 100644 src/greptime.rs diff --git a/Cargo.lock b/Cargo.lock index 78a5f3b..c4bfc2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", "getrandom 0.3.4", "once_cell", "serde", @@ -88,7 +89,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -99,7 +100,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -138,6 +139,250 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "arrow" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378530e55cd479eda3c14eb345310799717e6f76d0c332041e8487022166b471" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ipc", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0ab212d2c1886e802f51c5212d78ebbcbb0bec980fff9dadc1eb8d45cd0b738" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "num-traits", +] + +[[package]] +name = "arrow-array" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd33d3e92f207444098c75b42de99d329562be0cf686b307b097cc52b4e999e" +dependencies = [ + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "chrono-tz 0.10.4", + "half", + "hashbrown 0.17.0", + "num-complex", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrow-buffer" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6cd424c2693bcdbc150d843dc9d4d137dd2de4782ce6df491ad11a3a0416c0" +dependencies = [ + "bytes", + "half", + "num-bigint", + "num-traits", +] + +[[package]] +name = "arrow-cast" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c5aefb56a2c02e9e2b30746241058b85f8983f0fcff2ba0c6d09006e1cded7f" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-ord", + "arrow-schema", + "arrow-select", + "atoi", + "base64 0.22.1", + "chrono", + "comfy-table", + "half", + "lexical-core", + "num-traits", + "ryu", +] + +[[package]] +name = "arrow-csv" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94e8cf7e517657a52b91ea1263acf38c4ca62a84655d72458a3359b12ab97de" +dependencies = [ + "arrow-array", + "arrow-cast", + "arrow-schema", + "chrono", + "csv", + "csv-core", + "regex", +] + +[[package]] +name = "arrow-data" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c88210023a2bfee1896af366309a3028fc3bcbd6515fa29a7990ee1baa08ee0" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrow-flight" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28abfe8bf9f124e5fc83b334af4fa58f8d0323ad25312ccb2d1da50178415704" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-ipc", + "arrow-schema", + "base64 0.22.1", + "bytes", + "futures", + "prost", + "prost-types", + "tonic", + "tonic-prost", +] + +[[package]] +name = "arrow-ipc" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238438f0834483703d88896db6fe5a7138b2230debc31b34c0336c2996e3c64f" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "flatbuffers 25.12.19", + "lz4_flex", + "zstd", +] + +[[package]] +name = "arrow-json" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "205ca2119e6d679d5c133c6f30e68f027738d95ed948cf77677ea69c7800036b" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-ord", + "arrow-schema", + "arrow-select", + "chrono", + "half", + "indexmap", + "itoa", + "lexical-core", + "memchr", + "num-traits", + "ryu", + "serde_core", + "serde_json", + "simdutf8", +] + +[[package]] +name = "arrow-ord" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bffd8fd2579286a5d63bac898159873e5094a79009940bcb42bbfce4f19f1d0" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", +] + +[[package]] +name = "arrow-row" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab5994731204603c73ba69267616c50f80780774c6bb0476f1f830625115e0c" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] + +[[package]] +name = "arrow-schema" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f633dbfdf39c039ada1bf9e34c694816eb71fbb7dc78f613993b7245e078a1ed" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "arrow-select" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd065c54172ac787cf3f2f8d4107e0d3fdc26edba76fdf4f4cc170258942222" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num-traits", +] + +[[package]] +name = "arrow-string" +version = "58.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29dd7cda3ab9692f43a2e4acc444d760cc17b12bb6d8232ddf64e9bab7c06b42" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num-traits", + "regex", + "regex-syntax", +] + [[package]] name = "assert_cmd" version = "2.2.2" @@ -153,6 +398,28 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -164,6 +431,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic" version = "0.6.1" @@ -185,6 +461,49 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "base64" version = "0.13.1" @@ -293,7 +612,7 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", @@ -488,7 +807,17 @@ checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" dependencies = [ "chrono", "chrono-tz-build", - "phf", + "phf 0.11.3", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf 0.12.1", ] [[package]] @@ -498,7 +827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ "parse-zoneinfo", - "phf", + "phf 0.11.3", "phf_codegen", ] @@ -568,7 +897,7 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.117", @@ -593,7 +922,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "unicode-segmentation", + "unicode-width", ] [[package]] @@ -623,6 +962,26 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + [[package]] name = "constant_time_eq" version = "0.4.2" @@ -856,7 +1215,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" dependencies = [ "lab", - "phf", + "phf 0.11.3", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", ] [[package]] @@ -1088,6 +1468,7 @@ dependencies = [ "dirs", "dunce", "git2", + "greptimedb-ingester", "notify", "predicates", "r2d2", @@ -1104,7 +1485,7 @@ dependencies = [ "tempfile", "tokei", "tokio", - "toml", + "toml 0.8.23", "tracing", "tracing-subscriber", "tree-sitter", @@ -1319,7 +1700,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1406,7 +1787,19 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", "proc-macro2", "quote", "syn 2.0.117", @@ -1465,7 +1858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1568,6 +1961,26 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flatbuffers" +version = "24.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" +dependencies = [ + "bitflags 2.11.1", + "rustc_version", +] + [[package]] name = "flate2" version = "1.1.9" @@ -1977,6 +2390,59 @@ dependencies = [ "memmap2", ] +[[package]] +name = "greptime-proto" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fc58f8ba94c488192d5928f24caf16c503fb83f4784f617e83febe08647b08" +dependencies = [ + "prost", + "prost-types", + "serde", + "serde_json", + "strum 0.25.0", + "strum_macros 0.25.3", + "tonic", + "tonic-prost", +] + +[[package]] +name = "greptimedb-ingester" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b2782573f08c0ba58d8ab90a5d5991a3d43449a6b49b60fa07b140fa27778a" +dependencies = [ + "arrow", + "arrow-array", + "arrow-flight", + "arrow-ipc", + "arrow-schema", + "async-stream", + "async-trait", + "base64 0.22.1", + "dashmap", + "derive_builder", + "enum_dispatch", + "flatbuffers 24.12.23", + "futures", + "futures-util", + "greptime-proto", + "hyper", + "lazy_static", + "parking_lot", + "prost", + "rand 0.9.4", + "serde", + "serde_json", + "snafu", + "tokio", + "tokio-stream", + "tokio-util", + "toml 0.9.12+spec-1.1.0", + "tonic", + "tower", +] + [[package]] name = "h2" version = "0.4.13" @@ -2054,6 +2520,12 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -2148,7 +2620,13 @@ dependencies = [ name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humansize" @@ -2173,6 +2651,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2195,6 +2674,19 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -2491,7 +2983,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2645,6 +3137,63 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + [[package]] name = "libc" version = "0.2.185" @@ -2779,6 +3328,9 @@ name = "lz4_flex" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db9a0d582c2874f68138a16ce1867e0ffde6c0bb0a0df85e1f36d04146db488a" +dependencies = [ + "twox-hash", +] [[package]] name = "mac_address" @@ -2815,6 +3367,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "measure_time" version = "0.9.0" @@ -2996,7 +3554,17 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", ] [[package]] @@ -3036,6 +3604,15 @@ dependencies = [ "itoa", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3296,7 +3873,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared 0.12.1", ] [[package]] @@ -3306,7 +3892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", ] [[package]] @@ -3315,7 +3901,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.6", ] @@ -3326,7 +3912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", "proc-macro2", "quote", "syn 2.0.117", @@ -3341,6 +3927,35 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -3469,6 +4084,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + [[package]] name = "pulp" version = "0.22.2" @@ -3648,7 +4295,7 @@ dependencies = [ "itertools 0.14.0", "kasuari", "lru", - "strum", + "strum 0.27.2", "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", @@ -3700,7 +4347,7 @@ dependencies = [ "itertools 0.14.0", "line-clipping", "ratatui-core", - "strum", + "strum 0.27.2", "time", "unicode-segmentation", "unicode-width", @@ -3909,7 +4556,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3922,7 +4569,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4113,6 +4760,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4201,6 +4857,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "1.0.2" @@ -4238,6 +4900,27 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "socket2" version = "0.6.3" @@ -4245,7 +4928,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4295,13 +4978,32 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", ] [[package]] @@ -4310,7 +5012,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.117", @@ -4568,7 +5270,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4578,7 +5280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" dependencies = [ "chrono", - "chrono-tz", + "chrono-tz 0.9.0", "globwalk", "humansize", "lazy_static", @@ -4610,7 +5312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4621,7 +5323,7 @@ checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ "fnv", "nom", - "phf", + "phf 0.11.3", "phf_codegen", ] @@ -4664,7 +5366,7 @@ dependencies = [ "ordered-float 4.6.0", "pest", "pest_derive", - "phf", + "phf 0.11.3", "sha2", "signal-hook", "siphasher", @@ -4764,6 +5466,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -4814,7 +5525,7 @@ dependencies = [ "table_formatter", "tera", "term_size", - "toml", + "toml 0.8.23", ] [[package]] @@ -4859,6 +5570,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4897,6 +5609,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -4905,6 +5628,7 @@ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -4917,11 +5641,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -4931,6 +5670,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -4939,10 +5687,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", ] [[package]] @@ -4951,6 +5708,55 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "flate2", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", + "zstd", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -4959,11 +5765,15 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", + "indexmap", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -5123,6 +5933,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + [[package]] name = "typed-path" version = "0.12.3" @@ -5677,7 +6493,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -5996,6 +6812,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + [[package]] name = "winsafe" version = "0.0.19" @@ -6024,7 +6846,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "wit-parser", ] @@ -6035,7 +6857,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "indexmap", "prettyplease", "syn 2.0.117", diff --git a/Cargo.toml b/Cargo.toml index 7209f79..fee782d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ lang-rust = ["dep:tree-sitter-rust"] lang-python = ["dep:tree-sitter-python"] lang-js-ts = ["dep:tree-sitter-typescript"] lang-go = ["dep:tree-sitter-go"] +greptimedb = ["dep:greptimedb-ingester"] [dependencies] clap = { version = "4", features = ["derive"] } @@ -82,6 +83,7 @@ devbase-registry-relation = { path = "crates/devbase-registry-relation" } devbase-registry-call-graph = { path = "crates/devbase-registry-call-graph" } devbase-registry-dead-code = { path = "crates/devbase-registry-dead-code" } devbase-registry-code-symbols = { path = "crates/devbase-registry-code-symbols" } +greptimedb-ingester = { version = "0.18", optional = true } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } diff --git a/docs/plans/greptimedb-integration.md b/docs/plans/greptimedb-integration.md new file mode 100644 index 0000000..cfbba2e --- /dev/null +++ b/docs/plans/greptimedb-integration.md @@ -0,0 +1,75 @@ +# GreptimeDB 集成计划 + +## 目标 +将 devbase 的时序数据(health、code_metrics、stars)从 SQLite 单表迁移到 GreptimeDB,实现趋势分析、Burn-rate 监控与大时间窗口聚合。 + +## 架构原则 +- **SQLite 保留为 Registry OLTP**:repo 元数据、关系图谱、vault notes 继续存 SQLite。 +- **GreptimeDB 作为 OLAP 时序库**:health、metrics、stars 等时间序列数据双写。 +- **Feature-gated**:`--features greptimedb` 才编译接入层,零成本抽象。 +- **异步写入**:使用 `greptimedb-ingester` gRPC 客户端,批量异步提交。 + +## Schema 设计 + +### health_metrics +```sql +CREATE TABLE health_metrics ( + repo_id STRING, + status STRING, + ahead INT, + behind INT, + checked_at TIMESTAMP, + TIME INDEX (checked_at), + PRIMARY KEY (repo_id, checked_at) +); +``` + +### code_metrics +```sql +CREATE TABLE code_metrics ( + repo_id STRING, + total_lines INT, + source_lines INT, + test_lines INT, + comment_lines INT, + file_count INT, + language_breakdown STRING, + updated_at TIMESTAMP, + TIME INDEX (updated_at), + PRIMARY KEY (repo_id, updated_at) +); +``` + +### stars_history +```sql +CREATE TABLE stars_history ( + repo_id STRING, + stars INT, + fetched_at TIMESTAMP, + TIME INDEX (fetched_at), + PRIMARY KEY (repo_id, fetched_at) +); +``` + +## 实施阶段 + +### Phase A: 基础架构(当前) +- [x] Cargo.toml feature gate + `greptimedb-ingester` 依赖 +- [x] `GreptimeConfig` 配置结构 +- [x] `src/greptime.rs` 空模块与连接管理 + +### Phase B: Health 双写 PoC +- [ ] `save_health` 后调用 `greptime::write_health` +- [ ] CLI `health` 命令增加 `--write-greptime` 标志 + +### Phase C: Metrics & Stars +- [ ] `run_metrics` 双写 +- [ ] `github-info` stars 双写 + +### Phase D: 查询适配 +- [ ] `query` 命令支持 `trend:` 前缀(从 GreptimeDB 读取时序趋势) +- [ ] Dashboard SQL 模板 + +## 兼容性 +- 无 `greptimedb` feature 时,100% 保持现有 SQLite-only 行为。 +- 连接失败时降级为仅 SQLite,打印 warning,不阻塞主流程。 diff --git a/src/commands/repo.rs b/src/commands/repo.rs index a013458..c1203d7 100644 --- a/src/commands/repo.rs +++ b/src/commands/repo.rs @@ -40,6 +40,7 @@ pub async fn run_health( }; let conn = ctx.conn()?; + let greptime_client = crate::greptime::GreptimeClient::new(&ctx.config.greptime); if json { let output = health::run_json( &conn, @@ -49,6 +50,7 @@ pub async fn run_health( ctx.config.cache.ttl_seconds, &ctx.i18n, &env_cache, + Some(&greptime_client), ) .await?; println!("{}", serde_json::to_string_pretty(&output)?); @@ -61,6 +63,7 @@ pub async fn run_health( ctx.config.cache.ttl_seconds, &ctx.i18n, &env_cache, + Some(&greptime_client), ) .await?; } diff --git a/src/config.rs b/src/config.rs index fcedb99..b21f03d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -107,6 +107,8 @@ pub struct Config { pub arxiv: ArxivConfig, #[serde(default)] pub scan: ScanConfig, + #[serde(default)] + pub greptime: GreptimeConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -490,3 +492,45 @@ max_tokens = 400 assert_eq!(cfg.daemon.interval_seconds, 3600); } } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GreptimeConfig { + #[serde(default = "default_greptime_enabled")] + pub enabled: bool, + #[serde(default = "default_greptime_endpoint")] + pub endpoint: String, + #[serde(default = "default_greptime_dbname")] + pub dbname: String, + #[serde(default = "default_greptime_username")] + pub username: String, + #[serde(default)] + pub password: Option, +} + +impl Default for GreptimeConfig { + fn default() -> Self { + Self { + enabled: default_greptime_enabled(), + endpoint: default_greptime_endpoint(), + dbname: default_greptime_dbname(), + username: default_greptime_username(), + password: None, + } + } +} + +fn default_greptime_enabled() -> bool { + false +} + +fn default_greptime_endpoint() -> String { + "127.0.0.1:4001".to_string() +} + +fn default_greptime_dbname() -> String { + "devbase".to_string() +} + +fn default_greptime_username() -> String { + "root".to_string() +} diff --git a/src/greptime.rs b/src/greptime.rs new file mode 100644 index 0000000..1ebab5b --- /dev/null +++ b/src/greptime.rs @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 juice094 +//! GreptimeDB integration layer (feature-gated). +//! +//! Provides optional async ingestion of time-series data: +//! - repo health (ahead/behind/status) +//! - code metrics (LOC/language breakdown) +//! - GitHub stars history +//! +//! When the `greptimedb` feature is disabled this module compiles to no-ops. + +use crate::config::GreptimeConfig; + +/// Shared GreptimeDB client handle. +pub struct GreptimeClient { + #[cfg(feature = "greptimedb")] + inner: Option, + #[cfg(not(feature = "greptimedb"))] + _placeholder: (), +} + +impl GreptimeClient { + /// Create a new client from configuration. + /// Returns a no-op client if disabled or if the feature is not compiled. + pub fn new(config: &GreptimeConfig) -> Self { + #[cfg(feature = "greptimedb")] + { + if !config.enabled { + return Self { inner: None }; + } + let client = + greptimedb_ingester::client::Client::with_urls(&[&config.endpoint, + ]); + let db = greptimedb_ingester::database::Database::new_with_dbname( + &config.dbname, + client, + ); + Self { inner: Some(db) } + } + #[cfg(not(feature = "greptimedb"))] + { + let _ = config; + Self { _placeholder: () } + } + } + + /// Write a health entry. No-op when feature is disabled. + pub async fn write_health( + &self, + repo_id: &str, + entry: &crate::registry::HealthEntry, + ) -> anyhow::Result<()> { + #[cfg(feature = "greptimedb")] + { + if let Some(db) = &self.inner { + use greptimedb_ingester::api::v1::{ + Row, RowInsertRequest, RowInsertRequests, Rows, + }; + use greptimedb_ingester::helpers::schema::{field, tag, timestamp}; + use greptimedb_ingester::helpers::values::{ + i64_value, string_value, timestamp_millisecond_value, + }; + use greptimedb_ingester::ColumnDataType; + + let schema = vec![ + tag("repo_id", ColumnDataType::String), + timestamp("checked_at", ColumnDataType::TimestampMillisecond), + field("status", ColumnDataType::String), + field("ahead", ColumnDataType::Int64), + field("behind", ColumnDataType::Int64), + ]; + + let checked_at_ms = entry.checked_at.timestamp_millis(); + let rows = vec![Row { + values: vec![ + string_value(repo_id.to_string()), + timestamp_millisecond_value(checked_at_ms), + string_value(entry.status.clone()), + i64_value(entry.ahead as i64), + i64_value(entry.behind as i64), + ], + }]; + + let req = RowInsertRequests { + inserts: vec![RowInsertRequest { + table_name: "health_metrics".to_string(), + rows: Some(Rows { schema, rows }), + }], + }; + + if let Err(e) = db.insert(req).await { + tracing::warn!("GreptimeDB write_health failed for {}: {}", repo_id, e); + } + } + } + let _ = repo_id; + let _ = entry; + Ok(()) + } + + /// Write code metrics. No-op when feature is disabled. + pub async fn write_metrics( + &self, + _repo_id: &str, + _metrics: &crate::registry::CodeMetrics, + ) -> anyhow::Result<()> { + #[cfg(feature = "greptimedb")] + { + // Phase C: convert CodeMetrics to GreptimeDB row batch. + } + Ok(()) + } + + /// Write stars snapshot. No-op when feature is disabled. + pub async fn write_stars( + &self, + _repo_id: &str, + _stars: u64, + ) -> anyhow::Result<()> { + #[cfg(feature = "greptimedb")] + { + // Phase C: convert stars to GreptimeDB row batch. + } + Ok(()) + } +} diff --git a/src/health.rs b/src/health.rs index 4e34cb8..0830b6f 100644 --- a/src/health.rs +++ b/src/health.rs @@ -63,6 +63,7 @@ pub fn compute_workspace_hash(root: &Path) -> anyhow::Result { Ok(hasher.finalize().to_hex().to_string()) } +#[allow(clippy::too_many_arguments)] pub async fn run_json( conn: &rusqlite::Connection, detail: bool, @@ -71,6 +72,7 @@ pub async fn run_json( ttl_seconds: i64, i18n: &I18n, env_cache: &EnvVersionCache, + greptime_client: Option<&crate::greptime::GreptimeClient>, ) -> anyhow::Result { let start = std::time::Instant::now(); let (total_repos, dirty_repos, behind_upstream, no_upstream_count, repo_details) = { @@ -133,6 +135,11 @@ pub async fn run_json( if let Err(e) = reg_health::save_health(conn, &repo.id, &new_health) { tracing::warn!("Failed to save health for {}: {}", repo.id, e); } + if let Some(client) = greptime_client + && let Err(e) = client.write_health(&repo.id, &new_health).await + { + tracing::warn!("GreptimeDB health write failed for {}: {}", repo.id, e); + } (status, ahead, behind) } } @@ -153,6 +160,11 @@ pub async fn run_json( { tracing::warn!("Failed to save health for {}: {}", repo.id, e); } + if let Some(client) = greptime_client + && let Err(e) = client.write_health(&repo.id, &new_health).await + { + tracing::warn!("GreptimeDB health write failed for {}: {}", repo.id, e); + } (status, ahead, behind) } } @@ -288,6 +300,7 @@ pub async fn run_json( })) } +#[allow(clippy::too_many_arguments)] pub async fn run( conn: &rusqlite::Connection, detail: bool, @@ -296,8 +309,9 @@ pub async fn run( ttl_seconds: i64, i18n: &I18n, env_cache: &EnvVersionCache, + greptime_client: Option<&crate::greptime::GreptimeClient>, ) -> anyhow::Result<()> { - let result = run_json(conn, detail, limit, page, ttl_seconds, i18n, env_cache).await?; + let result = run_json(conn, detail, limit, page, ttl_seconds, i18n, env_cache, greptime_client).await?; let summary = result["summary"] .as_object() @@ -471,7 +485,7 @@ impl HealthClient for AppContext { self.set_env_cache(fresh.clone())?; fresh }; - run_json(&conn, detail, 0, 1, self.config.cache.ttl_seconds, &self.i18n, &env_cache).await + run_json(&conn, detail, 0, 1, self.config.cache.ttl_seconds, &self.i18n, &env_cache, None).await } } diff --git a/src/lib.rs b/src/lib.rs index 714f1a5..149dfe4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod dependency_graph; pub mod digest; pub mod discovery_engine; pub mod embedding; +pub mod greptime; pub mod health; pub mod i18n; pub mod knowledge_engine; From 23ebf80ab89c93a68a88a2c99ba40297910b07f7 Mon Sep 17 00:00:00 2001 From: juice094 <160722440+juice094@users.noreply.github.com> Date: Fri, 15 May 2026 13:12:26 +0800 Subject: [PATCH 2/2] feat(sync): transparent skipped-repo reporting with reason breakdown - Extend collect_tasks to return Vec (id, path, reason) - Add 'skipped' JSON array to sync --dry-run output - Print first 10 skipped repos with reasons in human-readable sync output - Fix test assertion for new return type Users can now see exactly why each repo was skipped: - 'unmanaged' (no mirror/reference/third-party/collaborative/team tag) - 'excluded' (matched --exclude list) - 'path_excluded' (matched exclude_paths pattern) --- src/greptime.rs | 18 +++++------------- src/health.rs | 12 +++++++++--- src/sync.rs | 35 ++++++++++++++++++++++++++--------- src/sync/tasks.rs | 28 +++++++++++++++++++++++----- src/sync/tests.rs | 2 +- 5 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/greptime.rs b/src/greptime.rs index 1ebab5b..37c311d 100644 --- a/src/greptime.rs +++ b/src/greptime.rs @@ -28,13 +28,9 @@ impl GreptimeClient { if !config.enabled { return Self { inner: None }; } - let client = - greptimedb_ingester::client::Client::with_urls(&[&config.endpoint, - ]); - let db = greptimedb_ingester::database::Database::new_with_dbname( - &config.dbname, - client, - ); + let client = greptimedb_ingester::client::Client::with_urls(&[&config.endpoint]); + let db = + greptimedb_ingester::database::Database::new_with_dbname(&config.dbname, client); Self { inner: Some(db) } } #[cfg(not(feature = "greptimedb"))] @@ -53,6 +49,7 @@ impl GreptimeClient { #[cfg(feature = "greptimedb")] { if let Some(db) = &self.inner { + use greptimedb_ingester::ColumnDataType; use greptimedb_ingester::api::v1::{ Row, RowInsertRequest, RowInsertRequests, Rows, }; @@ -60,7 +57,6 @@ impl GreptimeClient { use greptimedb_ingester::helpers::values::{ i64_value, string_value, timestamp_millisecond_value, }; - use greptimedb_ingester::ColumnDataType; let schema = vec![ tag("repo_id", ColumnDataType::String), @@ -112,11 +108,7 @@ impl GreptimeClient { } /// Write stars snapshot. No-op when feature is disabled. - pub async fn write_stars( - &self, - _repo_id: &str, - _stars: u64, - ) -> anyhow::Result<()> { + pub async fn write_stars(&self, _repo_id: &str, _stars: u64) -> anyhow::Result<()> { #[cfg(feature = "greptimedb")] { // Phase C: convert stars to GreptimeDB row batch. diff --git a/src/health.rs b/src/health.rs index 0830b6f..ba4f364 100644 --- a/src/health.rs +++ b/src/health.rs @@ -138,7 +138,11 @@ pub async fn run_json( if let Some(client) = greptime_client && let Err(e) = client.write_health(&repo.id, &new_health).await { - tracing::warn!("GreptimeDB health write failed for {}: {}", repo.id, e); + tracing::warn!( + "GreptimeDB health write failed for {}: {}", + repo.id, + e + ); } (status, ahead, behind) } @@ -311,7 +315,8 @@ pub async fn run( env_cache: &EnvVersionCache, greptime_client: Option<&crate::greptime::GreptimeClient>, ) -> anyhow::Result<()> { - let result = run_json(conn, detail, limit, page, ttl_seconds, i18n, env_cache, greptime_client).await?; + let result = + run_json(conn, detail, limit, page, ttl_seconds, i18n, env_cache, greptime_client).await?; let summary = result["summary"] .as_object() @@ -485,7 +490,8 @@ impl HealthClient for AppContext { self.set_env_cache(fresh.clone())?; fresh }; - run_json(&conn, detail, 0, 1, self.config.cache.ttl_seconds, &self.i18n, &env_cache, None).await + run_json(&conn, detail, 0, 1, self.config.cache.ttl_seconds, &self.i18n, &env_cache, None) + .await } } diff --git a/src/sync.rs b/src/sync.rs index 01bfe7b..6704cae 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -38,8 +38,9 @@ pub async fn run_json( let config = crate::config::Config::load().unwrap_or_default(); let all_repos = repo::list_repos(conn)?; let total_registered = all_repos.len(); - let (tasks, skipped_unmanaged) = + let (tasks, skipped_repos) = collect_tasks(conn, filter_tags, exclude, &config.scan.exclude_paths).await?; + let skipped_unmanaged = skipped_repos.iter().filter(|s| s.reason == "unmanaged").count(); if filter_tags.is_none() && tasks.is_empty() && total_registered > 0 { println!("{}", i18n.log.hint_unmanaged_repos.replace("{}", &total_registered.to_string())); } @@ -93,10 +94,16 @@ pub async fn run_json( }, ); + let skipped_json: Vec = skipped_repos + .into_iter() + .map(|s| serde_json::json!({"id": s.id, "path": s.path, "reason": s.reason})) + .collect(); + Ok(serde_json::json!({ "success": true, "dry_run": dry_run, "skipped_unmanaged": skipped_unmanaged, + "skipped": skipped_json, "results": results_json })) } @@ -112,8 +119,9 @@ pub async fn run( let config = crate::config::Config::load().unwrap_or_default(); let all_repos = repo::list_repos(conn)?; let total_registered = all_repos.len(); - let (tasks, skipped_unmanaged) = + let (tasks, skipped_repos) = collect_tasks(conn, filter_tags, exclude, &config.scan.exclude_paths).await?; + let skipped_unmanaged = skipped_repos.iter().filter(|s| s.reason == "unmanaged").count(); if filter_tags.is_none() && tasks.is_empty() && total_registered > 0 { println!("{}", i18n.log.hint_unmanaged_repos.replace("{}", &total_registered.to_string())); } @@ -180,13 +188,22 @@ pub async fn run( print_summary_table(&results_json, i18n); - if skipped_unmanaged > 0 { - println!( - "\n{}", - i18n.sync - .summary_unmanaged_skipped - .replace("{}", &skipped_unmanaged.to_string()) - ); + if !skipped_repos.is_empty() { + println!("\n Skipped repositories ({}):", skipped_repos.len()); + for s in skipped_repos.iter().take(10) { + println!(" [{}] {} (reason: {})", s.id, s.path, s.reason); + } + if skipped_repos.len() > 10 { + println!(" ... and {} more", skipped_repos.len() - 10); + } + if skipped_unmanaged > 0 { + println!( + "\n{}", + i18n.sync + .summary_unmanaged_skipped + .replace("{}", &skipped_unmanaged.to_string()) + ); + } } if dry_run { diff --git a/src/sync/tasks.rs b/src/sync/tasks.rs index 4506bc5..7a17af7 100644 --- a/src/sync/tasks.rs +++ b/src/sync/tasks.rs @@ -196,12 +196,19 @@ const MANAGED_TAGS: &[&str] = &[ "managed", ]; +#[derive(Debug, Clone)] +pub(super) struct SkippedRepoInfo { + pub id: String, + pub path: String, + pub reason: String, +} + pub(super) async fn collect_tasks( conn: &rusqlite::Connection, filter_tags: Option<&str>, exclude: Option<&str>, exclude_paths: &[String], -) -> anyhow::Result<(Vec, usize)> { +) -> anyhow::Result<(Vec, Vec)> { let repos = repo::list_repos(conn)?; let is_default_mode = filter_tags.is_none(); @@ -213,7 +220,7 @@ pub(super) async fn collect_tasks( .map(|e| e.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect()) .unwrap_or_default(); - let mut skipped_unmanaged = 0usize; + let mut skipped: Vec = Vec::new(); let tasks: Vec = repos .into_iter() .filter(|repo| { @@ -226,8 +233,19 @@ pub(super) async fn collect_tasks( let not_path_excluded = !crate::scan::is_excluded_path(&repo.local_path, exclude_paths, None); let included = tag_match && not_excluded && not_path_excluded; - if is_default_mode && !included && !tag_match { - skipped_unmanaged += 1; + if !included { + let reason = if !tag_match { + "unmanaged".to_string() + } else if !not_excluded { + "excluded".to_string() + } else { + "path_excluded".to_string() + }; + skipped.push(SkippedRepoInfo { + id: repo.id.clone(), + path: repo.local_path.to_string_lossy().to_string(), + reason, + }); } included }) @@ -245,7 +263,7 @@ pub(super) async fn collect_tasks( }) .collect(); - Ok((tasks, skipped_unmanaged)) + Ok((tasks, skipped)) } pub(super) fn map_action(action: &str, _message: &str) -> String { diff --git a/src/sync/tests.rs b/src/sync/tests.rs index 7f25de8..a6a5b98 100644 --- a/src/sync/tests.rs +++ b/src/sync/tests.rs @@ -299,7 +299,7 @@ async fn test_collect_tasks_default_mode_excludes_untagged() { // Default mode: only managed repo should be collected let (tasks, skipped) = tasks::collect_tasks(&conn, None, None, &[]).await.unwrap(); assert_eq!(tasks.len(), 1); - assert_eq!(skipped, 1); + assert_eq!(skipped.len(), 1); assert_eq!(tasks[0].id, "managed"); }