Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pinba Bundle Laravel Package

intaro/pinba-bundle

Symfony bundle integrating the Pinba PHP extension for real-time application monitoring and profiling. Collect timers, tags, and custom metrics, then send them to Pinba for performance analysis and bottleneck detection.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin Symfony bundle that translates framework activity (Doctrine, Twig, Memcache) into Pinba timers. Pinba itself is a MySQL storage engine + PHP extension: the extension sends per-request/per-timer UDP packets to a Pinba server, which stores them in pinba_request / pinba_timer tables you query with SQL. So the bundle is only one third of the stack — you need the pinba PHP extension and a reachable Pinba server, or the bundle can do nothing at all.

Minimal steps:

  1. Verify the prerequisite first. Run php -m | grep pinba and pinba_get_info() (or check pinba.server/pinba.port in php.ini). If the extension isn't installed or can't reach the server, stop here — the bundle is a no-op and every timer call silently does nothing. The extension is old (built for PHP 7.x lineages); confirm it loads on your PHP version before evaluating the bundle.
  2. Install and register.
    composer require intaro/pinba-bundle
    
    // config/bundles.php
    Intaro\PinbaBundle\IntaroPinbaBundle::class => ['all' => true],
    
  3. Configure the timers you want. Open DependencyInjection/Configuration.php in the installed version first — the key set is small but varies between releases. Expect something along the lines of enabling/disabling per-integration timers:
    # config/packages/intaro_pinba.yaml  (verify key names against your version)
    intaro_pinba:
        enabled: true
        timers:
            doctrine: true
            twig: true
            memcache: true
    
  4. Prove data is flowing. After one request to your app, query the server directly:
    SELECT script_name, req_count, timer_value, timer_cnt
    FROM pinba_request
    ORDER BY req_count DESC LIMIT 20;
    
    If pinba_request is empty, the problem is the extension/network, not Symfony.

Where to look first in the repo: DependencyInjection/ for the config surface and service wiring, then the listener/logger classes for Doctrine and Twig to see exactly what is timed and which tag names are emitted. Those tag names are your reporting schema, so read them before designing dashboards.

Best first use case: a slow GET endpoint where you suspect a handful of ORM queries or one Twig template. Enable Doctrine and Twig timers only, ship to a staging/prod-like environment with the Pinba server, and look at per-timer aggregates. Pinba's cost is a UDP send, so it is one of the few profilers you can leave on in production — that is the whole value proposition.

Implementation Patterns

Guard rails around every manual timer. Since the extension may be absent in CI/dev, write one tiny helper and use it everywhere instead of scattering function_exists() checks:

final class Pinba
{
    public static function start(array $tags = []): mixed
    {
        return function_exists('pinba_timer_start')
            ? \pinba_timer_start($tags)
            : null;
    }

    public static function stop(mixed $timer): void
    {
        if ($timer !== null) {
            \pinba_timer_stop($timer);
        }
    }
}

Time business operations, not just infrastructure. The Doctrine/Twig/Memcache timers give you a baseline; the real day-to-day win is tagging your own hot paths:

$timer = Pinba::start(['operation' => 'catalog.reindex', 'queue' => 'high']);
try {
    $this->reindexer->run($batch);
} finally {
    Pinba::stop($timer);
}

try/finally matters — a timer left open on an exception skews the request's timings and, depending on extension version, can linger for the rest of the worker's life.

Set meaningful request identity. Pinba aggregates by script_name and by request tags, so a raw /index.php name is useless. Use pinba_script_name_set() (and request-level tags via the extension's request API) in a kernel.request listener so you can group by route/controller:

public function onKernelRequest(RequestEvent $event): void
{
    if (!function_exists('pinba_script_name_set')) {
        return;
    }
    $route = $event->getRequest()->attributes->get('_route') ?? 'unknown';
    \pinba_script_name_set(sprintf('%s::%s', $event->getRequest()->getMethod(), $route));
}

Check first whether the bundle already sets a script name — if it does, don't double-set it.

Treat the bundle as a decorator, not a framework. Two integration styles work well:

  • Wrapping/decorating at the service boundary (your repository, your cache client, your renderer) so the timed unit matches the concept you care about, rather than whatever internal call the bundle intercepted.
  • Keeping the bundle's automatic instrumentation for discovery, then replacing it with explicit timers on the paths you actually alert on — the auto-timers are noisy per-SQL, while your explicit timers map cleanly to dashboards.

Reporting. The Pinba server is a MySQL database; point Grafana (MySQL datasource) or your BI tool at it and build panels off script_name + your tag keys. Standard shapes:

-- slowest scripts by average request time
SELECT script_name,
       SUM(req_count)                        AS requests,
       SUM(timer_value) / SUM(timer_cnt)      AS avg_ms
FROM pinba_request
WHERE __REQUEST_TIME__ IS NOT NULL
GROUP BY script_name
ORDER BY avg_ms DESC;

Column names shift between Pinba and Pinba2 schemas — inspect DESCRIBE before hardcoding queries into dashboards.

Custom timers via a compiler pass if you want tagging-based instrumentation: expose an interface, tag implementations pinba.timed, and decorate them in the compiler pass with start/stop. This keeps business code free of profiler calls and is the cleanest extension point the bundle leaves you.

Environment strategy. Enable in production/staging where the Pinba server exists, leave disabled (or leave the extension unloaded) in dev/CI. Because the bundle's services may reference timer classes unconditionally, test composer install + cache:clear in an environment without the extension — that is the failure mode you'll hit first.

Gotchas and Tips

  • Silent no-op is the default failure. No extension, wrong pinba.server, or blocked UDP port all produce zero data with zero errors. Always verify with a direct SQL query against the Pinba server before debugging Symfony wiring.
  • UDP means loss and approximation. At high request rates packets are dropped; counts and averages are estimates. Don't use Pinba for exact SLO accounting, billing, or anything requiring complete data — it's a sampling-friendly discovery tool.
  • Doctrine DBAL compatibility is the biggest risk. If the bundle's Doctrine integration hooks Doctrine\DBAL\Logging\SQLLogger, it will not work on DBAL 3+/4 (SQLLogger is deprecated and removed in favour of Doctrine\DBAL\Logging\Middleware). Check the integration class and your DBAL version; if mismatched, write a small middleware that calls pinba_timer_start/stop yourself.
  • Nesting double-counts. Twig render time includes partials; request time includes everything. Never sum timer values across nesting levels and call it "total".
  • Tag cardinality is the silent killer. Pinba builds a dictionary of tag names/values; putting user IDs, URLs, or SQL text into tags grows it unboundedly and degrades both the server and your queries. Keep tags to a bounded, low-cardinality set (operation, queue, template_group).
  • Timer leaks on exceptions are the classic bug — always finally.
  • Long-running workers and CLI don't produce useful Pinba data out of the box: no HTTP request, often no script name, and timers accumulate in worker memory. Set a script name per job (pinba_script_name_set('cron::reindex')) if you instrument CLI at all, and watch memory.
  • pinba_timer_add vs pinba_timer_start/stop: for work you measure yourself (e.g. a value you already have), pinba_timer_add($tags, $seconds) avoids nesting concerns and is cheaper.
  • Check version pinning against Symfony. With 22 stars, 0 dependents and a small maintenance surface, read composer.json require constraints before adopting; expect to vendor/fork rather than wait for a fix. The bundle's real code volume is small enough that forking the two listeners you need is often cheaper than fighting its Symfony/DBAL version constraints.
  • Extension points to know: the bundle's listeners are ordinary services — decorate them or set them to null in your config; and add your own timers through a tagged-service compiler pass rather than editing vendor code.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect