Repository navigation
feat: add paired Ruby SDK performance benchmarks - #323
dustinbyrne wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Not approved yet — waiting on the conditions below.
Re-add the stamphog label to request another review once you have addressed this.
stamphog can't auto-review this pull request because three gates refused it. The deny-list gate matched infra_cicd, since it adds .github/workflows/benchmarks.yml and .github/workflows/benchmark-report.yml. The size gate refused it because it has 1523 substantive lines against an 800-line ceiling. The tier gate classified it as T2-never (1897 lines across 13 files, two areas, a feature).
The refusal is final, so please ask a human reviewer to look at it. To make review easier, you could split the CI workflow changes from the benchmark scripts and fixtures so each part is smaller and more focused.
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✗ | matches: infra_cicd |
| size | ✗ | too large for auto-review (1523L substantive in global — ceiling is 800L; 1523L, 11F total, 1897L/13F incl. docs/generated/snapshots) |
| tier | ✗ | classified as T2-never: T2-never (1897L, 13F, two-areas, feat) |
| stamphog 2.6.1 | .stamphog/policy.yml @ unknown · reviewed head b28bf78 |
posthog-ruby-sync Compliance ReportDate: 2026-10-30T15:07:23.249799+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 10ms |
| Format Validation.Event Has Uuid | ✅ | 7ms |
| Format Validation.Event Has Lib Properties | ✅ | 10ms |
| Format Validation.Distinct Id Is String | ✅ | 7ms |
| Format Validation.Token Is Present | ✅ | 7ms |
| Format Validation.Custom Properties Preserved | ✅ | 7ms |
| Format Validation.Event Has Timestamp | ✅ | 10ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 8ms |
| Retry Behavior.Retries On 503 | ✅ | 5260ms |
| Retry Behavior.Does Not Retry On 400 | ❌ | 9ms |
| Retry Behavior.Does Not Retry On 401 | ❌ | 8ms |
| Retry Behavior.Respects Retry After Header | ✅ | 8017ms |
| Retry Behavior.Implements Backoff | ❌ | 305ms |
| Retry Behavior.Retries On 500 | ✅ | 5117ms |
| Retry Behavior.Retries On 502 | ✅ | 5154ms |
| Retry Behavior.Retries On 504 | ✅ | 5163ms |
| Retry Behavior.Max Retries Respected | ❌ | 530ms |
| Deduplication.Generates Unique Uuids | ✅ | 18ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5116ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10328ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5125ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 20ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 10ms |
| Compression.Sends Gzip When Enabled | ✅ | 6ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 7ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 4ms |
| Batch Format.Multiple Events Batched Together | ❌ | 18ms |
| Error Handling.Does Not Retry On 403 | ❌ | 6ms |
| Error Handling.Does Not Retry On 413 | ❌ | 5ms |
| Error Handling.Retries On 408 | ✅ | 5112ms |
Failures
retry_behavior.does_not_retry_on_400
500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'
retry_behavior.does_not_retry_on_401
500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'
retry_behavior.implements_backoff
500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'
retry_behavior.max_retries_respected
500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'
batch_format.multiple_events_batched_together
Expected 1 requests, got 5
error_handling.does_not_retry_on_403
500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'
error_handling.does_not_retry_on_413
500, message='Internal Server Error', url='http://sdk-adapter:8080/capture'
Feature_Flags Tests
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 10ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 8ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 9ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 9ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 10ms |
| Request Payload.Groups Round Trip | ✅ | 7ms |
| Request Payload.Groups Default To Empty Object | ✅ | 7ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 7ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ❌ | 7ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 7ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 2ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 7ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 10ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 7ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 111ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 139ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 8ms |
Failures
request_payload.disable_geoip_omitted_defaults_to_false
Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['distinct_id', 'groups', 'person_properties', 'group_properties', 'flag_keys_to_evaluate', 'token']
posthog-ruby-async Compliance ReportDate: 2026-10-30T15:08:04.462100+00:00
|
| Test | Status | Duration |
|---|---|---|
| Format Validation.Event Has Required Fields | ✅ | 107ms |
| Format Validation.Event Has Uuid | ✅ | 104ms |
| Format Validation.Event Has Lib Properties | ✅ | 107ms |
| Format Validation.Distinct Id Is String | ✅ | 105ms |
| Format Validation.Token Is Present | ✅ | 105ms |
| Format Validation.Custom Properties Preserved | ✅ | 106ms |
| Format Validation.Event Has Timestamp | ✅ | 106ms |
| Format Validation.Non Utc Event Timestamp Is Converted To Utc | ✅ | 6ms |
| Retry Behavior.Retries On 503 | ✅ | 5310ms |
| Retry Behavior.Does Not Retry On 400 | ✅ | 2108ms |
| Retry Behavior.Does Not Retry On 401 | ✅ | 2107ms |
| Retry Behavior.Respects Retry After Header | ✅ | 8112ms |
| Retry Behavior.Implements Backoff | ✅ | 15516ms |
| Retry Behavior.Retries On 500 | ✅ | 5207ms |
| Retry Behavior.Retries On 502 | ✅ | 5210ms |
| Retry Behavior.Retries On 504 | ✅ | 5210ms |
| Retry Behavior.Max Retries Respected | ✅ | 15620ms |
| Deduplication.Generates Unique Uuids | ✅ | 108ms |
| Deduplication.Preserves Uuid On Retry | ✅ | 5209ms |
| Deduplication.Preserves Uuid And Timestamp On Retry | ✅ | 10407ms |
| Deduplication.Preserves Uuid And Timestamp On Batch Retry | ✅ | 5213ms |
| Deduplication.No Duplicate Events In Batch | ✅ | 110ms |
| Deduplication.Different Events Have Different Uuids | ✅ | 105ms |
| Compression.Sends Gzip When Enabled | ✅ | 105ms |
| Batch Format.Uses Proper Batch Structure | ✅ | 104ms |
| Batch Format.Flush With No Events Sends Nothing | ✅ | 3ms |
| Batch Format.Multiple Events Batched Together | ✅ | 107ms |
| Error Handling.Does Not Retry On 403 | ✅ | 2105ms |
| Error Handling.Does Not Retry On 413 | ✅ | 2107ms |
| Error Handling.Retries On 408 | ✅ | 5210ms |
Feature_Flags Tests
View Details
| Test | Status | Duration |
|---|---|---|
| Request Payload.Request With Person Properties Device Id | ✅ | 106ms |
| Request Payload.Flags Request Uses V2 Query Param | ✅ | 106ms |
| Request Payload.Flags Request Hits Flags Path Not Decide | ✅ | 106ms |
| Request Payload.Flags Request Omits Authorization Header | ✅ | 106ms |
| Request Payload.Token In Flags Body Matches Init | ✅ | 108ms |
| Request Payload.Groups Round Trip | ✅ | 107ms |
| Request Payload.Groups Default To Empty Object | ✅ | 105ms |
| Request Payload.Disable Geoip False Propagates As Geoip Disable False | ✅ | 106ms |
| Request Payload.Disable Geoip Omitted Defaults To False | ❌ | 105ms |
| Request Payload.Flag Keys To Evaluate Contains Only Requested Key | ✅ | 106ms |
| Request Lifecycle.No Flags Request On Init Alone | ✅ | 2ms |
| Request Lifecycle.No Flags Request On Normal Capture | ✅ | 104ms |
| Request Lifecycle.Two Flag Calls Produce Two Remote Requests | ✅ | 108ms |
| Request Lifecycle.Mock Response Value Is Returned To Caller | ✅ | 105ms |
| Retry Behavior.Retries Flags On 502 | ✅ | 208ms |
| Retry Behavior.Retries Flags On 504 | ✅ | 244ms |
| Side Effect Events.Get Feature Flag Captures Feature Flag Called Event | ✅ | 107ms |
Failures
request_payload.disable_geoip_omitted_defaults_to_false
Field 'geoip_disable' not found in /flags request body at path 'geoip_disable'. Available keys: ['distinct_id', 'groups', 'person_properties', 'group_properties', 'flag_keys_to_evaluate', 'token']
💡 Motivation and Context
Add every-PR performance reporting for the Ruby SDK using the same
sdk-benchmarks/v1workloads as Node, Python and .NET. Compares separately built and installed merge-base/head gems using the same head-side benchmark code.No SDK runtime, public API or default behavior changes. No release changeset is needed. Automatic comments activate after the trusted caller lands on the default branch. Shared pins match the existing integrations; mainline repinning follows the shared PR's squash merge.
Related integrations: PostHog/posthog-js#5454, PostHog/posthog-python#1135, PostHog/posthog-dotnet#358.
💚 How did you test it?
bundle exec rspec: 2019 examples, 0 failures, 40 pending (optional OpenTelemetry integration bundle).bundle exec rubocop: 145 files, no offenses;bundle exec rake public_api:checkunchanged.Local durations are correctness/execution evidence only; hosted CI establishes the final latency budget. Intentional native scope differences and unsupported metrics are documented in CONTRIBUTING.md.
📝 Checklist
🤖 Agent context
Autonomy: Human-driven (agent-assisted).
Implemented with Pi using file/shell tools and a fresh read-only reviewer. The benchmark preserves public SDK lifecycle and transport behavior; native Ruby counters are used where meaningful instead of equating allocation volume with retained memory. Human review is required.