Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

The most important reason to provide custom is probably the convenience of collecting of desired metrics using one platform, the same client, through the same API. This feature can simplify user application.

Famous databases with custom metrics: Oracle DBPostgresOracle CoherenceMS SQL ServerIBM DB2

Examples of custom metric usages

  • Some metrics may not be provided yet and user registers own.
  • Metrics for load balancing.
  • Various metrics of business processes or related to user application.
  • Administration metrics.
  • Security-related metrics.

Examples of databases with custom metrics

...

Implementation proposal

General approach

...

Risks and Assumptions

  • Custom metrics might be are just another way to hit performance and consume additional resources.
  • There could be race conditions on metric registrations. Metrics with incompatible types can be concurrently registered with the same names.
  • We do not provide read-only metric wrappers for the internal metrics. User can cast read-only metric to writeable implementation or interface and change value even of an iternal metric. But we already have public Metric#reset(), IgniteSpiContext#removeMetricRegistry(). And current ReadOnlyMetricRegistry doesn't return write-protected metric implementation.
  • We do not expose API of all internal metrics. At least with the first tickets.
  • Custom metrics aren't stored and require re-registration after node restart. At least with the first tickets.

...

An API to expose internals read-only internal metrics was already proposed. Might be joined with the custom metrics.

To give an user the ability to register additional metrics, we could either:

  • Refactor a bit our current metrics. Create interfaces for writable metric registry, metric manager. Create interfaces for essential metrics like int, double, long, longAdder, boolean, object and the gauge metrics (IntGauge, LongGauge, etc.).
  • Provide tiny-enough facade with consumers and suppliers for int, long, double value metrics.

...

/**
* Allows to manage custom metrics and to obtain read-only internal metrics.
*
* Note: Names of custom metric registries are required to start with 'custom.' (lower case) and may have additional
* dot-separated qualifiers. The prefix 'custon' is automatically added if missed. For example, if provided custom registry name
* is "a.b.c.mname", it is automatically extended to "custom.a.b.c.mname".
*
* Any custom name or dot-separated name part cannot can't have any spaces and must not be empty. Spaces are removed.
*
* Examples of custom metric registry names: "custom", "custom.admin", "custom.admin.sessions", "custom.processes", etc.
*
* @see ReadOnlyMetricRegistry
* @see IgniteMetricRegistry
*/
@IgniteExperimental
public interface IgniteMetrics extends Iterable<ReadOnlyMetricRegistry> {
    /**
    * Gets or creates custom metric registry named "custom." + {@code registryName}.
    *
    * @return {@link IgniteMetricRegistry} registry.
    */
    IgniteMetricRegistry customRegistry(String registryName);

    /**
    * Gets or creates custom metric registry named "custom.".
    *
    * @return {@link IgniteMetricRegistry} registry.
   */
    default IgniteMetricRegistry customRegistry() {
        return customRegistry(null);
    }

    /**
    * Gets metric registry including the Ignite's internal registries.
    *
    * @return Certain read-only metric registry.
    */
    @Nullable ReadOnlyMetricRegistry findRegistry(String registryName);

    /** */
    void removeCustomRegistry(String registryName);

    /** Removes custom metric registry with name '.custom'./
    default void removeCustomRegistry() {
        removeCustomRegistry(null);
    }
}

...

/** Updatable simple long value metric. */
@IgniteExperimental
public interface LongValueMetric extends LongSumMetric {
    /** Sets long metric value. */
    void value(long value);
}

4. Minimal

...

custom interface

Instead of #3, we could bring only a minimal metric management interface.

...

/** */
public static final class TestCustomMetricsService implements TestService {
    /** */
    @IgniteInstanceResource
    private Ignite ignite;

    /** */
    @ServiceContextResource
    private ServiceContext ctx;

    /** */
    private AtomicReference<UUID> remoteId;

    /** */
    private final AtomicInteger metricValue = new AtomicInteger();

    /** {@inheritDoc} */
    @Override public void init() throws Exception {
        remoteId = new AtomicReference<>();
          

        // Registers metric "custom.service.svc.filteredInvocation"

        ignite.metrics().customRegistry(regName(ctx.name())).gauge("filteredInvocation", metricValue::get, "Counter of speceific service invocation.");

        // Registers metric "custom.service.svc.loaded"
        ignite.metrics().customRegistry(regName(ctx.name())).gauge("loaded", () -> metricValue.get() >= 100, "Load flag.");

        // Registers metric "custom.service.svc.remote.classId"
        ignite.metrics().customRegistry(regName(ctx.name())).gauge("remote.classId", () -> remoteId.get(), UUID.class, "Remote system class id.");
    }

    /** {@inheritDoc} */
    @Override public void cancel() {
        refresh();

        ignite.metrics().customRegistry(regName(ctx.name())).remove(COUNTER_METRIC_NAME);
    }

    /** {@inheritDoc} */
    @Override public void refresh() {
        metricValue.set(0);

        remoteId.set(null);
    }

    /** */
    @Override public void invoke(int param) {
        if (ctx.isCancelled())
            return;

        remoteId.compareAndSet(null, UUID.randomUUID());

        // Updates metric sometimes.
        if (!ctx.isCancelled() && param % 10 == 0)
            metricValue.set(param / 10);
    }

    /** */
    private static String regName(String svcName) {
        return "service." + svcName;
    }
}

...

/** */
private static final class TestCustomMetricsComputeTask extends ComputeTaskAdapter<Void, Long> {
    /** */
    private static final class TestComputeJob extends ComputeJobAdapter {
        /** Ignite instance. */
        @IgniteInstanceResource
        private Ignite ignite;

        /** {@inheritDoc} */
        @Override public Long execute() throws IgniteException {
            long val = 0;

            // Some job limit.
            long limit = 300 + ThreadLocalRandom.current().nextLong(700);


            // Registers metric "custom.task.test.current"

            LongValueMetric metricCur = ignite.metrics().customRegistry(METRIC_REGISTRY"task.test").longMetric("task.testcurrent", null);

            // Registers metric "custom.task.test.total.sum"

            LongSumMetric metricTotal = ignite.metrics().customRegistry(METRIC_REGISTRY"task.test").longAdderMetric("total.sum", null);

            // Registers metric "custom.task.test.ticks"           

            LongSumMetric metricTicks = ignite.metrics().customRegistry(METRIC_REGISTRY"task.test").longAdderMetric("ticks", null);

            while (!isCancelled() && val < limit) {
                // Does some job.
                try {
                    U.sleep(ThreadLocalRandom.current().nextInt(50));
                }
                catch (IgniteInterruptedCheckedException ignored) {
                    //No op.
                }

                long increment = ThreadLocalRandom.current().nextLong(100);

                val += increment;

                metricTicks.increment()

...