initial commit

This commit is contained in:
Matthew Goslett 2017-07-27 11:11:02 +02:00
commit bf1a4e1879
17 changed files with 822 additions and 0 deletions

View file

@ -0,0 +1,34 @@
<?php
namespace Superbalist\LaravelPrometheusExporter;
interface CollectorInterface
{
/**
* Return the name of the collector.
*
* @return string
*/
public function getName();
/**
* Register all metrics associated with the collector.
*
* The metrics needs to be registered on the exporter object.
* eg:
* ```php
* $exporter->registerCounter('search_requests_total', 'The total number of search requests.');
* ```
*
* @param PrometheusExporter $exporter
*/
public function registerMetrics(PrometheusExporter $exporter);
/**
* Collect metrics data, if need be, before exporting.
*
* As an example, this may be used to perform time consuming database queries and set the value of a counter
* or gauge.
*/
public function collect();
}

41
src/MetricsController.php Normal file
View file

@ -0,0 +1,41 @@
<?php
namespace Superbalist\LaravelPrometheusExporter;
use Illuminate\Routing\Controller;
use Prometheus\RenderTextFormat;
class MetricsController extends Controller
{
/**
* @var $prometheusExporter
*/
protected $prometheusExporter;
/**
* @param PrometheusExporter $prometheusExporter
*/
public function __construct(PrometheusExporter $prometheusExporter)
{
$this->prometheusExporter = $prometheusExporter;
}
/**
* GET /metrics
*
* The route path is configurable in the prometheus.metrics_route_path config var, or the
* PROMETHEUS_METRICS_ROUTE_PATH env var.
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function getMetrics()
{
$metrics = $this->prometheusExporter->export();
$renderer = new RenderTextFormat();
$result = $renderer->render($metrics);
return response($result)
->header('Content-Type', RenderTextFormat::MIME_TYPE);
}
}

235
src/PrometheusExporter.php Normal file
View file

@ -0,0 +1,235 @@
<?php
namespace Superbalist\LaravelPrometheusExporter;
use InvalidArgumentException;
use Prometheus\CollectorRegistry;
class PrometheusExporter
{
/**
* @var string
*/
protected $namespace;
/**
* @var CollectorRegistry
*/
protected $prometheus;
/**
* @var array
*/
protected $collectors = [];
/**
* @param string $namespace
* @param CollectorRegistry $prometheus
* @param array $collectors
*/
public function __construct($namespace, CollectorRegistry $prometheus, array $collectors = [])
{
$this->namespace = $namespace;
$this->prometheus = $prometheus;
foreach ($collectors as $collector) {
/** @var CollectorInterface $collector */
$this->registerCollector($collector);
}
}
/**
* Return the metric namespace.
*
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* Return the CollectorRegistry.
*
* @return CollectorRegistry
*/
public function getPrometheus()
{
return $this->prometheus;
}
/**
* Register a collector.
*
* @param CollectorInterface $collector
*/
public function registerCollector(CollectorInterface $collector)
{
$name = $collector->getName();
if (!isset($this->collectors[$name])) {
$this->collectors[$name] = $collector;
$collector->registerMetrics($this->prometheus);
}
}
/**
* Return all collectors.
*
* @return array
*/
public function getCollectors()
{
return $this->collectors;
}
/**
* Return a collector by name.
*
* @param string $name
* @return CollectorInterface
*/
public function getCollector($name)
{
if (!isset($this->collectors[$name])) {
throw new InvalidArgumentException(sprintf('The collector "%s" is not registered.', $name));
}
return $this->collectors[$name];
}
/**
* Register a counter.
*
* @param string $name
* @param string $help
* @param array $labels
* @return \Prometheus\Counter
* @see https://prometheus.io/docs/concepts/metric_types/#counter
*/
public function registerCounter($name, $help, $labels = [])
{
return $this->prometheus->registerCounter($this->namespace, $name, $help, $labels);
}
/**
* Return a counter.
*
* @param string $name
* @return \Prometheus\Counter
*/
public function getCounter($name)
{
return $this->prometheus->getCounter($this->namespace, $name);
}
/**
* Return or register a counter.
*
* @param string $name
* @param string $help
* @param array $labels
* @return \Prometheus\Counter
* @see https://prometheus.io/docs/concepts/metric_types/#counter
*/
public function getOrRegisterCounter($name, $help, $labels = [])
{
return $this->prometheus->getOrRegisterCounter($this->namespace, $name, $help, $labels);
}
/**
* Register a gauge.
*
* @param string $name
* @param string $help
* @param array $labels
* @return \Prometheus\Gauge
* @see https://prometheus.io/docs/concepts/metric_types/#gauge
*/
public function registerGauge($name, $help, $labels = [])
{
return $this->prometheus->registerGauge($this->namespace, $name, $help, $labels);
}
/**
* Return a gauge.
*
* @param string $name
* @return \Prometheus\Counter
*/
public function getGauge($name)
{
return $this->prometheus->getCounter($this->namespace, $name);
}
/**
* Return or register a gauge.
*
* @param string $name
* @param string $help
* @param array $labels
* @return \Prometheus\Gauge
* @see https://prometheus.io/docs/concepts/metric_types/#gauge
*/
public function getOrRegisterGauge($name, $help, $labels = [])
{
return $this->prometheus->getOrRegisterGauge($this->namespace, $name, $help, $labels);
}
/**
* Register a histogram.
*
* @param string $name
* @param string $help
* @param array $labels
* @param array $buckets
* @return \Prometheus\Histogram
* @see https://prometheus.io/docs/concepts/metric_types/#histogram
*/
public function registerHistogram($name, $help, $labels = [], $buckets = null)
{
return $this->prometheus->registerHistogram($this->namespace, $name, $help, $labels, $buckets);
}
/**
* Return a histogram.
*
* @param string $name
* @return \Prometheus\Histogram
*/
public function getHistogram($name)
{
return $this->prometheus->getHistogram($this->namespace, $name);
}
/**
* Return or register a histogram.
*
* @param string $name
* @param string $help
* @param array $labels
* @param array $buckets
* @return \Prometheus\Histogram
* @see https://prometheus.io/docs/concepts/metric_types/#histogram
*/
public function getOrRegisterHistogram($name, $help, $labels = [], $buckets = null)
{
return $this->prometheus->getOrRegisterHistogram($this->namespace, $name, $help, $labels, $buckets);
}
/**
* Export the metrics from all collectors.
*
* @return \Prometheus\MetricFamilySamples[]
*/
public function export()
{
foreach ($this->collectors as $collector) {
/** @var CollectorInterface $collector */
$collector->collect();
}
return $this->prometheus->getMetricFamilySamples();
}
}

18
src/PrometheusFacade.php Normal file
View file

@ -0,0 +1,18 @@
<?php
namespace Superbalist\LaravelPrometheusExporter;
use Illuminate\Support\Facades\Facade;
class PrometheusFacade extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'prometheus';
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Superbalist\LaravelPrometheusExporter;
use Illuminate\Support\ServiceProvider;
use Prometheus\CollectorRegistry;
use Prometheus\Storage\Adapter;
class PrometheusServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*/
public function boot()
{
$this->publishes([
__DIR__ . '/../config/prometheus.php' => config_path('prometheus.php'),
]);
if (config('prometheus.metrics_route_enabled')) {
$this->loadRoutesFrom(__DIR__ . '/routes.php');
}
$exporter = $this->app->make(PrometheusExporter::class); /** @var PrometheusExporter $exporter */
foreach (config('prometheus.collectors') as $class) {
$collector = $this->app->make($class);
$exporter->registerCollector($collector);
}
}
/**
* Register bindings in the container.
*/
public function register()
{
$this->mergeConfigFrom(__DIR__ . '/../config/prometheus.php', 'prometheus');
$this->app->singleton(PrometheusExporter::class, function ($app) {
$adapter = $app['prometheus.storage_adapter'];
$prometheus = new CollectorRegistry($adapter);
return new PrometheusExporter(config('prometheus.namespace'), $prometheus);
});
$this->app->alias(PrometheusExporter::class, 'prometheus');
$this->app->bind('prometheus.storage_adapter_factory', function ($app) {
return new StorageAdapterFactory($app);
});
$this->app->bind(Adapter::class, function ($app) {
$factory = $app['prometheus.storage_adapter_factory']; /** @var StorageAdapterFactory $factory */
$driver = config('prometheus.storage_adapter');
$configs = config('storage_adapters');
$config = array_get($configs, $driver, []);
return $factory->make($driver, $config);
});
$this->app->alias(Adapter::class, 'prometheus.storage_adapter');
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
'prometheus',
'prometheus.storage_adapter_factory',
'prometheus.storage_adapter',
];
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Superbalist\LaravelPrometheusExporter;
use Illuminate\Contracts\Container\Container;
use InvalidArgumentException;
use Prometheus\Storage\Adapter;
use Prometheus\Storage\APC;
use Prometheus\Storage\InMemory;
use Prometheus\Storage\Redis;
class StorageAdapterFactory
{
/**
* @var Container
*/
protected $container;
/**
* @param Container $container
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* Factory a storage adapter.
*
* @param string $driver
* @param array $config
* @return Adapter
*/
public function make($driver, array $config = [])
{
switch ($driver) {
case 'memory':
return new InMemory();
case 'redis':
return $this->makeRedisAdapter($config);
case 'apc':
return new APC();
}
throw new InvalidArgumentException(sprintf('The driver [%s] is not supported.', $driver));
}
/**
* Factory a redis storage adapter.
*
* @param array $config
* @return Redis
*/
protected function makeRedisAdapter(array $config)
{
if (isset($config['prefix'])) {
Redis::setPrefix($config['prefix']);
}
return new Redis($config);
}
}

5
src/routes.php Normal file
View file

@ -0,0 +1,5 @@
<?php
Route::get(config('prometheus.metrics_route_path'), 'MetricsController@getMetrics')
->middleware(config('prometheus.metrics_route_middleware'))
->name('metrics');