diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c360961 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +# Don't commit test data or logs +test/*.log +*.log + +# Don't commit Elasticsearch data +elasticsearch-data/ + +# Don't commit Docker volumes +.docker-data/ + +# OS files +.DS_Store +Thumbs.db + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo + +# Compiled extension (built inside Docker) +modules/ +*.lo +*.la +.libs/ diff --git a/DOCKER_README.md b/DOCKER_README.md new file mode 100644 index 0000000..22c77cf --- /dev/null +++ b/DOCKER_README.md @@ -0,0 +1,292 @@ +# Docker Setup for PHP APM with Elasticsearch + +Complete Docker environment for testing PHP APM extension with Elasticsearch driver. + +## 🚀 Quick Start + +### 1. Build and Start Containers + +```bash +cd /Users/AnyKey/Projects/php-apm + +# Build and start all services +docker-compose up -d + +# View logs +docker-compose logs -f +``` + +### 2. Wait for Services to Start + +The services will be available at: +- **PHP Application**: http://localhost:8080 +- **Elasticsearch**: http://localhost:9200 +- **Kibana**: http://localhost:5601 + +Wait about 30 seconds for Elasticsearch and Kibana to fully start. + +### 3. Test APM + +Open http://localhost:8080 in your browser and click "Run Test" to generate sample events. + +## đŸ“Ļ What's Included + +### Services + +1. **php-app** (PHP 5.6 with APM Extension) + - Apache web server + - APM extension with Elasticsearch driver enabled + - Pre-configured to send data to Elasticsearch + - Batch sending enabled (10 docs or 5 seconds) + +2. **elasticsearch** (Elasticsearch 7.17.10) + - Single-node cluster + - No security (for testing only) + - Stores APM events and statistics + +3. **kibana** (Kibana 7.17.10) + - Web UI for Elasticsearch + - Visualize APM data + - Create dashboards + +### Configuration + +APM is pre-configured with: +```ini +apm.enabled=On +apm.elasticsearch_enabled=On +apm.elasticsearch_host=elasticsearch +apm.elasticsearch_port=9200 +apm.elasticsearch_index=apm-logs +apm.elasticsearch_batch_size=10 +apm.elasticsearch_batch_timeout=5 +``` + +## 🔍 Verifying Installation + +### Check APM Extension + +```bash +docker-compose exec php-app php -m | grep apm +``` + +Expected output: `apm` + +### Check APM Configuration + +```bash +docker-compose exec php-app php -i | grep elasticsearch +``` + +### Check Elasticsearch Connection + +```bash +curl http://localhost:9200/_cat/health?v +``` + +## 📊 Viewing Data + +### Via Command Line + +```bash +# View all APM documents +curl http://localhost:9200/apm-logs/_search?pretty + +# Count documents +curl http://localhost:9200/apm-logs/_count + +# View index info +curl http://localhost:9200/_cat/indices?v | grep apm +``` + +### Via Kibana + +1. Open http://localhost:5601 +2. Go to **Management** → **Stack Management** → **Index Patterns** +3. Create index pattern: `apm-logs*` +4. Go to **Discover** to view documents +5. Create visualizations and dashboards + +## đŸ› ī¸ Docker Commands + +### Start Services + +```bash +docker-compose up -d +``` + +### Stop Services + +```bash +docker-compose down +``` + +### Stop and Remove Data + +```bash +docker-compose down -v +``` + +### Rebuild PHP Container + +```bash +docker-compose build --no-cache php-app +docker-compose up -d php-app +``` + +### View Logs + +```bash +# All services +docker-compose logs -f + +# Specific service +docker-compose logs -f php-app +docker-compose logs -f elasticsearch +``` + +### Execute Commands in Container + +```bash +# PHP shell +docker-compose exec php-app bash + +# Test curl from PHP container +docker-compose exec php-app curl http://elasticsearch:9200 +``` + +## 🐛 Troubleshooting + +### APM Extension Not Loading + +Check build logs: +```bash +docker-compose build php-app 2>&1 | tee build.log +``` + +Verify extension file exists: +```bash +docker-compose exec php-app ls -l /usr/local/lib/php/extensions/ +``` + +### No Data in Elasticsearch + +1. Check if APM can reach Elasticsearch: +```bash +docker-compose exec php-app ping -c 3 elasticsearch +docker-compose exec php-app curl -v http://elasticsearch:9200 +``` + +2. Check APM debug output (if compiled with `--with-debugfile`): +```bash +docker-compose exec php-app tail -f /tmp/apm.debug +``` + +3. Verify PHP errors are being triggered: +```bash +docker-compose exec php-app php -r "trigger_error('Test', E_USER_WARNING);" +``` + +### Elasticsearch Won't Start + +Check memory limits: +```bash +docker stats +``` + +Elasticsearch needs at least 512MB RAM. Adjust in `docker-compose.yml` if needed. + +### Port Already in Use + +If ports are already taken, edit `docker-compose.yml`: +```yaml +ports: + - "8081:80" # Change 8080 to 8081 +``` + +## 📈 Performance Tuning + +### Batch Size + +Larger batches = fewer HTTP requests but higher latency: +```ini +apm.elasticsearch_batch_size=50 +``` + +### Batch Timeout + +Shorter timeout = more real-time but more requests: +```ini +apm.elasticsearch_batch_timeout=1 +``` + +### Disable Unused Features + +```ini +apm.store_stacktrace=Off +apm.store_cookies=Off +apm.store_post=Off +``` + +## 🔐 Production Considerations + +This setup is for **testing only**. For production: + +1. Enable Elasticsearch security: +```yaml +environment: + - xpack.security.enabled=true + - ELASTIC_PASSWORD=changeme +``` + +2. Use strong passwords +3. Enable HTTPS +4. Restrict network access +5. Use proper resource limits +6. Enable authentication in APM config + +## 📝 Example PHP Test Script + +Create `test/custom-test.php`: +```php + +``` + +Run it: +```bash +docker-compose exec php-app php /var/www/html/custom-test.php +``` + +## 🎓 Next Steps + +1. **Create Kibana Dashboards** + - Visualize error rates + - Monitor response times + - Track memory usage + +2. **Set Up Alerts** + - Alert on error spikes + - Monitor slow requests + - Track resource usage + +3. **Optimize Configuration** + - Tune batch settings + - Adjust thresholds + - Enable/disable features + +4. **Test in Production-Like Environment** + - Add load testing + - Test with realistic data + - Monitor performance impact diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a83ba2c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +# PHP 5.6 with APM Extension and Elasticsearch Support +FROM php:5.6-apache + +RUN echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.list +# Install dependencies (only curl needed for Elasticsearch driver) +RUN apt-get update && apt-get install -y \ + git \ + autoconf \ + build-essential \ + libcurl4-openssl-dev \ + --allow-unauthenticated \ + && rm -rf /var/lib/apt/lists/* + +# Copy APM source code +COPY . /tmp/php-apm + +# Build and install APM extension +WORKDIR /tmp/php-apm +RUN phpize \ + && ./configure \ + --enable-apm \ + --without-sqlite3 \ + --without-mysql \ + --disable-statsd \ + --disable-socket \ + --enable-elasticsearch \ + && make \ + && make install + +# Install APM extension +RUN echo "extension=apm.so" > /usr/local/etc/php/conf.d/apm.ini + +# Configure APM for Elasticsearch +RUN echo "; APM Configuration" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.enabled=On" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.event_enabled=On" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.application_id=\"Docker PHP 5.6 App\"" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "; Elasticsearch driver" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.elasticsearch_enabled=On" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.elasticsearch_stats_enabled=On" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.elasticsearch_host=elasticsearch" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.elasticsearch_port=9200" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.elasticsearch_index=apm-logs" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.elasticsearch_batch_size=10" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.elasticsearch_batch_timeout=5" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "; Disable other drivers" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.sqlite_enabled=Off" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.mysql_enabled=Off" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.statsd_enabled=Off" >> /usr/local/etc/php/conf.d/apm.ini \ + && echo "apm.socket_enabled=Off" >> /usr/local/etc/php/conf.d/apm.ini + +# Clean up +RUN rm -rf /tmp/php-apm + +# Enable Apache mod_rewrite +RUN a2enmod rewrite + +# Set working directory +WORKDIR /var/www/html + +# Expose port 80 +EXPOSE 80 + +CMD ["apache2-foreground"] diff --git a/Dockerfile.build-legacy b/Dockerfile.build-legacy new file mode 100644 index 0000000..7102129 --- /dev/null +++ b/Dockerfile.build-legacy @@ -0,0 +1,20 @@ +# Use old Debian for GLIBC compatibility +FROM debian:wheezy + +# Configure old repositories +RUN echo "deb http://archive.debian.org/debian wheezy main" > /etc/apt/sources.list && echo "deb http://archive.debian.org/debian-security wheezy/updates main" >> /etc/apt/sources.list + +# Install PHP and build dependencies +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y php5-dev php5-cli git autoconf build-essential libcurl4-openssl-dev curl --force-yes && rm -rf /var/lib/apt/lists/* + +# Copy source code +COPY . /tmp/php-apm +WORKDIR /tmp/php-apm + +# Build extension +RUN phpize && ./configure --enable-apm --without-sqlite3 --without-mysql --disable-statsd --disable-socket --enable-elasticsearch --with-debugfile=/tmp/apm.log && make && make install + +# Copy the built extension to output +RUN mkdir -p /output && cp $(php-config --extension-dir)/apm.so /output/apm.so && php -v > /output/php-version.txt && ldd $(php-config --extension-dir)/apm.so > /output/dependencies.txt || true + +CMD ["cat", "/output/apm.so"] diff --git a/Dockerfile.test74 b/Dockerfile.test74 new file mode 100644 index 0000000..feb9316 --- /dev/null +++ b/Dockerfile.test74 @@ -0,0 +1,21 @@ +FROM php:7.4-apache + +RUN apt-get update && apt-get install -y \ + git \ + autoconf \ + build-essential \ + libcurl4-openssl-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY . /tmp/php-apm +WORKDIR /tmp/php-apm + +RUN phpize \ + && ./configure \ + --enable-apm \ + --without-sqlite3 \ + --without-mysql \ + --disable-statsd \ + --disable-socket \ + --enable-elasticsearch \ + && make diff --git a/EXTENSION_README.md b/EXTENSION_README.md new file mode 100644 index 0000000..38bf819 --- /dev/null +++ b/EXTENSION_README.md @@ -0,0 +1,193 @@ +# PHP APM Extension - Multiple Versions + +Built extensions for different PHP versions and system compatibility. + +## Available Builds + +### 1. apm.so - PHP 5.6 (Modern Systems) +- **PHP Version**: 5.6.x +- **API Version**: 20131226 +- **GLIBC**: 2.14+ (Debian 8+, Ubuntu 14.04+) +- **Size**: 238K +- **Built on**: Debian Stretch + +**Installation:** +```bash +cp apm.so $(php-config --extension-dir)/ +echo 'extension=apm.so' > /etc/php/5.6/mods-available/apm.ini +php5enmod apm +``` + +### 2. apm-php53.so - PHP 5.3/5.4 (Old Systems) +- **PHP Version**: 5.3.x, 5.4.x +- **API Version**: 20100525 (PHP 5.3), 20100412 (PHP 5.4) +- **GLIBC**: 2.13+ (Debian 7, Ubuntu 12.04+) +- **Size**: 203K +- **Built on**: Debian Wheezy + +**Installation for PHP 5.3:** +```bash +cp apm-php53.so /usr/lib/php5/20100525/apm.so +echo 'extension=apm.so' > /etc/php5/conf.d/apm.ini +service php5-fpm restart +``` + +**Installation for PHP 5.4:** +```bash +cp apm-php53.so /usr/lib/php5/20100412/apm.so +echo 'extension=apm.so' > /etc/php5/conf.d/apm.ini +service php5-fpm restart +``` + +## Checking Your PHP Version + +```bash +# Get PHP version +php -v + +# Get API version (extension directory) +php-config --extension-dir + +# Check GLIBC version +ldd --version +``` + +## Common Installation Directories + +| PHP Version | Extension Directory | Config Directory | +|-------------|-------------------|------------------| +| PHP 5.3 | `/usr/lib/php5/20090626` or `/usr/lib/php5/20100525` | `/etc/php5/conf.d/` | +| PHP 5.4 | `/usr/lib/php5/20100412` | `/etc/php5/conf.d/` | +| PHP 5.5 | `/usr/lib/php/20121212` | `/etc/php/5.5/mods-available/` | +| PHP 5.6 | `/usr/lib/php/20131226` | `/etc/php/5.6/mods-available/` | + +## Troubleshooting + +### Error: GLIBC version not found + +**Problem:** +``` +version `GLIBC_2.14' not found +``` + +**Solution:** Use `apm-php53.so` instead - it's compiled for older GLIBC. + +### Error: Undefined symbol + +**Problem:** +``` +undefined symbol: zend_parse_parameters_ex +``` + +**Solution:** You're using the wrong build. Check your PHP API version: +```bash +php -i | grep "PHP API" +``` + +Then use the matching build. + +### Error: Cannot load shared libraries + +**Problem:** +``` +libcurl.so.4: cannot open shared object file +``` + +**Solution:** Install libcurl: +```bash +# Debian/Ubuntu +apt-get install libcurl3 + +# CentOS/RHEL +yum install libcurl +``` + +### Checking Dependencies + +```bash +ldd apm-php53.so +``` + +This shows all required shared libraries. + +## Configuration + +After installation, configure APM in php.ini or a separate config file: + +```ini +; Enable APM +extension=apm.so +apm.enabled=On +apm.event_enabled=On + +; Elasticsearch driver +apm.elasticsearch_enabled=On +apm.elasticsearch_host=localhost +apm.elasticsearch_port=9200 +apm.elasticsearch_index=apm-logs + +; Batch sending +apm.elasticsearch_batch_size=10 +apm.elasticsearch_batch_timeout=5 +``` + +See [apm.ini](apm.ini) for all configuration options. + +## Rebuilding + +### For PHP 5.6: +```bash +./build-extension.sh +``` + +### For PHP 5.3/5.4: +```bash +./build-extension-php53.sh +``` + +## Testing + +After installation, verify the extension is loaded: + +```bash +php -m | grep apm +``` + +Should output: `apm` + +View configuration: + +```bash +php -i | grep apm +``` + +## Supported Features + +All builds include: +- ✅ Elasticsearch driver with batch sending +- ✅ Event tracking (errors, exceptions) +- ✅ Statistics collection (CPU, memory, duration) +- ✅ Configurable thresholds +- ❌ SQLite3 driver (disabled) +- ❌ MySQL driver (disabled) +- ❌ StatsD driver (disabled) +- ❌ Socket driver (disabled) + +## System Requirements + +### Minimum: +- Linux x86_64 +- GLIBC 2.13+ +- libcurl4 + +### Recommended: +- GLIBC 2.14+ +- PHP 5.4+ +- Elasticsearch 6.x or 7.x + +## Notes + +- Extensions are not portable between different PHP versions +- Always match the API version (check extension directory) +- Debian Wheezy build (`apm-php53.so`) has better old system compatibility +- Both builds are 64-bit only diff --git a/apm-debug.so b/apm-debug.so new file mode 100755 index 0000000..f76bd1a Binary files /dev/null and b/apm-debug.so differ diff --git a/apm-php53.so b/apm-php53.so new file mode 100755 index 0000000..83e7700 Binary files /dev/null and b/apm-php53.so differ diff --git a/apm.c b/apm.c index 40a59f8..bc66c04 100644 --- a/apm.c +++ b/apm.c @@ -53,6 +53,9 @@ #ifdef APM_DRIVER_SOCKET # include "driver_socket.h" #endif +#ifdef APM_DRIVER_ELASTICSEARCH +# include "driver_elasticsearch.h" +#endif ZEND_DECLARE_MODULE_GLOBALS(apm); static PHP_GINIT_FUNCTION(apm); @@ -242,6 +245,33 @@ PHP_INI_BEGIN() /* process silenced events? */ STD_PHP_INI_BOOLEAN("apm.socket_process_silenced_events", "1", PHP_INI_PERDIR, OnUpdateBool, socket_process_silenced_events, zend_apm_globals, apm_globals) #endif + +#ifdef APM_DRIVER_ELASTICSEARCH + /* Boolean controlling whether the driver is active or not */ + STD_PHP_INI_BOOLEAN("apm.elasticsearch_enabled", "0", PHP_INI_PERDIR, OnUpdateBool, elasticsearch_enabled, zend_apm_globals, apm_globals) + /* Boolean controlling the collection of stats */ + STD_PHP_INI_BOOLEAN("apm.elasticsearch_stats_enabled", "1", PHP_INI_ALL, OnUpdateBool, elasticsearch_stats_enabled, zend_apm_globals, apm_globals) + /* Control which exceptions to collect (0: none exceptions collected, 1: collect uncaught exceptions (default), 2: collect ALL exceptions) */ + STD_PHP_INI_ENTRY("apm.elasticsearch_exception_mode","1", PHP_INI_PERDIR, OnUpdateLongGEZero, elasticsearch_exception_mode, zend_apm_globals, apm_globals) + /* error_reporting of the driver */ + STD_PHP_INI_ENTRY("apm.elasticsearch_error_reporting", NULL, PHP_INI_ALL, OnUpdateAPMelasticsearchErrorReporting, elasticsearch_error_reporting, zend_apm_globals, apm_globals) + /* Elasticsearch host */ + STD_PHP_INI_ENTRY("apm.elasticsearch_host", "localhost", PHP_INI_PERDIR, OnUpdateString, elasticsearch_host, zend_apm_globals, apm_globals) + /* Elasticsearch port */ + STD_PHP_INI_ENTRY("apm.elasticsearch_port", "9200", PHP_INI_PERDIR, OnUpdateLong, elasticsearch_port, zend_apm_globals, apm_globals) + /* Elasticsearch index */ + STD_PHP_INI_ENTRY("apm.elasticsearch_index", "apm-logs", PHP_INI_PERDIR, OnUpdateString, elasticsearch_index, zend_apm_globals, apm_globals) + /* Elasticsearch username (optional) */ + STD_PHP_INI_ENTRY("apm.elasticsearch_username", "", PHP_INI_PERDIR, OnUpdateString, elasticsearch_username, zend_apm_globals, apm_globals) + /* Elasticsearch password (optional) */ + STD_PHP_INI_ENTRY("apm.elasticsearch_password", "", PHP_INI_PERDIR, OnUpdateString, elasticsearch_password, zend_apm_globals, apm_globals) + /* Batch size - number of messages to buffer before sending */ + STD_PHP_INI_ENTRY("apm.elasticsearch_batch_size", "10", PHP_INI_PERDIR, OnUpdateLong, elasticsearch_batch_size, zend_apm_globals, apm_globals) + /* Batch timeout in seconds - maximum time to wait before sending buffered messages */ + STD_PHP_INI_ENTRY("apm.elasticsearch_batch_timeout", "5", PHP_INI_PERDIR, OnUpdateLong, elasticsearch_batch_timeout, zend_apm_globals, apm_globals) + /* process silenced events? */ + STD_PHP_INI_BOOLEAN("apm.elasticsearch_process_silenced_events", "1", PHP_INI_PERDIR, OnUpdateBool, elasticsearch_process_silenced_events, zend_apm_globals, apm_globals) +#endif PHP_INI_END() static PHP_GINIT_FUNCTION(apm) @@ -274,6 +304,10 @@ static PHP_GINIT_FUNCTION(apm) *next = apm_driver_socket_create(); next = &(*next)->next; #endif +#ifdef APM_DRIVER_ELASTICSEARCH + *next = apm_driver_elasticsearch_create(); + next = &(*next)->next; +#endif } static void recursive_free_driver(apm_driver_entry **driver) diff --git a/apm.ini b/apm.ini index 055884d..da95b01 100644 --- a/apm.ini +++ b/apm.ini @@ -97,3 +97,29 @@ extension=apm.so ; Socket path (accept multiple entries, separated by "|", prefixed with "file:" or "tcp:") ; Example: apm.socket_path=file:/var/tmp/apm.sock|tcp:localhost:1234 ; apm.socket_path=file:/tmp/apm.sock + +; Elasticsearch configuration +; Whether to enable the elasticsearch driver +; apm.elasticsearch_enabled=On +; Whether to collect stats for the Elasticsearch driver +; apm.elasticsearch_stats_enabled=On +; Error reporting level specific to the elasticsearch driver +; apm.elasticsearch_error_reporting=E_ALL|E_STRICT +; Control which exceptions to collect (0: none exceptions collected, 1: collect uncaught exceptions (default), 2: collect ALL exceptions) +; apm.elasticsearch_exception_mode=1 +; Stores silenced events +; apm.elasticsearch_process_silenced_events = On +; Elasticsearch host +; apm.elasticsearch_host=localhost +; Elasticsearch port +; apm.elasticsearch_port=9200 +; Elasticsearch index name +; apm.elasticsearch_index=apm-logs +; Elasticsearch username (optional, for basic auth) +; apm.elasticsearch_username= +; Elasticsearch password (optional, for basic auth) +; apm.elasticsearch_password= +; Batch size - number of documents to buffer before sending (default: 10) +; apm.elasticsearch_batch_size=10 +; Batch timeout in seconds - maximum time to wait before sending buffered documents (default: 5) +; apm.elasticsearch_batch_timeout=5 diff --git a/apm.so b/apm.so new file mode 100755 index 0000000..b39a7a2 Binary files /dev/null and b/apm.so differ diff --git a/backtrace.c b/backtrace.c index 692a1a5..7f7ecf2 100644 --- a/backtrace.c +++ b/backtrace.c @@ -16,10 +16,13 @@ +----------------------------------------------------------------------+ */ -#include "php_apm.h" +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include "php.h" +#include "php_apm.h" #include "zend_types.h" -#include "ext/standard/php_string.h" #if PHP_VERSION_ID >= 70000 #include "Zend/zend_generators.h" @@ -27,11 +30,12 @@ #include "backtrace.h" -ZEND_DECLARE_MODULE_GLOBALS(apm); - -static void debug_print_backtrace_args(zval *arg_array TSRMLS_DC, smart_str *trace_str); -static void append_flat_zval_r(zval *expr TSRMLS_DC, smart_str *trace_str, char depth); -static void append_flat_hash(HashTable *ht TSRMLS_DC, smart_str *trace_str, char is_object, char depth); +static void debug_print_backtrace_args(zval *arg_array TSRMLS_DC, + smart_str *trace_str); +static void append_flat_zval_r(zval *expr TSRMLS_DC, smart_str *trace_str, + char depth); +static void append_flat_hash(HashTable *ht TSRMLS_DC, smart_str *trace_str, + char is_object, char depth); #if PHP_VERSION_ID >= 70000 static void debug_backtrace_get_args(zend_execute_data *call, zval *arg_array); @@ -42,666 +46,700 @@ static zval *debug_backtrace_get_args(void ***curpos TSRMLS_DC); static int append_variable(zval *expr, smart_str *trace_str); static char *apm_addslashes(char *str, uint length, int *new_length); -void append_backtrace(smart_str *trace_str TSRMLS_DC) -{ - /* backtrace variables */ - zend_execute_data *ptr, *skip; - int lineno; - const char *function_name; - const char *filename; - char *call_type; - const char *include_filename = NULL; -# if PHP_VERSION_ID >= 70000 - zval arg_array; - zend_execute_data *call; - zend_string *class_name = NULL; - zend_object *object; - zend_function *func; -# else - const char *class_name = NULL; - zval *arg_array = NULL; - const char *free_class_name = NULL; - zend_uint class_name_len = 0; -# endif - int indent = 0; - +void append_backtrace(smart_str *trace_str TSRMLS_DC) { + /* backtrace variables */ + zend_execute_data *ptr, *skip; + int lineno; + const char *function_name; + const char *filename; + char *call_type; + const char *include_filename = NULL; +#if PHP_VERSION_ID >= 70000 + zval arg_array; + zend_execute_data *call; + zend_string *class_name = NULL; + zend_object *object; + zend_function *func; +#else + const char *class_name = NULL; + zval *arg_array = NULL; + const char *free_class_name = NULL; + zend_uint class_name_len = 0; +#endif + int indent = 0; #if PHP_VERSION_ID >= 70000 - ZVAL_UNDEF(&arg_array); - //FIXME? ptr = EX(prev_execute_data); - ptr = EG(current_execute_data); - call = ptr; + ZVAL_UNDEF(&arg_array); + // FIXME? ptr = EX(prev_execute_data); + ptr = EG(current_execute_data); + call = ptr; #else - ptr = EG(current_execute_data); + ptr = EG(current_execute_data); #endif - while (ptr) { - class_name = NULL; - call_type = NULL; + while (ptr) { + class_name = NULL; + call_type = NULL; #if PHP_VERSION_ID >= 70000 - ZVAL_UNDEF(&arg_array); + ZVAL_UNDEF(&arg_array); - ptr = zend_generator_check_placeholder_frame(ptr); + ptr = zend_generator_check_placeholder_frame(ptr); #else - arg_array = NULL; + arg_array = NULL; #endif - skip = ptr; - /* skip internal handler */ + skip = ptr; + /* skip internal handler */ #if PHP_VERSION_ID >= 70000 - if ((!skip->func || !ZEND_USER_CODE(skip->func->common.type)) && + if ((!skip->func || !ZEND_USER_CODE(skip->func->common.type)) && #else - if (!skip->op_array && + if (!skip->op_array && #endif - skip->prev_execute_data && + skip->prev_execute_data && #if PHP_VERSION_ID >= 70000 - skip->prev_execute_data->func && - ZEND_USER_CODE(skip->prev_execute_data->func->common.type) && - skip->prev_execute_data->opline->opcode != ZEND_DO_ICALL && - skip->prev_execute_data->opline->opcode != ZEND_DO_UCALL && + skip->prev_execute_data->func && + ZEND_USER_CODE(skip->prev_execute_data->func->common.type) && + skip->prev_execute_data->opline->opcode != ZEND_DO_ICALL && + skip->prev_execute_data->opline->opcode != ZEND_DO_UCALL && #else - skip->prev_execute_data->opline && + skip->prev_execute_data->opline && #endif - skip->prev_execute_data->opline->opcode != ZEND_DO_FCALL && - skip->prev_execute_data->opline->opcode != ZEND_DO_FCALL_BY_NAME && - skip->prev_execute_data->opline->opcode != ZEND_INCLUDE_OR_EVAL) { - skip = skip->prev_execute_data; - } + skip->prev_execute_data->opline->opcode != ZEND_DO_FCALL && + skip->prev_execute_data->opline->opcode != ZEND_DO_FCALL_BY_NAME && + skip->prev_execute_data->opline->opcode != ZEND_INCLUDE_OR_EVAL) { + skip = skip->prev_execute_data; + } #if PHP_VERSION_ID >= 70000 - if (skip->func && ZEND_USER_CODE(skip->func->common.type)) { - filename = skip->func->op_array.filename->val; - if (skip->opline->opcode == ZEND_HANDLE_EXCEPTION) { - if (EG(opline_before_exception)) { - lineno = EG(opline_before_exception)->lineno; - } else { - lineno = skip->func->op_array.line_end; - } - } else { - lineno = skip->opline->lineno; - } - } + if (skip->func && skip->func->type != ZEND_INTERNAL_FUNCTION && + ZEND_USER_CODE(skip->func->common.type)) { + filename = (skip->func->op_array.filename) + ? ZSTR_VAL(skip->func->op_array.filename) + : NULL; + if (skip->opline->opcode == ZEND_HANDLE_EXCEPTION) { + if (EG(opline_before_exception)) { + lineno = EG(opline_before_exception)->lineno; + } else { + lineno = skip->func->op_array.line_end; + } + } else { + lineno = skip->opline->lineno; + } + } #else - if (skip->op_array) { - filename = skip->op_array->filename; - lineno = skip->opline->lineno; - } + if (skip->op_array) { + filename = skip->op_array->filename; + lineno = skip->opline->lineno; + } #endif - else { - filename = NULL; - lineno = 0; - } + else { + filename = NULL; + lineno = 0; + } #if PHP_VERSION_ID >= 70000 - /* $this may be passed into regular internal functions */ - object = Z_OBJ(call->This); - - if (call->func) { - func = call->func; - function_name = (func->common.scope && func->common.scope->trait_aliases) ? - ZSTR_VAL(zend_resolve_method_name( - (object ? object->ce : func->common.scope), func)) : - (func->common.function_name ? - ZSTR_VAL(func->common.function_name) : NULL); - } else { - func = NULL; - function_name = NULL; - } - - if (function_name) { - if (object) { - if (func->common.scope) { - class_name = func->common.scope->name; - } else if (object->handlers->get_class_name == std_object_handlers.get_class_name) { - class_name = object->ce->name; - } else { - class_name = object->handlers->get_class_name(object); - } - - call_type = "->"; - } else if (func->common.scope) { - class_name = func->common.scope->name; - call_type = "::"; - } else { - class_name = NULL; - call_type = NULL; - } - if (func->type != ZEND_EVAL_CODE) { - debug_backtrace_get_args(call, &arg_array); - } - } + /* $this may be passed into regular internal functions */ + object = (Z_TYPE(call->This) == IS_OBJECT) ? Z_OBJ(call->This) : NULL; + + if (call->func) { + func = call->func; + function_name = + (func->common.scope && func->common.scope->trait_aliases) + ? ZSTR_VAL(zend_resolve_method_name( + (object ? object->ce : func->common.scope), func)) + : (func->common.function_name + ? ZSTR_VAL(func->common.function_name) + : NULL); + } else { + func = NULL; + function_name = NULL; + } + + if (function_name) { + if (object) { + if (func->common.scope) { + class_name = func->common.scope->name; + } else if (object->handlers->get_class_name == + std_object_handlers.get_class_name) { + class_name = object->ce->name; + } else { + class_name = object->handlers->get_class_name(object); + } + + call_type = "->"; + } else if (func->common.scope) { + class_name = func->common.scope->name; + call_type = "::"; + } else { + class_name = NULL; + call_type = NULL; + } + if (func->type != ZEND_EVAL_CODE) { + debug_backtrace_get_args(call, &arg_array); + } + } #else - function_name = ptr->function_state.function->common.function_name; - - if (function_name) { - if (ptr->object) { - if (ptr->function_state.function->common.scope) { - class_name = ptr->function_state.function->common.scope->name; - class_name_len = strlen(class_name); - } else { - int dup = zend_get_object_classname(ptr->object, &class_name, &class_name_len TSRMLS_CC); - if(!dup) { - free_class_name = class_name; - } - } - - call_type = "->"; - } else if (ptr->function_state.function->common.scope) { - class_name = ptr->function_state.function->common.scope->name; - class_name_len = strlen(class_name); - call_type = "::"; - } else { - class_name = NULL; - call_type = NULL; - } - if ((! ptr->opline) || ((ptr->opline->opcode == ZEND_DO_FCALL_BY_NAME) || (ptr->opline->opcode == ZEND_DO_FCALL))) { - if (ptr->function_state.arguments) { - arg_array = debug_backtrace_get_args(&ptr->function_state.arguments TSRMLS_CC); - } - } - } + function_name = ptr->function_state.function->common.function_name; + + if (function_name) { + if (ptr->object) { + if (ptr->function_state.function->common.scope) { + class_name = ptr->function_state.function->common.scope->name; + class_name_len = strlen(class_name); + } else { + int dup = zend_get_object_classname(ptr->object, &class_name, + &class_name_len TSRMLS_CC); + if (!dup) { + free_class_name = class_name; + } + } + + call_type = "->"; + } else if (ptr->function_state.function->common.scope) { + class_name = ptr->function_state.function->common.scope->name; + class_name_len = strlen(class_name); + call_type = "::"; + } else { + class_name = NULL; + call_type = NULL; + } + if ((!ptr->opline) || ((ptr->opline->opcode == ZEND_DO_FCALL_BY_NAME) || + (ptr->opline->opcode == ZEND_DO_FCALL))) { + if (ptr->function_state.arguments) { + arg_array = debug_backtrace_get_args( + &ptr->function_state.arguments TSRMLS_CC); + } + } + } #endif - else { - /* i know this is kinda ugly, but i'm trying to avoid extra cycles in the main execution loop */ - zend_bool build_filename_arg = 1; + else { + /* i know this is kinda ugly, but i'm trying to avoid extra cycles in the + * main execution loop */ + zend_bool build_filename_arg = 1; #if PHP_VERSION_ID >= 70000 - if (!ptr->func || !ZEND_USER_CODE(ptr->func->common.type) || ptr->opline->opcode != ZEND_INCLUDE_OR_EVAL) { + if (!ptr->func || !ZEND_USER_CODE(ptr->func->common.type) || + ptr->opline->opcode != ZEND_INCLUDE_OR_EVAL) { #else - if (!ptr->opline || ptr->opline->opcode != ZEND_INCLUDE_OR_EVAL) { + if (!ptr->opline || ptr->opline->opcode != ZEND_INCLUDE_OR_EVAL) { #endif - /* can happen when calling eval from a custom sapi */ - function_name = "unknown"; - build_filename_arg = 0; - } else - switch (ptr->opline->op2.constant) { - case ZEND_EVAL: - function_name = "eval"; - build_filename_arg = 0; - break; - case ZEND_INCLUDE: - function_name = "include"; - break; - case ZEND_REQUIRE: - function_name = "require"; - break; - case ZEND_INCLUDE_ONCE: - function_name = "include_once"; - break; - case ZEND_REQUIRE_ONCE: - function_name = "require_once"; - break; - default: - /* this can actually happen if you use debug_backtrace() in your error_handler and - * you're in the top-scope */ - function_name = "unknown"; - build_filename_arg = 0; - break; - } - - if (build_filename_arg && include_filename) { + /* can happen when calling eval from a custom sapi */ + function_name = "unknown"; + build_filename_arg = 0; + } else + switch (ptr->opline->op2.constant) { + case ZEND_EVAL: + function_name = "eval"; + build_filename_arg = 0; + break; + case ZEND_INCLUDE: + function_name = "include"; + break; + case ZEND_REQUIRE: + function_name = "require"; + break; + case ZEND_INCLUDE_ONCE: + function_name = "include_once"; + break; + case ZEND_REQUIRE_ONCE: + function_name = "require_once"; + break; + default: + /* this can actually happen if you use debug_backtrace() in your + * error_handler and you're in the top-scope */ + function_name = "unknown"; + build_filename_arg = 0; + break; + } + + if (build_filename_arg && include_filename) { #if PHP_VERSION_ID >= 70000 - array_init(&arg_array); - add_next_index_string(&arg_array, include_filename); + array_init(&arg_array); + add_next_index_string(&arg_array, include_filename); #else - MAKE_STD_ZVAL(arg_array); - array_init(arg_array); - add_next_index_string(arg_array, include_filename, 1); + MAKE_STD_ZVAL(arg_array); + array_init(arg_array); + add_next_index_string(arg_array, include_filename, 1); #endif - } - call_type = NULL; - } - smart_str_appendc(trace_str, '#'); - smart_str_append_long(trace_str, indent); - smart_str_appendc(trace_str, ' '); - if (class_name) { + } + call_type = NULL; + } + smart_str_appendc(trace_str, '#'); + smart_str_append_long(trace_str, indent); + smart_str_appendc(trace_str, ' '); + if (class_name) { #if PHP_VERSION_ID >= 70000 - smart_str_appends(trace_str, ZSTR_VAL(class_name)); + smart_str_appends(trace_str, ZSTR_VAL(class_name)); #else - smart_str_appends(trace_str, class_name); + smart_str_appends(trace_str, class_name); #endif - /* here, call_type is either "::" or "->" */ - smart_str_appendl(trace_str, call_type, 2); - } - if (function_name) { - smart_str_appends(trace_str, function_name); - } else { - smart_str_appendl(trace_str, "main", 4); - } - smart_str_appendc(trace_str, '('); + /* here, call_type is either "::" or "->" */ + smart_str_appendl(trace_str, call_type, 2); + } + if (function_name) { + smart_str_appends(trace_str, function_name); + } else { + smart_str_appendl(trace_str, "main", 4); + } + smart_str_appendc(trace_str, '('); #if PHP_VERSION_ID >= 70000 - if (Z_TYPE(arg_array) != IS_UNDEF) { - debug_print_backtrace_args(&arg_array, trace_str); + if (Z_TYPE(arg_array) != IS_UNDEF) { + debug_print_backtrace_args(&arg_array, trace_str); #else - if (arg_array) { - debug_print_backtrace_args(arg_array TSRMLS_CC, trace_str); + if (arg_array) { + debug_print_backtrace_args(arg_array TSRMLS_CC, trace_str); #endif - zval_ptr_dtor(&arg_array); - } - if (filename) { - smart_str_appendl(trace_str, ") called at [", sizeof(") called at [") - 1); - smart_str_appends(trace_str, filename); - smart_str_appendc(trace_str, ':'); - smart_str_append_long(trace_str, lineno); - smart_str_appendl(trace_str, "]\n", 2); - } else { + zval_ptr_dtor(&arg_array); + } + if (filename) { + smart_str_appendl(trace_str, ") called at [", + sizeof(") called at [") - 1); + smart_str_appends(trace_str, filename); + smart_str_appendc(trace_str, ':'); + smart_str_append_long(trace_str, lineno); + smart_str_appendl(trace_str, "]\n", 2); + } else { #if PHP_VERSION_ID >= 70000 - zend_execute_data *prev_call = skip; + zend_execute_data *prev_call = skip; #endif - zend_execute_data *prev = skip->prev_execute_data; + zend_execute_data *prev = skip->prev_execute_data; - while (prev) { + while (prev) { #if PHP_VERSION_ID >= 70000 - if (prev_call && - prev_call->func && - !ZEND_USER_CODE(prev_call->func->common.type)) { - prev = NULL; - break; - } - if (prev->func && ZEND_USER_CODE(prev->func->common.type)) { - zend_printf(") called at [%s:%d]\n", prev->func->op_array.filename->val, prev->opline->lineno); - break; - } - prev_call = prev; + if (prev_call && prev_call->func && + !ZEND_USER_CODE(prev_call->func->common.type)) { + prev = NULL; + break; + } + if (prev->func && ZEND_USER_CODE(prev->func->common.type)) { + zend_printf(") called at [%s:%d]\n", + prev->func->op_array.filename->val, prev->opline->lineno); + break; + } + prev_call = prev; #else - if (prev->function_state.function && - prev->function_state.function->common.type != ZEND_USER_FUNCTION) { - prev = NULL; - break; - } - if (prev->op_array) { - smart_str_appendl(trace_str, ") called at [", sizeof(") called at [") - 1); - smart_str_appends(trace_str, prev->op_array->filename); - smart_str_appendc(trace_str, ':'); - smart_str_append_long(trace_str, (long) prev->opline->lineno); - smart_str_appendl(trace_str, "]\n", 2); - break; - } + if (prev->function_state.function && + prev->function_state.function->common.type != ZEND_USER_FUNCTION) { + prev = NULL; + break; + } + if (prev->op_array) { + smart_str_appendl(trace_str, ") called at [", + sizeof(") called at [") - 1); + smart_str_appends(trace_str, prev->op_array->filename); + smart_str_appendc(trace_str, ':'); + smart_str_append_long(trace_str, (long)prev->opline->lineno); + smart_str_appendl(trace_str, "]\n", 2); + break; + } #endif - prev = prev->prev_execute_data; - } - if (!prev) { - smart_str_appendl(trace_str, ")\n", 2); - } - } - include_filename = filename; - ptr = skip->prev_execute_data; - ++indent; + prev = prev->prev_execute_data; + } + if (!prev) { + smart_str_appendl(trace_str, ")\n", 2); + } + } + include_filename = filename; + ptr = skip->prev_execute_data; + ++indent; #if PHP_VERSION_ID >= 70000 - call = skip; + call = skip; #else - if (free_class_name) { - efree((char *) free_class_name); - free_class_name = NULL; - } + if (free_class_name) { + efree((char *)free_class_name); + free_class_name = NULL; + } #endif - } + } } #if PHP_VERSION_ID >= 70000 -static void debug_print_backtrace_args(zval *arg_array, smart_str *trace_str) -{ - zval *tmp; - int i = 0; - - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arg_array), tmp) { - if (i++) { - smart_str_appendl(trace_str, ", ", 2); - } - append_flat_zval_r(tmp, trace_str, 0); - } ZEND_HASH_FOREACH_END(); +static void debug_print_backtrace_args(zval *arg_array, smart_str *trace_str) { + zval *tmp; + int i = 0; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arg_array), tmp) { + if (i++) { + smart_str_appendl(trace_str, ", ", 2); + } + append_flat_zval_r(tmp, trace_str, 0); + } + ZEND_HASH_FOREACH_END(); } #else -static void debug_print_backtrace_args(zval *arg_array TSRMLS_DC, smart_str *trace_str) -{ - zval **tmp; - HashPosition iterator; - int i = 0; - - zend_hash_internal_pointer_reset_ex(arg_array->value.ht, &iterator); - while (zend_hash_get_current_data_ex(arg_array->value.ht, (void **) &tmp, &iterator) == SUCCESS) { - if (i++) { - smart_str_appendl(trace_str, ", ", 2); - } - append_flat_zval_r(*tmp TSRMLS_CC, trace_str, 0); - zend_hash_move_forward_ex(arg_array->value.ht, &iterator); - } +static void debug_print_backtrace_args(zval *arg_array TSRMLS_DC, + smart_str *trace_str) { + zval **tmp; + HashPosition iterator; + int i = 0; + + zend_hash_internal_pointer_reset_ex(arg_array->value.ht, &iterator); + while (zend_hash_get_current_data_ex(arg_array->value.ht, (void **)&tmp, + &iterator) == SUCCESS) { + if (i++) { + smart_str_appendl(trace_str, ", ", 2); + } + append_flat_zval_r(*tmp TSRMLS_CC, trace_str, 0); + zend_hash_move_forward_ex(arg_array->value.ht, &iterator); + } } #endif -static void append_flat_zval_r(zval *expr TSRMLS_DC, smart_str *trace_str, char depth) -{ - if (depth >= APM_G(dump_max_depth)) { - smart_str_appendl(trace_str, "/* [...] */", sizeof("/* [...] */") - 1); - return; - } +static void append_flat_zval_r(zval *expr TSRMLS_DC, smart_str *trace_str, + char depth) { + if (depth >= APM_G(dump_max_depth)) { + smart_str_appendl(trace_str, "/* [...] */", sizeof("/* [...] */") - 1); + return; + } - switch (Z_TYPE_P(expr)) { + switch (Z_TYPE_P(expr)) { #if PHP_VERSION_ID >= 70000 - case IS_REFERENCE: - ZVAL_DEREF(expr); - smart_str_appendc(trace_str, '&'); - append_flat_zval_r(expr, trace_str, depth); - break; + case IS_REFERENCE: + ZVAL_DEREF(expr); + smart_str_appendc(trace_str, '&'); + append_flat_zval_r(expr, trace_str, depth); + break; #endif - case IS_ARRAY: - smart_str_appendc(trace_str, '['); -#if PHP_VERSION_ID >= 70000 - if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(expr)) && ++Z_ARRVAL_P(expr)->u.v.nApplyCount>1) { + case IS_ARRAY: + smart_str_appendc(trace_str, '['); +#if PHP_VERSION_ID >= 70300 + if (GC_IS_RECURSIVE(Z_ARRVAL_P(expr))) { + smart_str_appendl(trace_str, " *RECURSION*", sizeof(" *RECURSION*") - 1); + return; + } + GC_PROTECT_RECURSION(Z_ARRVAL_P(expr)); +#elif PHP_VERSION_ID >= 70000 + if (ZEND_HASH_GET_APPLY_COUNT(Z_ARRVAL_P(expr)) > 0) { + smart_str_appendl(trace_str, " *RECURSION*", sizeof(" *RECURSION*") - 1); + return; + } + ZEND_HASH_INC_APPLY_COUNT(Z_ARRVAL_P(expr)); #else - if (++Z_ARRVAL_P(expr)->nApplyCount>1) { + if (++Z_ARRVAL_P(expr)->nApplyCount > 1) { + smart_str_appendl(trace_str, " *RECURSION*", sizeof(" *RECURSION*") - 1); + Z_ARRVAL_P(expr)->nApplyCount--; + return; + } #endif - smart_str_appendl(trace_str, " *RECURSION*", sizeof(" *RECURSION*") - 1); -#if PHP_VERSION_ID >= 70000 - Z_ARRVAL_P(expr)->u.v.nApplyCount--; + append_flat_hash(Z_ARRVAL_P(expr) TSRMLS_CC, trace_str, 0, depth + 1); + smart_str_appendc(trace_str, ']'); +#if PHP_VERSION_ID >= 70300 + GC_UNPROTECT_RECURSION(Z_ARRVAL_P(expr)); +#elif PHP_VERSION_ID >= 70000 + ZEND_HASH_DEC_APPLY_COUNT(Z_ARRVAL_P(expr)); #else - Z_ARRVAL_P(expr)->nApplyCount--; + Z_ARRVAL_P(expr)->nApplyCount--; #endif - return; - } - append_flat_hash(Z_ARRVAL_P(expr) TSRMLS_CC, trace_str, 0, depth + 1); - smart_str_appendc(trace_str, ']'); + break; + case IS_OBJECT: { + HashTable *properties = NULL; #if PHP_VERSION_ID >= 70000 - if (ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(expr))) { - Z_ARRVAL_P(expr)->u.v.nApplyCount--; - } -#else - Z_ARRVAL_P(expr)->nApplyCount--; + zend_string *class_name = + Z_OBJ_HANDLER_P(expr, get_class_name)(Z_OBJ_P(expr)); + smart_str_appends(trace_str, ZSTR_VAL(class_name)); + smart_str_appendl(trace_str, " Object (", sizeof(" Object (") - 1); + zend_string_release(class_name); + +#if PHP_VERSION_ID >= 70300 + if (GC_IS_RECURSIVE(Z_OBJ_P(expr))) { + smart_str_appendl(trace_str, " *RECURSION*", sizeof(" *RECURSION*") - 1); + return; + } +#elif PHP_VERSION_ID >= 70100 + if (Z_OBJ_APPLY_COUNT_P(expr) > 0) { + smart_str_appendl(trace_str, " *RECURSION*", sizeof(" *RECURSION*") - 1); + return; + } #endif - break; - case IS_OBJECT: - { - HashTable *properties = NULL; -#if PHP_VERSION_ID >= 70000 - zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(Z_OBJ_P(expr)); - smart_str_appends(trace_str, ZSTR_VAL(class_name)); - smart_str_appendl(trace_str, " Object (", sizeof(" Object (") - 1); - zend_string_release(class_name); - - if (Z_OBJ_APPLY_COUNT_P(expr) > 0) { - smart_str_appendl(trace_str, " *RECURSION*", sizeof(" *RECURSION*") - 1); - return; - } #else - char *class_name = NULL; - zend_uint clen; - if (Z_OBJ_HANDLER_P(expr, get_class_name)) { - Z_OBJ_HANDLER_P(expr, get_class_name)(expr, (const char **) &class_name, &clen, 0 TSRMLS_CC); - } - if (class_name) { - smart_str_appendl(trace_str, class_name, clen); - smart_str_appendl(trace_str, " Object (", sizeof(" Object (") - 1); - } else { - smart_str_appendl(trace_str, "Unknown Class Object (", sizeof("Unknown Class Object (") - 1); - } - if (class_name) { - efree(class_name); - } + char *class_name = NULL; + zend_uint clen; + if (Z_OBJ_HANDLER_P(expr, get_class_name)) { + Z_OBJ_HANDLER_P(expr, get_class_name)(expr, (const char **)&class_name, + &clen, 0 TSRMLS_CC); + } + if (class_name) { + smart_str_appendl(trace_str, class_name, clen); + smart_str_appendl(trace_str, " Object (", sizeof(" Object (") - 1); + } else { + smart_str_appendl(trace_str, "Unknown Class Object (", + sizeof("Unknown Class Object (") - 1); + } + if (class_name) { + efree(class_name); + } #endif - if (Z_OBJ_HANDLER_P(expr, get_properties)) { - properties = Z_OBJPROP_P(expr); - } - if (properties) { -#if PHP_VERSION_ID >= 70000 - Z_OBJ_INC_APPLY_COUNT_P(expr); + if (Z_OBJ_HANDLER_P(expr, get_properties)) { + properties = Z_OBJPROP_P(expr); + } + if (properties) { +#if PHP_VERSION_ID >= 70300 + GC_PROTECT_RECURSION(Z_OBJ_P(expr)); +#elif PHP_VERSION_ID >= 70000 + Z_OBJ_INC_APPLY_COUNT_P(expr); #else - if (++properties->nApplyCount>1) { - smart_str_appendl(trace_str, " *RECURSION*", sizeof(" *RECURSION*") - 1); - properties->nApplyCount--; - return; - } + if (++properties->nApplyCount > 1) { + smart_str_appendl(trace_str, " *RECURSION*", + sizeof(" *RECURSION*") - 1); + properties->nApplyCount--; + return; + } #endif - append_flat_hash(properties TSRMLS_CC, trace_str, 1, depth + 1); -#if PHP_VERSION_ID >= 70000 - Z_OBJ_DEC_APPLY_COUNT_P(expr); + append_flat_hash(properties TSRMLS_CC, trace_str, 1, depth + 1); +#if PHP_VERSION_ID >= 70300 + GC_UNPROTECT_RECURSION(Z_OBJ_P(expr)); +#elif PHP_VERSION_ID >= 70000 + Z_OBJ_DEC_APPLY_COUNT_P(expr); #else - properties->nApplyCount--; + properties->nApplyCount--; #endif - } - smart_str_appendc(trace_str, ')'); - break; - } - default: - append_variable(expr, trace_str); - break; - } + } + smart_str_appendc(trace_str, ')'); + break; + } + default: + append_variable(expr, trace_str); + break; + } } -static void append_flat_hash(HashTable *ht TSRMLS_DC, smart_str *trace_str, char is_object, char depth) -{ - int i = 0; +static void append_flat_hash(HashTable *ht TSRMLS_DC, smart_str *trace_str, + char is_object, char depth) { + int i = 0; #if PHP_VERSION_ID >= 70000 - zval *tmp; - zend_string *string_key; - zend_ulong num_key; + zval *tmp; + zend_string *string_key; + zend_ulong num_key; - ZEND_HASH_FOREACH_KEY_VAL_IND(ht, num_key, string_key, tmp) { + ZEND_HASH_FOREACH_KEY_VAL_IND(ht, num_key, string_key, tmp) { #else - zval **tmp; - char *string_key, *temp; - ulong num_key; - int new_len; - uint str_len; - HashPosition iterator; - - zend_hash_internal_pointer_reset_ex(ht, &iterator); - while (zend_hash_get_current_data_ex(ht, (void **) &tmp, &iterator) == SUCCESS) { + zval **tmp; + char *string_key, *temp; + ulong num_key; + int new_len; + uint str_len; + HashPosition iterator; + + zend_hash_internal_pointer_reset_ex(ht, &iterator); + while (zend_hash_get_current_data_ex(ht, (void **)&tmp, &iterator) == + SUCCESS) { #endif - if (depth >= APM_G(dump_max_depth)) { - smart_str_appendl(trace_str, "/* [...] */", sizeof("/* [...] */") - 1); - return; - } - - if (i++ > 0) { - smart_str_appendl(trace_str, ", ", 2); - } - smart_str_appendc(trace_str, '['); + if (depth >= APM_G(dump_max_depth)) { + smart_str_appendl(trace_str, "/* [...] */", sizeof("/* [...] */") - 1); + return; + } + + if (i++ > 0) { + smart_str_appendl(trace_str, ", ", 2); + } + smart_str_appendc(trace_str, '['); #if PHP_VERSION_ID >= 70000 - if (string_key) { - smart_str_appendl(trace_str, ZSTR_VAL(string_key), ZSTR_LEN(string_key)); - } else { - smart_str_append_long(trace_str, num_key); - } - - smart_str_appendl(trace_str, "] => ", 5); - append_flat_zval_r(tmp, trace_str, depth); - } ZEND_HASH_FOREACH_END(); + if (string_key) { + smart_str_appendl(trace_str, ZSTR_VAL(string_key), ZSTR_LEN(string_key)); + } else { + smart_str_append_long(trace_str, num_key); + } + + smart_str_appendl(trace_str, "] => ", 5); + append_flat_zval_r(tmp, trace_str, depth); + } + ZEND_HASH_FOREACH_END(); #else - switch (zend_hash_get_current_key_ex(ht, &string_key, &str_len, &num_key, 0, &iterator)) { - case HASH_KEY_IS_STRING: - if (is_object) { - if (*string_key == '\0') { - do { - ++string_key; - --str_len; - } while (*(string_key) != '\0'); - ++string_key; - --str_len; - } - } - smart_str_appendc(trace_str, '"'); - - if (str_len > 0) { - temp = apm_addslashes(string_key, str_len - 1, &new_len); - smart_str_appendl(trace_str, temp, new_len); - if (temp) { - efree(temp); - } - } - else - { - smart_str_appendl(trace_str, "*unknown key*", sizeof("*unknown key*") - 1); - } - - smart_str_appendc(trace_str, '"'); - break; - case HASH_KEY_IS_LONG: - smart_str_append_long(trace_str, (long) num_key); - break; - } - - smart_str_appendl(trace_str, "] => ", 5); - append_flat_zval_r(*tmp TSRMLS_CC, trace_str, depth); - zend_hash_move_forward_ex(ht, &iterator); - } + switch (zend_hash_get_current_key_ex(ht, &string_key, &str_len, &num_key, 0, + &iterator)) { + case HASH_KEY_IS_STRING: + if (is_object) { + if (*string_key == '\0') { + do { + ++string_key; + --str_len; + } while (*(string_key) != '\0'); + ++string_key; + --str_len; + } + } + smart_str_appendc(trace_str, '"'); + + if (str_len > 0) { + temp = apm_addslashes(string_key, str_len - 1, &new_len); + smart_str_appendl(trace_str, temp, new_len); + if (temp) { + efree(temp); + } + } else { + smart_str_appendl(trace_str, "*unknown key*", + sizeof("*unknown key*") - 1); + } + + smart_str_appendc(trace_str, '"'); + break; + case HASH_KEY_IS_LONG: + smart_str_append_long(trace_str, (long)num_key); + break; + } + + smart_str_appendl(trace_str, "] => ", 5); + append_flat_zval_r(*tmp TSRMLS_CC, trace_str, depth); + zend_hash_move_forward_ex(ht, &iterator); + } #endif } -static int append_variable(zval *expr, smart_str *trace_str) -{ - zval expr_copy; - int use_copy; - char is_string = 0; - char * temp; - int new_len; +static int append_variable(zval *expr, smart_str *trace_str) { + zval expr_copy; + int use_copy; + char is_string = 0; + char *temp; + int new_len; - if (Z_TYPE_P(expr) == IS_STRING) { - smart_str_appendc(trace_str, '"'); - is_string = 1; - } + if (Z_TYPE_P(expr) == IS_STRING) { + smart_str_appendc(trace_str, '"'); + is_string = 1; + } #if PHP_VERSION_ID >= 70000 - use_copy = zend_make_printable_zval(expr, &expr_copy); + use_copy = zend_make_printable_zval(expr, &expr_copy); #else - zend_make_printable_zval(expr, &expr_copy, &use_copy); + zend_make_printable_zval(expr, &expr_copy, &use_copy); #endif - if (use_copy) { - expr = &expr_copy; - } - if (Z_STRLEN_P(expr) == 0) { /* optimize away empty strings */ - if (is_string) { - smart_str_appendc(trace_str, '"'); - } - if (use_copy) { - zval_dtor(expr); - } - return 0; - } - - if (is_string) { - temp = apm_addslashes(Z_STRVAL_P(expr), Z_STRLEN_P(expr), &new_len); - smart_str_appendl(trace_str, temp, new_len); - smart_str_appendc(trace_str, '"'); - if (temp) { - efree(temp); - } - } else { - smart_str_appendl(trace_str, Z_STRVAL_P(expr), Z_STRLEN_P(expr)); - } - - if (use_copy) { - zval_dtor(expr); - } - return Z_STRLEN_P(expr); + if (use_copy) { + expr = &expr_copy; + } + if (Z_STRLEN_P(expr) == 0) { /* optimize away empty strings */ + if (is_string) { + smart_str_appendc(trace_str, '"'); + } + if (use_copy) { + zval_dtor(expr); + } + return 0; + } + + if (is_string) { + temp = apm_addslashes(Z_STRVAL_P(expr), Z_STRLEN_P(expr), &new_len); + smart_str_appendl(trace_str, temp, new_len); + smart_str_appendc(trace_str, '"'); + if (temp) { + efree(temp); + } + } else { + smart_str_appendl(trace_str, Z_STRVAL_P(expr), Z_STRLEN_P(expr)); + } + + if (use_copy) { + zval_dtor(expr); + } + return Z_STRLEN_P(expr); } #if PHP_VERSION_ID >= 70000 -static void debug_backtrace_get_args(zend_execute_data *call, zval *arg_array) -{ - uint32_t num_args = ZEND_CALL_NUM_ARGS(call); - - array_init_size(arg_array, num_args); - if (num_args) { - uint32_t i = 0; - zval *p = ZEND_CALL_ARG(call, 1); - - zend_hash_real_init(Z_ARRVAL_P(arg_array), 1); - ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(arg_array)) { - if (call->func->type == ZEND_USER_FUNCTION) { - uint32_t first_extra_arg = call->func->op_array.num_args; - - if (ZEND_CALL_NUM_ARGS(call) > first_extra_arg) { - while (i < first_extra_arg) { - if (Z_OPT_REFCOUNTED_P(p)) Z_ADDREF_P(p); - ZEND_HASH_FILL_ADD(p); - zend_hash_next_index_insert_new(Z_ARRVAL_P(arg_array), p); - p++; - i++; - } - p = ZEND_CALL_VAR_NUM(call, call->func->op_array.last_var + call->func->op_array.T); - } - } - - while (i < num_args) { - if (Z_OPT_REFCOUNTED_P(p)) Z_ADDREF_P(p); - ZEND_HASH_FILL_ADD(p); - p++; - i++; - } - } ZEND_HASH_FILL_END(); - } +static void debug_backtrace_get_args(zend_execute_data *call, zval *arg_array) { + uint32_t num_args = ZEND_CALL_NUM_ARGS(call); + + array_init_size(arg_array, num_args); + if (num_args) { + uint32_t i = 0; + zval *p = ZEND_CALL_ARG(call, 1); + + zend_hash_real_init(Z_ARRVAL_P(arg_array), 1); + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(arg_array)) { + if (call->func->type == ZEND_USER_FUNCTION) { + uint32_t first_extra_arg = call->func->op_array.num_args; + + if (ZEND_CALL_NUM_ARGS(call) > first_extra_arg) { + while (i < first_extra_arg) { + if (Z_OPT_REFCOUNTED_P(p)) + Z_ADDREF_P(p); + ZEND_HASH_FILL_ADD(p); + zend_hash_next_index_insert_new(Z_ARRVAL_P(arg_array), p); + p++; + i++; + } + p = ZEND_CALL_VAR_NUM(call, call->func->op_array.last_var + + call->func->op_array.T); + } + } + + while (i < num_args) { + if (Z_OPT_REFCOUNTED_P(p)) + Z_ADDREF_P(p); + ZEND_HASH_FILL_ADD(p); + p++; + i++; + } + } + ZEND_HASH_FILL_END(); + } } #else -static zval *debug_backtrace_get_args(void ***curpos TSRMLS_DC) -{ - void **p = *curpos; - zval *arg_array, **arg; - int arg_count = - (int)(zend_uintptr_t) *p; - - MAKE_STD_ZVAL(arg_array); - array_init_size(arg_array, arg_count); - p -= arg_count; - - while (--arg_count >= 0) { - arg = (zval **) p++; - if (*arg) { - if (Z_TYPE_PP(arg) != IS_OBJECT) { - SEPARATE_ZVAL_TO_MAKE_IS_REF(arg); - } - Z_ADDREF_PP(arg); - add_next_index_zval(arg_array, *arg); - } else { - add_next_index_null(arg_array); - } - } - - return arg_array; +static zval *debug_backtrace_get_args(void ***curpos TSRMLS_DC) { + void **p = *curpos; + zval *arg_array, **arg; + int arg_count = (int)(zend_uintptr_t)*p; + + MAKE_STD_ZVAL(arg_array); + array_init_size(arg_array, arg_count); + p -= arg_count; + + while (--arg_count >= 0) { + arg = (zval **)p++; + if (*arg) { + if (Z_TYPE_PP(arg) != IS_OBJECT) { + SEPARATE_ZVAL_TO_MAKE_IS_REF(arg); + } + Z_ADDREF_PP(arg); + add_next_index_zval(arg_array, *arg); + } else { + add_next_index_null(arg_array); + } + } + + return arg_array; } #endif -static char *apm_addslashes(char *str, uint length, int *new_length) -{ - /* maximum string length, worst case situation */ - char *new_str; - char *source, *target; - char *end; - int local_new_length; - - if (!new_length) { - new_length = &local_new_length; - } - - if (!str) { - *new_length = 0; - return str; - } - new_str = (char *) safe_emalloc(2, (length ? length : (length = strlen(str))), 1); - source = str; - end = source + length; - target = new_str; - - while (source < end) { - switch (*source) { - case '\0': - *target++ = '\\'; - *target++ = '0'; - break; - case '\"': - case '\\': - *target++ = '\\'; - /* break is missing *intentionally* */ - default: - *target++ = *source; - break; - } - - source++; - } - - *target = 0; - *new_length = target - new_str; - return (char *) erealloc(new_str, *new_length + 1); +static char *apm_addslashes(char *str, uint length, int *new_length) { + /* maximum string length, worst case situation */ + char *new_str; + char *source, *target; + char *end; + int local_new_length; + + if (!new_length) { + new_length = &local_new_length; + } + + if (!str) { + *new_length = 0; + return str; + } + new_str = + (char *)safe_emalloc(2, (length ? length : (length = strlen(str))), 1); + source = str; + end = source + length; + target = new_str; + + while (source < end) { + switch (*source) { + case '\0': + *target++ = '\\'; + *target++ = '0'; + break; + case '\"': + case '\\': + *target++ = '\\'; + /* break is missing *intentionally* */ + default: + *target++ = *source; + break; + } + + source++; + } + + *target = 0; + *new_length = target - new_str; + return (char *)erealloc(new_str, *new_length + 1); } diff --git a/build-extension-php53.sh b/build-extension-php53.sh new file mode 100755 index 0000000..9eefa9d --- /dev/null +++ b/build-extension-php53.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# Build apm.so for different PHP versions and old GLIBC + +set -e + +PHP_VERSION=${1:-"5.3"} + +echo "🔨 Building apm.so for PHP $PHP_VERSION (compatible with old GLIBC)..." +echo "" + +# Create a temporary Dockerfile for building with old base +cat > Dockerfile.build-legacy << EOF +# Use old Debian for GLIBC compatibility +FROM debian:wheezy + +# Configure old repositories +RUN echo "deb http://archive.debian.org/debian wheezy main" > /etc/apt/sources.list && \ + echo "deb http://archive.debian.org/debian-security wheezy/updates main" >> /etc/apt/sources.list + +# Install PHP and build dependencies +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + php5-dev \ + php5-cli \ + git \ + autoconf \ + build-essential \ + libcurl4-openssl-dev \ + curl \ + --force-yes \ + && rm -rf /var/lib/apt/lists/* + +# Copy source code +COPY . /tmp/php-apm +WORKDIR /tmp/php-apm + +# Build extension +RUN phpize && \ + ./configure \ + --enable-apm \ + --without-sqlite3 \ + --without-mysql \ + --disable-statsd \ + --disable-socket \ + --enable-elasticsearch && \ + --with-debugfile= + make && \ + make install + +# Copy the built extension to output +RUN mkdir -p /output && \ + cp \$(php-config --extension-dir)/apm.so /output/apm.so && \ + php -v > /output/php-version.txt && \ + ldd \$(php-config --extension-dir)/apm.so > /output/dependencies.txt || true + +CMD ["cat", "/output/apm.so"] +EOF + +# Build the image +echo "đŸ“Ļ Building Docker image (this may take a while)..." +docker build -f Dockerfile.build-legacy -t apm-builder-legacy . 2>&1 | grep -E "(Step|Successfully|Error)" || true + +# Create a container and copy files +echo "📄 Extracting apm.so..." +CONTAINER_ID=$(docker create apm-builder-legacy) +docker cp $CONTAINER_ID:/output/apm.so ./apm-php53.so +docker cp $CONTAINER_ID:/output/php-version.txt ./build-info.txt 2>/dev/null || true +docker cp $CONTAINER_ID:/output/dependencies.txt ./dependencies.txt 2>/dev/null || true +docker rm $CONTAINER_ID > /dev/null 2>&1 + +# Clean up +rm Dockerfile.build-legacy +docker rmi apm-builder-legacy > /dev/null 2>&1 + +# Get file info +FILE_SIZE=$(ls -lh apm-php53.so | awk '{print $5}') +PHP_INFO=$(cat build-info.txt 2>/dev/null | head -1 || echo "PHP 5.3") + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "✅ Build completed successfully!" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "đŸ“Ļ Extension file: ./apm-php53.so" +echo "📏 Size: $FILE_SIZE" +echo "🔧 Built for: $PHP_INFO" +echo "" +echo "📋 Dependencies:" +[ -f dependencies.txt ] && cat dependencies.txt || echo " (check dependencies.txt)" +echo "" +echo "🔧 To install:" +echo " cp apm-php53.so /usr/lib/php5/20100525/apm.so" +echo " echo 'extension=apm.so' > /etc/php5/conf.d/apm.ini" +echo "" +echo "📋 Verify:" +echo " php -m | grep apm" +echo "" diff --git a/build-extension.sh b/build-extension.sh new file mode 100755 index 0000000..0841d41 --- /dev/null +++ b/build-extension.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# Build standalone apm.so extension + +set -e + +echo "🔨 Building standalone apm.so for PHP 5.6..." +echo "" + +# Create a temporary Dockerfile for building +cat > Dockerfile.build << 'EOF' +FROM php:5.6-cli + +# Fix Debian repositories +RUN echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.list + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + git \ + autoconf \ + build-essential \ + libcurl4-openssl-dev \ + --allow-unauthenticated \ + && rm -rf /var/lib/apt/lists/* + +# Copy source code +COPY . /tmp/php-apm +WORKDIR /tmp/php-apm + +# Build extension +RUN phpize \ + && ./configure \ + --enable-apm \ + --without-sqlite3 \ + --without-mysql \ + --disable-statsd \ + --disable-socket \ + --enable-elasticsearch \ + && make \ + && make install + +# Copy the built extension to a known location +RUN mkdir -p /output && \ + cp $(php-config --extension-dir)/apm.so /output/apm.so + +CMD ["cat", "/output/apm.so"] +EOF + +# Build the image +echo "đŸ“Ļ Building Docker image..." +docker build -f Dockerfile.build -t apm-builder . > /dev/null 2>&1 + +# Create a container and copy the .so file +echo "📄 Extracting apm.so..." +CONTAINER_ID=$(docker create apm-builder) +docker cp $CONTAINER_ID:/output/apm.so ./apm.so +docker rm $CONTAINER_ID > /dev/null 2>&1 + +# Clean up +rm Dockerfile.build +docker rmi apm-builder > /dev/null 2>&1 + +# Get file info +FILE_SIZE=$(ls -lh apm.so | awk '{print $5}') +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "✅ Build completed successfully!" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "đŸ“Ļ Extension file: ./apm.so" +echo "📏 Size: $FILE_SIZE" +echo "" +echo "🔧 To install:" +echo " 1. Copy to PHP extension directory:" +echo " cp apm.so \$(php-config --extension-dir)/" +echo "" +echo " 2. Add to php.ini:" +echo " echo 'extension=apm.so' >> \$(php --ini | grep 'Loaded Configuration' | awk '{print \$4}')" +echo "" +echo " 3. Configure APM (see apm.ini for examples)" +echo "" +echo "📋 Verify installation:" +echo " php -m | grep apm" +echo "" diff --git a/build-info.txt b/build-info.txt new file mode 100644 index 0000000..57dea85 --- /dev/null +++ b/build-info.txt @@ -0,0 +1,3 @@ +PHP 5.4.45-0+deb7u14 (cli) (built: May 9 2018 16:47:00) +Copyright (c) 1997-2014 The PHP Group +Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies diff --git a/config.m4 b/config.m4 index 647bcaa..41c7824 100644 --- a/config.m4 +++ b/config.m4 @@ -35,6 +35,8 @@ PHP_ARG_ENABLE(statsd, enable support for statsd, [ --enable-statsd Enable statsd support], yes, no) PHP_ARG_ENABLE(socket, enable support for socket, [ --enable-socket Enable socket support], yes, no) +PHP_ARG_ENABLE(elasticsearch, enable support for elasticsearch, +[ --enable-elasticsearch Enable elasticsearch support], yes, no) PHP_ARG_WITH(debugfile, enable the debug file, [ --with-debugfile=[FILE] Location of debugging file (/tmp/apm.debug by default)], no, no) PHP_ARG_WITH(defaultdb, set default sqlite3 default DB path, @@ -199,6 +201,43 @@ if test "$PHP_APM" != "no"; then AC_DEFINE(APM_DRIVER_SOCKET, 1, [activate socket driver]) fi - PHP_NEW_EXTENSION(apm, apm.c backtrace.c $sqlite3_driver $mysql_driver $statsd_driver $socket_driver, $ext_shared) + if test "$PHP_ELASTICSEARCH" != "no"; then + elasticsearch_driver="driver_elasticsearch.c" + AC_DEFINE(APM_DRIVER_ELASTICSEARCH, 1, [activate elasticsearch driver]) + + dnl Check for libcurl + AC_MSG_CHECKING([for libcurl]) + if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) + fi + + if test "$PKG_CONFIG" != "no" && $PKG_CONFIG --exists libcurl; then + CURL_CFLAGS=`$PKG_CONFIG --cflags libcurl` + CURL_LIBS=`$PKG_CONFIG --libs libcurl` + AC_MSG_RESULT([found via pkg-config]) + PHP_EVAL_LIBLINE($CURL_LIBS, APM_SHARED_LIBADD) + PHP_EVAL_INCLINE($CURL_CFLAGS) + else + AC_MSG_RESULT([checking manually]) + for i in /usr/local /usr; do + if test -f $i/include/curl/curl.h; then + CURL_DIR=$i + AC_MSG_RESULT([found in $i]) + break + fi + done + + if test -z "$CURL_DIR"; then + AC_MSG_ERROR([libcurl not found. Please install libcurl development package (e.g., libcurl4-openssl-dev)]) + fi + + PHP_ADD_INCLUDE($CURL_DIR/include) + PHP_ADD_LIBRARY_WITH_PATH(curl, $CURL_DIR/lib, APM_SHARED_LIBADD) + fi + + AC_DEFINE(HAVE_CURL,1,[libcurl found and included]) + fi + + PHP_NEW_EXTENSION(apm, apm.c backtrace.c $sqlite3_driver $mysql_driver $statsd_driver $socket_driver $elasticsearch_driver, $ext_shared) PHP_SUBST(APM_SHARED_LIBADD) fi diff --git a/dependencies.txt b/dependencies.txt new file mode 100644 index 0000000..3724db6 --- /dev/null +++ b/dependencies.txt @@ -0,0 +1,28 @@ + linux-vdso.so.1 => (0x00007fbfc3753000) + libcurl.so.4 => /usr/lib/x86_64-linux-gnu/libcurl.so.4 (0x00007fbfc2f8b000) + libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbfc2bfe000) + libidn.so.11 => /usr/lib/x86_64-linux-gnu/libidn.so.11 (0x00007fbfc29ca000) + libssh2.so.1 => /usr/lib/x86_64-linux-gnu/libssh2.so.1 (0x00007fbfc27a1000) + liblber-2.4.so.2 => /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 (0x00007fbfc2592000) + libldap_r-2.4.so.2 => /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2 (0x00007fbfc2341000) + librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fbfc2139000) + libgssapi_krb5.so.2 => /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007fbfc1ef9000) + libssl.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007fbfc1c98000) + libcrypto.so.1.0.0 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007fbfc189e000) + librtmp.so.0 => /usr/lib/x86_64-linux-gnu/librtmp.so.0 (0x00007fbfc1684000) + libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fbfc146d000) + /lib64/ld-linux-x86-64.so.2 (0x00007fbfc3400000) + libgcrypt.so.11 => /lib/x86_64-linux-gnu/libgcrypt.so.11 (0x00007fbfc11ee000) + libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fbfc0fd8000) + libsasl2.so.2 => /usr/lib/x86_64-linux-gnu/libsasl2.so.2 (0x00007fbfc0dbd000) + libgnutls.so.26 => /usr/lib/x86_64-linux-gnu/libgnutls.so.26 (0x00007fbfc0afd000) + libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fbfc08e1000) + libkrb5.so.3 => /usr/lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007fbfc060d000) + libk5crypto.so.3 => /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007fbfc03e4000) + libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007fbfc01e0000) + libkrb5support.so.0 => /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007fbfbffd7000) + libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fbfbfdd3000) + libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007fbfbfbcf000) + libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007fbfbf9cc000) + libtasn1.so.3 => /usr/lib/x86_64-linux-gnu/libtasn1.so.3 (0x00007fbfbf7bb000) + libp11-kit.so.0 => /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007fbfbf5a9000) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f6f362d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,79 @@ +services: + # Elasticsearch for APM data + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:7.17.10 + container_name: apm-elasticsearch + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ports: + - "9200:9200" + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + networks: + - apm-network + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + # Kibana for visualization (optional) + kibana: + image: docker.elastic.co/kibana/kibana:7.17.10 + container_name: apm-kibana + environment: + - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 + ports: + - "5601:5601" + depends_on: + elasticsearch: + condition: service_healthy + networks: + - apm-network + + # PHP 5.6 with APM extension + php: + build: + context: . + dockerfile: Dockerfile + container_name: apm-php56 + ports: + - "8080:80" + volumes: + - ./test:/var/www/html + depends_on: + elasticsearch: + condition: service_healthy + networks: + - apm-network + environment: + - PHP_DISPLAY_ERRORS=1 + - PHP_ERROR_REPORTING=E_ALL + + php-legacy: + build: + context: . + dockerfile: Dockerfile.build-legacy + container_name: apm-php53 + ports: + - "8085:80" + volumes: + - ./test:/var/www/html + depends_on: + elasticsearch: + condition: service_healthy + networks: + - apm-network + environment: + - PHP_DISPLAY_ERRORS=1 + - PHP_ERROR_REPORTING=E_ALL + +volumes: + elasticsearch-data: + driver: local + +networks: + apm-network: + driver: bridge diff --git a/driver_elasticsearch.c b/driver_elasticsearch.c new file mode 100644 index 0000000..8c7da2f --- /dev/null +++ b/driver_elasticsearch.c @@ -0,0 +1,464 @@ +/* + +----------------------------------------------------------------------+ + | APM stands for Alternative PHP Monitor | + +----------------------------------------------------------------------+ + | Copyright (c) 2008-2014 Davide Mendolia, Patrick Allaert | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Patrick Allaert | + +----------------------------------------------------------------------+ +*/ + +#include +#include +#include +#include +#include "php_apm.h" +#include "php_ini.h" +#include "driver_elasticsearch.h" +#include "SAPI.h" + +ZEND_EXTERN_MODULE_GLOBALS(apm); + +APM_DRIVER_CREATE(elasticsearch) + +/* Simple JSON string escape function */ +static void append_json_string(smart_str *dest, const char *src) +{ + const char *p; + if (!src) { + smart_str_appends(dest, "\"\""); + return; + } + + smart_str_appendc(dest, '"'); + for (p = src; *p; p++) { + switch (*p) { + case '"': + smart_str_appends(dest, "\\\""); + break; + case '\\': + smart_str_appends(dest, "\\\\"); + break; + case '\n': + smart_str_appends(dest, "\\n"); + break; + case '\r': + smart_str_appends(dest, "\\r"); + break; + case '\t': + smart_str_appends(dest, "\\t"); + break; + default: + smart_str_appendc(dest, *p); + } + } + smart_str_appendc(dest, '"'); +} + +/* Helper function to build JSON for events */ +static void build_event_json(smart_str *json, int type, char *error_filename, uint error_lineno, char *msg, char *trace TSRMLS_DC) +{ + char timestamp[32]; + time_t now = time(NULL); + const char *type_string; + + strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S", gmtime(&now)); + + switch(type) { + case E_EXCEPTION: + type_string = "exception"; + break; + case E_ERROR: + type_string = "error"; + break; + case E_WARNING: + type_string = "warning"; + break; + case E_PARSE: + type_string = "parse_error"; + break; + case E_NOTICE: + type_string = "notice"; + break; + case E_CORE_ERROR: + type_string = "core_error"; + break; + case E_CORE_WARNING: + type_string = "core_warning"; + break; + case E_COMPILE_ERROR: + type_string = "compile_error"; + break; + case E_COMPILE_WARNING: + type_string = "compile_warning"; + break; + case E_USER_ERROR: + type_string = "user_error"; + break; + case E_USER_WARNING: + type_string = "user_warning"; + break; + case E_USER_NOTICE: + type_string = "user_notice"; + break; + case E_STRICT: + type_string = "strict"; + break; + case E_RECOVERABLE_ERROR: + type_string = "recoverable_error"; + break; + case E_DEPRECATED: + type_string = "deprecated"; + break; + case E_USER_DEPRECATED: + type_string = "user_deprecated"; + break; + default: + type_string = "unknown"; + } + + smart_str_appends(json, "{\"@timestamp\":\""); + smart_str_appends(json, timestamp); + smart_str_appends(json, "\",\"type\":\"event\",\"event_type\":\""); + smart_str_appends(json, type_string); + smart_str_appends(json, "\",\"severity\":"); + smart_str_append_long(json, type); + + if (APM_G(application_id)) { + smart_str_appends(json, ",\"application_id\":\""); + smart_str_appends(json, APM_G(application_id)); + smart_str_appends(json, "\""); + } + + if (error_filename) { + smart_str_appends(json, ",\"file\":\""); + smart_str_appends(json, error_filename); + smart_str_appends(json, "\""); + } + + if (error_lineno > 0) { + smart_str_appends(json, ",\"line\":"); + smart_str_append_long(json, error_lineno); + } + + if (msg) { + smart_str_appends(json, ",\"message\":"); + append_json_string(json, msg); + } + + if (trace) { + smart_str_appends(json, ",\"backtrace\":"); + append_json_string(json, trace); + } + + smart_str_appends(json, "}"); +} + +/* Helper function to build JSON for stats */ +static void build_stats_json(smart_str *json TSRMLS_DC) +{ + char timestamp[32]; + time_t now = time(NULL); + + strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S", gmtime(&now)); + + smart_str_appends(json, "{\"@timestamp\":\""); + smart_str_appends(json, timestamp); + smart_str_appends(json, "\",\"type\":\"stats\""); + + if (APM_G(application_id)) { + smart_str_appends(json, ",\"application_id\":\""); + smart_str_appends(json, APM_G(application_id)); + smart_str_appends(json, "\""); + } + + smart_str_appends(json, ",\"duration\":"); + smart_str_append_long(json, (long)(APM_G(duration) / 1000)); + +#ifdef HAVE_GETRUSAGE + smart_str_appends(json, ",\"user_cpu\":"); + smart_str_append_long(json, (long)(APM_G(user_cpu) / 1000)); + + smart_str_appends(json, ",\"sys_cpu\":"); + smart_str_append_long(json, (long)(APM_G(sys_cpu) / 1000)); +#endif + + smart_str_appends(json, ",\"mem_peak_usage\":"); + smart_str_append_long(json, APM_G(mem_peak_usage)); + + smart_str_appends(json, ",\"response_code\":"); + smart_str_append_long(json, SG(sapi_headers).http_response_code); + + extract_data(TSRMLS_C); + + if (APM_RD(uri_found)) { + smart_str_appends(json, ",\"uri\":"); + append_json_string(json, APM_RD_STRVAL(uri)); + } + + if (APM_RD(host_found)) { + smart_str_appends(json, ",\"host\":"); + append_json_string(json, APM_RD_STRVAL(host)); + } + + if (APM_RD(method_found)) { + smart_str_appends(json, ",\"method\":"); + append_json_string(json, APM_RD_STRVAL(method)); + } + + smart_str_appends(json, "}"); +} + +/* Flush buffer to Elasticsearch using Bulk API */ +static int flush_buffer_to_elasticsearch(TSRMLS_D) +{ + CURL *curl; + CURLcode res; + char url[512]; + char auth[256]; + struct curl_slist *headers = NULL; + int success = 0; + + if (APM_G(elasticsearch_buffer_count) == 0) { + return 1; /* Nothing to send */ + } + +#if PHP_VERSION_ID >= 70000 + if (!APM_G(elasticsearch_buffer).s || ZSTR_LEN(APM_G(elasticsearch_buffer).s) == 0) { +#else + if (!APM_G(elasticsearch_buffer).c || APM_G(elasticsearch_buffer).len == 0) { +#endif + return 1; /* Empty buffer */ + } + + smart_str_0(&APM_G(elasticsearch_buffer)); + + curl = curl_easy_init(); + if (!curl) { + APM_DEBUG("[Elasticsearch driver] Failed to initialize CURL\n"); + return 0; + } + + /* Build URL for Bulk API: http://host:port/_bulk */ + snprintf(url, sizeof(url), "http://%s:%u/_bulk", + APM_G(elasticsearch_host), + APM_G(elasticsearch_port)); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_POST, 1L); +#if PHP_VERSION_ID >= 70000 + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ZSTR_VAL(APM_G(elasticsearch_buffer).s)); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, ZSTR_LEN(APM_G(elasticsearch_buffer).s)); +#else + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, APM_G(elasticsearch_buffer).c); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, APM_G(elasticsearch_buffer).len); +#endif + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2L); + + /* Set Content-Type header for Bulk API */ + headers = curl_slist_append(headers, "Content-Type: application/x-ndjson"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + /* Set basic auth if configured */ + if (APM_G(elasticsearch_username) && strlen(APM_G(elasticsearch_username)) > 0) { + snprintf(auth, sizeof(auth), "%s:%s", + APM_G(elasticsearch_username), + APM_G(elasticsearch_password) ? APM_G(elasticsearch_password) : ""); + curl_easy_setopt(curl, CURLOPT_USERPWD, auth); + } + + APM_DEBUG("[Elasticsearch driver] Sending batch of %d documents to %s\n", + APM_G(elasticsearch_buffer_count), url); + + res = curl_easy_perform(curl); + if (res == CURLE_OK) { + success = 1; + APM_DEBUG("[Elasticsearch driver] Batch sent successfully\n"); + } else { + APM_DEBUG("[Elasticsearch driver] CURL error: %s\n", curl_easy_strerror(res)); + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + /* Clear buffer */ + smart_str_free(&APM_G(elasticsearch_buffer)); + memset(&APM_G(elasticsearch_buffer), 0, sizeof(smart_str)); + APM_G(elasticsearch_buffer_count) = 0; + APM_G(elasticsearch_buffer_start_time) = 0; + + return success; +} + +/* Add document to buffer */ +static void add_to_buffer(smart_str *document TSRMLS_DC) +{ + char index_action[512]; + + if (!document) { + return; + } + + smart_str_0(document); + + /* Bulk API format: action line, then document line */ + snprintf(index_action, sizeof(index_action), + "{\"index\":{\"_index\":\"%s\"}}\n", + APM_G(elasticsearch_index)); + + smart_str_appends(&APM_G(elasticsearch_buffer), index_action); + +#if PHP_VERSION_ID >= 70000 + if (document->s) { + smart_str_append(&APM_G(elasticsearch_buffer), document->s); + } +#else + if (document->c) { + smart_str_appendl(&APM_G(elasticsearch_buffer), document->c, document->len); + } +#endif + + smart_str_appendc(&APM_G(elasticsearch_buffer), '\n'); + + APM_G(elasticsearch_buffer_count)++; + + /* Set start time for timeout if this is the first document */ + if (APM_G(elasticsearch_buffer_count) == 1) { + APM_G(elasticsearch_buffer_start_time) = time(NULL); + } + + /* Check if we should flush based on batch size */ + if (APM_G(elasticsearch_buffer_count) >= APM_G(elasticsearch_batch_size)) { + APM_DEBUG("[Elasticsearch driver] Batch size reached (%d), flushing\n", + APM_G(elasticsearch_buffer_count)); + flush_buffer_to_elasticsearch(TSRMLS_C); + } +} + +/* Check and flush buffer if timeout reached */ +static void check_buffer_timeout(TSRMLS_D) +{ + time_t now; + + if (APM_G(elasticsearch_buffer_count) == 0) { + return; + } + + now = time(NULL); + if ((now - APM_G(elasticsearch_buffer_start_time)) >= APM_G(elasticsearch_batch_timeout)) { + APM_DEBUG("[Elasticsearch driver] Batch timeout reached (%ld seconds), flushing\n", + (long)(now - APM_G(elasticsearch_buffer_start_time))); + flush_buffer_to_elasticsearch(TSRMLS_C); + } +} + +/* Insert an event in Elasticsearch */ +void apm_driver_elasticsearch_process_event(PROCESS_EVENT_ARGS) +{ + smart_str document = {0}; + + build_event_json(&document, type, error_filename, error_lineno, msg, trace TSRMLS_CC); + add_to_buffer(&document TSRMLS_CC); + smart_str_free(&document); + + /* Check timeout after adding */ + check_buffer_timeout(TSRMLS_C); +} + +/* Module initialization */ +int apm_driver_elasticsearch_minit(int module_number TSRMLS_DC) +{ + if (!(APM_G(enabled) && APM_G(elasticsearch_enabled))) { + return SUCCESS; + } + + /* Initialize CURL globally */ + curl_global_init(CURL_GLOBAL_DEFAULT); + + /* Validate configuration */ + if (!APM_G(elasticsearch_host) || strlen(APM_G(elasticsearch_host)) == 0) { + zend_error(E_CORE_WARNING, "APM Elasticsearch driver: elasticsearch_host is not configured"); + APM_G(elasticsearch_enabled) = 0; + return SUCCESS; + } + + if (!APM_G(elasticsearch_index) || strlen(APM_G(elasticsearch_index)) == 0) { + zend_error(E_CORE_WARNING, "APM Elasticsearch driver: elasticsearch_index is not configured"); + APM_G(elasticsearch_enabled) = 0; + return SUCCESS; + } + + /* Validate batch configuration */ + if (APM_G(elasticsearch_batch_size) < 1) { + APM_G(elasticsearch_batch_size) = 1; + } + if (APM_G(elasticsearch_batch_timeout) < 1) { + APM_G(elasticsearch_batch_timeout) = 1; + } + + APM_DEBUG("[Elasticsearch driver] Initialized with host=%s:%u, index=%s, batch_size=%ld, batch_timeout=%ld\n", + APM_G(elasticsearch_host), APM_G(elasticsearch_port), APM_G(elasticsearch_index), + APM_G(elasticsearch_batch_size), APM_G(elasticsearch_batch_timeout)); + + return SUCCESS; +} + +/* Request initialization */ +int apm_driver_elasticsearch_rinit(TSRMLS_D) +{ + /* Initialize buffer */ + memset(&APM_G(elasticsearch_buffer), 0, sizeof(smart_str)); + APM_G(elasticsearch_buffer_count) = 0; + APM_G(elasticsearch_buffer_start_time) = 0; + + return SUCCESS; +} + +/* Module shutdown */ +int apm_driver_elasticsearch_mshutdown(SHUTDOWN_FUNC_ARGS) +{ + if (!(APM_G(enabled) && APM_G(elasticsearch_enabled))) { + return SUCCESS; + } + + curl_global_cleanup(); + + return SUCCESS; +} + +/* Request shutdown */ +int apm_driver_elasticsearch_rshutdown(TSRMLS_D) +{ + /* Flush any remaining buffered data */ + if (APM_G(elasticsearch_buffer_count) > 0) { + APM_DEBUG("[Elasticsearch driver] Flushing remaining %d documents at request shutdown\n", + APM_G(elasticsearch_buffer_count)); + flush_buffer_to_elasticsearch(TSRMLS_C); + } + + /* Clean up buffer */ + smart_str_free(&APM_G(elasticsearch_buffer)); + + return SUCCESS; +} + +/* Process statistics */ +void apm_driver_elasticsearch_process_stats(TSRMLS_D) +{ + smart_str document = {0}; + + build_stats_json(&document TSRMLS_CC); + add_to_buffer(&document TSRMLS_CC); + smart_str_free(&document); + + /* Check timeout after adding */ + check_buffer_timeout(TSRMLS_C); +} diff --git a/driver_elasticsearch.h b/driver_elasticsearch.h new file mode 100644 index 0000000..7a7ff95 --- /dev/null +++ b/driver_elasticsearch.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | APM stands for Alternative PHP Monitor | + +----------------------------------------------------------------------+ + | Copyright (c) 2008-2014 Davide Mendolia, Patrick Allaert | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Patrick Allaert | + +----------------------------------------------------------------------+ +*/ + +#ifndef DRIVER_ELASTICSEARCH_H +#define DRIVER_ELASTICSEARCH_H + +#include "zend_API.h" + +#define APM_E_elasticsearch APM_E_ALL + +apm_driver_entry * apm_driver_elasticsearch_create(); +void apm_driver_elasticsearch_process_event(PROCESS_EVENT_ARGS); +void apm_driver_elasticsearch_process_stats(TSRMLS_D); +int apm_driver_elasticsearch_minit(int TSRMLS_DC); +int apm_driver_elasticsearch_rinit(TSRMLS_D); +int apm_driver_elasticsearch_mshutdown(); +int apm_driver_elasticsearch_rshutdown(TSRMLS_D); + +PHP_INI_MH(OnUpdateAPMelasticsearchErrorReporting); + +#endif diff --git a/php-apm.code-workspace b/php-apm.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/php-apm.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/php_apm.h b/php_apm.h index 4cd0ea7..dc9829b 100644 --- a/php_apm.h +++ b/php_apm.h @@ -22,27 +22,35 @@ #define PHP_APM_VERSION "2.1.2" #ifdef HAVE_CONFIG_H -# include "config.h" +#include "config.h" #endif #include "php.h" #include "zend_errors.h" #if PHP_VERSION_ID >= 70000 -# include "zend_smart_str.h" +#include "zend_smart_str.h" +#if PHP_VERSION_ID < 70300 +#define ZEND_HASH_GET_APPLY_COUNT(ht) ((ht)->u.v.nApplyCount) +#define ZEND_HASH_INC_APPLY_COUNT(ht) (++(ht)->u.v.nApplyCount) +#define ZEND_HASH_DEC_APPLY_COUNT(ht) (--(ht)->u.v.nApplyCount) +#define Z_OBJ_APPLY_COUNT_P(ov) (Z_OBJ_P(ov)->gc.u.v.nApplyCount) +#define Z_OBJ_INC_APPLY_COUNT_P(ov) (++Z_OBJ_P(ov)->gc.u.v.nApplyCount) +#define Z_OBJ_DEC_APPLY_COUNT_P(ov) (--Z_OBJ_P(ov)->gc.u.v.nApplyCount) +#endif #else -# include "ext/standard/php_smart_str.h" +#include "ext/standard/php_smart_str.h" #endif #ifndef E_EXCEPTION -# define E_EXCEPTION (1<<15L) +#define E_EXCEPTION (1 << 15L) #endif #ifdef APM_DRIVER_SQLITE3 - #include +#include #endif #ifdef APM_DRIVER_MYSQL - #include +#include #endif #ifdef PHP_WIN32 @@ -58,134 +66,131 @@ #define APM_EVENT_ERROR 1 #define APM_EVENT_EXCEPTION 2 -#define PROCESS_EVENT_ARGS int type, char * error_filename, uint error_lineno, char * msg, char * trace TSRMLS_DC +#define PROCESS_EVENT_ARGS \ + int type, char *error_filename, uint error_lineno, char *msg, \ + char *trace TSRMLS_DC typedef struct apm_event { - int event_type; - int type; - char * error_filename; - uint error_lineno; - char * msg; - char * trace; + int event_type; + int type; + char *error_filename; + uint error_lineno; + char *msg; + char *trace; } apm_event; typedef struct apm_event_entry { - apm_event event; - struct apm_event_entry *next; + apm_event event; + struct apm_event_entry *next; } apm_event_entry; typedef struct apm_driver { - void (* process_event)(PROCESS_EVENT_ARGS); - void (* process_stats)(TSRMLS_D); - int (* minit)(int TSRMLS_DC); - int (* rinit)(TSRMLS_D); - int (* mshutdown)(SHUTDOWN_FUNC_ARGS); - int (* rshutdown)(TSRMLS_D); - zend_bool (* is_enabled)(TSRMLS_D); - zend_bool (* want_event)(int, int, char * TSRMLS_DC); - zend_bool (* want_stats)(TSRMLS_D); - int (* error_reporting)(TSRMLS_D); - zend_bool is_request_created; + void (*process_event)(PROCESS_EVENT_ARGS); + void (*process_stats)(TSRMLS_D); + int (*minit)(int TSRMLS_DC); + int (*rinit)(TSRMLS_D); + int (*mshutdown)(SHUTDOWN_FUNC_ARGS); + int (*rshutdown)(TSRMLS_D); + zend_bool (*is_enabled)(TSRMLS_D); + zend_bool (*want_event)(int, int, char *TSRMLS_DC); + zend_bool (*want_stats)(TSRMLS_D); + int (*error_reporting)(TSRMLS_D); + zend_bool is_request_created; } apm_driver; typedef struct apm_driver_entry { - apm_driver driver; - struct apm_driver_entry *next; + apm_driver driver; + struct apm_driver_entry *next; } apm_driver_entry; #if PHP_VERSION_ID >= 70000 -# define RD_DEF(var) zval *var; zend_bool var##_found; +#define RD_DEF(var) \ + zval *var; \ + zend_bool var##_found; #else -# define RD_DEF(var) zval **var; zend_bool var##_found; +#define RD_DEF(var) \ + zval **var; \ + zend_bool var##_found; #endif typedef struct apm_request_data { - RD_DEF(uri); - RD_DEF(host); - RD_DEF(ip); - RD_DEF(referer); - RD_DEF(ts); - RD_DEF(script); - RD_DEF(method); - - zend_bool initialized, cookies_found, post_vars_found; - smart_str cookies, post_vars; + RD_DEF(uri); + RD_DEF(host); + RD_DEF(ip); + RD_DEF(referer); + RD_DEF(ts); + RD_DEF(script); + RD_DEF(method); + + zend_bool initialized, cookies_found, post_vars_found; + smart_str cookies, post_vars; } apm_request_data; - #ifdef ZTS -#define APM_GLOBAL(driver, v) TSRMG(apm_globals_id, zend_apm_globals *, driver##_##v) +#define APM_GLOBAL(driver, v) \ + TSRMG(apm_globals_id, zend_apm_globals *, driver##_##v) #else #define APM_GLOBAL(driver, v) (apm_globals.driver##_##v) #endif #if PHP_VERSION_ID >= 70000 -# define apm_error_reporting_new_value (new_value && new_value->val) ? atoi(new_value->val) +#define apm_error_reporting_new_value \ + (new_value && new_value->val) ? atoi(new_value->val) #else -# define apm_error_reporting_new_value new_value ? atoi(new_value) +#define apm_error_reporting_new_value new_value ? atoi(new_value) #endif -#define APM_DRIVER_CREATE(name) \ -void apm_driver_##name##_process_event(PROCESS_EVENT_ARGS); \ -void apm_driver_##name##_process_stats(TSRMLS_D); \ -int apm_driver_##name##_minit(int TSRMLS_DC); \ -int apm_driver_##name##_rinit(TSRMLS_D); \ -int apm_driver_##name##_mshutdown(); \ -int apm_driver_##name##_rshutdown(TSRMLS_D); \ -PHP_INI_MH(OnUpdateAPM##name##ErrorReporting) \ -{ \ - APM_GLOBAL(name, error_reporting) = (apm_error_reporting_new_value : APM_E_##name); \ - return SUCCESS; \ -} \ -zend_bool apm_driver_##name##_is_enabled(TSRMLS_D) \ -{ \ - return APM_GLOBAL(name, enabled); \ -} \ -int apm_driver_##name##_error_reporting(TSRMLS_D) \ -{ \ - return APM_GLOBAL(name, error_reporting); \ -} \ -zend_bool apm_driver_##name##_want_event(int event_type, int error_level, char *msg TSRMLS_DC) \ -{ \ - return \ - APM_GLOBAL(name, enabled) \ - && ( \ - (event_type == APM_EVENT_EXCEPTION && APM_GLOBAL(name, exception_mode) == 2) \ - || \ - (event_type == APM_EVENT_ERROR && ((APM_GLOBAL(name, exception_mode) == 1) || (strncmp(msg, "Uncaught exception", 18) != 0)) && (error_level & APM_GLOBAL(name, error_reporting))) \ - ) \ - && ( \ - !APM_G(currently_silenced) || APM_GLOBAL(name, process_silenced_events) \ - ) \ - ; \ -} \ -zend_bool apm_driver_##name##_want_stats(TSRMLS_D) \ -{ \ - return \ - APM_GLOBAL(name, enabled) \ - && ( \ - APM_GLOBAL(name, stats_enabled)\ - ) \ - ; \ -} \ -apm_driver_entry * apm_driver_##name##_create() \ -{ \ - apm_driver_entry * driver_entry; \ - driver_entry = (apm_driver_entry *) malloc(sizeof(apm_driver_entry)); \ - driver_entry->driver.process_event = apm_driver_##name##_process_event; \ - driver_entry->driver.minit = apm_driver_##name##_minit; \ - driver_entry->driver.rinit = apm_driver_##name##_rinit; \ - driver_entry->driver.mshutdown = apm_driver_##name##_mshutdown; \ - driver_entry->driver.rshutdown = apm_driver_##name##_rshutdown; \ - driver_entry->driver.process_stats = apm_driver_##name##_process_stats; \ - driver_entry->driver.is_enabled = apm_driver_##name##_is_enabled; \ - driver_entry->driver.error_reporting = apm_driver_##name##_error_reporting; \ - driver_entry->driver.want_event = apm_driver_##name##_want_event; \ - driver_entry->driver.want_stats = apm_driver_##name##_want_stats; \ - driver_entry->driver.is_request_created = 0; \ - driver_entry->next = NULL; \ - return driver_entry; \ -} +#define APM_DRIVER_CREATE(name) \ + void apm_driver_##name##_process_event(PROCESS_EVENT_ARGS); \ + void apm_driver_##name##_process_stats(TSRMLS_D); \ + int apm_driver_##name##_minit(int TSRMLS_DC); \ + int apm_driver_##name##_rinit(TSRMLS_D); \ + int apm_driver_##name##_mshutdown(); \ + int apm_driver_##name##_rshutdown(TSRMLS_D); \ + PHP_INI_MH(OnUpdateAPM##name##ErrorReporting) { \ + APM_GLOBAL(name, error_reporting) = \ + (apm_error_reporting_new_value : APM_E_##name); \ + return SUCCESS; \ + } \ + zend_bool apm_driver_##name##_is_enabled(TSRMLS_D) { \ + return APM_GLOBAL(name, enabled); \ + } \ + int apm_driver_##name##_error_reporting(TSRMLS_D) { \ + return APM_GLOBAL(name, error_reporting); \ + } \ + zend_bool apm_driver_##name##_want_event(int event_type, int error_level, \ + char *msg TSRMLS_DC) { \ + return APM_GLOBAL(name, enabled) && \ + ((event_type == APM_EVENT_EXCEPTION && \ + APM_GLOBAL(name, exception_mode) == 2) || \ + (event_type == APM_EVENT_ERROR && \ + ((APM_GLOBAL(name, exception_mode) == 1) || \ + (strncmp(msg, "Uncaught exception", 18) != 0)) && \ + (error_level & APM_GLOBAL(name, error_reporting)))) && \ + (!APM_G(currently_silenced) || \ + APM_GLOBAL(name, process_silenced_events)); \ + } \ + zend_bool apm_driver_##name##_want_stats(TSRMLS_D) { \ + return APM_GLOBAL(name, enabled) && (APM_GLOBAL(name, stats_enabled)); \ + } \ + apm_driver_entry *apm_driver_##name##_create() { \ + apm_driver_entry *driver_entry; \ + driver_entry = (apm_driver_entry *)malloc(sizeof(apm_driver_entry)); \ + driver_entry->driver.process_event = apm_driver_##name##_process_event; \ + driver_entry->driver.minit = apm_driver_##name##_minit; \ + driver_entry->driver.rinit = apm_driver_##name##_rinit; \ + driver_entry->driver.mshutdown = apm_driver_##name##_mshutdown; \ + driver_entry->driver.rshutdown = apm_driver_##name##_rshutdown; \ + driver_entry->driver.process_stats = apm_driver_##name##_process_stats; \ + driver_entry->driver.is_enabled = apm_driver_##name##_is_enabled; \ + driver_entry->driver.error_reporting = \ + apm_driver_##name##_error_reporting; \ + driver_entry->driver.want_event = apm_driver_##name##_want_event; \ + driver_entry->driver.want_stats = apm_driver_##name##_want_stats; \ + driver_entry->driver.is_request_created = 0; \ + driver_entry->next = NULL; \ + return driver_entry; \ + } PHP_MINIT_FUNCTION(apm); PHP_MSHUTDOWN_FUNCTION(apm); @@ -195,8 +200,16 @@ PHP_MINFO_FUNCTION(apm); #ifdef APM_DEBUGFILE #define APM_INIT_DEBUG APM_G(debugfile) = fopen(APM_DEBUGFILE, "a+"); -#define APM_DEBUG(...) if (APM_G(debugfile)) { fprintf(APM_G(debugfile), __VA_ARGS__); fflush(APM_G(debugfile)); } -#define APM_SHUTDOWN_DEBUG if (APM_G(debugfile)) { fclose(APM_G(debugfile)); APM_G(debugfile) = NULL; } +#define APM_DEBUG(...) \ + if (APM_G(debugfile)) { \ + fprintf(APM_G(debugfile), __VA_ARGS__); \ + fflush(APM_G(debugfile)); \ + } +#define APM_SHUTDOWN_DEBUG \ + if (APM_G(debugfile)) { \ + fclose(APM_G(debugfile)); \ + APM_G(debugfile) = NULL; \ + } #else #define APM_INIT_DEBUG #define APM_DEBUG(...) @@ -205,139 +218,171 @@ PHP_MINFO_FUNCTION(apm); /* Extension globals */ ZEND_BEGIN_MODULE_GLOBALS(apm) - /* Boolean controlling whether the extension is globally active or not */ - zend_bool enabled; - /* Application identifier, helps identifying which application is being monitored */ - char *application_id; - /* Boolean controlling whether the event monitoring is active or not */ - zend_bool event_enabled; - /* Boolean controlling whether the stacktrace should be generated or not */ - zend_bool store_stacktrace; - /* Boolean controlling whether the ip should be generated or not */ - zend_bool store_ip; - /* Boolean controlling whether the cookies should be generated or not */ - zend_bool store_cookies; - /* Boolean controlling whether the POST variables should be generated or not */ - zend_bool store_post; - /* Time (in ms) before a request is considered for stats */ - long stats_duration_threshold; - /* User CPU time usage (in ms) before a request is considered for stats */ - long stats_user_cpu_threshold; - /* System CPU time usage (in ms) before a request is considered for stats */ - long stats_sys_cpu_threshold; - /* Maximum recursion depth used when dumping a variable */ - long dump_max_depth; - /* Determines whether we're currently silenced */ - zend_bool currently_silenced; - - apm_driver_entry *drivers; - smart_str *buffer; - - /* Structure used to store request data */ - apm_request_data request_data; - - float duration; - - long mem_peak_usage; +/* Boolean controlling whether the extension is globally active or not */ +zend_bool enabled; +/* Application identifier, helps identifying which application is being + * monitored */ +char *application_id; +/* Boolean controlling whether the event monitoring is active or not */ +zend_bool event_enabled; +/* Boolean controlling whether the stacktrace should be generated or not */ +zend_bool store_stacktrace; +/* Boolean controlling whether the ip should be generated or not */ +zend_bool store_ip; +/* Boolean controlling whether the cookies should be generated or not */ +zend_bool store_cookies; +/* Boolean controlling whether the POST variables should be generated or not */ +zend_bool store_post; +/* Time (in ms) before a request is considered for stats */ +long stats_duration_threshold; +/* User CPU time usage (in ms) before a request is considered for stats */ +long stats_user_cpu_threshold; +/* System CPU time usage (in ms) before a request is considered for stats */ +long stats_sys_cpu_threshold; +/* Maximum recursion depth used when dumping a variable */ +long dump_max_depth; +/* Determines whether we're currently silenced */ +zend_bool currently_silenced; + +apm_driver_entry *drivers; +smart_str *buffer; + +/* Structure used to store request data */ +apm_request_data request_data; + +float duration; + +long mem_peak_usage; #ifdef HAVE_GETRUSAGE - float user_cpu; +float user_cpu; - float sys_cpu; +float sys_cpu; #endif #ifdef APM_DEBUGFILE - FILE * debugfile; +FILE *debugfile; #endif #ifdef APM_DRIVER_SQLITE3 - /* Boolean controlling whether the driver is active or not */ - zend_bool sqlite3_enabled; - /* Boolean controlling the collection of stats */ - zend_bool sqlite3_stats_enabled; - /* Control which exceptions to collect (0: none exceptions collected, 1: collect uncaught exceptions (default), 2: collect ALL exceptions) */ - long sqlite3_exception_mode; - /* driver error reporting */ - int sqlite3_error_reporting; - /* Path to the SQLite database file */ - char *sqlite3_db_path; - /* The actual db file */ - char sqlite3_db_file[MAXPATHLEN]; - /* DB handle */ - sqlite3 *sqlite3_event_db; - /* Max timeout to wait for storing the event in the DB */ - long sqlite3_timeout; - /* Request ID */ - sqlite3_int64 sqlite3_request_id; - /* Boolean to ensure request content is only inserted once */ - zend_bool sqlite3_is_request_created; - /* Option to process silenced events */ - zend_bool sqlite3_process_silenced_events; +/* Boolean controlling whether the driver is active or not */ +zend_bool sqlite3_enabled; +/* Boolean controlling the collection of stats */ +zend_bool sqlite3_stats_enabled; +/* Control which exceptions to collect (0: none exceptions collected, 1: collect + * uncaught exceptions (default), 2: collect ALL exceptions) */ +long sqlite3_exception_mode; +/* driver error reporting */ +int sqlite3_error_reporting; +/* Path to the SQLite database file */ +char *sqlite3_db_path; +/* The actual db file */ +char sqlite3_db_file[MAXPATHLEN]; +/* DB handle */ +sqlite3 *sqlite3_event_db; +/* Max timeout to wait for storing the event in the DB */ +long sqlite3_timeout; +/* Request ID */ +sqlite3_int64 sqlite3_request_id; +/* Boolean to ensure request content is only inserted once */ +zend_bool sqlite3_is_request_created; +/* Option to process silenced events */ +zend_bool sqlite3_process_silenced_events; #endif #ifdef APM_DRIVER_MYSQL - /* Boolean controlling whether the driver is active or not */ - zend_bool mysql_enabled; - /* Boolean controlling the collection of stats */ - zend_bool mysql_stats_enabled; - /* Control which exceptions to collect (0: none exceptions collected, 1: collect uncaught exceptions (default), 2: collect ALL exceptions) */ - long mysql_exception_mode; - /* driver error reporting */ - int mysql_error_reporting; - /* MySQL host */ - char *mysql_db_host; - /* MySQL port */ - unsigned int mysql_db_port; - /* MySQL user */ - char *mysql_db_user; - /* MySQL password */ - char *mysql_db_pass; - /* MySQL database */ - char *mysql_db_name; - /* DB handle */ - MYSQL *mysql_event_db; - /* Option to process silenced events */ - zend_bool mysql_process_silenced_events; - - /* Boolean to ensure request content is only inserted once */ - zend_bool mysql_is_request_created; +/* Boolean controlling whether the driver is active or not */ +zend_bool mysql_enabled; +/* Boolean controlling the collection of stats */ +zend_bool mysql_stats_enabled; +/* Control which exceptions to collect (0: none exceptions collected, 1: collect + * uncaught exceptions (default), 2: collect ALL exceptions) */ +long mysql_exception_mode; +/* driver error reporting */ +int mysql_error_reporting; +/* MySQL host */ +char *mysql_db_host; +/* MySQL port */ +unsigned int mysql_db_port; +/* MySQL user */ +char *mysql_db_user; +/* MySQL password */ +char *mysql_db_pass; +/* MySQL database */ +char *mysql_db_name; +/* DB handle */ +MYSQL *mysql_event_db; +/* Option to process silenced events */ +zend_bool mysql_process_silenced_events; + +/* Boolean to ensure request content is only inserted once */ +zend_bool mysql_is_request_created; #endif #ifdef APM_DRIVER_STATSD - /* Boolean controlling whether the driver is active or not */ - zend_bool statsd_enabled; - /* Boolean controlling the collection of stats */ - zend_bool statsd_stats_enabled; - /* (unused for StatsD) */ - long statsd_exception_mode; - /* (unused for StatsD) */ - int statsd_error_reporting; - /* StatsD host */ - char *statsd_host; - /* StatsD port */ - unsigned int statsd_port; - /* StatsD key prefix */ - char *statsd_prefix; - /* addinfo for StatsD server */ - struct addrinfo *statsd_servinfo; - /* Option to process silenced events */ - zend_bool statsd_process_silenced_events; +/* Boolean controlling whether the driver is active or not */ +zend_bool statsd_enabled; +/* Boolean controlling the collection of stats */ +zend_bool statsd_stats_enabled; +/* (unused for StatsD) */ +long statsd_exception_mode; +/* (unused for StatsD) */ +int statsd_error_reporting; +/* StatsD host */ +char *statsd_host; +/* StatsD port */ +unsigned int statsd_port; +/* StatsD key prefix */ +char *statsd_prefix; +/* addinfo for StatsD server */ +struct addrinfo *statsd_servinfo; +/* Option to process silenced events */ +zend_bool statsd_process_silenced_events; #endif #ifdef APM_DRIVER_SOCKET - /* Boolean controlling whether the driver is active or not */ - zend_bool socket_enabled; - /* Boolean controlling the collection of stats */ - zend_bool socket_stats_enabled; - /* (unused for socket driver) */ - long socket_exception_mode; - /* (unused for socket driver) */ - int socket_error_reporting; - /* Option to process silenced events */ - zend_bool socket_process_silenced_events; - /* socket path */ - char *socket_path; - apm_event_entry *socket_events; - apm_event_entry **socket_last_event; +/* Boolean controlling whether the driver is active or not */ +zend_bool socket_enabled; +/* Boolean controlling the collection of stats */ +zend_bool socket_stats_enabled; +/* (unused for socket driver) */ +long socket_exception_mode; +/* (unused for socket driver) */ +int socket_error_reporting; +/* Option to process silenced events */ +zend_bool socket_process_silenced_events; +/* socket path */ +char *socket_path; +apm_event_entry *socket_events; +apm_event_entry **socket_last_event; +#endif +#ifdef APM_DRIVER_ELASTICSEARCH +/* Boolean controlling whether the driver is active or not */ +zend_bool elasticsearch_enabled; +/* Boolean controlling the collection of stats */ +zend_bool elasticsearch_stats_enabled; +/* Control which exceptions to collect */ +long elasticsearch_exception_mode; +/* driver error reporting */ +int elasticsearch_error_reporting; +/* Option to process silenced events */ +zend_bool elasticsearch_process_silenced_events; +/* Elasticsearch host */ +char *elasticsearch_host; +/* Elasticsearch port */ +unsigned int elasticsearch_port; +/* Elasticsearch index */ +char *elasticsearch_index; +/* Elasticsearch username (optional) */ +char *elasticsearch_username; +/* Elasticsearch password (optional) */ +char *elasticsearch_password; +/* Batch sending configuration */ +long elasticsearch_batch_size; +long elasticsearch_batch_timeout; +/* Batch buffer */ +smart_str elasticsearch_buffer; +int elasticsearch_buffer_count; +time_t elasticsearch_buffer_start_time; #endif ZEND_END_MODULE_GLOBALS(apm) @@ -352,24 +397,27 @@ ZEND_EXTERN_MODULE_GLOBALS(apm) #define APM_RD(data) APM_G(request_data).data #if PHP_VERSION_ID >= 70000 -# define APM_RD_STRVAL(var) Z_STRVAL_P(APM_RD(var)) -# define APM_RD_SMART_STRVAL(var) APM_RD(var).s->val +#define APM_RD_STRVAL(var) Z_STRVAL_P(APM_RD(var)) +#define APM_RD_SMART_STRVAL(var) APM_RD(var).s->val #else -# define APM_RD_STRVAL(var) Z_STRVAL_PP(APM_RD(var)) -# define APM_RD_SMART_STRVAL(var) APM_RD(var).c +#define APM_RD_STRVAL(var) Z_STRVAL_PP(APM_RD(var)) +#define APM_RD_SMART_STRVAL(var) APM_RD(var).c #endif #define SEC_TO_USEC(sec) ((sec) * 1000000.00) #define USEC_TO_SEC(usec) ((usec) / 1000000.00) #if PHP_VERSION_ID >= 70000 -# define zend_is_auto_global_compat(name) (zend_is_auto_global_str(ZEND_STRL((name)))) -# define add_assoc_long_compat(array, key, value) add_assoc_long_ex((array), (key), (sizeof(key) - 1), (value)); +#define zend_is_auto_global_compat(name) \ + (zend_is_auto_global_str(ZEND_STRL((name)))) +#define add_assoc_long_compat(array, key, value) \ + add_assoc_long_ex((array), (key), (sizeof(key) - 1), (value)); #else -# define zend_is_auto_global_compat(name) (zend_is_auto_global(ZEND_STRL((name)) TSRMLS_CC)) -# define add_assoc_long_compat(array, key, value) add_assoc_long_ex((array), (key), (sizeof(key)), (value)); +#define zend_is_auto_global_compat(name) \ + (zend_is_auto_global(ZEND_STRL((name)) TSRMLS_CC)) +#define add_assoc_long_compat(array, key, value) \ + add_assoc_long_ex((array), (key), (sizeof(key)), (value)); #endif void extract_data(TSRMLS_D); #endif - diff --git a/start-docker.sh b/start-docker.sh new file mode 100755 index 0000000..cbb40f3 --- /dev/null +++ b/start-docker.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +echo "🚀 Starting PHP APM with Elasticsearch..." +echo "" + +# Check if Docker is running +if ! docker info > /dev/null 2>&1; then + echo "❌ Error: Docker is not running. Please start Docker first." + exit 1 +fi + +# Build and start containers +echo "đŸ“Ļ Building and starting containers..." +docker-compose up -d --build + +if [ $? -ne 0 ]; then + echo "❌ Failed to start containers" + exit 1 +fi + +echo "" +echo "âŗ Waiting for services to start..." +echo "" + +# Wait for Elasticsearch +echo "Waiting for Elasticsearch..." +for i in {1..30}; do + if curl -s http://localhost:9200/_cluster/health > /dev/null 2>&1; then + echo "✅ Elasticsearch is ready!" + break + fi + echo -n "." + sleep 2 +done + +echo "" +echo "Waiting for PHP application..." +for i in {1..10}; do + if curl -s http://localhost:8080 > /dev/null 2>&1; then + echo "✅ PHP application is ready!" + break + fi + echo -n "." + sleep 1 +done + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "✅ All services started successfully!" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo "📍 Services:" +echo " 🌐 PHP Application: http://localhost:8080" +echo " 📊 Elasticsearch: http://localhost:9200" +echo " 📈 Kibana: http://localhost:5601" +echo "" +echo "🔍 Quick checks:" +echo " View APM config: http://localhost:8080" +echo " Run test: http://localhost:8080?test=1" +echo " View logs: curl http://localhost:9200/apm-logs/_search?pretty" +echo "" +echo "📚 Documentation: cat DOCKER_README.md" +echo "" +echo "🛑 To stop: docker-compose down" +echo "🔄 To restart: docker-compose restart" +echo "📋 View logs: docker-compose logs -f" +echo "" diff --git a/test/index.php b/test/index.php new file mode 100644 index 0000000..814e271 --- /dev/null +++ b/test/index.php @@ -0,0 +1,210 @@ + + + + + + PHP APM Elasticsearch Test + + + +
+

🚀 PHP APM with Elasticsearch - Test Page

+ +
+ PHP Version:
+ APM Extension: +
+ + '; + echo '

APM Configuration:

'; + echo '
';
+            $apm_config = array(
+                'apm.enabled' => ini_get('apm.enabled'),
+                'apm.elasticsearch_enabled' => ini_get('apm.elasticsearch_enabled'),
+                'apm.elasticsearch_host' => ini_get('apm.elasticsearch_host'),
+                'apm.elasticsearch_port' => ini_get('apm.elasticsearch_port'),
+                'apm.elasticsearch_index' => ini_get('apm.elasticsearch_index'),
+                'apm.elasticsearch_batch_size' => ini_get('apm.elasticsearch_batch_size'),
+                'apm.elasticsearch_batch_timeout' => ini_get('apm.elasticsearch_batch_timeout'),
+            );
+            foreach ($apm_config as $key => $value) {
+                printf("%-35s = %s\n", $key, $value ?: '(not set)');
+            }
+            echo '
'; + echo '
'; + } + + // Test different error levels + if (isset($_GET['test'])) { + echo '

📊 Generating Test Events...

'; + + // Generate some load for stats + echo '
Simulating workload...
'; + $data = array(); + for ($i = 0; $i < 10000; $i++) { + $data[] = str_repeat('x', 100); + } + usleep(100000); // 100ms delay + + // Trigger different types of errors + echo '
'; + echo '

Triggering Test Errors:

'; + + // Notice + echo '

1. Triggering E_USER_NOTICE...

'; + trigger_error("This is a test NOTICE", E_USER_NOTICE); + + // Warning + echo '

2. Triggering E_USER_WARNING...

'; + trigger_error("This is a test WARNING", E_USER_WARNING); + + // Deprecated + echo '

3. Triggering E_USER_DEPRECATED...

'; + trigger_error("This is a test DEPRECATED warning", E_USER_DEPRECATED); + + echo '
'; + + echo '
'; + echo '

✅ Test events generated! Check Elasticsearch for data.

'; + echo '

Documents should appear in the apm-logs index.

'; + echo '
'; + } + ?> + +

đŸŽ¯ Actions

+ + +

🔗 Useful Links

+ + +

📚 Example Queries

+
+

View all APM documents:

+
curl http://localhost:9200/apm-logs/_search?pretty
+ +

View only error events:

+
curl -X POST http://localhost:9200/apm-logs/_search?pretty -H 'Content-Type: application/json' -d '
+{
+  "query": {
+    "term": { "type.keyword": "event" }
+  }
+}'
+ +

View statistics:

+
curl -X POST http://localhost:9200/apm-logs/_search?pretty -H 'Content-Type: application/json' -d '
+{
+  "query": {
+    "term": { "type.keyword": "stats" }
+  }
+}'
+ +

Count documents by type:

+
curl -X POST http://localhost:9200/apm-logs/_search?pretty -H 'Content-Type: application/json' -d '
+{
+  "size": 0,
+  "aggs": {
+    "by_type": {
+      "terms": { "field": "type.keyword" }
+    }
+  }
+}'
+
+ +

🐛 Debugging

+
+

If events are not appearing in Elasticsearch:

+
    +
  • Check that Elasticsearch is running: curl http://localhost:9200
  • +
  • Verify APM extension is loaded (check above)
  • +
  • Check Docker logs: docker-compose logs php-app
  • +
  • View network connectivity: docker-compose exec php-app ping elasticsearch
  • +
+
+ + + diff --git a/test/info.php b/test/info.php new file mode 100644 index 0000000..968c8df --- /dev/null +++ b/test/info.php @@ -0,0 +1,3 @@ + \ No newline at end of file