#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2020 -- leonerd@leonerd.org.uk

package Metrics::Any::Collector;

use strict;
use warnings;

our $VERSION = '0.01';

use Metrics::Any::Adapter;

=head1 NAME

C<Metrics::Any::Collector> - module-side of the monitoring metrics reporting API

=head1 SYNOPSIS

   use Metrics::Any '$metrics';

   $metrics->make_counter( thing =>
      name => "things_done",
   );

   sub do_thing {
      $metrics->inc_counter( 'thing' );
   }

=head1 DESCRIPTION

Instances of this class provide an API for individual modules to declare
metadata about metrics they will report, and to report individual values or
observations on those metrics. An instance should be obtained for a reporting
module by the C<use Metrics::Any> statement.

The collector acts primarily as a proxy for the application's configured
L<Metrics::Any::Adapter> instance. The proxy will lazily create an adapter
when required to first actually report a metric value, but until then any
metadata stored by the L</make_counter> method will not create one. This lazy
deferral allows a certain amount of flexibility with module load order and
application startup. By carefully writing module code to not report any values
of metrics until the main activity has actually begin, it should be possible
to allow programs to configure the metric reporting in a flexible manner
during program startup.

=cut

# Not public API; used by Metrics::Any::import_into
sub new
{
   my $class = shift;
   my ( $pkg ) = @_;

   return bless {
      pkg => $pkg,
      adapter => undef,
      deferred => [],
   }, $class;
}

=head1 METHODS

=cut

sub adapter
{
   my $self = shift;
   return $self->{adapter} if $self->{adapter};

   my $adapter = $self->{adapter} = Metrics::Any::Adapter->adapter;
   foreach my $call ( @{ $self->{deferred} } ) {
      my ( $method, @args ) = @$call;
      $self->$method( @args );
   }
   undef $self->{deferred};
   return $adapter;
}

=head2 make_counter

   $collector->make_counter( $handle, %args )

Requests the creation of a new counter metric. The C<$handle> name should be
unique within the collector instance, though does not need to be unique across
the entire program, as it will be namespaced by the collector instance.

The remaining C<%args> are passed through to the same method on the adapter
instance once it is available.

The following named arguments are recognised:

=over 4

=item name => STRING

A string name to use for reporting this metric to its upstream service.

=item description => STRING

Optional human-readable description. May be used for debugging or other
purposes.

=item labels => ARRAY[ STRING ]

Optional reference to an array of string names to use as label names.

A labelled metric will expect to receive as many additional values to its
C</inc_counter> call as there are label names. Each additional value will be
associated with the corresponding label.

Note that not all metric reporting adapters may be able to represent all of
the labels. Each should document what its behaviour will be.

=back

=cut

sub make_counter
{
   my $self = shift;
   my ( $handle, %args ) = @_;

   if( !$self->{adapter} ) {
      push @{ $self->{deferred} }, [ make_counter => $handle, %args ];
      return;
   }

   $self->adapter->make_counter( "$self->{pkg}/$handle", %args );
}

=head2 inc_counter

   $collector->inc_counter( $handle, @labelvalues )

Reports that the counter metric value be incremented by one. The C<$handle>
name must match one earlier created by L</make_counter>.

The remaining C<@labelvalues> if present are passed through to the same method
on the adapter instance, and should correspond to the label names given when
the counter was created.

=cut

sub inc_counter
{
   my $self = shift;
   my ( $handle, @args ) = @_;

   $self->adapter->inc_counter( "$self->{pkg}/$handle", @args );
}

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;
