Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,10 @@ jobs:
- name: Setup php-fpm for Linux
if: matrix.os == 'ubuntu-24.04'
run: |
sudo apt-get update
sudo apt-get update
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
sudo apt-get update
sudo apt-get install -y php${{ matrix.flag.php_version }}-fpm
sudo ln -sf /usr/sbin/php-fpm${{ matrix.flag.php_version }} /usr/sbin/php-fpm

Expand Down Expand Up @@ -171,8 +174,18 @@ jobs:
# Build mixture for cargo test.
- name: Docker compose
run: |
docker compose up -d --wait
docker compose ps
for i in 1 2 3; do
if docker compose up -d --wait && docker compose ps; then
break
fi
echo "docker compose up failed (attempt ${i}/3), retrying in 10s..."
docker compose down --remove-orphans 2>/dev/null || true
if [ "${i}" -lt 3 ]; then
sleep 10
else
exit 1
fi
done

# Try cargo test.
- name: Cargo test
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ members = [
]

[workspace.package]
version = "1.1.0"
version = "1.2.0"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change needs to be reflected in Cargo.lock.

authors = ["Apache Software Foundation", "jmjoy <jmjoy@apache.org>", "Yanlong He <heyanlong@apache.org>"]
edition = "2024"
rust-version = "1.85"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

<img src="http://skywalking.apache.org/assets/logo.svg" alt="Sky Walking logo" height="90px" align="right" />

**SkyWalking PHP** The PHP Agent for Apache SkyWalking, which provides the native tracing abilities for PHP project.
**SkyWalking PHP** The PHP Agent for Apache SkyWalking, which provides native tracing and PHP Health Metrics (PHM)
runtime reporting for PHP projects.

**SkyWalking** an APM(application performance monitor) system, especially designed for
microservices, cloud native and container-based (Docker, Kubernetes, Mesos) architectures.
Expand Down
2 changes: 2 additions & 0 deletions docs/en/configuration/ini-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ This is the configuration list supported in `php.ini`.
| 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`. | |
| skywalking_agent.psr_logging_level | The log level reported to SkyWalking, based on PSR-3, one of `Off`, `Debug`, `Info`, Notice`, Warning`, Error`, Critical`, Alert`, Emergency`. | Off |
| skywalking_agent.metrics_enable | Enable PHP Health Metrics (PHM) meter reporting via native MeterReportService. **Linux only** (requires `/proc`). Default **On** on Linux when the agent is active; default **Off** on macOS/Windows. Set to `Off` to disable on Linux. Reports six process meters: CPU utilization, memory used/peak, virtual memory, thread count, and open FD count. See [PHP agent README](../setup/service-agent/php-agent/README.md#php-health-metrics-phm). | On (Linux); Off (other) |
| skywalking_agent.metrics_report_period | PHM meter collection interval in seconds. Process meters are sampled by the forked reporter worker via `/proc` (parent PHP process PID). **Linux only.** | 30 |
42 changes: 42 additions & 0 deletions docs/en/setup/service-agent/php-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,48 @@ Refer to the Configuration section for more configuration items.
> Enabling it by default will cause extra meaningless consumption when skywalking agent is not
> needed (such as simply executing a php script).

### PHP Health Metrics (PHM)

> **Platform:** PHM process meters are **Linux only**. The forked reporter worker reads the
> parent PHP process via `/proc` (`/proc/{pid}/status`, `stat`, and `fd`). They are not available
> on macOS or Windows. Trace and other agent features are unchanged.

When `reporter_type` is `grpc` or `kafka`, the forked reporter worker boots
`skywalking::metrics::Metricer` in `start_worker`, alongside heartbeat reporting. A background
collector samples `/proc` for the parent PHP process (`getppid()`), updates Gauges, and `Metricer`
reports meter data to OAP through the same path as traces and logs. PHM does not run when
`reporter_type = standalone`.

PHM reports PHP runtime meters through the native Meter protocol (MeterReportService), without
requiring HTTP traffic, similar to Python PVM and Ruby runtime meters.
**PHM is enabled by default on Linux** when the agent is active (`skywalking_agent.enable = On`).
To disable it or tune the interval, use `php.ini`:

```ini
; Disable PHM if not needed (default is On on Linux).
; skywalking_agent.metrics_enable = Off

; Report interval in seconds (default 30).
skywalking_agent.metrics_report_period = 30
```

PHM reports six process meters (aligned with OAP `php-runtime.yaml` and Horizon UI widgets):

| Agent meter name | OAP / UI expression | Source |
| --- | --- | --- |
| `instance_php_process_cpu_utilization` | `meter_instance_php_process_cpu_utilization` | `/proc/{pid}/stat` utime+stime delta |
| `instance_php_memory_used_mb` | `meter_instance_php_memory_used_mb` | `/proc/{pid}/status` VmRSS |
| `instance_php_memory_peak_mb` | `meter_instance_php_memory_peak_mb` | `/proc/{pid}/status` VmHWM |
| `instance_php_virtual_memory_mb` | `meter_instance_php_virtual_memory_mb` | `/proc/{pid}/status` VmSize |
| `instance_php_thread_count` | `meter_instance_php_thread_count` | `/proc/{pid}/status` Threads |
| `instance_php_open_fd_count` | `meter_instance_php_open_fd_count` | `/proc/{pid}/fd` count |

On the OAP side, activate the `php-runtime` entry in
`agent-analyzer.default.meterAnalyzerActiveFiles`. Horizon UI shows the widgets on the **General
Service → Instance** dashboard when data is available.

See [INI Settings](../../../configuration/ini-settings.md) for all PHM options.

## Run

Start `php-fpm` server:
Expand Down
17 changes: 17 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ const SKYWALKING_AGENT_STANDALONE_SOCKET_PATH: &str = "skywalking_agent.standalo
/// `Info`, Notice`, Warning`, Error`, Critical`, Alert`, Emergency`.
const SKYWALKING_AGENT_PSR_LOGGING_LEVEL: &str = "skywalking_agent.psr_logging_level";

/// Whether to report PHP Health Metrics (PHM) via native meter protocol.
/// Default is **On on Linux** when the agent extension is active (`/proc`
/// sampling only); **Off** on other platforms.
const SKYWALKING_AGENT_METRICS_ENABLE: &str = "skywalking_agent.metrics_enable";

/// PHM report period in seconds. Meters are sampled at most once per period.
const SKYWALKING_AGENT_METRICS_REPORT_PERIOD: &str = "skywalking_agent.metrics_report_period";

#[php_get_module]
pub fn get_module() -> Module {
let mut module = Module::new(
Expand Down Expand Up @@ -214,6 +222,15 @@ pub fn get_module() -> Module {
"".to_string(),
Policy::System,
);
#[cfg(target_os = "linux")]
module.add_ini(SKYWALKING_AGENT_METRICS_ENABLE, true, Policy::System);
#[cfg(not(target_os = "linux"))]
module.add_ini(SKYWALKING_AGENT_METRICS_ENABLE, false, Policy::System);
module.add_ini(
SKYWALKING_AGENT_METRICS_REPORT_PERIOD,
30i64,
Policy::System,
);

// Hooks.
module.on_module_init(module::init);
Expand Down
15 changes: 13 additions & 2 deletions src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ pub static PSR_LOGGING_LEVEL: Lazy<PsrLogLevel> = Lazy::new(|| {
.into()
});

pub static METRICS_ENABLE: Lazy<bool> =
Lazy::new(|| ini_get::<bool>(SKYWALKING_AGENT_METRICS_ENABLE));

pub static METRICS_REPORT_PERIOD: Lazy<i64> =
Lazy::new(|| ini_get::<i64>(SKYWALKING_AGENT_METRICS_REPORT_PERIOD));

pub fn init() {
if !is_enable() {
return;
Expand All @@ -193,6 +199,8 @@ pub fn init() {
Lazy::force(&KAFKA_PRODUCER_CONFIG);
Lazy::force(&INJECT_CONTEXT);
Lazy::force(&PSR_LOGGING_LEVEL);
Lazy::force(&METRICS_ENABLE);
Lazy::force(&METRICS_REPORT_PERIOD);

if let Err(err) = try_init_logger() {
eprintln!("skywalking_agent: initialize logger failed: {}", err);
Expand Down Expand Up @@ -228,7 +236,6 @@ pub fn init() {
return;
}

// Initialize Agent worker.
init_worker();

let reporter = Arc::new(Reporter::new(&*SOCKET_FILE_PATH));
Expand All @@ -239,7 +246,11 @@ pub fn init() {
reporter.clone(),
));

logger::set_global_logger(Logger::new(&*SERVICE_NAME, &*SERVICE_INSTANCE, reporter));
logger::set_global_logger(Logger::new(
&*SERVICE_NAME,
&*SERVICE_INSTANCE,
reporter.clone(),
));

// Hook functions.
register_execute_functions();
Expand Down
26 changes: 23 additions & 3 deletions src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@
// 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, METRICS_ENABLE, METRICS_REPORT_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,
};
#[cfg(feature = "kafka-reporter")]
use crate::module::{KAFKA_BOOTSTRAP_SERVERS, KAFKA_PRODUCER_CONFIG};
#[cfg(feature = "kafka-reporter")]
use skywalking_php_worker::reporter::KafkaReporterConfiguration;
use skywalking_php_worker::{
HeartBeatConfiguration, WorkerConfiguration, new_tokio_runtime,
phm::PhmConfiguration,
reporter::{GrpcReporterConfiguration, ReporterConfiguration},
start_worker,
};
Expand Down Expand Up @@ -78,6 +80,7 @@ pub fn init_worker() {
heartbeat_period: *HEARTBEAT_PERIOD,
properties_report_period_factor: *PROPERTIES_REPORT_PERIOD_FACTOR,
}),
phm: phm_configuration(),
reporter_config,
};

Expand Down Expand Up @@ -106,3 +109,20 @@ fn worker_threads() -> usize {
worker_threads as usize
}
}

#[cfg(target_os = "linux")]
fn phm_configuration() -> Option<PhmConfiguration> {
if !*METRICS_ENABLE {
return None;
}
Some(PhmConfiguration {
service_name: SERVICE_NAME.clone(),
service_instance: SERVICE_INSTANCE.clone(),
report_period_secs: *METRICS_REPORT_PERIOD,
})
}

#[cfg(not(target_os = "linux"))]
fn phm_configuration() -> Option<PhmConfiguration> {
None
}
62 changes: 39 additions & 23 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,10 @@ use axum::{
routing::any,
};
use futures_util::future::join_all;
use libc::{SIGTERM, kill, pid_t};
use libc::{SIGKILL, SIGTERM, kill, pid_t};
use once_cell::sync::Lazy;
use std::{
env,
fs::File,
io::{self, Cursor},
net::SocketAddr,
process::{ExitStatus, Stdio},
sync::Arc,
thread,
time::Duration,
env, fs::File, io::Cursor, net::SocketAddr, process::Stdio, sync::Arc, thread, time::Duration,
};
use tokio::{
net::TcpStream,
Expand Down Expand Up @@ -112,16 +105,13 @@ pub async fn teardown(fixture: Fixture) {
fixture.http_server_1_handle.abort();
fixture.http_server_2_handle.abort();

let results = join_all([
kill_command(fixture.php_fpm_1_child),
kill_command(fixture.php_fpm_2_child),
kill_command(fixture.php_swoole_1_child),
kill_command(fixture.php_swoole_2_child),
join_all([
stop_child(fixture.php_fpm_1_child),
stop_child(fixture.php_fpm_2_child),
stop_child(fixture.php_swoole_1_child),
stop_child(fixture.php_swoole_2_child),
])
.await;
for result in results {
assert!(result.unwrap().success());
}
}

fn setup_logging() {
Expand Down Expand Up @@ -319,8 +309,22 @@ fn setup_php_fpm(index: usize, fpm_addr: &str) -> Child {
"-d",
"skywalking_agent.psr_logging_level=Warning",
];
let mut args: Vec<String> = args.iter().map(|s| (*s).to_string()).collect();
if index == 1 {
args.extend([
"-d".to_owned(),
"skywalking_agent.metrics_enable=On".to_owned(),
"-d".to_owned(),
"skywalking_agent.metrics_report_period=5".to_owned(),
]);
} else {
args.extend([
"-d".to_owned(),
"skywalking_agent.metrics_enable=Off".to_owned(),
]);
}
info!(cmd = args.join(" "), "start command");
let child = Command::new(args[0])
let child = Command::new(&args[0])
.args(&args[1..])
.stdin(Stdio::null())
.stdout(File::create("/tmp/fpm-skywalking-stdout.log").unwrap())
Expand Down Expand Up @@ -364,6 +368,8 @@ fn setup_php_swoole(index: usize) -> Child {
"skywalking_agent.enable_zend_observer={}",
*ENABLE_ZEND_OBSERVER
),
"-d",
"skywalking_agent.metrics_enable=Off",
&format!("tests/php/swoole/main.{}.php", index),
];
info!(cmd = args.join(" "), "start command");
Expand All @@ -378,11 +384,21 @@ fn setup_php_swoole(index: usize) -> Child {
child
}

async fn kill_command(mut child: Child) -> io::Result<ExitStatus> {
if let Some(id) = child.id() {
unsafe {
kill(id as pid_t, SIGTERM);
async fn stop_child(mut child: Child) {
let Some(id) = child.id() else {
let _ = child.wait().await;
return;
};
unsafe {
kill(id as pid_t, SIGTERM);
}
match tokio::time::timeout(Duration::from_secs(5), child.wait()).await {
Ok(Ok(_)) | Ok(Err(_)) => {}
Err(_) => {
unsafe {
kill(id as pid_t, SIGKILL);
}
let _ = child.wait().await;
}
}
child.wait().await
}
28 changes: 28 additions & 0 deletions tests/data/expected_context.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2059,3 +2059,31 @@ logItems:
- {key: bar, value: 'false'}
- {key: baz, value: test}
layer: ''
meterItems:
- serviceName: skywalking-agent-test-1
meterSize: ge 6
meters:
- meterId:
name: instance_php_process_cpu_utilization
tags: []
singleValue: ge 0
- meterId:
name: instance_php_memory_used_mb
tags: []
singleValue: ge 0
- meterId:
name: instance_php_memory_peak_mb
tags: []
singleValue: ge 0
- meterId:
name: instance_php_virtual_memory_mb
tags: []
singleValue: ge 0
- meterId:
name: instance_php_thread_count
tags: []
singleValue: ge 1
- meterId:
name: instance_php_open_fd_count
tags: []
singleValue: ge 1
3 changes: 2 additions & 1 deletion tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ async fn run_e2e() {
request_swoole_2_predis().await;
request_swoole_2_mongodb().await;
request_swoole_2_memcache().await;
sleep(Duration::from_secs(3)).await;
// Wait for PHM meter report (metrics_report_period=5s on FPM test-1).
sleep(Duration::from_secs(8)).await;
request_collector_validate().await;
}

Expand Down
Loading
Loading