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

Cpu Core Counter Laravel Package

fidry/cpu-core-counter

A tiny PHP utility for detecting CPU core counts, with logical and physical finders, built-in fallbacks, and memoized results. It also estimates cores available for parallel processes and lets you customize or reorder finders. Requires Composer.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require fidry/cpu-core-counter

There is no service provider, facade or config file — it is a plain PHP utility with a handful of classes under Fidry\CpuCoreCounter. In a Laravel app you introduce it yourself, and the right first move is to wrap it in a tiny singleton so nothing else in the codebase talks to the counter directly.

// app/Support/Cpu.php
namespace App\Support;

use Fidry\CpuCoreCounter\CpuCoreCounter;

final class Cpu
{
    public static function count(int $fallback = 1): int
    {
        return app(CpuCoreCounter::class)->getCountWithFallback($fallback);
    }
}
// app/Providers/AppServiceProvider.php  (register())
$this->app->singleton(CpuCoreCounter::class, fn () => new CpuCoreCounter());

First use case worth wiring up: sizing parallel work. Laravel 11's Concurrency facade, Horizon supervisors, and any Process-pool style fan-out all benefit from a number that reflects the machine the code actually runs on rather than a hardcoded 8 in a config file.

use Illuminate\Support\Facades\Concurrency;

$chunks = Concurrency::run(
    array_map(fn ($slice) => fn () => $this->process($slice), $slices)
);

Size the pool from Cpu::count() (or getAvailableForParallelisation() when the work is CPU-bound and you want headroom).

Where to look first, in order:

  1. CpuCoreCounter::getCountWithFallback() — this is the call you will use 90% of the time. It never throws.
  2. CpuCoreCounter::getAvailableForParallelisation() — returns a result object whose availableCpus is a sensible concurrency number (it accounts for the fact you should not saturate every core).
  3. bin/diagnose.php and bin/execute.php in the vendor dir — run these before you trust the output anywhere.
php vendor/fidry/cpu-core-counter/bin/diagnose.php   # per-finder detail
php vendor/fidry/cpu-core-counter/bin/execute.php    # what each finder returns

Implementation Patterns

Never call the counter from a hot path. It shells out (nproc, lscpu, sysctl, wmic, reads /proc/cpuinfo) on first resolution. Results are memoized per CpuCoreCounter instance, so bind it as a singleton and resolve once. In config files resolved at boot, do the resolution in the provider, not inside config/*.php.

Expose it as config, not as a scattered call. Compute once at boot and publish into the config repository:

// AppServiceProvider::register()
$this->app->singleton(CpuCoreCounter::class, fn () => new CpuCoreCounter());

// AppServiceProvider::boot()
config([
    'queue.connections.redis.worker_limit' => app(CpuCoreCounter::class)->getCountWithFallback(4),
]);

Horizon supervisors. The most natural Laravel fit — derive maxProcesses per supervisor from real cores instead of a per-environment hand-tuned number:

// config/horizon.php
'environments' => [
    'production' => [
        'supervisor-default' => [
            'maxProcesses'  => max(2, \App\Support\Cpu::count(4) * 2), // IO-bound: over-subscribe
            'queue'         => ['default'],
            'balance'       => 'auto',
        ],
        'supervisor-heavy' => [
            'maxProcesses'  => max(1, \App\Support\Cpu::count(4)),    // CPU-bound: 1 per core
            'queue'         => ['rendering'],
        ],
    ],
],

The rule of thumb that makes this worth doing: CPU-bound pools get ~count() workers, IO-bound pools get 2–4 × count().

Prefer getAvailableForParallelisation() for in-request fan-out. If you are forking with Concurrency, spatie/async, or a Process pool inside a web request, you want the headroom the package computes, not the raw core count — saturating every core from inside an FPM request is how you get latency cliffs.

$result = app(CpuCoreCounter::class)->getAvailableForParallelisation();

$pool = max(1, min($slices, (int) $result->availableCpus));

Custom finder chains for known deployments. When you know the platform, pin the finders so behaviour is deterministic and cheap:

use Fidry\CpuCoreCounter\CpuCoreCounter;
use Fidry\CpuCoreCounter\Finder\{CpuInfoFinder, HwLogicalFinder, DummyCpuCoreFinder};

$this->app->singleton(CpuCoreCounter::class, fn () => new CpuCoreCounter([
    new CpuInfoFinder(),
    new HwLogicalFinder(),
    new DummyCpuCoreFinder(4),   // terminal fallback, never throws
]));

Use FinderRegistry::getDefaultLogicalFinders() / ::getDefaultPhysicalFinders() (or CpuCoreCounter::getDefaultFinders()) as the base and array_filter out the ones that misbehave on your platform — that is the documented way to disable e.g. WindowsWmicFinder.

Logical vs physical. Default is logical (matches how PHP itself was built, and is what you want for worker counts). Switch to physical only when you are modelling genuinely physical throughput (licensing math, per-socket workloads, hardware reporting) via FinderRegistry::getDefaultPhysicalFinders().

Diagnostics belong in CI, not in prod debugging sessions. Add a small artisan command that runs the default finders and asserts a sane result, and call it in your deploy smoke test:

Artisan::command('cpu:diagnose', function () {
    $counter  = new CpuCoreCounter();
    $this->line('cores: '.$counter->getCountWithFallback(1));
    $this->line('parallel: '.$counter->getAvailableForParallelisation()->availableCpus);
    $this->line($counter->trace());   // human-readable "what happened"
});

Run it against a container built from your real image, not your laptop.

Gotchas and Tips

  • getCount() throws. NumberOfCpuCoreNotFound is the failure mode on stripped containers, minimal Alpine images, or when proc_open/shell_exec are in disable_functions. In a Laravel app the exception will surface as a 500 at boot if you call it from a provider without a fallback. Always go through getCountWithFallback() or append a DummyCpuCoreFinder.
  • disable_functions is the classic production break. Many hardened PHP images disable proc_open and shell_exec, which kills the executor-based finders. The package degrades to file-based finders (and then to your fallback), so a fallback is not optional. Check php -i | grep disable_functions in the deployed image.
  • Memoization is per-instance, not global. new CpuCoreCounter() twice = two resolution passes. Bind a singleton. Conversely, memoization means Octane workers keep the boot-time value forever — fine for static hardware, wrong if you resize containers under a long-lived worker.
  • Kubernetes limits are honoured, but verify the units. KUBERNETES_CPU_LIMIT with millicores (e.g. 1500m) is supported since 1.3.0, but a 500m limit means "half a core" — decide explicitly whether you want max(1, …). A CPU-limited pod does not get faster by spawning count() workers; it gets throttled. Cap your pools by the limit, not by the node's core count, and sanity-check with diagnose.php inside a pod.
  • CpuAffinityFinder and online CPUs. Fixed in 1.4.1 — only online CPUs are counted. If you pin CPUs in Docker (--cpuset-cpus) and see wrong numbers, that is the finder to look at.
  • FinderRegistry::get*Finders() is explicitly outside the BC promise. New finders can appear and the order can change in a minor release. If you depend on a specific order, build your own array and do not call the registry at runtime.
  • trace() is your first debugger. When the count looks wrong, call CpuCoreCounter::trace() and compare against execute.php; it names the finder that answered. Guessing at finder order is a waste of an afternoon.
  • Don't tune on the numbers alone. Physical/logical/affinity/cgroup limits can all disagree. The only meaningful validation is: set the worker count, run a realistic load, watch latency and CPU throttling (container_cpu_cfs_throttled_periods in k8s).
  • Package-level risks worth stating to your team. Zero dependents despite the stars, a single-maintainer project, and a 1k-LOC surface that invokes OS binaries. Mitigate by (a) pinning a version, (b) keeping the App\Support\Cpu wrapper as the single call site so swapping in a one-liner shell_exec('nproc') is trivial, and (c) always passing a fallback. The wrapper is your exit strategy.
  • Fixed-hardware apps gain nothing. If every host is identical and provisioned by you, a config value is clearer and faster than subprocess parsing at boot. Adopt this when environments are heterogeneous, containerised, or dynamically sized — otherwise skip it.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
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
cgv/localized-resource