Metrics

How to create and manage performance metrics with Pip.Services.

Key takeaways

Performance metrics Values that result from the measurement of an application's non-functional traits, which can provide useful insight into how the application performs.
ICounters Interface that defines methods for performance counters that measure execution metrics.
CachedCounters Performance counters that store their measurements in memory.
LogCounters Performance counters that periodically send their measurements to a logger
NullCounters Component that is used to create dummy performance counters.
Performance metrics tools Tools used to analyze performance metrics. Examples are Prometheus and Datadog.
CompositeCounters Component that is used to group counters from different components into a single one.

Introduction

This tutorial will teach you how to create and manage performance metrics using components from the Pip.Services toolkit. First, we will learn how counters are defined in the toolkit and how to add them to a component. Then, we will see several options of managing the obtained performance metrics, such as storing them in memory, showing them in the console, sending them to external tools, and grouping them into composite counters. We will also examine a dummy component that can be used to simulate counters.

Counters

Pip.Services’ Components module includes a package called Count, which contains several interfaces and classes that can be used to create performance counters.

Among these interfaces is one called ICounters, which defines methods for measuring execution performance. One such method is increment(), which increases by a defined value each time it is called. Other important methods are beginTiming() and stats(), which are used to calculate time intervals and common statistics (minimum, maximum, and average), respectively. There is no stopTiming() method in this interface due to the fact that beginTiming() is supposed to return a CounterTiming object, which can be called to end the measurement and update the counter.

The ICounters interface is implemented by several classes. The figure below shows a simplified class diagram, displaying the relationships between this interface and the main classes used to build counters. These classes will be explained in more detail in the following sections.

figure 1

Pip.Services has several predefined counters, specified in the CounterType class. They are:

figure 2

A best practice when working with counters is to name them using to the following convention:

<service_or_component_name>.<method_name>.<counter_name>

Managing counters

In the next sections, we will construct examples that show how to define counters that will store their metrics in memory, in a logger, or in monitoring tools. We will also learn to create a dummy component that performs no real measurements and a composite counter component, capable of grouping multiple different counters into one.

Monitored component

First, we define a class that has two performance metrics: the number of times a method is called and its execution time. Both metrics will be performing measurements for a dummy function that simulates a task by printing two messages to the console. Finally, we call the dump() method to save the obtained values. The code below shows what this class looks like:

import { ICounters } from "pip-services3-components-nodex";

export class MyComponent {
    private _consoleLog: boolean = true;

    private counters: ICounters;

    public constructor(counters: ICounters) {
        this.counters = counters;

        if (this._consoleLog) {
            console.log("MyComponent has been created.");
        }
    }

    public mymethod(): void {
        this.counters.increment("mycomponent.mymethod.calls", 1);
        let timing = this.counters.beginTiming("mycomponent.mymethod.exec_time");

        try {
            if (this._consoleLog) {
                console.log("Hola amigo");
                console.log("Bonjour mon ami");
            }
        } finally {
            timing.endTiming();
        }
    }
}
using System;
using PipServices3.Components.Count;

class MyComponent
{
    ICounters counters;
    bool _consoleLog = true;

    public MyComponent(ICounters counters)
    {
        this.counters = counters;

        if (_consoleLog)
            Console.WriteLine("MyComponent has been created.");
    }

    public void MyMethod()
    {
        this.counters.Increment("mycomponent.mymethod.calls", 1);
        var timing = this.counters.BeginTiming("mycomponent.mymethod.exec_time");

        try
        {
            if (this._consoleLog)
            {
                Console.WriteLine("Hola amigo");
                Console.WriteLine("Bonjour mon ami");
            }
        }
        finally
        {
            timing.EndTiming();
        }
    }
}
import (
	"context"
	"fmt"

	ccount "github.com/pip-services3-gox/pip-services3-components-gox/count"
)

type MyComponent struct {
	_consoleLog bool
	_counters   ccount.ICounters
}

func NewMyComponentA(counters ccount.ICounters) *MyComponent {
	c := &MyComponent{}
	c._counters = counters

	if c._consoleLog {
		fmt.Println("MyComponent has been created.")
	}

	return c
}

func (c *MyComponent) Mymethod() {
	c._counters.Increment(context.Background(), "mycomponent.mymethod.calls", 1)
	timing := c._counters.BeginTiming(context.Background(), "mycomponent.mymethod.exec_time")

	defer timing.EndTiming()
	if c._consoleLog {
		fmt.Println("Hola amigo")
		fmt.Println("Bonjour mon ami")
	}
}
import 'package:pip_services3_components/pip_services3_components.dart';

void main(List<String> argument) async {}

class MyComponent {
  bool _consoleLog = true;

  ICounters _counters;

  MyComponent(ICounters counters) : _counters = counters {
    if (_consoleLog) {
      print('MyComponent has been created.');
    }
  }

  void mymethod() {
    _counters.increment('mycomponent.mymethod.calls', 1);
    var timing = _counters.beginTiming('mycomponent.mymethod.exec_time');

    try {
      if (_consoleLog) {
        print('Hola amigo');
        print('Bonjour mon ami');
      }
    } finally {
      timing.endTiming();
    }
  }
}

from pip_services3_components.count import ICounters

class MyComponent:
    _console_log = True

    def __init__(self, counters: ICounters):
        self.counters = counters
        
        if self._console_log:
            print("MyComponent has been created.")

    def mymethod(self):
        self.counters.increment("mycomponent.mymethod.calls", 1)
        timing = self.counters.begin_timing("mycomponent.mymethod.exec_time")
        try:
            if self._console_log:
                print("Hola amigo")
                print("Bonjour mon ami")
        finally:
            timing.end_timing()
        self.counters.dump()
Not available

Counters

Once we have defined the performance metrics in our class, we need to make the obtained values available. This can be done in several ways, such as storing them in memory for later use, showing them in the console, or using an external tool like Prometheus or Datadog for metric analysis.

The following subsections aim to explain how different components can be used to achieve various results. First, we will discuss CachedCounters, which store performance metrics in memory. Next, we will cover LogCounters, which send metrics to a logger. After that, we will introduce NullCounters, a dummy component often used for testing and modeling purposes. We will also briefly mention a few components capable of sending metrics to external tools, and then wrap up the tutorial with CompositeCounters, a component that groups counters measuring the same metrics but used differently into a single counter.

CachedCounters

The CachedCounters class is used to create performance counters and store their values in memory. This is an abstract class that is generally used to implement other counters, such as LogCounters, PrometheusCounters and DatadogCounters.

An important method declared in this class is save(), which, as the name would suggest, saves the current counters’ measurements. This method is abstract and therefore needs to be implemented by all subclasses. Another notable method of this class is dump(), which saves metrics data at certain time intervals.

In the example below, we use the previously defined component with CachedCounters. For this, we create a subclass of CachedCounters with a version of the save() method that simply prints a message. When developing real microservices, it is within this method that we can define what we want to do with our performance metrics.

Then, after passing an instance of our CachedCounters’ subclass (i.e. MyCachedCounters) to our component, we call the component’s myMethod(), get the counters, and print the results. The final code is:

import { CachedCounters, Counter, ICounters } from "pip-services3-components-nodex";

export async function main() {
    let countersCached = new MyCachedCounters();

    let mycomponentCached = new MyComponent(countersCached);

    let countExec = 2;

    for (let i = 0; i < countExec; i++)
        mycomponentCached.mymethod();

    let resultCached = countersCached.getAll();

    console.log("Metrics");

    for(let res of resultCached) {
        console.log("Count: " + res.count);
        console.log("Min: " + res.min);
        console.log("Max: " + res.max);
        console.log("Average: " + res.average);
        console.log("Time: " + res.time);
        console.log("Name: " + res.name);
        console.log("Type: " + res.type);
        console.log("-----------------");
    }
}

export class MyCachedCounters extends CachedCounters {
    protected save(counters: Counter[]): void {
        console.log("Saving " + counters[0].name + " and " + counters[1].name);
    }
}
 
using System;
using System.Collections.Generic;
using System.Linq;
using PipServices3.Components.Count;

class Program
{
    static void Main(string[] args)
    {
        var countersCached = new MyCachedCounters();

        var mycomponentCached = new MyComponent(countersCached);

        var countExec = 2;

        for (var i = 0; i < countExec; i++)
            mycomponentCached.MyMethod();

        var resultCached = countersCached.GetAll();

        Console.WriteLine("Metrics");

        foreach (var res in resultCached)
        {
            Console.WriteLine("Count: " + res.Count);
            Console.WriteLine("Min: " + res.Min);
            Console.WriteLine("Max: " + res.Max);
            Console.WriteLine("Average: " + res.Average);
            Console.WriteLine("Time: " + res.Time.ToString());
            Console.WriteLine("Name: " + res.Name);
            Console.WriteLine("Type: " + res.Type);
            Console.WriteLine("-----------------");
        }
    }
}

class MyCachedCounters : CachedCounters
{
    protected override void Save(IEnumerable<Counter> counters)
    {
        var countersList = counters.ToList();
        Console.WriteLine("Saving " + countersList[0].Name + " and " + countersList[1].Name);
    }
}
 
import (
	"context"
	"fmt"

	ccount "github.com/pip-services3-gox/pip-services3-components-gox/count"
)

func main() {
	countersCached := NewMyCachedCounters()
	mycomponentCached := NewMyComponent(countersCached)

	countExec := 2

	for i := 0; i < countExec; i++ {
		mycomponentCached.Mymethod(context.Background())
	}

	resultCached := countersCached.GetAll()

	fmt.Println("Metrics")

	for _, res := range resultCached {
		fmt.Printf("Count: %d\n", res.Count())
		fmt.Printf("Min: %02f\n", res.Min())
		fmt.Printf("Max: %02f\n", res.Max())
		fmt.Printf("Average: %02f\n", res.Average())
		fmt.Printf("Time: %s\n", res.Time())
		fmt.Printf("Name: %s\n", res.Name())
		fmt.Printf("Type: %d\n", res.Type())
		fmt.Printf("-----------------")
	}
}

type MyCachedCounters struct {
	*ccount.CachedCounters
}

func NewMyCachedCounters() *MyCachedCounters {
	c := &MyCachedCounters{}
	c.CachedCounters = ccount.InheritCacheCounters(c)
	return c
}

func (c *MyCachedCounters) Save(ctx context.Context, counters []ccount.Counter) error {
	fmt.Println("Saving " + counters[0].Name + " and " + counters[1].Name)
	return nil
}
 
import 'package:pip_services3_components/pip_services3_components.dart';

void main(List<String> argument) async {
  var countersCached = MyCachedCounters();

  var mycomponentCached = MyComponent(countersCached);

  var countExec = 2;

  for (var i = 0; i < countExec; i++) {
    mycomponentCached.mymethod();
  }

  var resultCached = countersCached.getAll();

  print('Metrics');

  for (var res in resultCached) {
    print('Count: ' + res.count.toString());
    print('Min: ' + res.min.toString());
    print('Max: ' + res.max.toString());
    print('Average: ' + res.average.toString());
    print('Time: ' + res.time.toString());
    print('Name: ' + res.name.toString());
    print('Type: ' + res.type.toString());
    print('-----------------');
  }
}

class MyCachedCounters extends CachedCounters {
  @override
  void save(List<Counter> counters) {
    print('Saving ' + counters[0].name + ' and ' + counters[1].name);
  }
}
 
from pip_services3_components.count import CachedCounters

class MyCachedCounters (CachedCounters):
    def _save(self, counters):
        print("Saving " + counters[0].name + " and " + counters[1].name)

countersCached = MyCachedCounters()

mycomponentCached = MyComponent(countersCached)

count_exec = 2

for i in range(count_exec):
    mycomponentCached.mymethod()
    
resultCached = countersCached.get_all()

print("Metrics")

for res in resultCached:
    print("Count: " + str(res.count))
    print("Min: " + str(res.min))
    print("Max: " + str(res.max))
    print("Average: " + str(res.average))
    print("Time: " + str(res.time))
    print("Name: " + res.name)
    print("Type: " + str(res.type))
    print("-----------------")
 
Not available

Which, after running, produces the following output:

figure 3

As we can see, the save() method was called automatically. Since the Increment counter was only counting the number of times MyMethod was called, it returns the invocation count, but does not supply any statistics data (i.e. minimum, maximum, average). On the other hand, the Interval counter, which measures the execution time, provides these statistics.

LogCounters

Counters can also be made to output their metrics to the console. This can be done with the LogCounters class, which can be used to create performance counters that periodically dump the obtained measurements to a logger.

Containers use LogCounters by default. Once we create a container, the container’s factory will create a LogCounters component, which uses the ConsoleLogger component. Thus, all component metrics measured by the counter will be outputted to the console.

In our next example, we use the LogCounters class, referencing the ConsoleLogger component as a dependency. Then, we call myMethod() and analyze the results as we did in the previous example. The following code shows how to do this:

import { Descriptor, References } from "pip-services3-commons-nodex";
import { CachedCounters, ConsoleLogger, Counter, ICounters, LogCounters } from "pip-services3-components-nodex";

export async function main() {
    let counters = new LogCounters();
    counters.setReferences(References.fromTuples(
        new Descriptor("pip-services", "logger", "console", "default", "1.0"), new ConsoleLogger()))

    let mycomponentLog = new MyComponent(counters);

    let countExec = 2;

    for (let i = 0; i < countExec; i++) {
        mycomponentLog.mymethod();
    }
        
    let resultLog = counters.getAll();

    console.log("Metrics");

    for (let res of resultLog) {
        console.log("Count: " + res.count);
        console.log("Min: " + res.min);
        console.log("Max: " + res.max);
        console.log("Average: " + res.average);
        console.log("Time: " + res.time);
        console.log("Name: " + res.name);
        console.log("Type: " + res.type);
        console.log("-----------------");
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using PipServices3.Commons.Refer;
using PipServices3.Components.Count;
using PipServices3.Components.Log;

class Program
{
    static void Main(string[] args)
    {
        var counters = new LogCounters();
        counters.SetReferences(References.FromTuples(
            new Descriptor("pip-services", "logger", "console", "default", "1.0"), new ConsoleLogger())
        );

        var mycomponentLog = new MyComponent(counters);

        var countExec = 2;

        for (var i = 0; i < countExec; i++)
        {
            mycomponentLog.MyMethod();
        }

        var resultLog = counters.GetAll();

        Console.WriteLine("Metrics");

        foreach (var res in resultLog)
        {
            Console.WriteLine("Count: " + res.Count);
            Console.WriteLine("Min: " + res.Min);
            Console.WriteLine("Max: " + res.Max);
            Console.WriteLine("Average: " + res.Average);
            Console.WriteLine("Time: " + res.Time.ToString());
            Console.WriteLine("Name: " + res.Name);
            Console.WriteLine("Type: " + res.Type);
            Console.WriteLine("-----------------");
        }
    }
}
import (
	"context"
	"fmt"

	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	ccount "github.com/pip-services3-gox/pip-services3-components-gox/count"
	clog "github.com/pip-services3-gox/pip-services3-components-gox/log"
)

func main() {
	counters := ccount.NewLogCounters()
	counters.SetReferences(context.Background(), cref.NewReferencesFromTuples(context.Background(),
		cref.NewDescriptor("pip-services", "logger", "console", "default", "1.0"),
		clog.NewConsoleLogger(),
	))

	mycomponentLog := NewMyComponent(counters)

	countExec := 2

	for i := 0; i < countExec; i++ {
		mycomponentLog.Mymethod(context.Background())
	}

	resultLog := counters.GetAll()

	fmt.Println("Metrics")

	for _, res := range resultLog {
		fmt.Printf("Count: %d\n", res.Count())
		fmt.Printf("Min: %02f\n", res.Min())
		fmt.Printf("Max: %02f\n", res.Max())
		fmt.Printf("Average: %02f\n", res.Average())
		fmt.Printf("Time: %s\n", res.Time())
		fmt.Printf("Name: %s\n", res.Name())
		fmt.Printf("Type: %d\n", res.Type())
		fmt.Printf("-----------------")
	}
}
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_components/pip_services3_components.dart';

void main(List<String> argument) async {
  var counters = LogCounters();
  counters.setReferences(References.fromTuples([
    Descriptor('pip-services', 'logger', 'console', 'default', '1.0'),
    ConsoleLogger()
  ]));

  var mycomponentLog = MyComponent(counters);

  var countExec = 2;

  for (var i = 0; i < countExec; i++) {
    mycomponentLog.mymethod();
  }

  var resultLog = counters.getAll();

  print('Metrics');

  for (var res in resultLog) {
    print('Count: ' + res.count.toString());
    print('Min: ' + res.min.toString());
    print('Max: ' + res.max.toString());
    print('Average: ' + res.average.toString());
    print('Time: ' + res.time.toString());
    print('Name: ' + res.name.toString());
    print('Type: ' + res.type.toString());
    print('-----------------');
  }
}
from pip_services3_components.count import LogCounters

from pip_services3_commons.refer import References, Descriptor
from pip_services3_components.log import ConsoleLogger

counters = LogCounters()
counters.set_references(References.from_tuples(
            Descriptor("pip-services", "logger", "console", "default", "1.0"), ConsoleLogger()))

mycomponentLog = MyComponent(counters)

count_exec = 2

for i in range(count_exec):
    mycomponentLog.mymethod()
    
resultLog = counters.get_all()

print("Metrics")

for res in resultLog:
    print("Count: " + str(res.count))
    print("Min: " + str(res.min))
    print("Max: " + str(res.max))
    print("Average: " + str(res.average))
    print("Time: " + str(res.time))
    print("Name: " + res.name)
    print("Type: " + str(res.type))
    print("-----------------")
Not available

Which, after running, produces the following results:

figure 4

As we can see, the main difference from the previous example is the instantaneous and automatic printing of the counters’ measurements to the console. The remaining messages remained mostly unchanged.

NullCounters

If we are testing our application or want to create a prototype, we can use NullCounters, which is a dummy component and produces no real measurements. In this manner, we can simulate the existence of a counters component, without having to actually deal with one.

In this case, we need to delete the call to the counters’ dump() method from our component, as there will be no values to save. The resulting code should look like this:

import { CachedCounters, Counter, ICounters, NullCounters } from "pip-services3-components-nodex";

export async function main() {
    let countersNull = new NullCounters();

    let mycomponentNull = new MyComponent(countersNull);

    let countExec = 2;

    for (let i = 0; i < countExec; i++)
        mycomponentNull.mymethod();
}

export class MyComponent {
    private _consoleLog: boolean = true;

    private counters: ICounters;

    public constructor(counters: ICounters) {
        this.counters = counters;

        if (this._consoleLog) {
            console.log("MyComponent has been created.");
        }
    }

    public mymethod(): void {
        this.counters.increment("mycomponent.mymethod.calls", 1);
        let timing = this.counters.beginTiming("mycomponent.mymethod.exec_time");

        try {
            if (this._consoleLog) {
                console.log("Hola amigo");
                console.log("Bonjour mon ami");
            }
        } finally {
            timing.endTiming();
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using PipServices3.Components.Count;

namespace ExampleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var countersNull = new NullCounters();

            var mycomponentNull = new MyComponent(countersNull);

            var countExec = 2;

            for (var i = 0; i < countExec; i++)
                mycomponentNull.MyMethod();
        }
    }

    class MyCachedCounters : CachedCounters
    {
        protected override void Save(IEnumerable<Counter> counters)
        {
            var countersList = counters.ToList();
            Console.WriteLine("Saving " + countersList[0].Name + " and " + countersList[1].Name);
        }
    }

    class MyComponent
    {
        ICounters counters;
        bool _consoleLog = true;

        public MyComponent(ICounters counters)
        {
            this.counters = counters;

            if (_consoleLog)
                Console.WriteLine("MyComponent has been created.");
        }

        public void MyMethod()
        {
            this.counters.Increment("mycomponent.mymethod.calls", 1);
            var timing = this.counters.BeginTiming("mycomponent.mymethod.exec_time");

            try
            {
                if (this._consoleLog)
                {
                    Console.WriteLine("Hola amigo");
                    Console.WriteLine("Bonjour mon ami");
                }
            }
            finally
            {
                timing.EndTiming();
            }
        }
    }
}
import (
	"context"
	"fmt"

	ccount "github.com/pip-services3-gox/pip-services3-components-gox/count"
)

func main() {
	countersNull := ccount.NewNullCounters()

	mycomponentNull := NewMyComponent(countersNull)

	countExec := 2

	for i := 0; i < countExec; i++ {
		mycomponentNull.Mymethod(context.Background())
	}
}

type MyComponent struct {
	_consoleLog bool
	_counters   ccount.ICounters
}

func NewMyComponent(counters ccount.ICounters) *MyComponent {
	c := &MyComponent{}
	c._counters = counters

	if c._consoleLog {
		fmt.Println("MyComponent has been created.")
	}

	return c
}

func (c *MyComponent) Mymethod(ctx context.Context) {
	c._counters.Increment(context.Background(), "mycomponent.mymethod.calls", 1)
	timing := c._counters.BeginTiming(context.Background(), "mycomponent.mymethod.exec_time")

	defer timing.EndTiming(ctx)
	if c._consoleLog {
		fmt.Println("Hola amigo")
		fmt.Println("Bonjour mon ami")
	}
}
import 'package:pip_services3_components/pip_services3_components.dart';

void main(List<String> argument) async {
  var countersNull = NullCounters();

  var mycomponentNull = MyComponent(countersNull);

  var countExec = 2;

  for (var i = 0; i < countExec; i++) {
    mycomponentNull.mymethod();
  }
}

class MyCachedCounters extends CachedCounters {
  @override
  void save(List<Counter> counters) {
    print('Saving ' + counters[0].name + ' and ' + counters[1].name);
  }
}

class MyComponent {
  bool _consoleLog = true;

  ICounters _counters;

  MyComponent(ICounters counters) : _counters = counters {
    if (_consoleLog) {
      print('MyComponent has been created.');
    }
  }

  void mymethod() {
    _counters.increment('mycomponent.mymethod.calls', 1);
    var timing = _counters.beginTiming('mycomponent.mymethod.exec_time');

    try {
      if (_consoleLog) {
        print('Hola amigo');
        print('Bonjour mon ami');
      }
    } finally {
      timing.endTiming();
    }
  }
}
from pip_services3_components.count import ICounters
from pip_services3_components.count import NullCounters

class MyComponent:

    _console_log = True

    def __init__(self, counters: ICounters):
        self.counters = counters
        
        if self._console_log:
            print("MyComponent has been created.")

    def myMethod(self):
        self.counters.increment("mycomponent.mymethod.calls", 1)
        timing = self.counters.begin_timing("mycomponent.mymethod.exec_time")
        try:
            if self._console_log:
                print("Hola amigo")
                print("Bonjour mon ami")
        finally:
            timing.end_timing()

countersNull = NullCounters()   

mycomponentNull = MyComponent(countersNull)

count_exec = 2

for i in range(count_exec):
    mycomponentNull.myMethod()
Not available

Which, after running, produces the following outcome:

figure 5

Other counters

Performance metrics can also be sent to monitoring tools, which specialize in processing and displaying metrics data. Pip.Services provides counters that can be used with some of the most popular monitoring tools, such as Prometheus and Datadog.

CompositeCounters

Pip.Services also offers the CompositeCounters component, which can be used to group counters that need to collect the same performance metrics, but use them differently. Once collected, the metrics can be sent to different destinations, such as a console, a logger, and/or a monitoring tool.

An important method declared in this class is setReferences(), which registers all components that will receive the measurements. When using a container, this method finds all registered counters and connects to them.

In the example below, we have a monitored class similar to the one from the previous section, except for the fact that it utilizes a CompositeCounters component and has a setReferences() method added to it. We can use this component to store metrics in, for example, a CachedLogger and a Prometheus component.

To achieve this, we define two counters, a LogCounters component that stores values in a CachedLogger and PrometheusCounters component that connects to Prometheus. Then, we instantiate our monitored class and add references to both counters.

After running myMethod(), we can print the performance metrics obtained by both counters, just like we were doing in the previous example.

The following code shows how this can be done:

import { ConfigParams, Descriptor, IReferenceable, IReferences, References } from "pip-services3-commons-nodex";
import { CachedCounters, CachedLogger, CompositeCounters, Counter, ICounters, LogCounters, LogMessage, NullCounters } from "pip-services3-components-nodex";
import { PrometheusCounters } from "pip-services3-prometheus-nodex";

export async function main() {
    let countersLog = new LogCounters();
    
    let countersProm = new PrometheusCounters()
    countersProm.configure(ConfigParams.fromTuples(
        "connection.protocol", "http",
        "connection.host", "localhost",
        "connection.port", 8080
    ));

    let myComponent = new MyComponent();

    myComponent.setReferences(References.fromTuples(
        new Descriptor("pip-services", "counters", "logger", "default3", "1.0"), countersLog,
        new Descriptor("pip-services", "counters", "prometheus", "default4", "1.0"), countersProm,
        new Descriptor("pip-services", "logger", "cached", "default2", "1.0"), new MyCachedLogger()
    ));

    await countersProm.open("123");

    let countExec = 2;

    for (let i = 0; i < countExec; i++)
        myComponent.mymethod();
    
    let results = countersLog.getAll();

    console.log("Metrics to logger")
    printResults(results);

    results = countersProm.getAll();

    console.log("Metrics to Prometheus");
    printResults(results);
}

function printResults(results: Counter[]) {
    for (let result of results) {
        console.log("Count: " + result.count);
        console.log("Min: " + result.min);
        console.log("Max: " + result.max);
        console.log("Average: " + result.average);
        console.log("Time: " + result.time);
        console.log("Name: " + result.name);
        console.log("Type: " + result.type);
        console.log("-----------------");
    }
}

export class MyComponent implements IReferenceable {
    private _consoleLog: boolean = true;

    private counters: CompositeCounters = new CompositeCounters();

    public constructor() {
        if (this._consoleLog) {
            console.log("MyComponent has been created.");
        }
    }

    public setReferences(references: IReferences): void {
        this.counters.setReferences(references);
    }

    public mymethod(): void {
        this.counters.increment("mycomponent.mymethod.calls", 1);
        let timing = this.counters.beginTiming("mycomponent.mymethod.exec_time");

        try {
            if (this._consoleLog) {
                console.log("Hola amigo");
                console.log("Bonjour mon ami");
            }
        } finally {
            timing.endTiming();
        }
    }
}

export class MyCachedLogger extends CachedLogger {
    protected async save(messages: LogMessage[]): Promise<void> {
        console.log("Saving somewhere");
    } 
}
using System;
using System.Linq;
using System.Collections.Generic;

using PipServices3.Commons.Config;
using PipServices3.Commons.Refer;
using PipServices3.Components.Count;
using PipServices3.Components.Log;
using PipServices3.Prometheus.Count;

namespace ExampleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var countersLog = new LogCounters();

            var countersProm = new PrometheusCounters();
            countersProm.Configure(ConfigParams.FromTuples(
                "connection.protocol", "http",
                "connection.host", "localhost",
                "connection.port", 8080
            ));

            var myComponent = new MyComponent();

            myComponent.SetReferences(References.FromTuples(
                new Descriptor("pip-services", "counters", "logger", "default3", "1.0"), countersLog,
                new Descriptor("pip-services", "counters", "prometheus", "default4", "1.0"), countersProm,
                new Descriptor("pip-services", "logger", "cached", "default2", "1.0"), new MyCachedLogger()
            ));

            countersProm.OpenAsync("123").Wait();

            var countExec = 2;

            for (var i = 0; i < countExec; i++)
                myComponent.MyMethod();

            var results = countersLog.GetAll();

            Console.WriteLine("Metrics to logger");
            PrintResults(results);

            results = countersProm.GetAll();

            Console.WriteLine("Metrics to Prometheus");
            PrintResults(results);
        }

        static void PrintResults(IEnumerable<Counter> results)
        {
            foreach (var result in results)
            {
                Console.WriteLine("Count: " + result.Count);
                Console.WriteLine("Min: " + result.Min);
                Console.WriteLine("Max: " + result.Max);
                Console.WriteLine("Average: " + result.Average);
                Console.WriteLine("Time: " + result.Time);
                Console.WriteLine("Name: " + result.Name);
                Console.WriteLine("Type: " + result.Type);
                Console.WriteLine("-----------------");
            }
        }
    }

    class MyComponent: IReferenceable
    {
        CompositeCounters counters = new CompositeCounters();
        bool _consoleLog = true;

        public MyComponent()
        {
            if (_consoleLog)
                Console.WriteLine("MyComponent has been created.");
        }

        public void MyMethod()
        {
            this.counters.Increment("mycomponent.mymethod.calls", 1);
            var timing = this.counters.BeginTiming("mycomponent.mymethod.exec_time");

            try
            {
                if (this._consoleLog)
                {
                    Console.WriteLine("Hola amigo");
                    Console.WriteLine("Bonjour mon ami");
                }
            }
            finally
            {
                timing.EndTiming();
            }
        }

        public void SetReferences(IReferences references)
        {
            counters.SetReferences(references);
        }
    }

    class MyCachedLogger : CachedLogger
    {
        protected override void Save(List<LogMessage> messages)
        {
            Console.WriteLine("Saving somewhere");
        }
    }
}
import (
	"context"
	"fmt"

	cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	cref "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
	ccount "github.com/pip-services3-gox/pip-services3-components-gox/count"
	clog "github.com/pip-services3-gox/pip-services3-components-gox/log"
	pcount "github.com/pip-services3-gox/pip-services3-prometheus-gox/count"
)

func main() {
	countersLog := ccount.NewLogCounters()

	countersProm := pcount.NewPrometheusCounters()
	countersProm.Configure(context.Background(), cconf.NewConfigParamsFromTuples(
		"connection.protocol", "http",
		"connection.host", "localhost",
		"connection.port", 8080,
	))

	myComponent := NewMyComponent()

	myComponent.SetReferences(context.Background(), cref.NewReferencesFromTuples(context.Background(),
		cref.NewDescriptor("pip-services", "counters", "logger", "default3", "1.0"), countersLog,
		cref.NewDescriptor("pip-services", "counters", "prometheus", "default4", "1.0"), countersProm,
		cref.NewDescriptor("pip-services", "logger", "cached", "default2", "1.0"), NewMyCachedLogger(),
	))

	err := countersProm.Open(context.Background(), "123")
	if err != nil {
		panic(err)
	}

	countExec := 2

	for i := 0; i < countExec; i++ {
		myComponent.Mymethod(context.Background())
	}

	results := countersLog.GetAll()
	counters := make([]ccount.Counter, 0)

	for _, val := range results {
		counters = append(counters, val.GetCounter())
	}

	fmt.Println("Metrics to logger")
	PrintResults(counters)

	results = countersProm.GetAll()

	for _, val := range results {
		counters = append(counters, val.GetCounter())
	}

	fmt.Println("Metrics to Prometheus")
	PrintResults(counters)
}

func PrintResults(results []ccount.Counter) {
	for _, res := range results {
		fmt.Printf("Count: %d\n", res.Count)
		fmt.Printf("Min: %02f\n", res.Min)
		fmt.Printf("Max: %02f\n", res.Max)
		fmt.Printf("Average: %02f\n", res.Average)
		fmt.Printf("Time: %s\n", res.Time)
		fmt.Printf("Name: %s\n", res.Name)
		fmt.Printf("Type: %d\n", res.Type)
		fmt.Printf("-----------------")
	}
}

type MyComponent struct {
	_consoleLog bool
	counters    *ccount.CompositeCounters
}

func NewMyComponent() *MyComponent {
	c := &MyComponent{
		counters: ccount.NewCompositeCounters(),
	}

	if c._consoleLog {
		fmt.Println("MyComponent has been created.")
	}

	return c
}

func (c *MyComponent) SetReferences(ctx context.Context, references cref.IReferences) {
	c.counters.SetReferences(ctx, references)
}

func (c *MyComponent) Mymethod(ctx context.Context) {
	c.counters.Increment(context.Background(), "mycomponent.mymethod.calls", 1)
	timing := c.counters.BeginTiming(context.Background(), "mycomponent.mymethod.exec_time")

	defer timing.EndTiming(ctx)
	if c._consoleLog {
		fmt.Println("Hola amigo")
		fmt.Println("Bonjour mon ami")
	}
}

type MyCachedLogger struct {
	*clog.CachedLogger
}

func NewMyCachedLogger() *MyCachedLogger {
	c := &MyCachedLogger{}
	c.CachedLogger = clog.InheritCachedLogger(c)
	return c
}

func (c *MyCachedLogger) Save(ctx context.Context, messages []clog.LogMessage) error {
	fmt.Println("Saving somewhere")
	return nil
}
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_components/pip_services3_components.dart';
import 'package:pip_services3_prometheus/pip_services3_prometheus.dart';

void main(List<String> argument) async {
  var countersLog = LogCounters();

  var countersProm = PrometheusCounters();
  countersProm.configure(ConfigParams.fromTuples([
    'connection.protocol',
    'http',
    'connection.host',
    'localhost',
    'connection.port',
    8080
  ]));

  var myComponent = MyComponent();

  myComponent.setReferences(References.fromTuples([
    Descriptor('pip-services', 'counters', 'logger', 'default3', '1.0'),
    countersLog,
    Descriptor('pip-services', 'counters', 'prometheus', 'default4', '1.0'),
    countersProm,
    Descriptor('pip-services', 'logger', 'cached', 'default2', '1.0'),
    MyCachedLogger()
  ]));

  await countersProm.open('123');

  var countExec = 2;

  for (var i = 0; i < countExec; i++) {
    myComponent.mymethod();
  }

  var results = countersLog.getAll();

  print('Metrics to logger');
  printResults(results);

  results = countersProm.getAll();

  print('Metrics to Prometheus');
  printResults(results);
}

void printResults(List<Counter> results) {
  for (var result in results) {
    print('Count: ' + result.count.toString());
    print('Min: ' + result.min.toString());
    print('Max: ' + result.max.toString());
    print('Average: ' + result.average.toString());
    print('Time: ' + result.time.toString());
    print('Name: ' + result.name.toString());
    print('Type: ' + result.type.toString());
    print('-----------------');
  }
}

class MyComponent implements IReferenceable {
  bool _consoleLog = true;

  final CompositeCounters _counters = CompositeCounters();

  MyComponent() {
    if (_consoleLog) {
      print('MyComponent has been created.');
    }
  }

  void mymethod() {
    _counters.increment('mycomponent.mymethod.calls', 1);
    var timing = _counters.beginTiming('mycomponent.mymethod.exec_time');

    try {
      if (_consoleLog) {
        print('Hola amigo');
        print('Bonjour mon ami');
      }
    } finally {
      timing.endTiming();
    }
  }

  @override
  void setReferences(IReferences references) {
    _counters.setReferences(references);
  }
}

class MyCachedLogger extends CachedLogger {
  @override
  Future save(List<LogMessage> messages) async {
    print('Saving somewhere');
  }
}
from pip_services3_components.count import ICounters, CompositeCounters
from pip_services3_commons.refer import IReferenceable, IReferences

_console_log = True

class MyComponent(IReferenceable):
    _counters: CompositeCounters = CompositeCounters()

    def __init__(self):
        self._counters = counters
        
        if _console_log:
            print("MyComponent has been created.")
            
    def setReferences(self, references: IReferences):
        self._counters.set_references(references)   
            
    def myMethod(self):
        self._counters.increment("mycomponent.mymethod.calls", 1)
        timing = self._counters.begin_timing("mycomponent.mymethod.exec_time")
        try:
            if _console_log:
                print("Hola amigo")
                print("Bonjour mon ami")
        finally:
            timing.end_timing()
            
# Cached logger

class MyCachedLogger ():
    def _save(self, counters):
        print("\tSaving somewhere")
from pip_services3_commons.refer import References, Descriptor  
from pip_services3_components.count import LogCounters
from pip_services3_components.log import CachedLogger

countersLog1 = LogCounters()
countersLog1.set_references(References.from_tuples(
            Descriptor("pip-services", "logger", "cached", "default2", "1.0"), MyCachedLogger()))

# Prometheus

from pip_services3_prometheus.count import PrometheusCounters
from pip_services3_commons.config import ConfigParams

countersProm = PrometheusCounters()
countersProm.configure(ConfigParams.from_tuples(
    "connection.protocol", "http",
    "connection.host", "localhost",
    "connection.port", 8080
))

countersProm.open("123")

# Composite counters

from pip_services3_components.count import CompositeCounters
counters = CompositeCounters()
counters.set_references(References.from_tuples(
            Descriptor("pip-services", "counters", "logger", "default3", "1.0"), countersLog1))
counters.set_references(References.from_tuples(
            Descriptor("pip-services", "counters", "prometheus", "default4", "1.0"), countersProm))
            
myComponent = MyComponent()

count_exec = 2

for i in range(count_exec):
    myComponent.myMethod()
    
result = countersLog1.get_all()

print("\nMetrics to logger")

for i in result:
    print("Count: " + str(i.count))
    print("Min: " + str(i.min))
    print("Max: " + str(i.max))
    print("Average: " + str(i.average))
    print("Time: " + str(i.time))
    print("Name: " + i.name)
    print("Type: " + str(i.type))
    print("-----------------")
    
result = countersProm.get_all()

print("\nMetrics to Prometheus")

for i in result:
    print("Count: " + str(i.count))
    print("Min: " + str(i.min))
    print("Max: " + str(i.max))
    print("Average: " + str(i.average))
    print("Time: " + str(i.time))
    print("Name: " + i.name)
    print("Type: " + str(i.type))
    print("-----------------")
    
    
Not available

Which, after running, produces the following output:

figure 6

Wrapping up

In this tutorial, we have seen how to create, calculate, store and use performance metrics. We built an example where we created a call-counter and an execution time counter, added these metrics to one of our component’s methods, and demonstrated how to save the obtained values to memory and a logger. We also learned how to create NullCounters, which is a dummy component that performs no real measurements, but is useful for testing and modeling purposes. Finally, we understood how to group several counters via the CompositeCounters class and send the measured values to other tools for future use.