Stats: add copy to statsd feature

Enables copying a metric to statsd at the specified namespace.

Bug: T240685
Change-Id: I15d6f100a31fbb98ad89568a2024e926cb47f937
This commit is contained in:
Cole White 2023-03-03 16:10:54 +00:00
parent 7c4c423cce
commit 13f556ab3a
9 changed files with 106 additions and 3 deletions

View file

@ -1957,7 +1957,8 @@ return [
\Wikimedia\Stats\OutputFormats::getNewFormatter( $format ),
$config->get( MainConfigNames::StatsTarget )
);
return new StatsFactory( 'core', $cache, $emitter, LoggerFactory::getInstance( 'Stats' ) );
$factory = new StatsFactory( 'core', $cache, $emitter, LoggerFactory::getInstance( 'Stats' ) );
return $factory->withStatsdDataFactory( $services->getStatsdDataFactory() );
},
'TalkPageNotificationManager' => static function (

View file

@ -21,6 +21,7 @@ declare( strict_types=1 );
namespace Wikimedia\Stats\Metrics;
use IBufferingStatsdDataFactory;
use Wikimedia\Stats\Exceptions\IllegalOperationException;
use Wikimedia\Stats\Sample;
use Wikimedia\Stats\StatsUtils;
@ -61,6 +62,9 @@ class BaseMetric implements BaseMetricInterface {
/** @var Sample[] */
private array $samples = [];
/** @var IBufferingStatsdDataFactory|null */
private ?IBufferingStatsdDataFactory $statsdDataFactory = null;
/** @inheritDoc */
public function __construct( string $component, string $name ) {
$this->component = $component;
@ -113,6 +117,17 @@ class BaseMetric implements BaseMetricInterface {
$this->workingLabels[$key] = StatsUtils::normalizeString( $value );
}
/** @inheritDoc */
public function getStatsdDataFactory() {
return $this->statsdDataFactory;
}
/** @inheritDoc */
public function withStatsdDataFactory( $statsdDataFactory ): BaseMetric {
$this->statsdDataFactory = $statsdDataFactory;
return $this;
}
/**
* Registers a label key
*

View file

@ -21,6 +21,7 @@ declare( strict_types=1 );
namespace Wikimedia\Stats\Metrics;
use IBufferingStatsdDataFactory;
use Wikimedia\Stats\Sample;
/**
@ -120,4 +121,17 @@ interface BaseMetricInterface {
* @return void
*/
public function clearLabels(): void;
/**
* Gets StatsD Data Factory instance or null.
*/
public function getStatsdDataFactory();
/**
* StatsD Data Factory instance to copy metrics to.
*
* @param IBufferingStatsdDataFactory $statsdDataFactory
* @return BaseMetricInterface
*/
public function withStatsdDataFactory( IBufferingStatsdDataFactory $statsdDataFactory );
}

View file

@ -45,6 +45,9 @@ class CounterMetric implements MetricInterface {
*/
private const TYPE_INDICATOR = "c";
/** @var string|null */
private ?string $statsdNamespace = null;
/** @var BaseMetricInterface */
private BaseMetricInterface $baseMetric;
@ -74,6 +77,10 @@ class CounterMetric implements MetricInterface {
* @return void
*/
public function incrementBy( int $value, array $labels = [] ): void {
if ( $this->statsdNamespace !== null ) {
$this->baseMetric->getStatsdDataFactory()->updateCount( $this->statsdNamespace, $value );
$this->statsdNamespace = null;
}
$this->baseMetric->addSample( new Sample( $this->baseMetric->getLabelValues(), $value ) );
}
@ -131,6 +138,14 @@ class CounterMetric implements MetricInterface {
return $this;
}
/** @inheritDoc */
public function copyToStatsdAt( string $statsdNamespace ): CounterMetric {
if ( $this->baseMetric->getStatsdDataFactory() !== null ) {
$this->statsdNamespace = $statsdNamespace;
}
return $this;
}
/** @inheritDoc */
public function fresh(): CounterMetric {
$this->baseMetric->clearLabels();

View file

@ -45,6 +45,9 @@ class GaugeMetric implements MetricInterface {
*/
private const TYPE_INDICATOR = "g";
/** @var string|null */
private ?string $statsdNamespace = null;
/** @var BaseMetricInterface */
private BaseMetricInterface $baseMetric;
@ -64,6 +67,10 @@ class GaugeMetric implements MetricInterface {
* @return void
*/
public function set( float $value ): void {
if ( $this->statsdNamespace !== null ) {
$this->baseMetric->getStatsdDataFactory()->updateCount( $this->statsdNamespace, $value );
$this->statsdNamespace = null;
}
$this->baseMetric->addSample( new Sample( $this->baseMetric->getLabelValues(), $value ) );
}
@ -121,6 +128,14 @@ class GaugeMetric implements MetricInterface {
return $this;
}
/** @inheritDoc */
public function copyToStatsdAt( string $statsdNamespace ) {
if ( $this->baseMetric->getStatsdDataFactory() !== null ) {
$this->statsdNamespace = $statsdNamespace;
}
return $this;
}
/** @inheritDoc */
public function fresh(): GaugeMetric {
$this->baseMetric->clearLabels();

View file

@ -78,6 +78,14 @@ interface MetricInterface {
*/
public function withLabel( string $key, string $value );
/**
* Copies metric operation to StatsD at provided namespace.
*
* @param string $statsdNamespace
* @return CounterMetric|GaugeMetric|TimingMetric|NullMetric
*/
public function copyToStatsdAt( string $statsdNamespace );
/**
* Returns metric with cleared labels.
*

View file

@ -46,6 +46,9 @@ class TimingMetric implements MetricInterface {
*/
private const TYPE_INDICATOR = "ms";
/** @var string|null */
private ?string $statsdNamespace = null;
/** @var BaseMetricInterface */
private BaseMetricInterface $baseMetric;
@ -91,6 +94,10 @@ class TimingMetric implements MetricInterface {
* @return void
*/
public function observe( float $value ): void {
if ( $this->statsdNamespace !== null ) {
$this->baseMetric->getStatsdDataFactory()->timing( $this->statsdNamespace, $value );
$this->statsdNamespace = null;
}
$this->baseMetric->addSample( new Sample( $this->baseMetric->getLabelValues(), $value ) );
}
@ -148,6 +155,14 @@ class TimingMetric implements MetricInterface {
return $this;
}
/** @inheritDoc */
public function copyToStatsdAt( string $statsdNamespace ) {
if ( $this->baseMetric->getStatsdDataFactory() !== null ) {
$this->statsdNamespace = $statsdNamespace;
}
return $this;
}
/** @inheritDoc */
public function fresh(): TimingMetric {
$this->baseMetric->clearLabels();

View file

@ -21,6 +21,7 @@ declare( strict_types=1 );
namespace Wikimedia\Stats;
use IBufferingStatsdDataFactory;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use TypeError;
@ -63,6 +64,9 @@ class StatsFactory {
/** @var LoggerInterface */
private LoggerInterface $logger;
/** @var IBufferingStatsdDataFactory|null */
private ?IBufferingStatsdDataFactory $statsdDataFactory = null;
/**
* StatsFactory builds, configures, and caches Metrics.
*
@ -113,6 +117,11 @@ class StatsFactory {
return $this;
}
public function withStatsdDataFactory( IBufferingStatsdDataFactory $statsdDataFactory ): StatsFactory {
$this->statsdDataFactory = $statsdDataFactory;
return $this;
}
/**
* Makes a new CounterMetric or fetches one from cache.
*
@ -179,7 +188,9 @@ class StatsFactory {
if ( $metric === null ) {
$baseMetric = new BaseMetric( $this->component, $name );
$metric = new $className(
$baseMetric->withStaticLabels( $this->staticLabelKeys, $this->staticLabelValues ),
$baseMetric
->withStatsdDataFactory( $this->statsdDataFactory )
->withStaticLabels( $this->staticLabelKeys, $this->staticLabelValues ),
$this->logger
);
$this->cache->set( $this->component, $name, $metric );

View file

@ -2,6 +2,7 @@
namespace Wikimedia\Tests\Stats;
use IBufferingStatsdDataFactory;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use UDPTransport;
@ -19,6 +20,10 @@ use Wikimedia\Stats\StatsFactory;
class StatsEmitterTest extends TestCase {
public function testSend() {
// set up a mock statsd data factory
$statsd = $this->createMock( IBufferingStatsdDataFactory::class );
$statsd->expects( $this->exactly( 1 ) )->method( "updateCount" );
// initialize cache
$cache = new StatsCache();
@ -40,7 +45,11 @@ class StatsEmitterTest extends TestCase {
// initialize metrics factory
$m = new StatsFactory( 'test', $cache, $emitter, new NullLogger );
$m->getCounter( 'bar' )->increment();
// inject statsd factory
$m->withStatsdDataFactory( $statsd );
// populate metric with statsd copy
$m->getCounter( 'bar' )->copyToStatsdAt( 'test.metric' )->increment();
// fetch same metric from cache and use it
$metric = $m->getCounter( 'bar' );