diff --git a/crates/obs/src/telemetry/mod.rs b/crates/obs/src/telemetry/mod.rs index 9fe29a833..85b4573b4 100644 --- a/crates/obs/src/telemetry/mod.rs +++ b/crates/obs/src/telemetry/mod.rs @@ -92,30 +92,33 @@ pub(crate) fn init_telemetry(config: &OtelConfig) -> Result Option { +pub(super) fn init_profiler(config: &OtelConfig) -> Option { use pyroscope::backend::{BackendConfig, PprofConfig, pprof_backend}; use pyroscope::pyroscope::PyroscopeAgentBuilder; use rustfs_config::VERSION; @@ -529,10 +527,19 @@ fn init_profiler(config: &OtelConfig) -> Option { return None; } - let endpoint = config.profiling_endpoint.as_ref()?.as_str(); - if endpoint.is_empty() { + let Some(endpoint) = config + .profiling_endpoint + .as_deref() + .map(str::trim) + .filter(|endpoint| !endpoint.is_empty()) + else { + warn!( + backend = "pyroscope", + result = "profiling_endpoint_missing", + "Profiling export is enabled but no profiling endpoint was configured" + ); return None; - } + }; // Configure Pyroscope Agent let backend = pprof_backend(PprofConfig::default(), BackendConfig::default()); @@ -540,15 +547,33 @@ fn init_profiler(config: &OtelConfig) -> Option { let version = config.service_version.as_deref().unwrap_or(VERSION); let sample_rate = 100; // 100 Hz - let agent = PyroscopeAgentBuilder::new(endpoint, service_name, sample_rate, "pyroscope-rs", "1.0.1", backend) + let agent = match PyroscopeAgentBuilder::new(endpoint, service_name, sample_rate, "pyroscope-rs", "1.0.1", backend) .tags(vec![("version", version), ("profile_type", "cpu")]) .build() - .ok()?; + { + Ok(agent) => agent, + Err(err) => { + warn!( + backend = "pyroscope", + endpoint, + result = "profiling_agent_build_failed", + error = %err, + "Profiling export agent initialization failed" + ); + return None; + } + }; match agent.start() { Ok(agent) => Some(agent), Err(err) => { - eprintln!("Pyroscope agent start error: {err:?}"); + warn!( + backend = "pyroscope", + endpoint, + result = "profiling_agent_start_failed", + error = ?err, + "Profiling export agent failed to start" + ); None } } @@ -558,7 +583,18 @@ fn init_profiler(config: &OtelConfig) -> Option { feature = "pyroscope", any(target_os = "macos", all(target_os = "linux", target_env = "gnu", target_arch = "x86_64")) )))] -fn init_profiler(_config: &OtelConfig) -> Option { +pub(super) fn init_profiler(config: &OtelConfig) -> Option { + if config + .profiling_export_enabled + .unwrap_or(rustfs_config::DEFAULT_OBS_PROFILING_EXPORT_ENABLED) + { + warn!( + backend = "pyroscope", + result = "profiling_feature_not_compiled", + required_feature = "pyroscope", + "Profiling export is enabled but this binary was built without Pyroscope support" + ); + } None } diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index 2a9864a30..e5f975113 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -56,7 +56,7 @@ sftp = ["rustfs-protocols/sftp"] license = [] io-scheduler-debug = [] # Enable debug information in I/O scheduler tracing-chunk-debug = [] # Enable per-chunk tracing in data plane (high noise, for debugging only) -full = ["metrics-gpu", "ftps", "swift", "webdav", "sftp"] +full = ["metrics-gpu", "ftps", "swift", "webdav", "sftp", "pyroscope"] manual-test-runners = [] rio-v2 = ["rustfs-ecstore/rio-v2"] pyroscope = ["rustfs-obs/pyroscope"] diff --git a/rustfs/src/profiling.rs b/rustfs/src/profiling.rs index 42369f483..9bd0e66d5 100644 --- a/rustfs/src/profiling.rs +++ b/rustfs/src/profiling.rs @@ -12,7 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use tracing::{debug, info}; +use rustfs_config::{ + ENV_CPU_DURATION_SECS, ENV_CPU_FREQ, ENV_CPU_INTERVAL_SECS, ENV_CPU_MODE, ENV_ENABLE_PROFILING, ENV_MEM_INTERVAL_SECS, + ENV_MEM_PERIODIC, ENV_OUTPUT_DIR, +}; +use tracing::{debug, info, warn}; const LOG_COMPONENT_PROFILING: &str = "profiling"; const LOG_SUBSYSTEM_CPU: &str = "cpu"; @@ -23,7 +27,32 @@ const MEMORY_PPROF_UNSUPPORTED_REASON: &str = "mimalloc_memory_pprof_unsupported pub const LOCAL_CPU_PPROF_UNSUPPORTED_SUMMARY: &str = "local CPU pprof dumps are not supported; use Pyroscope export instead"; pub const MEMORY_PPROF_UNSUPPORTED_SUMMARY: &str = "memory pprof dumps are not supported with the mimalloc allocator"; +const LEGACY_PROFILING_ENV_KEYS: [&str; 8] = [ + ENV_ENABLE_PROFILING, + ENV_CPU_MODE, + ENV_CPU_FREQ, + ENV_CPU_INTERVAL_SECS, + ENV_CPU_DURATION_SECS, + ENV_MEM_PERIODIC, + ENV_MEM_INTERVAL_SECS, + ENV_OUTPUT_DIR, +]; + pub async fn init_from_env() { + let legacy_keys = legacy_profiling_env_keys_present(); + if !legacy_keys.is_empty() { + warn!( + component = LOG_COMPONENT_PROFILING, + subsystem = LOG_SUBSYSTEM_RUNTIME, + event = "profiling_legacy_env_ignored", + ignored_keys = %legacy_keys.join(", "), + recommended_enable_key = "RUSTFS_OBS_PROFILING_EXPORT_ENABLED", + recommended_endpoint_key = "RUSTFS_OBS_PROFILING_ENDPOINT", + output_dir_key = ENV_OUTPUT_DIR, + "Legacy local profiling environment variables are ignored; use observability profiling export settings instead" + ); + } + info!( component = LOG_COMPONENT_PROFILING, subsystem = LOG_SUBSYSTEM_RUNTIME, @@ -88,6 +117,33 @@ fn unsupported_message(summary: &str) -> String { ) } +fn legacy_profiling_env_keys_present() -> Vec<&'static str> { + LEGACY_PROFILING_ENV_KEYS + .into_iter() + .filter(|key| std::env::var_os(key).is_some()) + .collect() +} + fn target_env() -> &'static str { option_env!("CARGO_CFG_TARGET_ENV").unwrap_or("unknown") } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn legacy_profiling_env_keys_present_reports_legacy_overrides() { + temp_env::with_vars( + [ + (ENV_ENABLE_PROFILING, Some("true")), + (ENV_OUTPUT_DIR, Some("/tmp/rustfs-profiles")), + ], + || { + let keys = legacy_profiling_env_keys_present(); + assert!(keys.contains(&ENV_ENABLE_PROFILING)); + assert!(keys.contains(&ENV_OUTPUT_DIR)); + }, + ); + } +}