feat: add telemetry

This commit is contained in:
Alexander Medvedev 2026-09-06 12:42:57 +02:00
parent a6af44ba52
commit fc70942c64
10 changed files with 556 additions and 96 deletions

168
Cargo.lock generated
View File

@ -135,7 +135,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -146,7 +146,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell_polyfill", "once_cell_polyfill",
"windows-sys 0.61.2", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -281,7 +281,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -458,7 +458,7 @@ checksum = "46d07918caa9eeaaf06b7873925c53a61daac173539b4f7715090745e44e4e69"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -502,7 +502,7 @@ dependencies = [
"maybe-owned", "maybe-owned",
"rustix", "rustix",
"rustix-linux-procfs", "rustix-linux-procfs",
"windows-sys 0.61.2", "windows-sys 0.60.2",
"winx", "winx",
] ]
@ -686,7 +686,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -731,7 +731,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -1404,7 +1404,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -1538,7 +1538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -1728,7 +1728,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -2226,9 +2226,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.14.1" version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.17.1", "hashbrown 0.17.1",
@ -2733,7 +2733,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -2770,7 +2770,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -3270,7 +3270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.14.0", "itertools 0.13.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 2.0.119",
@ -3302,7 +3302,7 @@ checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -3328,7 +3328,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin" name = "pumpkin"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"aes 0.9.3", "aes 0.9.3",
"arc-swap", "arc-swap",
@ -3399,17 +3399,17 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-api-macros" name = "pumpkin-api-macros"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
name = "pumpkin-codecs" name = "pumpkin-codecs"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"serde_json", "serde_json",
"tracing", "tracing",
@ -3431,13 +3431,13 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
"syn 3.0.4", "syn 3.0.5",
"wit-encoder", "wit-encoder",
] ]
[[package]] [[package]]
name = "pumpkin-config" name = "pumpkin-config"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"pumpkin-util", "pumpkin-util",
"serde", "serde",
@ -3448,7 +3448,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-data" name = "pumpkin-data"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"crc-fast", "crc-fast",
"criterion", "criterion",
@ -3461,7 +3461,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-fuzzer" name = "pumpkin-fuzzer"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"clap", "clap",
"colored", "colored",
@ -3471,7 +3471,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-gametest" name = "pumpkin-gametest"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"pumpkin-data", "pumpkin-data",
@ -3486,7 +3486,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-inventory" name = "pumpkin-inventory"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"pumpkin-data", "pumpkin-data",
"pumpkin-nbt", "pumpkin-nbt",
@ -3501,19 +3501,19 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-macros" name = "pumpkin-macros"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"pumpkin-data", "pumpkin-data",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
name = "pumpkin-nbt" name = "pumpkin-nbt"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"bytes", "bytes",
"cesu8", "cesu8",
@ -3528,7 +3528,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-plugin-api" name = "pumpkin-plugin-api"
version = "0.1.0" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"postcard", "postcard",
"serde_json", "serde_json",
@ -3539,7 +3539,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-plugin-runtime" name = "pumpkin-plugin-runtime"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"futures", "futures",
"thiserror 2.0.20", "thiserror 2.0.20",
@ -3551,7 +3551,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-plugin-utils" name = "pumpkin-plugin-utils"
version = "0.1.0" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"pumpkin-plugin-api", "pumpkin-plugin-api",
"reqwest", "reqwest",
@ -3566,7 +3566,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-protocol" name = "pumpkin-protocol"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"aes 0.9.3", "aes 0.9.3",
"async-compression", "async-compression",
@ -3591,7 +3591,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-util" name = "pumpkin-util"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"base64 0.23.1", "base64 0.23.1",
"bytes", "bytes",
@ -3614,7 +3614,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha2 0.11.0", "sha2 0.11.0",
"syn 3.0.4", "syn 3.0.5",
"thiserror 2.0.20", "thiserror 2.0.20",
"uuid", "uuid",
"webpki-root-certs", "webpki-root-certs",
@ -3622,7 +3622,7 @@ dependencies = [
[[package]] [[package]]
name = "pumpkin-world" name = "pumpkin-world"
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"bitflags 2.13.1", "bitflags 2.13.1",
@ -3671,7 +3671,7 @@ dependencies = [
"libc", "libc",
"log", "log",
"socket2", "socket2",
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -3931,7 +3931,7 @@ checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -3954,9 +3954,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc" name = "rtc"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9005c36795ad076abd36db3ea9ae0275a60395944647d58c1f2bc3e118dddba" checksum = "66355ae7cad547376873a2ee7968eee1e3727492a241c05bf511b01db4cef077"
dependencies = [ dependencies = [
"bytes", "bytes",
"hex", "hex",
@ -3989,9 +3989,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-datachannel" name = "rtc-datachannel"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b61c7b8e9892094cba8a6c7fb8120ae863d32d432b00e560177b3d7ae6d2c6" checksum = "c1767cfa2571e226e16d1594518c5160d7bef10c6e0fe7a60a8f979051233ea1"
dependencies = [ dependencies = [
"bytes", "bytes",
"log", "log",
@ -4002,9 +4002,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-dtls" name = "rtc-dtls"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c737b1dd17a0ff63884a2f466b9588f0cffadfce5a724c0c368764d7c27f9c74" checksum = "4eb84feb0331735ab54ba58bfeb15300b517c283c5ee45e9f94d8727e6573ec5"
dependencies = [ dependencies = [
"aes 0.8.4", "aes 0.8.4",
"bytecheck", "bytecheck",
@ -4035,9 +4035,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-ice" name = "rtc-ice"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c06eeabd250a7693e1e8b28222b78c4a81a7c6ca7fe3cb99bbdff2f6c0ff0ab" checksum = "9ee1c117265c3460ef2d88b54bdd5048c7ee05d5de948310e9ca053ff5154f74"
dependencies = [ dependencies = [
"bytes", "bytes",
"crc", "crc",
@ -4054,9 +4054,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-interceptor" name = "rtc-interceptor"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ec2776ab86c0c03c3de8ec742aa13ccc3b2c82317590d0624f251bd37eae3b9" checksum = "a2a76412e39ae1a8a024be246009ffb5e6300f9703bc7b1ba41a3e0e1c5e63c1"
dependencies = [ dependencies = [
"log", "log",
"rand", "rand",
@ -4069,9 +4069,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-interceptor-derive" name = "rtc-interceptor-derive"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc31da3875839bc18e00997354a582e6decacdb23d39f8c004fc4fe2fd3fcd8b" checksum = "57385751766059daa81c7badbef27b894c358057be0413bf329875c6950ae125"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4080,9 +4080,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-mdns" name = "rtc-mdns"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cd53120b83321c8b8310cce9512c685d2d6356acef34647a99ea420e6f2249" checksum = "52e1e0868d134ff0f10cd6ee71ad0d53c4a81a733f9950947eb8806b0a090159"
dependencies = [ dependencies = [
"bytes", "bytes",
"log", "log",
@ -4093,9 +4093,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-media" name = "rtc-media"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0411e44fcdc1363487ae0c6a4612ec908b9b09175d05499c5de0b3c81f0b5e5" checksum = "fdd79fb066099a90edd43dfe5ef296021218def9070ce39dcd0756e309e8945f"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
@ -4107,9 +4107,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-rtcp" name = "rtc-rtcp"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fceaadac17b114140368dd68478d186ccfc293970f79cb8dd28f94dbbc0300be" checksum = "b515f133b14a119a3862c34b36c807bb985e90dc64515b2283b8e9a177d6ae3f"
dependencies = [ dependencies = [
"bytes", "bytes",
"rtc-shared", "rtc-shared",
@ -4117,9 +4117,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-rtp" name = "rtc-rtp"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "babb45ac2263340e2bb412d1921aa07ca441ae9b5589477221d1a50d98750ba3" checksum = "f8a5509cd26a487309ac0d933b100af8976c399c1082009d4d4364a205968127"
dependencies = [ dependencies = [
"bytes", "bytes",
"memchr", "memchr",
@ -4130,9 +4130,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-sctp" name = "rtc-sctp"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2513391b524b88b041277faba617e0531ad726729861ea8933015393fd68ab05" checksum = "2f786b8224032e25084a52401fcc28a8b6a4a5b1b1971152ad01113279628861"
dependencies = [ dependencies = [
"bytes", "bytes",
"crc32c", "crc32c",
@ -4146,9 +4146,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-sdp" name = "rtc-sdp"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d136a422965289e3d32d8e110789d8fa2ac669a4474a4084fa1eeac32758ee" checksum = "6cb942d52e95a0b6b6178374e88e90b069d8aa7078e6e581e1490db2521978e8"
dependencies = [ dependencies = [
"rand", "rand",
"rtc-shared", "rtc-shared",
@ -4157,9 +4157,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-shared" name = "rtc-shared"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce81c72adbce3a2f2ad8e2b97674d2482be83cb3bfd9a153341b04b1a87ca24" checksum = "393280ae9959b5245df9eb38a5cd02b373a98021599d36111c339a4696bf8488"
dependencies = [ dependencies = [
"aes 0.8.4", "aes 0.8.4",
"aes-gcm", "aes-gcm",
@ -4179,9 +4179,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-srtp" name = "rtc-srtp"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b880d5f6d436b3215c7bcfd3fb5ffe2a752fea0236440ccd98b6bf1c3561df8" checksum = "c77ba0abc312a93840746c27d4d1f16efc71baa9d3838ca9dda65e478e41461d"
dependencies = [ dependencies = [
"aes 0.8.4", "aes 0.8.4",
"byteorder", "byteorder",
@ -4198,9 +4198,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-stun" name = "rtc-stun"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06b19c36a200df6071b92a25583a19ab0f9c6d5ad036d8c3ff5d392802cc926e" checksum = "dc22012b96070d048d16bd6d2b0ca451bc8e920a28fb850b8f9644f21e32a826"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@ -4217,9 +4217,9 @@ dependencies = [
[[package]] [[package]]
name = "rtc-turn" name = "rtc-turn"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebf0b5fbb94085c86be7277c38ae8f1c21ec75ab47ba975be0984409732020dd" checksum = "55fd9c23215c3e22ae698b423ae86992a89cb0c24440e3e7e2932dcad75aa3f0"
dependencies = [ dependencies = [
"bytes", "bytes",
"log", "log",
@ -4278,7 +4278,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -4345,7 +4345,7 @@ dependencies = [
"security-framework", "security-framework",
"security-framework-sys", "security-framework-sys",
"webpki-root-certs", "webpki-root-certs",
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -4514,7 +4514,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -4539,7 +4539,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -4709,7 +4709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -4793,9 +4793,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.4" version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4851,7 +4851,7 @@ dependencies = [
"fastrand", "fastrand",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -4900,7 +4900,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -5005,7 +5005,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]
@ -5770,9 +5770,9 @@ dependencies = [
[[package]] [[package]]
name = "webrtc" name = "webrtc"
version = "0.20.4" version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3daa8f2f6366331ae3275a6c02a855c6fb3faa1d16960498d7daaf61c96e76bd" checksum = "91ce8fa1c8b8aa8753b7313a26e46e27d973e11945e35f525ac6700062c3b295"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-channel", "async-channel",
@ -5808,7 +5808,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -6411,7 +6411,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn 3.0.5",
] ]
[[package]] [[package]]

View File

@ -105,7 +105,7 @@ struct_excessive_bools = "allow"
unused_async_trait_impl = "allow" unused_async_trait_impl = "allow"
[workspace.package] [workspace.package]
version = "0.1.0-dev+26.2-26.45" version = "0.1.0+26.2-26.45"
edition = "2024" edition = "2024"
rust-version = "1.96" rust-version = "1.96"
license = "GPL-3.0" license = "GPL-3.0"
@ -147,7 +147,7 @@ serde_json = { version = "1.0", default-features = false, features = ["std"] }
tempfile = { version = "3.27.0", default-features = false } tempfile = { version = "3.27.0", default-features = false }
proc-macro-error2 = { version = "2", default-features = false } proc-macro-error2 = { version = "2", default-features = false }
aes = { version = "0.9", default-features = false } aes = { version = "0.9", default-features = false }
async-compression = { version = "0.4.43", default-features = false } async-compression = { version = "0.4.44", default-features = false }
base64 = { version = "0.23.1", default-features = false, features = ["std"] } base64 = { version = "0.23.1", default-features = false, features = ["std"] }
bitflags = { version = "2.13.1", default-features = false, features = ["std"] } bitflags = { version = "2.13.1", default-features = false, features = ["std"] }
cesu8 = { version = "1.1", default-features = false } cesu8 = { version = "1.1", default-features = false }
@ -166,7 +166,7 @@ indexmap = { version = "2.14", default-features = false, features = ["std"] }
itertools = { version = "0.15.0", default-features = false, features = ["use_std"] } itertools = { version = "0.15.0", default-features = false, features = ["use_std"] }
libc = { version = "0.2", default-features = false } libc = { version = "0.2", default-features = false }
libloading = { version = "0.9", default-features = false, features = ["std"] } libloading = { version = "0.9", default-features = false, features = ["std"] }
lru = { version = "0.18.3", default-features = false } lru = { version = "0.18.4", default-features = false }
lz4-java-wrc = { version = "0.2.0", default-features = false, features = ["lz4_flex"] } lz4-java-wrc = { version = "0.2.0", default-features = false, features = ["lz4_flex"] }
md5 = { version = "0.8", default-features = false } md5 = { version = "0.8", default-features = false }
num-bigint = { version = "0.5", default-features = false, features = ["std"] } num-bigint = { version = "0.5", default-features = false, features = ["std"] }
@ -226,7 +226,7 @@ wasmtime-wasi-http = { git = "https://github.com/bytecodealliance/wasmtime", rev
# needed for interacting with wasmtime-wasi-http - keep in sync # needed for interacting with wasmtime-wasi-http - keep in sync
hyper = { version = "^1.11", default-features = false } hyper = { version = "^1.11", default-features = false }
axum = { version = "0.8.9", default-features = false, features = ["http1", "tokio"] } axum = { version = "0.8.9", default-features = false, features = ["http1", "tokio"] }
webrtc = { version = "0.20.3" } webrtc = { version = "0.20.5" }
async-trait = "0.1" async-trait = "0.1"
wit-bindgen = { version = "0.61", default-features = false, features = ["macros"] } wit-bindgen = { version = "0.61", default-features = false, features = ["macros"] }
@ -234,6 +234,6 @@ postcard = { version = "1.1", default-features = false, features = ["alloc"] }
tracing-serde-structured = { version = "0.4", default-features = false } tracing-serde-structured = { version = "0.4", default-features = false }
wasmparser = { version = "0.258", default-features = false } wasmparser = { version = "0.258", default-features = false }
wasm-encoder = { version = "0.258", default-features = false, features = ["component-model", "std"] } wasm-encoder = { version = "0.258", default-features = false, features = ["component-model", "std"] }
webpki-root-certs = { version = "1.0.8", default-features = false } webpki-root-certs = { version = "1.0.9", default-features = false }
# ring-only: must match the provider the rest of the tree (wasmtime-wasi-http, rtc) uses # ring-only: must match the provider the rest of the tree (wasmtime-wasi-http, rtc) uses
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"] } rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"] }

View File

@ -41,6 +41,10 @@ pub use networking::rcon::RCONConfig;
pub use plugins::{PluginOverride, PluginsConfig}; pub use plugins::{PluginOverride, PluginsConfig};
pub use pvp::PVPConfig; pub use pvp::PVPConfig;
pub use server_links::ServerLinksConfig; pub use server_links::ServerLinksConfig;
pub use telemetry::TelemetryConfig;
/// Telemetry configuration options.
pub mod telemetry;
mod commands; mod commands;
@ -77,6 +81,9 @@ pub struct PumpkinConfig {
/// Advanced and feature-specific configuration settings. /// Advanced and feature-specific configuration settings.
#[serde(flatten)] #[serde(flatten)]
pub advanced: AdvancedConfiguration, pub advanced: AdvancedConfiguration,
/// Anonymous telemetry configuration settings.
#[serde(default)]
pub telemetry: TelemetryConfig,
} }
impl LoadConfiguration for PumpkinConfig { impl LoadConfiguration for PumpkinConfig {
@ -87,6 +94,7 @@ impl LoadConfiguration for PumpkinConfig {
fn validate(&self) { fn validate(&self) {
self.basic.validate(); self.basic.validate();
self.advanced.validate(); self.advanced.validate();
self.telemetry.validate();
let min_vd = NonZero::<u8>::MIN; let min_vd = NonZero::<u8>::MIN;
let Some(max_vd) = NonZero::new(64) else { let Some(max_vd) = NonZero::new(64) else {

View File

@ -0,0 +1,73 @@
use serde::{Deserialize, Serialize};
/// Telemetry configuration options.
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(default)]
pub struct TelemetryConfig {
/// Whether anonymous telemetry is enabled. Default is true.
pub enabled: bool,
/// Custom telemetry backend ingestion endpoint.
pub endpoint: String,
/// Heartbeat interval in seconds (default: 300 seconds / 5 minutes). Minimum 60s.
pub interval_secs: u64,
/// Whether to opt-in to displaying this server in the public community directory.
pub public: bool,
/// Public server name displayed on the analytics dashboard if public is true.
pub server_name: Option<String>,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
enabled: true,
endpoint: "https://market.pumpkinmc.org/api/v1/rest/telemetry/heartbeat".to_string(),
interval_secs: 300,
public: false,
server_name: None,
}
}
}
impl TelemetryConfig {
/// Validates telemetry configuration options.
pub const fn validate(&self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn telemetry_default() {
let default_config = TelemetryConfig::default();
assert!(default_config.enabled);
assert_eq!(
default_config.endpoint,
"https://market.pumpkinmc.org/api/v1/rest/telemetry/heartbeat"
);
assert_eq!(default_config.interval_secs, 300);
assert!(!default_config.public);
assert_eq!(default_config.server_name, None);
}
#[test]
fn telemetry_toml_deserialization() {
let toml_str = r#"
enabled = false
endpoint = "http://localhost:5000/api/v1/rest/telemetry/heartbeat"
interval_secs = 60
public = true
server_name = "Test SMP"
"#;
let config: TelemetryConfig = toml::from_str(toml_str).unwrap();
assert!(!config.enabled);
assert_eq!(
config.endpoint,
"http://localhost:5000/api/v1/rest/telemetry/heartbeat"
);
assert_eq!(config.interval_secs, 60);
assert!(config.public);
assert_eq!(config.server_name.as_deref(), Some("Test SMP"));
}
}

View File

@ -1,7 +1,6 @@
[package] [package]
name = "pumpkin-plugin-api" name = "pumpkin-plugin-api"
# This should be different from the pumpkin server version. version.workspace = true
version = "0.1.0"
edition.workspace = true edition.workspace = true
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "pumpkin-plugin-utils" name = "pumpkin-plugin-utils"
version = "0.1.0" version.workspace = true
edition.workspace = true edition.workspace = true
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Utilities for Pumpkin plugins: licensing, lease caching, and update checks." description = "Utilities for Pumpkin plugins: licensing, lease caching, and update checks."

View File

@ -24,7 +24,7 @@ use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer};
use crate::plugin::server::server_command::ServerCommandEvent; use crate::plugin::server::server_command::ServerCommandEvent;
use crate::server::{Server, ticker::Ticker}; use crate::server::{Server, ticker::Ticker};
use plugin::server::server_load::{LoadType, ServerLoadEvent}; use plugin::server::server_load::{LoadType, ServerLoadEvent};
use pumpkin_config::{AdvancedConfiguration, BasicConfiguration}; use pumpkin_config::{AdvancedConfiguration, BasicConfiguration, TelemetryConfig};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::{Color, NamedColor}; use pumpkin_util::text::color::{Color, NamedColor};
use rustyline::Editor; use rustyline::Editor;
@ -61,6 +61,7 @@ pub mod logging;
pub mod net; pub mod net;
pub mod plugin; pub mod plugin;
pub mod server; pub mod server;
pub mod telemetry;
pub mod world; pub mod world;
pub struct LoggingConfig { pub struct LoggingConfig {
@ -248,9 +249,16 @@ impl PumpkinServer {
pub async fn new( pub async fn new(
basic_config: BasicConfiguration, basic_config: BasicConfiguration,
advanced_config: AdvancedConfiguration, advanced_config: AdvancedConfiguration,
telemetry_config: TelemetryConfig,
vanilla_data: VanillaData, vanilla_data: VanillaData,
) -> Self { ) -> Self {
let server = Server::new(basic_config, advanced_config, vanilla_data).await; let server = Server::new(
basic_config,
advanced_config,
telemetry_config,
vanilla_data,
)
.await;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
adjust_file_descriptor_limit(); adjust_file_descriptor_limit();
@ -466,6 +474,8 @@ impl PumpkinServer {
.fire(&self.server, &mut ServerLoadEvent::new(LoadType::Startup)) .fire(&self.server, &mut ServerLoadEvent::new(LoadType::Startup))
.await; .await;
self.server.start_telemetry();
while !SHOULD_STOP.load(Ordering::Relaxed) { while !SHOULD_STOP.load(Ordering::Relaxed) {
if !self if !self
.unified_listener_task(&mut master_client_id, &tasks, &bedrock_clients) .unified_listener_task(&mut master_client_id, &tasks, &bedrock_clients)

View File

@ -120,7 +120,13 @@ async fn main() {
} }
}); });
let pumpkin_server = PumpkinServer::new(config.basic, config.advanced, vanilla_data).await; let pumpkin_server = PumpkinServer::new(
config.basic,
config.advanced,
config.telemetry,
vanilla_data,
)
.await;
let plugin_wait_time = pumpkin_server.init_plugins().await; let plugin_wait_time = pumpkin_server.init_plugins().await;
let time_elapsed = time.elapsed().saturating_sub(plugin_wait_time); let time_elapsed = time.elapsed().saturating_sub(plugin_wait_time);

View File

@ -20,7 +20,7 @@ use crate::{
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use connection_cache::{CachedBranding, CachedStatus}; use connection_cache::{CachedBranding, CachedStatus};
use key_store::KeyStore; use key_store::KeyStore;
use pumpkin_config::{AdvancedConfiguration, BasicConfiguration}; use pumpkin_config::{AdvancedConfiguration, BasicConfiguration, TelemetryConfig};
use pumpkin_data::dimension::Dimension; use pumpkin_data::dimension::Dimension;
use pumpkin_util::permission::PermissionManager; use pumpkin_util::permission::PermissionManager;
use pumpkin_util::text::color::NamedColor; use pumpkin_util::text::color::NamedColor;
@ -70,6 +70,7 @@ use crate::server::scheduler::TaskScheduler;
pub struct Server { pub struct Server {
pub basic_config: BasicConfiguration, pub basic_config: BasicConfiguration,
pub advanced_config: AdvancedConfiguration, pub advanced_config: AdvancedConfiguration,
pub telemetry_config: TelemetryConfig,
pub data: VanillaData, pub data: VanillaData,
@ -160,6 +161,7 @@ impl Server {
pub async fn new( pub async fn new(
basic_config: BasicConfiguration, basic_config: BasicConfiguration,
advanced_config: AdvancedConfiguration, advanced_config: AdvancedConfiguration,
telemetry_config: TelemetryConfig,
vanilla_data: VanillaData, vanilla_data: VanillaData,
) -> Arc<Self> { ) -> Arc<Self> {
let permission_manager = Arc::new(PermissionManager::new()); let permission_manager = Arc::new(PermissionManager::new());
@ -278,6 +280,7 @@ impl Server {
let server = Self { let server = Self {
basic_config, basic_config,
advanced_config, advanced_config,
telemetry_config,
data: vanilla_data, data: vanilla_data,
plugin_manager: Arc::new(PluginManager::new(verify_plugin_signatures)), plugin_manager: Arc::new(PluginManager::new(verify_plugin_signatures)),
permission_manager, permission_manager,
@ -999,6 +1002,17 @@ impl Server {
false false
} }
/// Returns the maximum number of players allowed on the server.
#[must_use]
pub const fn max_players(&self) -> u32 {
self.advanced_config.networking.java.max_players
}
/// Starts the background telemetry task if enabled in configuration.
pub fn start_telemetry(self: &Arc<Self>) {
crate::telemetry::start_telemetry(self.clone());
}
/// Generates a new container id. /// Generates a new container id.
pub fn new_container_id(&self) -> u32 { pub fn new_container_id(&self) -> u32 {
self.container_id.fetch_add(1, Ordering::SeqCst) self.container_id.fetch_add(1, Ordering::SeqCst)

View File

@ -0,0 +1,350 @@
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
use pumpkin_config::TelemetryConfig;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::server::Server;
/// Filename used to persist the server ID across restarts.
pub const SERVER_ID_FILENAME: &str = ".pumpkin-server-id";
/// Information about an active plugin on the server.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PluginInfo {
/// The name of the plugin.
pub name: String,
/// The version of the plugin.
pub version: String,
}
/// The telemetry heartbeat payload transmitted to the backend.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct HeartbeatPayload {
/// Pseudonymous UUID identifying this server installation.
pub server_id: Uuid,
/// The server software implementation type (e.g. "pumpkin").
pub server_type: String,
/// The Pumpkin server software version.
pub server_version: String,
/// The Minecraft game version string.
pub minecraft_version: String,
/// The network protocol version number.
pub protocol_version: i32,
/// Current number of connected players.
pub online_players: usize,
/// Maximum number of players allowed.
pub max_players: u32,
/// Host operating system name.
pub os: String,
/// Host CPU architecture.
pub arch: String,
/// Number of available CPU cores.
pub cpu_cores: usize,
/// CPU model name or brand.
pub cpu_model: String,
/// RAM allocated/used by the system in MiB.
pub ram_allocated_mb: u64,
/// Total physical RAM available to the system in MiB.
pub total_ram_mb: u64,
/// List of active plugins.
pub plugins: Vec<PluginInfo>,
/// Whether the server has opted into public directory listing.
pub is_public: bool,
/// Public name of the server if opted into public directory listing.
#[serde(skip_serializing_if = "Option::is_none")]
pub public_name: Option<String>,
}
/// The response payload received from the telemetry backend.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct HeartbeatResponse {
/// Response status ("ok", "throttled", etc.).
pub status: String,
/// Status or error message from the backend.
#[serde(default)]
pub message: Option<String>,
/// Confirmed server ID acknowledged by the backend.
#[serde(default)]
pub server_id: Option<String>,
/// Recommended seconds to wait before the next heartbeat.
#[serde(default)]
pub next_ping_seconds: Option<u64>,
}
/// Loads an existing server UUID from the given path or generates and persists a new one.
pub fn get_or_create_server_id(path: &Path) -> Uuid {
if path.exists()
&& let Ok(content) = std::fs::read_to_string(path)
{
let trimmed = content.trim();
if let Ok(id) = Uuid::parse_str(trimmed) {
return id;
}
tracing::warn!(
"Failed to parse server ID from {}, generating a new one",
path.display()
);
}
let id = Uuid::new_v4();
if let Err(err) = std::fs::write(path, id.to_string()) {
tracing::warn!("Failed to save server ID to {}: {err}", path.display());
}
id
}
/// Resolves the server ID by checking default locations or generating a new one.
#[must_use]
pub fn resolve_server_id() -> Uuid {
let root_path = Path::new(SERVER_ID_FILENAME);
if root_path.exists() {
return get_or_create_server_id(root_path);
}
let data_path = Path::new("data").join(SERVER_ID_FILENAME);
if data_path.exists() {
return get_or_create_server_id(&data_path);
}
get_or_create_server_id(root_path)
}
/// Builds the telemetry heartbeat payload from current server and runtime state.
#[must_use]
pub fn build_heartbeat_payload(
server: &Server,
server_id: Uuid,
telemetry_config: &TelemetryConfig,
) -> HeartbeatPayload {
let os = sysinfo::System::long_os_version().unwrap_or_else(|| {
let name = sysinfo::System::name().unwrap_or_else(|| std::env::consts::OS.to_string());
sysinfo::System::os_version().map_or(name.clone(), |ver| format!("{name} {ver}"))
});
let arch = std::env::consts::ARCH.to_string();
let cpu_cores = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get);
let (ram_allocated_mb, total_ram_mb, cpu_model) = if sysinfo::IS_SUPPORTED_SYSTEM {
let mut sys = sysinfo::System::new_all();
sys.refresh_all();
let ram_allocated = sys.used_memory() / (1024 * 1024);
let total_ram = sys.total_memory() / (1024 * 1024);
let cpu_model = sys.cpus().first().map_or_else(
|| "Unknown".to_string(),
|c| {
let brand = c.brand().trim();
if brand.is_empty() {
"Unknown".to_string()
} else {
brand.to_string()
}
},
);
(ram_allocated, total_ram, cpu_model)
} else {
(0, 0, "Unknown".to_string())
};
let plugins = server
.plugin_manager
.active_plugins()
.into_iter()
.map(|p| PluginInfo {
name: p.name,
version: p.version,
})
.collect();
let is_public = telemetry_config.public;
let public_name = if is_public {
telemetry_config.server_name.clone()
} else {
None
};
HeartbeatPayload {
server_id,
server_type: "pumpkin".to_string(),
server_version: env!("CARGO_PKG_VERSION").to_string(),
minecraft_version: pumpkin_data::packet::CURRENT_MC_VERSION.to_string(),
protocol_version: pumpkin_data::packet::CURRENT_MC_VERSION.protocol_version(),
online_players: server.get_player_count(),
max_players: server.advanced_config.networking.java.max_players,
os,
arch,
cpu_cores,
cpu_model,
ram_allocated_mb,
total_ram_mb,
plugins,
is_public,
public_name,
}
}
/// Starts the background telemetry reporting task using the server's telemetry configuration.
pub fn start_telemetry(server: Arc<Server>) {
let config = server.telemetry_config.clone();
start_telemetry_with_config(server, &config);
}
/// Starts the background telemetry reporting task with a specific configuration.
pub fn start_telemetry_with_config(server: Arc<Server>, config: &TelemetryConfig) {
if !config.enabled {
tracing::trace!("Telemetry is disabled in configuration.");
return;
}
tracing::info!(
"Anonymous server telemetry is enabled. Sending periodic heartbeats to {}",
config.endpoint
);
let user_agent = format!("Pumpkin-Server/{}", env!("CARGO_PKG_VERSION"));
let client = match reqwest::Client::builder()
.timeout(Duration::from_secs(10))
.user_agent(&user_agent)
.build()
{
Ok(c) => c,
Err(err) => {
tracing::debug!("Failed to create telemetry HTTP client: {err}");
return;
}
};
let server_id = resolve_server_id();
let interval_secs = config.interval_secs.max(60);
let endpoint = config.endpoint.clone();
let config = config.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(interval_secs));
while !crate::SHOULD_STOP.load(Ordering::Relaxed) {
interval.tick().await;
if crate::SHOULD_STOP.load(Ordering::Relaxed) {
break;
}
let payload = build_heartbeat_payload(&server, server_id, &config);
let res = client
.post(&endpoint)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.header(reqwest::header::USER_AGENT, &user_agent)
.json(&payload)
.send()
.await;
match res {
Ok(resp) => {
if resp.status().is_success() {
tracing::trace!("Telemetry heartbeat successfully transmitted");
} else {
tracing::debug!(
"Telemetry heartbeat received non-success HTTP status: {}",
resp.status()
);
}
}
Err(err) => {
tracing::debug!("Telemetry heartbeat failed to send: {err}");
}
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn server_id_generation_and_persistence() {
let dir = tempfile::tempdir().unwrap();
let id_path = dir.path().join(".pumpkin-server-id");
assert!(!id_path.exists());
let id1 = get_or_create_server_id(&id_path);
assert!(id_path.exists());
let id2 = get_or_create_server_id(&id_path);
assert_eq!(id1, id2);
// Corrupted file test
std::fs::write(&id_path, "not-a-uuid").unwrap();
let id3 = get_or_create_server_id(&id_path);
assert_ne!(id1, id3);
}
#[test]
fn heartbeat_payload_serialization() {
let payload = HeartbeatPayload {
server_id: Uuid::parse_str("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d").unwrap(),
server_type: "pumpkin".to_string(),
server_version: "0.3.2".to_string(),
minecraft_version: "1.21.4".to_string(),
protocol_version: 769,
online_players: 42,
max_players: 100,
os: "Ubuntu 24.04 LTS".to_string(),
arch: "x86_64".to_string(),
cpu_cores: 8,
cpu_model: "AMD Ryzen 7 5800X 8-Core Processor".to_string(),
ram_allocated_mb: 8192,
total_ram_mb: 16384,
plugins: vec![
PluginInfo {
name: "Essential Admin Tools".to_string(),
version: "1.0.0".to_string(),
},
PluginInfo {
name: "PumpkinAuth".to_string(),
version: "1.2.0".to_string(),
},
],
is_public: true,
public_name: Some("My High-Performance Pumpkin SMP".to_string()),
};
let json_str = serde_json::to_string_pretty(&payload).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(parsed["server_id"], "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d");
assert_eq!(parsed["server_type"], "pumpkin");
assert_eq!(parsed["server_version"], "0.3.2");
assert_eq!(parsed["minecraft_version"], "1.21.4");
assert_eq!(parsed["protocol_version"], 769);
assert_eq!(parsed["online_players"], 42);
assert_eq!(parsed["max_players"], 100);
assert_eq!(parsed["os"], "Ubuntu 24.04 LTS");
assert_eq!(parsed["arch"], "x86_64");
assert_eq!(parsed["cpu_cores"], 8);
assert_eq!(parsed["cpu_model"], "AMD Ryzen 7 5800X 8-Core Processor");
assert_eq!(parsed["ram_allocated_mb"], 8192);
assert_eq!(parsed["total_ram_mb"], 16384);
assert_eq!(parsed["is_public"], true);
assert_eq!(parsed["public_name"], "My High-Performance Pumpkin SMP");
assert_eq!(parsed["plugins"].as_array().unwrap().len(), 2);
}
#[test]
fn heartbeat_response_deserialization() {
let json_data = r#"{
"status": "ok",
"message": "Heartbeat accepted",
"server_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"next_ping_seconds": 300
}"#;
let resp: HeartbeatResponse = serde_json::from_str(json_data).unwrap();
assert_eq!(resp.status, "ok");
assert_eq!(resp.message.as_deref(), Some("Heartbeat accepted"));
assert_eq!(
resp.server_id.as_deref(),
Some("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d")
);
assert_eq!(resp.next_ping_seconds, Some(300));
}
}