diff --git a/docs/en/configuration/ini-settings.md b/docs/en/configuration/ini-settings.md index dd883ba..9a35c4d 100644 --- a/docs/en/configuration/ini-settings.md +++ b/docs/en/configuration/ini-settings.md @@ -3,7 +3,7 @@ This is the configuration list supported in `php.ini`. | Configuration Item | Description | Default Value | -| ------------------------------------------------ |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------- | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | skywalking_agent.enable | Enable skywalking_agent extension or not. | Off | | skywalking_agent.log_file | Log file path. | /tmp/skywalking-agent.log | | skywalking_agent.log_level | Log level: one of `OFF`, `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`. | INFO | @@ -20,9 +20,9 @@ This is the configuration list supported in `php.ini`. | skywalking_agent.heartbeat_period | Agent heartbeat report period. Unit, second. | 30 | | skywalking_agent.properties_report_period_factor | The agent sends the instance properties to the backend every heartbeat_period * properties_report_period_factor seconds. | 10 | | skywalking_agent.enable_zend_observer | Whether to use `zend observer` instead of `zend_execute_ex` to hook the functions, this feature is only available for PHP8+. | Off | -| skywalking_agent.reporter_type | Reporter type, optional values are `grpc` and `kafka`. | grpc | +| skywalking_agent.reporter_type | Reporter type, optional values are `grpc`, `kafka` and `standalone`. | grpc | | skywalking_agent.kafka_bootstrap_servers | A list of host/port pairs to use for connect to the Kafka cluster. Only available when `reporter_type` is `kafka`. | | | skywalking_agent.kafka_producer_config | Configure Kafka Producer configuration in JSON format `{"key": "value}`. Only available when `reporter_type` is `kafka`. | {} | -| skywalking_agent.inject_context | Whether to enable automatic injection of skywalking context variables (such as `SW_TRACE_ID`). For `php-fpm` mode, it will be injected into the `$_SERVER` variable. For `swoole` mode, it will be injected into the `$request->server` variable. | Off | -| skywalking_agent.instance_name | Instance name. You can set ${HOSTNAME}, refer to [Example #1]( https://www.php.net/manual/en/install.fpm.configuration.php) | | - +| skywalking_agent.inject_context | Whether to enable automatic injection of skywalking context variables (such as `SW_TRACE_ID`). For `php-fpm` mode, it will be injected into the `$_SERVER` variable. For `swoole` mode, it will be injected into the `$request->server` variable. | Off | +| skywalking_agent.instance_name | Instance name. You can set `${HOSTNAME}`, refer to [Example #1](https://www.php.net/manual/en/install.fpm.configuration.php) | | +| skywalking_agent.standalone_socket_path | Unix domain socket file path of standalone skywalking php worker. Only available when `reporter_type` is `standalone`. | | diff --git a/docs/en/reporter/standalone-reporter.md b/docs/en/reporter/standalone-reporter.md new file mode 100644 index 0000000..692ab2e --- /dev/null +++ b/docs/en/reporter/standalone-reporter.md @@ -0,0 +1,42 @@ +# Standalone reporter + +When the reporter type is `grpc` or `kafka`, the `skywalking_agent` extension forks a child process during +the extension initialization phase to act as a worker process for sending data to the SkyWalking OAP server +or Kafka. + +However, this approach has some limitations, such as: + +1. It cannot be used with the `php-fpm` daemon mode. +2. Multiple worker processes can be redundant when there are several `php-fpm` processes on the instance. + +To address these issues, `skywalking_agent` introduces a new reporter type: `standalone`. + +With the `standalone` reporter type, the `skywalking_agent` extension no longer forks a child process. +Instead, the user needs to manually start an independent worker process. + +## Steps + +1. Compile the standalone `skywalking-php-worker` binary: + + ```shell + cargo build -p skywalking-php-worker --bin skywalking-php-worker --all-features --release + ``` + +2. Run `skywalking-php-worker`: + + Assuming the socket file path is `/tmp/skywalking-php-worker.sock` and the SkyWalking OAP server address is `127.0.0.1:11800`, the command is: + + ```shell + ./target/release/skywalking-php-worker -s /tmp/skywalking-php-worker.sock grpc --server-addr 127.0.0.1:11800 + ``` + + For additional parameters, refer to `./target/release/skywalking-php-worker --help`. + +3. Configure `php.ini`: + + ```ini + [skywalking_agent] + extension = skywalking_agent.so + skywalking_agent.reporter_type = standalone + skywalking_agent.standalone_socket_path = /tmp/skywalking-php-worker.sock + ``` diff --git a/docs/menu.yml b/docs/menu.yml index f7f9e1a..947ffa7 100644 --- a/docs/menu.yml +++ b/docs/menu.yml @@ -34,6 +34,8 @@ catalog: catalog: - name: "Kafka Reporter" path: "/en/reporter/kafka-reporter" + - name: "Standalone Reporter" + path: "/en/reporter/standalone-reporter" - name: "Contribution" catalog: - name: "Compiling Guidance" diff --git a/src/lib.rs b/src/lib.rs index 5a54bb9..ceb951d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,7 +91,8 @@ const SKYWALKING_AGENT_PROPERTIES_REPORT_PERIOD_FACTOR: &str = /// PHP8's jit. const SKYWALKING_AGENT_ENABLE_ZEND_OBSERVER: &str = "skywalking_agent.enable_zend_observer"; -/// Reporter type, optional values are `grpc` and `kafka`, default is `grpc`. +/// Reporter type, optional values are `grpc`, `kafka` and `standalone`, default +/// is `grpc`. const SKYWALKING_AGENT_REPORTER_TYPE: &str = "skywalking_agent.reporter_type"; /// A list of host/port pairs to use for establishing the initial connection to @@ -108,6 +109,10 @@ const SKYWALKING_AGENT_KAFKA_PRODUCER_CONFIG: &str = "skywalking_agent.kafka_pro /// `$request->server` variable. const SKYWALKING_AGENT_INJECT_CONTEXT: &str = "skywalking_agent.inject_context"; +/// Unix domain socket file path of standalone skywalking php worker. Only +/// available when `reporter_type` is `standalone`. +const SKYWALKING_AGENT_STANDALONE_SOCKET_PATH: &str = "skywalking_agent.standalone_socket_path"; + #[php_get_module] pub fn get_module() -> Module { let mut module = Module::new( @@ -194,6 +199,11 @@ pub fn get_module() -> Module { Policy::System, ); module.add_ini(SKYWALKING_AGENT_INJECT_CONTEXT, false, Policy::System); + module.add_ini( + SKYWALKING_AGENT_STANDALONE_SOCKET_PATH, + "".to_string(), + Policy::System, + ); // Hooks. module.on_module_init(module::init); diff --git a/src/module.rs b/src/module.rs index a527a48..36a7d74 100644 --- a/src/module.rs +++ b/src/module.rs @@ -92,6 +92,12 @@ pub static RUNTIME_DIR: Lazy = Lazy::new(|| { }); pub static SOCKET_FILE_PATH: Lazy = Lazy::new(|| { + if is_standalone_reporter_type() { + return PathBuf::from(get_str_ini_with_default( + SKYWALKING_AGENT_STANDALONE_SOCKET_PATH, + )); + } + let mut dir = RUNTIME_DIR.clone(); let dur = SystemTime::now() @@ -263,7 +269,9 @@ fn try_init_logger() -> anyhow::Result<()> { let file = open_options.open(path)?; - let filter = EnvFilter::new(format!("info,skywalking_agent={}", log_level)); + let filter = EnvFilter::new(format!( + "info,skywalking_agent={log_level},skywalking_php_worker={log_level}" + )); let subscriber = FmtSubscriber::builder() .with_env_filter(filter) @@ -285,3 +293,8 @@ fn get_module_registry() -> &'static ZArr { pub fn is_enable() -> bool { *IS_ENABLE } + +#[inline] +pub fn is_standalone_reporter_type() -> bool { + REPORTER_TYPE.as_str() == "standalone" +} diff --git a/src/worker.rs b/src/worker.rs index dbeaa60..145b9fb 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -14,9 +14,9 @@ // limitations under the License. use crate::module::{ - AUTHENTICATION, ENABLE_TLS, HEARTBEAT_PERIOD, PROPERTIES_REPORT_PERIOD_FACTOR, REPORTER_TYPE, - SERVER_ADDR, SERVICE_INSTANCE, SERVICE_NAME, SOCKET_FILE_PATH, SSL_CERT_CHAIN_PATH, - SSL_KEY_PATH, SSL_TRUSTED_CA_PATH, WORKER_THREADS, + is_standalone_reporter_type, AUTHENTICATION, ENABLE_TLS, HEARTBEAT_PERIOD, + PROPERTIES_REPORT_PERIOD_FACTOR, REPORTER_TYPE, SERVER_ADDR, SERVICE_INSTANCE, SERVICE_NAME, + SOCKET_FILE_PATH, SSL_CERT_CHAIN_PATH, SSL_KEY_PATH, SSL_TRUSTED_CA_PATH, WORKER_THREADS, }; #[cfg(feature = "kafka-reporter")] use crate::module::{KAFKA_BOOTSTRAP_SERVERS, KAFKA_PRODUCER_CONFIG}; @@ -31,6 +31,10 @@ use std::{cmp::Ordering, num::NonZeroUsize, process::exit, thread::available_par use tracing::error; pub fn init_worker() { + if is_standalone_reporter_type() { + return; + } + unsafe { // TODO Shutdown previous worker before fork if there is a PHP-FPM reload // operation. diff --git a/worker/Cargo.toml b/worker/Cargo.toml index 2780dc9..a6f6d82 100644 --- a/worker/Cargo.toml +++ b/worker/Cargo.toml @@ -43,9 +43,9 @@ skywalking = { version = "0.8.0", features = ["management"] } tokio = { version = "1.29.1", features = ["full"] } tokio-stream = "0.1.14" tonic = { version = "0.8.3", features = ["tls", "tls-roots"] } -tracing = { version = "0.1.37", features = ["attributes"] } +tracing = { version = "0.1.37", features = ["attributes", "log"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"], optional = true } -# [[bin]] -# name = "skywalking-php-worker" -# required-features = ["standalone", "kafka-reporter"] +[[bin]] +name = "skywalking-php-worker" +required-features = ["standalone", "kafka-reporter"] diff --git a/worker/src/main.rs b/worker/src/main.rs new file mode 100644 index 0000000..d924e1f --- /dev/null +++ b/worker/src/main.rs @@ -0,0 +1,156 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use clap::Parser; +use skywalking_php_worker::{ + new_tokio_runtime, + reporter::{GrpcReporterConfiguration, KafkaReporterConfiguration, ReporterConfiguration}, + start_worker, WorkerConfiguration, +}; +use std::{num::NonZeroUsize, path::PathBuf, thread::available_parallelism}; +use tracing::log::LevelFilter; +use tracing_subscriber::{EnvFilter, FmtSubscriber}; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Path of socket file to listening + #[arg(short, long)] + socket_file_path: PathBuf, + + /// Count of worker threads, default is `nproc` + #[arg(long)] + worker_threads: Option, + + /// Log level, will be overwritten by env `RUST_LOG` + #[arg(short, long, default_value = "INFO")] + log_level: LevelFilter, + + /// Select reporter + #[command(subcommand)] + reporter: ReporterArgs, +} + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +enum ReporterArgs { + /// Report to Skywalking OAP via grpc protocol + Grpc { + /// skywalking server address + #[arg(long)] + server_addr: String, + + /// Skywalking agent authentication token + #[arg(long)] + authentication: Option, + + /// Wether to enable tls for gPRC + #[arg(long)] + enable_tls: bool, + + /// The gRPC SSL trusted ca file + #[arg(long, required_if_eq("enable_tls", "true"))] + ssl_cert_chain_path: Option, + + /// The private key file. Enable mTLS when ssl_key_path and + /// ssl_cert_chain_path exist + #[arg(long)] + ssl_key_path: Option, + + /// The certificate file. Enable mTLS when ssl_key_path and + /// ssl_cert_chain_path exist + #[arg(long)] + ssl_trusted_ca_path: Option, + }, + /// Report to kafka + Kafka { + /// A list of host/port pairs to use for establishing the initial + /// connection to the Kafka cluster. Only available when the + /// reporter type is `kafka` + #[arg(long)] + kafka_bootstrap_servers: String, + + /// Configure Kafka Producer configuration in JSON format. + /// Only available when the reporter type is `kafka` + #[arg(long)] + kafka_producer_config: Option, + }, +} + +impl From for ReporterConfiguration { + fn from(args: ReporterArgs) -> Self { + match args { + ReporterArgs::Grpc { + server_addr, + authentication, + enable_tls, + ssl_cert_chain_path, + ssl_key_path, + ssl_trusted_ca_path, + } => ReporterConfiguration::Grpc(GrpcReporterConfiguration { + server_addr, + authentication: authentication.unwrap_or_default(), + enable_tls, + ssl_cert_chain_path: ssl_cert_chain_path.unwrap_or_default(), + ssl_key_path: ssl_key_path.unwrap_or_default(), + ssl_trusted_ca_path: ssl_trusted_ca_path.unwrap_or_default(), + }), + ReporterArgs::Kafka { + kafka_bootstrap_servers, + kafka_producer_config, + } => ReporterConfiguration::Kafka(KafkaReporterConfiguration { + kafka_bootstrap_servers, + kafka_producer_config: kafka_producer_config.unwrap_or_default(), + }), + } + } +} + +fn init_logger(log_level: &LevelFilter) -> anyhow::Result<()> { + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| { + EnvFilter::new(format!( + "info,skywalking_agent={log_level},skywalking_php_worker={log_level}" + )) + }); + + let subscriber = FmtSubscriber::builder() + .with_env_filter(filter) + .with_ansi(false) + .finish(); + + tracing::subscriber::set_global_default(subscriber)?; + + Ok(()) +} + +fn worker_threads(worker_threads: Option) -> usize { + worker_threads.unwrap_or_else(|| available_parallelism().map(NonZeroUsize::get).unwrap_or(1)) +} + +fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + init_logger(&args.log_level)?; + + let rt = new_tokio_runtime(worker_threads(args.worker_threads)); + + rt.block_on(start_worker(WorkerConfiguration { + socket_file_path: args.socket_file_path, + heart_beat: None, + reporter_config: args.reporter.into(), + }))?; + + Ok(()) +} diff --git a/worker/src/reporter/reporter_grpc.rs b/worker/src/reporter/reporter_grpc.rs index d450b43..fdbdd18 100644 --- a/worker/src/reporter/reporter_grpc.rs +++ b/worker/src/reporter/reporter_grpc.rs @@ -21,9 +21,9 @@ use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity use tracing::{debug, info, warn}; pub struct GrpcReporterConfiguration { + pub server_addr: String, pub authentication: String, pub enable_tls: bool, - pub server_addr: String, pub ssl_cert_chain_path: String, pub ssl_key_path: String, pub ssl_trusted_ca_path: String,