Repository navigation
optimizations, hooks #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
53f8d62
b8996c1
ae8c889
d951282
94a3665
1324db0
5acbd4d
b35199b
678eb1b
4037b4e
3e097ce
fa9d4d3
8b5c752
baa8f30
6ca0f9e
328ab3c
76b9349
bedd581
149eb69
09c98b1
d5b14bd
93d35f2
21da586
459eeb9
e0aab17
101c871
42ef668
022e333
af4526e
03118af
dbd92b2
e54de32
9ee5338
bfe87fa
5e998b6
72d0b33
3c9309b
9935a9b
68a562a
96f38a9
8cb92c0
00f06b0
f501065
8c33bda
611267c
6e1d79d
1ce78dd
9de6f08
39223bf
8e06f14
3035f10
0c7952a
5243cd1
cf0d328
17c1d50
6887dd2
5fcd8a2
4e04945
ec97ffd
d112142
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| name: opencode | ||
|
|
||
| on: | ||
| issue_comment: | ||
| types: [created] | ||
| pull_request_review_comment: | ||
| types: [created] | ||
|
|
||
| jobs: | ||
| opencode: | ||
| if: | | ||
| contains(github.event.comment.body, ' /oc') || | ||
| startsWith(github.event.comment.body, '/oc') || | ||
| contains(github.event.comment.body, ' /opencode') || | ||
| startsWith(github.event.comment.body, '/opencode') | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| id-token: write | ||
| contents: read | ||
| pull-requests: read | ||
| issues: read | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Run opencode | ||
| uses: anomalyco/opencode/github@latest | ||
| env: | ||
| OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} | ||
| with: | ||
| model: opencode/deepseek-v4-flash-free | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| # Project Guidelines: php_trochilidae | ||
|
|
||
| This project is a C-based PHP extension for metrics collection. | ||
|
|
||
| ## Project Structure | ||
| - `trochilidae/`: Contains core functional sub-components. | ||
| - `tr_array.c/h`: Buffer and array manipulation (binary serialization). | ||
| - `tr_network.c/h`: TCP/UDP socket communication. | ||
| - `tr_timer.c/h`: High-resolution timers and Zend resource management. | ||
| - `tr_hooks.c`: Hooks for SAPI integration (intercepting output/execution). | ||
| - `utils.c/h`: Helper functions and utility macros. | ||
| - `docs/protocol.md`: Wire format specification for the UDP protocol. | ||
| - `tests/`: Contains `.phpt` files for integration testing. | ||
| - `.github/workflows/build.yml` build and run tests CI | ||
|
|
||
| ## Core Conventions | ||
| - **Naming:** | ||
| - Internal functions: `tr_` prefix. | ||
| - PHP-exposed functions: `trochilidae_` prefix. | ||
| - **State Management:** Use `ZEND_DECLARE_MODULE_GLOBALS` and access globals via the `TR_G()` macro. | ||
| - **Resources:** Use Zend resource registration (`zend_register_list_destructors_ex`) for automated lifecycle management. | ||
| - **Performance:** Maintain low overhead; use custom binary serialization over JSON where possible. | ||
| - **Error Handling:** Use `php_error_docref`. | ||
|
|
||
| ## Memory Allocator Convention | ||
| - **`trochilidae/tr_network.c`** uses the **system allocator** (`malloc`/`free`/`strdup`), NOT Zend allocator. This is a standalone networking utility that must also compile with stubs. | ||
| - **`trochilidae/tr_array.c`**, **`trochilidae/tr_timer.c`**, and **`trochilidae/tr_hooks.c`** use the **Zend allocator** (`emalloc`/`efree`/`estrdup`). | ||
| - **`trochilidae.c`** uses **both**: data crossing the boundary with `tr_network.c` (`client->host`, `pairs`) uses system allocator to match; PHP-internal data (`request_id`) uses Zend allocator. | ||
| - **Callers must match the callee's allocator**: if `parse_domain_port_pairs` allocates with `malloc`, the caller must free with `free` (not `efree`). Never mix allocators for the same pointer. | ||
| - **Test stubs** (`trochilidae/compat.h`): compile with `-DTROCHILIDAE_STANDALONE` to map `emalloc → malloc`, `efree → free`, `estrdup → strdup` for unit tests outside PHP. | ||
|
|
||
| ## Build & Test Workflow | ||
| 1. **Configuration:** `phpize && ./configure --enable-trochilidae` | ||
| 2. **Compilation:** `make` | ||
| 3. **PHP .phpt tests:** | ||
| `php -d extension=modules/trochilidae.so run-tests.php -d extension=modules/trochilidae.so tests/` | ||
| *Note:* The `-d extension=...` flag must be passed **both** to `run-tests.php` (so it propagates to child processes) and to the parent process for the skip checks. | ||
| 4. **C unit tests (network):** | ||
| `gcc -I. -DTROCHILIDAE_STANDALONE -DHAVE_CONFIG_H -D_GNU_SOURCE -g -O0 tests/unit_test.c trochilidae/tr_network.c trochilidae/utils.c -o tests/unit_test -lm && ./tests/unit_test` | ||
| 5. **C unit tests (internal — tr_array, tr_timer):** | ||
| `gcc -I. -DTROCHILIDAE_STANDALONE -DHAVE_CONFIG_H -g -O0 -Wall tests/tr_internal_test.c -o tests/tr_internal_test -lm && ./tests/tr_internal_test` | ||
| *Note:* Requires `config.h` from `./configure`. Uses `trochilidae/compat.h` with `-DTROCHILIDAE_STANDALONE` instead of real PHP headers. | ||
|
|
||
| 6. **Docker FPM repro test** (recommended for FPM crash validation): | ||
| `tests/run_fpm_repro_in_docker.sh` | ||
| - Builds extension inside `php:8.5-fpm-alpine`. | ||
| - Runs `.phpt` tests and then `tests/fpm_repro_test.sh` in FPM mode. | ||
| - Expected success output includes: `FPM repro test passed`. | ||
|
|
||
| ### E2E Tests (UDP protocol validation) | ||
|
|
||
| E2E tests verify that the extension correctly sends binary UDP packets (chunked protocol) by directing traffic to a local UDP test server that parses and validates the wire format. | ||
|
|
||
| 1. **Start the UDP test server:** | ||
| ```bash | ||
| php -d extension=modules/trochilidae.so tests/udp_test_server.php --port=30002 --timeout=3 --verbose | ||
| ``` | ||
| - Listens on `udp://0.0.0.0:30002` for chunked trochilidae packets. | ||
| - Reassembles chunks, parses the binary protocol, and prints the decoded fields. | ||
| - Exit code `0` — packet received and valid; `1` — timeout/error. | ||
| - Supports `--expect=FILE.json` for deterministic field validation. | ||
|
|
||
| 2. **Run PHP scripts that send metrics via the extension:** | ||
| ```bash | ||
| php -d extension=modules/trochilidae.so -d "trochilidae.server_list=127.0.0.1:30002" \ | ||
| -r 'trochilidae_set_tag("e2e", "test"); trochilidae_flush();' | ||
| ``` | ||
|
|
||
| 3. **Full automated E2E cycle** (as used in `tests/install_test.sh`): | ||
| ```bash | ||
| UDP_PORT=30008 | ||
| UDP_LOG=$(mktemp) | ||
| php -d extension=modules/trochilidae.so tests/udp_test_server.php \ | ||
| --port=$UDP_PORT --timeout=3 --verbose > "$UDP_LOG" 2>&1 & | ||
| UDP_PID=$! | ||
| # wait for "ready" in log, then run test PHP scripts | ||
| php -d extension=modules/trochilidae.so -d "trochilidae.server_list=127.0.0.1:$UDP_PORT" \ | ||
| -r 'trochilidae_set_tag("t", "1"); trochilidae_flush();' | ||
| sleep 0.5 | ||
| kill $UDP_PID 2>/dev/null; wait $UDP_PID 2>/dev/null || true | ||
| grep -q "=== Parsed packet ===" "$UDP_LOG" && echo "E2E PASS" || echo "E2E FAIL" | ||
| rm -f "$UDP_LOG" | ||
| ``` | ||
|
|
||
| 4. **Multi-chunk E2E test** (verifies chunking with small `chunk_size`): | ||
|
|
||
| ```bash | ||
| UDP_PORT=30009 | ||
| UDP_LOG=$(mktemp) | ||
| php -d extension=modules/trochilidae.so tests/udp_test_server.php \ | ||
| --port=$UDP_PORT --timeout=3 --verbose > "$UDP_LOG" 2>&1 & | ||
| UDP_PID=$! | ||
| sleep 0.5 | ||
| php -d extension=modules/trochilidae.so \ | ||
| -d trochilidae.chunk_size=120 \ | ||
| -d "trochilidae.server_list=127.0.0.1:$UDP_PORT" \ | ||
| -r ' | ||
| for ($i = 0; $i < 50; $i++) { | ||
| trochilidae_set_tag("k$i", str_repeat("x", 25)); | ||
| } | ||
| trochilidae_flush(); | ||
| ' | ||
| sleep 0.8 | ||
| kill $UDP_PID 2>/dev/null; wait $UDP_PID 2>/dev/null || true | ||
| chunks=$(grep -c "Received chunk" "$UDP_LOG") | ||
| echo "Chunks: $chunks" | ||
| grep -q "All chunks received" "$UDP_LOG" && echo "MULTI-CHUNK PASS" || echo "FAIL" | ||
| rm -f "$UDP_LOG" | ||
| ``` | ||
| Expected: ~21 chunks (99 bytes payload each with `chunk_size=120`, since `CHUNK_HEADER_SIZE=21`). | ||
|
|
||
| **Key points:** | ||
| - Tests that run with a real `trochilidae.server_list` setting emit UDP packets — those packets go to the configured server. | ||
| - For isolated .phpt tests that don't need a server, set `trochilidae.server_list=localhost` (the packets will be sent but never arrive, which is harmless). | ||
| - For full protocol validation, use the UDP test server as shown above. | ||
|
|
||
| ### FPM Repro Scripts (brief) | ||
|
|
||
| - `tests/fpm_repro_test.sh` — launches local php-fpm + UDP server, sends multiple FastCGI requests, fails on crash signatures (`zend_mm_heap corrupted`, `SIGABRT`, `SIGSEGV`) or UDP absence. | ||
| - `tests/fpm_repro_request.php` — request script with tag/timer activity and `$_SERVER` mutations (main FPM stress scenario). | ||
| - `tests/fpm_min_request.php` — minimal control request script (`echo` only). | ||
|
|
||
| Quick usage: | ||
|
|
||
| ```bash | ||
| # full run (build + phpt + fpm repro) in Docker | ||
| tests/run_fpm_repro_in_docker.sh | ||
|
|
||
| # run only FPM repro in container with minimal request script | ||
| docker run --rm -e REQUEST_SCRIPT=/work/tests/fpm_min_request.php \ | ||
| trochilidae-fpm-repro:local bash -lc '/work/tests/fpm_repro_test.sh' | ||
| ``` | ||
|
|
||
| ## Stubs | ||
| - `trochilidae/compat.h`: Standalone stubs for `emalloc`/`erealloc`/`efree`/`estrdup` and minimal Zend types — used by `tr_internal_test.c` and `unit_test.c` when compiled with `-DTROCHILIDAE_STANDALONE`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,11 +122,11 @@ RUN apk add --no-cache unzip git && \ | |
| # trochilidae | ||
| RUN apk add --no-cache build-base autoconf unzip git curl | ||
| ADD . /app | ||
| # php /app/test.php && \ | ||
| RUN phpize && ./configure --enable-trochilidae && make clean && make install && docker-php-ext-enable trochilidae && \ | ||
| php -m && \ | ||
| php -r "echo 'Test TR at ',date('c'), PHP_EOL;" && \ | ||
| echo "OK - exit" && \ | ||
| composer install --prefer-dist && \ | ||
| exit 1 | ||
| RUN phpize && \ | ||
| ./configure --enable-trochilidae && \ | ||
| make -j$(nproc) && \ | ||
| make install && \ | ||
| docker-php-ext-enable trochilidae && \ | ||
| php -m | grep trochilidae && \ | ||
| tests/install_test.sh | ||
|
Comment on lines
+125
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Добавьте запуск полного набора тестов в процесс валидации Docker. В данный момент проверка в Docker ограничена запуском smoke-теста 🔧 Предлагаемое исправление RUN phpize && \
./configure --enable-trochilidae && \
make -j$(nproc) && \
+ php -d extension=modules/trochilidae.so run-tests.php -d extension=modules/trochilidae.so tests/ && \
+ gcc -I. -Itests/stubs -DHAVE_CONFIG_H -g -O0 tests/unit_test.c trochilidae/tr_network.c trochilidae/utils.c -o tests/unit_test -lm && ./tests/unit_test && \
+ gcc -I. -Itests/stubs -DHAVE_CONFIG_H -g -O0 tests/tr_internal_test.c -o tests/tr_internal_test -lm && ./tests/tr_internal_test && \
make install && \
docker-php-ext-enable trochilidae && \
php -m | grep trochilidae && \
tests/install_test.sh🤖 Prompt for AI AgentsSource: Learnings |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,96 @@ | ||
| # PHP Trochilidae extension | ||
| A PHP extension for metrics collect | ||
| # PHP Trochilidae | ||
|
|
||
| [](https://www.codefactor.io/repository/github/xmlex/php_trochilidae/overview/main) | ||
| [](https://app.travis-ci.com/xMlex/php_trochilidae) | ||
| A PHP extension for collecting and sending application metrics (timers, tags, request data) to a remote collector via UDP. | ||
|
|
||
| ## Requirements | ||
|
|
||
| - PHP 7.4+ | ||
| - php-dev (for compilation) | ||
|
|
||
| ## Install | ||
|
|
||
| ```bash | ||
| phpize | ||
| ./configure --enable-trochilidae | ||
| make | ||
| make test | ||
| make install | ||
| ``` | ||
|
|
||
| Enable the extension in `php.ini`: | ||
|
|
||
| ```ini | ||
| extension=trochilidae.so | ||
| trochilidae.enabled=1 | ||
| trochilidae.server_list=collector1.local:30002,collector2.local:30002 | ||
| ``` | ||
| phpize && ./configure --enable-trochilidae && make && make test | ||
| php -d extension=`pwd`/modules/trochilidae.so -f test.php | ||
| run /home/mlex/CLionProjects/php_trochilidae/test.php | ||
|
|
||
| ## Configuration | ||
|
|
||
| | INI directive | Type | Default | Description | | ||
| |---|---|---|---| | ||
| | `trochilidae.enabled` | bool | `1` | Enable or disable the extension | | ||
| | `trochilidae.server_list` | string | empty | Comma-separated `host:port` pairs of collector servers | | ||
|
|
||
| ## How it works | ||
|
|
||
| Trochilidae hooks into the PHP request lifecycle: | ||
|
|
||
| 1. **RINIT** — resets metrics, collects pre-request data (tags, server vars) | ||
| 2. During the request, you can add tags and use timers via PHP functions | ||
| 3. **RSHUTDOWN** — automatically serialises and sends all collected metrics to the configured collector(s) via UDP | ||
|
|
||
| For CLI scripts, metrics are also sent on shutdown; call `trochilidae_flush()` explicitly if you need to send data before the end of the script. | ||
|
|
||
| ## Quick start | ||
|
|
||
| ```php | ||
| <?php | ||
|
|
||
| // Set tags and hostname | ||
| trochilidae_set_tag('env', 'production'); | ||
| trochilidae_set_hostname('app01'); | ||
|
|
||
| // Profile a section of code | ||
| trochilidae_timer_start('db_query'); | ||
| // ... do work ... | ||
| trochilidae_timer_stop('db_query'); | ||
|
|
||
| // Get stats for all timers | ||
| $info = trochilidae_timer_get_info(); | ||
| // $info['timers'][0]['startCount'] – start count | ||
| // $info['timers'][0]['stopCount'] – stop count | ||
| // $info['timers'][0]['totalExecutionTime'] – total time in seconds | ||
| // $info['timers'][0]['lastExecutionTime'] – last lap in seconds | ||
|
|
||
| //[optional] Send collected metrics (send by default, after execute script/request) | ||
| trochilidae_flush(); | ||
| ``` | ||
|
|
||
| TODO: | ||
| - ~~REQUEST_TIME from php~~ | ||
| - request id | ||
| - ~~timers~~ | ||
| ## PHP Functions | ||
|
|
||
| | Function | Description | | ||
| |---|---| | ||
| | `trochilidae_set_tag(string $key, string $value)` | Set a custom tag | | ||
| | `trochilidae_set_hostname(string $hostname)` | Override the default hostname | | ||
| | `trochilidae_timer_start(string $name)` | Start or resume a named timer | | ||
| | `trochilidae_timer_stop(string $name)` | Stop a named timer (no-op if not running) | | ||
| | `trochilidae_timer_get_info()` | Get all timer statistics as nested array | | ||
| | `trochilidae_flush()` | Send collected metrics to the collector | | ||
| | `trochilidae_reset()` | Reset all collected metrics | | ||
|
|
||
| ## TODO | ||
|
|
||
| ### 🟢 Protocol (`docs/protocol.md`) | ||
|
|
||
| | # | Change | Why | | ||
| |---|---|---|---| | ||
| | P2 | **tv_sec → 64-bit `long`** | Avoid Y2038 overflow (time_t is 64-bit on modern systems) | | ||
|
|
||
| ### 🔴 Stability & Scalability (code) | ||
|
|
||
| | # | Change | Where | Why | | ||
| |---|---|---|---| | ||
| | S10 | **IPv6 support (`sockaddr_storage` + `getaddrinfo`)** | `tr_network.h:62` | Currently `sockaddr_in` = IPv4 only | | ||
| | S12 | **Fix `tv_usec` calc: `1e6 * 1000` → `1e6`** | `trochilidae.c:502,508` | Double multiplication produces wrong microseconds | | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: xMlex/php_trochilidae
Length of output: 190
🏁 Script executed:
Repository: xMlex/php_trochilidae
Length of output: 1224
🏁 Script executed:
Repository: xMlex/php_trochilidae
Length of output: 483
🏁 Script executed:
Repository: xMlex/php_trochilidae
Length of output: 1224
🌐 Web query:
GitHub Actions issue_comment pull_request_review_comment GITHUB_REF default ref checkout behavior docs💡 Result:
When using
actions/checkoutin GitHub Actions, the default behavior for therefinput is to check out the reference or SHA that triggered the workflow [1][2]. If no specific reference is provided by the triggering event, it falls back to the repository's default branch [1][2]. For events likeissue_commentandpull_request_review_comment,GITHUB_REFdoes not point to the pull request branch by default [3][4]. Instead, these events typically result inGITHUB_REFpointing to the repository's default branch (or, in some configurations, a merge ref) [3][4]. Becauseactions/checkoutusesGITHUB_REFby default, simply using the action without specifying arefwill generally check out the default branch rather than the pull request's code [5][4]. To check out the pull request code when triggered byissue_commentorpull_request_review_comment, you must manually resolve and pass the pull request's head branch or commit SHA to therefinput ofactions/checkout[5][4]. A common pattern involves usingactions/github-scriptto fetch the pull request details from the event payload and setting an output variable, which is then used as therefin the checkout step [5]: - name: Get PR branch id: pr uses: actions/github-script@v7 with: script: | const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.payload.issue.number }); core.setOutput('head_ref', pr.head.ref); - uses: actions/checkout@v4 with: ref: ${{ steps.pr.outputs.head_ref }} Always ensure that your workflow has the necessary permissions (e.g.,contents: readandpull-requests: read) to access repository data and interact with the Pull Request API [5].Citations:
issue_commentevent? actions/checkout#331Укажите явный
refдля checkoutactions/checkout@v6здесь корректен, но безrefнаissue_comment/pull_request_review_commentworkflow может проверить не код PR. Передайте head PR явно.🤖 Prompt for AI Agents